Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrappers: allow sockets under $XDG_RUNTIME_DIR #6327

Merged
merged 5 commits into from Jun 24, 2019

Conversation

kubiko
Copy link
Contributor

@kubiko kubiko commented Jan 4, 2019

/run/user/0/$SNAP_NAME/ is permited path for sockets to be created, this is at the moment blocked
when socket is defined as part of daemon configuration

Signed-off-by: Ondrej Kubik ondrej.kubik@canonical.com

Thanks for helping us make a better snapd!
Have you signed the license agreement and read the contribution guide?

Copy link
Collaborator

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks okay but I don't remember the plan for /run/user/NNN vs /run/user/NNN/$SNAP_INSTANCE_NAME.

CC @jdstrand and @chipaca for comments.

snap/validate.go Outdated
@@ -272,7 +272,7 @@ func validateSocketAddrPath(socket *SocketInfo, fieldName string, path string) e
return fmt.Errorf("invalid %q: %q should be written as %q", fieldName, path, clean)
}

if !(strings.HasPrefix(path, "$SNAP_DATA/") || strings.HasPrefix(path, "$SNAP_COMMON/")) {
if !(strings.HasPrefix(path, "$SNAP_DATA/") || strings.HasPrefix(path, "$SNAP_COMMON/") || strings.HasPrefix(path, "/run/user/0/$SNAP_NAME/")) {
return fmt.Errorf(
"invalid %q: must have a prefix of $SNAP_DATA or $SNAP_COMMON", fieldName)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going this way, the message needs to be updated to include /run/user/0/...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @bboozzoo on this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did updated message, though with XDG_RUNTIME_DIR as @jdstrand proposed

snap/validate.go Outdated
@@ -272,7 +272,7 @@ func validateSocketAddrPath(socket *SocketInfo, fieldName string, path string) e
return fmt.Errorf("invalid %q: %q should be written as %q", fieldName, path, clean)
}

if !(strings.HasPrefix(path, "$SNAP_DATA/") || strings.HasPrefix(path, "$SNAP_COMMON/")) {
if !(strings.HasPrefix(path, "$SNAP_DATA/") || strings.HasPrefix(path, "$SNAP_COMMON/") || strings.HasPrefix(path, "/run/user/0/$SNAP_NAME/")) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should use $SNAP_INSTANCE_NAME

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I change it to XDG_RUNTIME_DIR, so there is no more need or SNAP_INSTANCE_NAME now

@kubiko
Copy link
Contributor Author

kubiko commented Jan 7, 2019

Updated to use $SNAP_INSTANCE_NAME

Copy link

@jdstrand jdstrand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearly there is a bug since we allow /run/user/[0-9]*/snap.@{SNAP_INSTANCE_NAME}/** but it feels wrong to make the user have to specify /run/user/*0*/... since in the future we might change that and this is exactly what XDG_RUNTIME_DIR is for. Eg:

$ sudo hello-world.env|grep XDG
XDG_RUNTIME_DIR=/run/user/0/snap.hello-world

Perhaps instead use || strings.HasPrefix(path, "$XDG_RUNTIME_DIR/")? I'm not sure if you'll have to expand that in the systemd unit or not, but this seems much more user friendly and in line with the other two variables.

snap/validate.go Outdated
@@ -272,7 +272,7 @@ func validateSocketAddrPath(socket *SocketInfo, fieldName string, path string) e
return fmt.Errorf("invalid %q: %q should be written as %q", fieldName, path, clean)
}

if !(strings.HasPrefix(path, "$SNAP_DATA/") || strings.HasPrefix(path, "$SNAP_COMMON/")) {
if !(strings.HasPrefix(path, "$SNAP_DATA/") || strings.HasPrefix(path, "$SNAP_COMMON/") || strings.HasPrefix(path, "/run/user/0/$SNAP_NAME/")) {
return fmt.Errorf(
"invalid %q: must have a prefix of $SNAP_DATA or $SNAP_COMMON", fieldName)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @bboozzoo on this point.

Copy link
Collaborator

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please adjust tests to update the validation routine.

@kubiko kubiko force-pushed the socket-validation-fix branch 4 times, most recently from 3a7e59b to 7b2f8d6 Compare May 22, 2019 10:18
@kubiko
Copy link
Contributor Author

kubiko commented May 22, 2019

@jdstrand @zyga @bboozzoo implementation is now updated to use XDG_RUNTIME_DIR instead of hardcoded /run/path as @jdstrand proposed, this seems way more reliable and also easier to use.
I have also updated tests around to test this scenario. Thanks for good feedback :)

@kubiko kubiko changed the title Allowing sockets under /run/user/0/$SNAP_NAME Allowing sockets under $XDG_RUNTIME_DIR May 22, 2019
Copy link

@jdstrand jdstrand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, but please consider adding the suggested comment (see inline).

@@ -600,6 +601,7 @@ func generateSnapSocketFiles(app *snap.AppInfo) (*map[string][]byte, error) {
func renderListenStream(socket *snap.SocketInfo) string {
snap := socket.App.Snap
listenStream := strings.Replace(socket.ListenStream, "$SNAP_DATA", snap.DataDir(), -1)
listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", snap.UserXdgRuntimeDir(sys.Geteuid()), -1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was slightly uncomfortable with this change since sys.Geteuid() is going to be whatever snapd is running as, which works for both the testsuite and typical "daemons run as root" case but would not work whenever we introduce User= and Group= in the generated systemd units. In that case, we would want to adjust sys.Geteuid() to be a lookup of the specified user/group, which is ok.

As a future reminder, can you add the following comment:

// TODO: when we support User/Group in the generated systemd unit,
// adjust this accordingly

Copy link
Contributor Author

@kubiko kubiko May 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdstrand I was thinking exactly same thing. But I was not sure if we have anything ready for this usecase. As I assume socket options will then need to expand to SNAP_USER_COMMON and SNAP_USER_DATA. But then we need way to identify what is user service should run under. And actually forbid use of SNAP_DATA/SNAP_COMMON if not running as root.
And indeed I used sys.Geteuid() trick to get right value in test and in actual run time
I will add comment, so make sure we keep this updated when introducing serviced for users.
I will add comment to make sure we capture this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer not to use sys.Geteuid() here. Its slightly misleading, when looking at this code it seems like the value in xdg-runtime-dir depends on the daemon uid. But in reality it depends on the setting of the snap.yaml for the given service. By default the service will run as root and once we have a way to express users and groups it will run as a user or group. So I think it would be slightly better to represent that here (especially since initially this will always be uid=0). Something like:

--- a/wrappers/services.go
+++ b/wrappers/services.go
@@ -603,7 +603,9 @@ func renderListenStream(socket *snap.SocketInfo) string {
        listenStream := strings.Replace(socket.ListenStream, "$SNAP_DATA", snap.DataDir(), -1)
        // TODO: when we support User/Group in the generated systemd unit,
        // adjust this accordingly
-       listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", snap.UserXdgRuntimeDir(sys.Geteuid()), -1)
+       serviceUserUid := sys.UserID(0)
+       runtimeDir := snap.UserXdgRuntimeDir(serviceUserUid)
+       listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", runtimeDir, -1)
        return strings.Replace(listenStream, "$SNAP_COMMON", snap.CommonDataDir(), -1)
 }
 
diff --git a/wrappers/services_test.go b/wrappers/services_test.go
index 87de9e55c5..29700bc577 100644
--- a/wrappers/services_test.go
+++ b/wrappers/services_test.go
@@ -25,7 +25,6 @@ import (
        "path/filepath"
        "regexp"
        "sort"
-       "strconv"
        "strings"
        "time"
 
@@ -33,7 +32,6 @@ import (
 
        "github.com/snapcore/snapd/dirs"
        "github.com/snapcore/snapd/osutil"
-       "github.com/snapcore/snapd/osutil/sys"
        "github.com/snapcore/snapd/progress"
        "github.com/snapcore/snapd/snap"
        "github.com/snapcore/snapd/snap/snaptest"
@@ -460,7 +458,7 @@ Service=snap.hello-snap.svc1.service
 FileDescriptorName=sock3
 ListenStream=%s
 
-`, filepath.Join(s.tempdir, "/run/user", strconv.FormatUint(uint64(sys.Geteuid()), 10), "snap.hello-snap/sock3.socket"))
+`, filepath.Join(s.tempdir, "/run/user/0/snap.hello-snap/sock3.socket"))
        c.Check(sock3File, testutil.FileContains, expected)
 }

@kubiko kubiko force-pushed the socket-validation-fix branch 2 times, most recently from 543eb6b to 87e4807 Compare May 23, 2019 22:28
@kubiko
Copy link
Contributor Author

kubiko commented Jun 6, 2019

Please adjust tests to update the validation routine.

@zyga can you please review updated tests as it should be fixed now

Copy link
Contributor

@mvo5 mvo5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good! +0.9 with one small suggestion, otherwise good to go.

@@ -600,6 +601,7 @@ func generateSnapSocketFiles(app *snap.AppInfo) (*map[string][]byte, error) {
func renderListenStream(socket *snap.SocketInfo) string {
snap := socket.App.Snap
listenStream := strings.Replace(socket.ListenStream, "$SNAP_DATA", snap.DataDir(), -1)
listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", snap.UserXdgRuntimeDir(sys.Geteuid()), -1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer not to use sys.Geteuid() here. Its slightly misleading, when looking at this code it seems like the value in xdg-runtime-dir depends on the daemon uid. But in reality it depends on the setting of the snap.yaml for the given service. By default the service will run as root and once we have a way to express users and groups it will run as a user or group. So I think it would be slightly better to represent that here (especially since initially this will always be uid=0). Something like:

--- a/wrappers/services.go
+++ b/wrappers/services.go
@@ -603,7 +603,9 @@ func renderListenStream(socket *snap.SocketInfo) string {
        listenStream := strings.Replace(socket.ListenStream, "$SNAP_DATA", snap.DataDir(), -1)
        // TODO: when we support User/Group in the generated systemd unit,
        // adjust this accordingly
-       listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", snap.UserXdgRuntimeDir(sys.Geteuid()), -1)
+       serviceUserUid := sys.UserID(0)
+       runtimeDir := snap.UserXdgRuntimeDir(serviceUserUid)
+       listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", runtimeDir, -1)
        return strings.Replace(listenStream, "$SNAP_COMMON", snap.CommonDataDir(), -1)
 }
 
diff --git a/wrappers/services_test.go b/wrappers/services_test.go
index 87de9e55c5..29700bc577 100644
--- a/wrappers/services_test.go
+++ b/wrappers/services_test.go
@@ -25,7 +25,6 @@ import (
        "path/filepath"
        "regexp"
        "sort"
-       "strconv"
        "strings"
        "time"
 
@@ -33,7 +32,6 @@ import (
 
        "github.com/snapcore/snapd/dirs"
        "github.com/snapcore/snapd/osutil"
-       "github.com/snapcore/snapd/osutil/sys"
        "github.com/snapcore/snapd/progress"
        "github.com/snapcore/snapd/snap"
        "github.com/snapcore/snapd/snap/snaptest"
@@ -460,7 +458,7 @@ Service=snap.hello-snap.svc1.service
 FileDescriptorName=sock3
 ListenStream=%s
 
-`, filepath.Join(s.tempdir, "/run/user", strconv.FormatUint(uint64(sys.Geteuid()), 10), "snap.hello-snap/sock3.socket"))
+`, filepath.Join(s.tempdir, "/run/user/0/snap.hello-snap/sock3.socket"))
        c.Check(sock3File, testutil.FileContains, expected)
 }

FileDescriptorName=sock3
ListenStream=%s

`, filepath.Join(s.tempdir, "/run/user", strconv.FormatUint(uint64(sys.Geteuid()), 10), "snap.hello-snap/sock3.socket"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is similar to above (and the diff will change this part too): when reading this its slightly misleading as it may make the reader think the value is dependent on the runtime uid. But its strictly dependent on the user configured in the service yaml. So I would prefer here to not use sys.Geteuid() as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mvo5 I updated code per proposal. Initially I was trying to avoid hardcoding uid but you are right that it actually made it worse and code was giving wrong impression.
And true, this will anyway need to be revisited when we land support for user services

XDG_RUNTIME_DIR (usually /run/user/<uid>/snap.$SNAP_INSTANCE_NAME/) is permited path for sockets to be created, this is at the moment blocked
when socket is defined as part of daemon configuration

Signed-off-by: Ondrej Kubik <ondrej.kubik@canonical.com>
…ation

Expand XDG_RUNTIME_DIR if used for socket activated service

Signed-off-by: Ondrej Kubik <ondrej.kubik@canonical.com>
…ME_DIR socket path

Update socket-activation test snap with socket in XDG_RUNTIME_DIR and SNAP_DATA path

Signed-off-by: Ondrej Kubik <ondrej.kubik@canonical.com>
@kubiko kubiko changed the title Allowing sockets under $XDG_RUNTIME_DIR wrappers: allow sockets under $XDG_RUNTIME_DIR Jun 17, 2019
Copy link
Collaborator

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-read the patch and the discussion, looks good to me.

@pedronis
Copy link
Collaborator

seems there are some actual SE Linux failures now, @bboozzoo maybe can help

@kubiko
Copy link
Contributor Author

kubiko commented Jun 20, 2019

seems there are some actual SE Linux failures now, @bboozzoo maybe can help

@pedronis yep @bboozzoo is already looking into this, he confirmed there is need to update SE profile

@bboozzoo
Copy link
Collaborator

----
type=AVC msg=audit(06/20/19 12:48:16.638:1057) : avc:  denied  { write } for  pid=12685 comm=snapd name=/ dev="tmpfs" ino=21744 scontext=system_u:system_r:snappy_t:s0 tcontext=system_u:object_r:user_tmp_t:s0 tclass=dir permissive=1 
----
type=AVC msg=audit(06/20/19 12:48:16.639:1058) : avc:  denied  { remove_name } for  pid=12685 comm=snapd name=snap.socket-activation dev="tmpfs" ino=146684 scontext=system_u:system_r:snappy_t:s0 tcontext=system_u:object_r:user_tmp_t:s0 tclass=dir permissive=1 
----
type=AVC msg=audit(06/20/19 12:48:16.639:1059) : avc:  denied  { rmdir } for  pid=12685 comm=snapd name=snap.socket-activation dev="tmpfs" ino=146684 scontext=system_u:system_r:snappy_t:s0 tcontext=system_u:object_r:user_tmp_t:s0 tclass=dir permissive=1 
----
type=AVC msg=audit(06/20/19 12:48:16.639:1060) : avc:  denied  { unlink } for  pid=12685 comm=snapd name=socket-xdg dev="tmpfs" ino=146685 scontext=system_u:system_r:snappy_t:s0 tcontext=system_u:object_r:user_tmp_t:s0 tclass=sock_file permissive=1

Yes, this is caused by snapd trying to remove a socket file under /run/. I'll look into adjusting the policy and will push a patch to this branch.

Adjust SELinux policy to allow snapd to remove socket files under /run/user (aka
$XDG_RUNTIME_DIR).

Fixes the following denials:
----
type=AVC msg=audit(06/24/19 07:52:12.092:324) : avc:  denied  { write } for
         pid=25331 comm=snapd name=/ dev="tmpfs" ino=21794
         scontext=system_u:system_r:snappy_t:s0
         tcontext =system_u:object_r:user_tmp_t:s0 tclass=dir permissive=1
----
type=AVC msg=audit(06/24/19 07:52:12.092:325) : avc:  denied  { remove_name }
         for pid=25331 comm=snapd name=snap.socket-activation dev="tmpfs" ino=69344
         scontext=system_u:sy stem_r:snappy_t:s0
         tcontext=system_u:object_r:user_tmp_t:s0 tclass=dir permissive=1
----
type=AVC msg=audit(06/24/19 07:52:12.092:326) : avc:  denied  { rmdir } for
         pid=25331 comm=snapd name=snap.socket-activation dev="tmpfs" ino=69344
         scontext=system_u:system_r :snappy_t:s0
         tcontext=system_u:object_r:user_tmp_t:s0 tclass=dir permissive=1
----
type=AVC msg=audit(06/24/19 07:52:12.093:327) : avc:  denied  { unlink } for
         pid=25331 comm=snapd name=socket-xdg dev="tmpfs" ino=69345
         scontext=system_u:system_r:snappy_t:s 0
         tcontext=system_u:object_r:user_tmp_t:s0 tclass=sock_file permissive=1

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
@bboozzoo
Copy link
Collaborator

Pushed a patch that extends the policy accordingly.

@bboozzoo bboozzoo merged commit e8aef1c into snapcore:master Jun 24, 2019
@kubiko kubiko deleted the socket-validation-fix branch July 6, 2019 22:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants