diff options
| author | Zygmunt Krynicki <me@zygoon.pl> | 2019-07-02 11:24:56 +0200 |
|---|---|---|
| committer | Zygmunt Krynicki <me@zygoon.pl> | 2019-07-02 11:24:56 +0200 |
| commit | 14d80edbc34b72e94622b58f1fdeefe35211014b (patch) | |
| tree | 8af188f03686c1dcb8dab71fdca76f8c5110429b /cmd/snap-confine | |
| parent | 766707bf0cbfec0d3640d58869d077a53afdb0e7 (diff) | |
| parent | 62bd1cd9592316e2807cff37478e4627b070c065 (diff) | |
Merge branch 'master' of github.com:snapcore/snapd into fix/exit-when-parent-died
Diffstat (limited to 'cmd/snap-confine')
| -rw-r--r-- | cmd/snap-confine/README.nvidia | 2 | ||||
| -rw-r--r-- | cmd/snap-confine/mount-support.c | 20 | ||||
| -rw-r--r-- | cmd/snap-confine/seccomp-support-ext.c | 36 | ||||
| -rw-r--r-- | cmd/snap-confine/snap-confine-args.c | 2 | ||||
| -rw-r--r-- | cmd/snap-confine/snap-confine-args.h | 2 | ||||
| -rw-r--r-- | cmd/snap-confine/snap-confine.apparmor.in | 4 | ||||
| -rw-r--r-- | cmd/snap-confine/snap-confine.c | 112 | ||||
| -rw-r--r-- | cmd/snap-confine/snap-device-helper-test.c | 31 | ||||
| -rw-r--r-- | cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml | 2 | ||||
| -rw-r--r-- | cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml | 2 | ||||
| -rw-r--r-- | cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml | 4 | ||||
| -rw-r--r-- | cmd/snap-confine/udev-support.c | 4 | ||||
| -rw-r--r-- | cmd/snap-confine/user-support.c | 6 |
13 files changed, 145 insertions, 82 deletions
diff --git a/cmd/snap-confine/README.nvidia b/cmd/snap-confine/README.nvidia index bdcae4c28d..972b48d82b 100644 --- a/cmd/snap-confine/README.nvidia +++ b/cmd/snap-confine/README.nvidia @@ -1,7 +1,7 @@ Nvidia on Arch ============== -On Arch nvidia support differs depending on the version of the driver user. +On Arch nvidia support differs depending on the version of the driver used. Free drivers should work out of the box without any changes. Proprietary drivers were tested on the following driver versions: diff --git a/cmd/snap-confine/mount-support.c b/cmd/snap-confine/mount-support.c index a85e22ad28..8d962798a7 100644 --- a/cmd/snap-confine/mount-support.c +++ b/cmd/snap-confine/mount-support.c @@ -354,8 +354,8 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config) dst, src); continue; } - // both source and destination exist and are either files or - // directories + // both source and destination exist where both are either files + // or both are directories sc_do_mount(src, dst, NULL, MS_BIND, NULL); sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); } @@ -408,8 +408,7 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config) // directory is always /snap. On the host it is a build-time configuration // option stored in SNAP_MOUNT_DIR. sc_must_snprintf(dst, sizeof dst, "%s/snap", scratch_dir); - sc_do_mount(SNAP_MOUNT_DIR, dst, NULL, MS_BIND | MS_REC | MS_SLAVE, - NULL); + sc_do_mount(SNAP_MOUNT_DIR, dst, NULL, MS_BIND | MS_REC, NULL); sc_do_mount("none", dst, NULL, MS_REC | MS_SLAVE, NULL); // Create the hostfs directory if one is missing. This directory is a part // of packaging now so perhaps this code can be removed later. @@ -679,14 +678,6 @@ void sc_ensure_shared_snap_mount(void) } } -static void sc_make_slave_mount_ns(void) -{ - // In our new mount namespace, recursively change all mounts - // to slave mode, so we see changes from the parent namespace - // but don't propagate our own changes. - sc_do_mount("none", "/", NULL, MS_REC | MS_SLAVE, NULL); -} - void sc_setup_user_mounts(struct sc_apparmor *apparmor, int snap_update_ns_fd, const char *snap_name) { @@ -702,6 +693,9 @@ void sc_setup_user_mounts(struct sc_apparmor *apparmor, int snap_update_ns_fd, return; } - sc_make_slave_mount_ns(); + // In our new mount namespace, recursively change all mounts + // to slave mode, so we see changes from the parent namespace + // but don't propagate our own changes. + sc_do_mount("none", "/", NULL, MS_REC | MS_SLAVE, NULL); sc_call_snap_update_ns_as_user(snap_update_ns_fd, snap_name, apparmor); } diff --git a/cmd/snap-confine/seccomp-support-ext.c b/cmd/snap-confine/seccomp-support-ext.c index c3892648df..770b6b5c33 100644 --- a/cmd/snap-confine/seccomp-support-ext.c +++ b/cmd/snap-confine/seccomp-support-ext.c @@ -62,33 +62,17 @@ size_t sc_read_seccomp_filter(const char *filename, char *buf, size_t buf_size) } void sc_apply_seccomp_filter(struct sock_fprog *prog) { - uid_t real_uid, effective_uid, saved_uid; int err; - if (getresuid(&real_uid, &effective_uid, &saved_uid) < 0) { - die("cannot call getresuid"); - } - - // If we can, raise privileges so that we can load the BPF into the kernel - // via 'prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)'. - debug("raising privileges to load seccomp profile"); - if (effective_uid != 0 && saved_uid == 0) { - if (seteuid(0) != 0) { - die("seteuid failed"); - } - if (geteuid() != 0) { - die("raising privs before seccomp_load did not work"); - } - } - - // Load filter into the kernel. + // Load filter into the kernel (by this point we have dropped to the + // calling user but still retain CAP_SYS_ADMIN). // // Importantly we are intentionally *not* setting NO_NEW_PRIVS because it - // interferes with exec transitions in AppArmor with certain snappy + // interferes with exec transitions in AppArmor with certain snapd // interfaces. Not setting NO_NEW_PRIVS does mean that applications can // adjust their sandbox if they have CAP_SYS_ADMIN or, if running on < 4.8 // kernels, break out of the seccomp via ptrace. Both CAP_SYS_ADMIN and - // 'ptrace (trace)' are blocked by AppArmor with typical snappy interfaces. + // 'ptrace (trace)' are blocked by AppArmor with typical snapd interfaces. err = seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_LOG, prog); if (err != 0) { /* The profile may fail to load using the "modern" interface. @@ -107,16 +91,4 @@ void sc_apply_seccomp_filter(struct sock_fprog *prog) { die("cannot apply seccomp profile"); } } - - /* Drop privileges again. */ - debug("dropping privileges after loading seccomp profile"); - if (geteuid() == 0) { - unsigned real_uid = getuid(); - if (seteuid(real_uid) != 0) { - die("seteuid failed"); - } - if (real_uid != 0 && geteuid() == 0) { - die("dropping privs after seccomp_load did not work"); - } - } } diff --git a/cmd/snap-confine/snap-confine-args.c b/cmd/snap-confine/snap-confine-args.c index 1f959880fb..f660a8aef9 100644 --- a/cmd/snap-confine/snap-confine-args.c +++ b/cmd/snap-confine/snap-confine-args.c @@ -37,7 +37,7 @@ struct sc_args { }; struct sc_args *sc_nonfatal_parse_args(int *argcp, char ***argvp, - sc_error **errorp) + sc_error ** errorp) { struct sc_args *args = NULL; sc_error *err = NULL; diff --git a/cmd/snap-confine/snap-confine-args.h b/cmd/snap-confine/snap-confine-args.h index 9aa3111fb7..f927b1cf5d 100644 --- a/cmd/snap-confine/snap-confine-args.h +++ b/cmd/snap-confine/snap-confine-args.h @@ -66,7 +66,7 @@ struct sc_args; **/ __attribute__((warn_unused_result)) struct sc_args *sc_nonfatal_parse_args(int *argcp, char ***argvp, - sc_error **errorp); + sc_error ** errorp); /** * Free the object describing command-line arguments to snap-confine. diff --git a/cmd/snap-confine/snap-confine.apparmor.in b/cmd/snap-confine/snap-confine.apparmor.in index 142c6d119e..9fd380c63f 100644 --- a/cmd/snap-confine/snap-confine.apparmor.in +++ b/cmd/snap-confine/snap-confine.apparmor.in @@ -38,6 +38,8 @@ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libudev.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libseccomp.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcap.so* mr, + # Needed to run /usr/bin/sh for snap-device-helper. + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libtinfo.so* mr, @LIBEXECDIR@/snap-confine mr, @@ -521,7 +523,7 @@ /{,var/lib/snapd/}snap/{core,snapd}/*/usr/lib/snapd/snap-discard-ns rix, /var/lib/snapd/hostfs/{,var/lib/snapd/}snap/{core,snapd}/*/usr/lib/snapd/snap-discard-ns rix, - # Allow mounting /var/lib/jenkinks from the host into the snap. + # Allow mounting /var/lib/jenkins from the host into the snap. mount options=(rw rbind) /var/lib/jenkins/ -> /tmp/snap.rootfs_*/var/lib/jenkins/, mount options=(rw rslave) -> /tmp/snap.rootfs_*/var/lib/jenkins/, } diff --git a/cmd/snap-confine/snap-confine.c b/cmd/snap-confine/snap-confine.c index 00812bd310..f13744e6df 100644 --- a/cmd/snap-confine/snap-confine.c +++ b/cmd/snap-confine/snap-confine.c @@ -23,9 +23,11 @@ #include <glob.h> #include <sched.h> #include <signal.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/capability.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -227,7 +229,8 @@ static void sc_restore_process_state(const sc_preserved_process_state * * designated user so UNIX permissions are in effect. */ if (fchdir(inner_cwd_fd) < 0) { if (errno == EPERM || errno == EACCES) { - debug("cannot access original working directory %s", orig_cwd); + debug("cannot access original working directory %s", + orig_cwd); goto the_void; } die("cannot restore original working directory via path"); @@ -237,9 +240,9 @@ static void sc_restore_process_state(const sc_preserved_process_state * * instruct the user and avoid potential confusion. This mostly applies to * tools that are invoked from /tmp. */ if (proc_state->file_info_orig_cwd.st_dev == - file_info_inner.st_dev - && proc_state->file_info_orig_cwd.st_ino == - file_info_inner.st_ino) { + file_info_inner.st_dev + && proc_state->file_info_orig_cwd.st_ino == + file_info_inner.st_ino) { /* The path of the original working directory points to the same * inode as before. */ debug("working directory restored to %s", orig_cwd); @@ -301,7 +304,8 @@ int main(int argc, char **argv) struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL; sc_preserved_process_state proc_state SC_CLEANUP(sc_cleanup_preserved_process_state) = { - .orig_umask = 0,.orig_cwd_fd = -1}; + .orig_umask = 0,.orig_cwd_fd = -1 + }; args = sc_nonfatal_parse_args(&argc, &argv, &err); sc_die_on_error(err); @@ -329,8 +333,12 @@ int main(int argc, char **argv) // Who are we? uid_t real_uid, effective_uid, saved_uid; gid_t real_gid, effective_gid, saved_gid; - getresuid(&real_uid, &effective_uid, &saved_uid); - getresgid(&real_gid, &effective_gid, &saved_gid); + if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0) { + die("getresuid failed"); + } + if (getresgid(&real_gid, &effective_gid, &saved_gid) != 0) { + die("getresgid failed"); + } debug("ruid: %d, euid: %d, suid: %d", real_uid, effective_uid, saved_uid); debug("rgid: %d, egid: %d, sgid: %d", @@ -358,9 +366,13 @@ int main(int argc, char **argv) sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; snap_context = sc_cookie_get_from_snapd(invocation.snap_instance, &err); - if (err != NULL) { - error("%s\n", sc_error_msg(err)); - } + /* While the cookie is normally present due to various protection + * mechanisms ensuring its creation from snapd, we are not considering + * it a critical error for snap-confine in the case it is absent. When + * absent snaps attempting to utilize snapctl to interact with snapd + * will fail but it is more important to run a little than break + * entirely in case snapd-side code is incorrect. Therefore error + * information is collected but discarded. */ } struct sc_apparmor apparmor; @@ -410,18 +422,48 @@ int main(int argc, char **argv) // For classic and confined snaps sc_selinux_set_snap_execcon(); #endif - if (sc_apply_seccomp_profile_for_security_tag(invocation.security_tag)) { - /* If the process is not explicitly unconfined then load the global - * profile as well. */ - sc_apply_global_seccomp_profile(); - } if (snap_context != NULL) { setenv("SNAP_COOKIE", snap_context, 1); // for compatibility, if facing older snapd. setenv("SNAP_CONTEXT", snap_context, 1); } + // Normally setuid/setgid not only permanently drops the UID/GID, but + // also clears the capabilities bounding sets (see "Effect of user ID + // changes on capabilities" in 'man capabilities'). To load a seccomp + // profile, we need either CAP_SYS_ADMIN or PR_SET_NO_NEW_PRIVS. Since + // NNP causes issues with AppArmor and exec transitions in certain + // snapd interfaces, keep CAP_SYS_ADMIN temporarily when we are + // permanently dropping privileges. + if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0) { + die("getresuid failed"); + } + debug("ruid: %d, euid: %d, suid: %d", + real_uid, effective_uid, saved_uid); + struct __user_cap_header_struct hdr = + { _LINUX_CAPABILITY_VERSION_3, 0 }; + struct __user_cap_data_struct cap_data[2] = { {0} }; + + // At this point in time, if we are going to permanently drop our + // effective_uid will not be '0' but our saved_uid will be '0'. Detect + // and save when we are in the this state so know when to setup the + // capabilities bounding set, regain CAP_SYS_ADMIN and later drop it. + bool keep_sys_admin = effective_uid != 0 && saved_uid == 0; + if (keep_sys_admin) { + debug("setting capabilities bounding set"); + // clear all 32 bit caps but SYS_ADMIN, with none inheritable + cap_data[0].effective = CAP_TO_MASK(CAP_SYS_ADMIN); + cap_data[0].permitted = cap_data[0].effective; + cap_data[0].inheritable = 0; + // clear all 64 bit caps + cap_data[1].effective = 0; + cap_data[1].permitted = 0; + cap_data[1].inheritable = 0; + if (capset(&hdr, cap_data) != 0) { + die("capset failed"); + } + } // Permanently drop if not root - if (geteuid() == 0) { + if (effective_uid == 0) { // Note that we do not call setgroups() here because its ok // that the user keeps the groups he already belongs to if (setgid(real_gid) != 0) @@ -434,6 +476,32 @@ int main(int argc, char **argv) if (real_uid != 0 && (getgid() == 0 || getegid() == 0)) die("permanently dropping privs did not work"); } + // Now that we've permanently dropped, regain SYS_ADMIN + if (keep_sys_admin) { + debug("regaining SYS_ADMIN"); + cap_data[0].effective = CAP_TO_MASK(CAP_SYS_ADMIN); + cap_data[0].permitted = cap_data[0].effective; + if (capset(&hdr, cap_data) != 0) { + die("capset regain failed"); + } + } + // Now that we've dropped and regained SYS_ADMIN, we can load the + // seccomp profiles. + if (sc_apply_seccomp_profile_for_security_tag(invocation.security_tag)) { + // If the process is not explicitly unconfined then load the + // global profile as well. + sc_apply_global_seccomp_profile(); + } + // Even though we set inheritable to 0, let's clear SYS_ADMIN + // explicitly + if (keep_sys_admin) { + debug("clearing SYS_ADMIN"); + cap_data[0].effective = 0; + cap_data[0].permitted = cap_data[0].effective; + if (capset(&hdr, cap_data) != 0) { + die("capset clear failed"); + } + } // and exec the new executable argv[0] = (char *)invocation.executable; debug("execv(%s, %s...)", invocation.executable, argv[0]); @@ -504,6 +572,12 @@ static void enter_non_classic_execution_environment(sc_invocation * inv, // Init and check rootfs_dir, apply any fallback behaviors. sc_check_rootfs_dir(inv); + /** Populate and join the device control group. */ + struct snappy_udev udev_s; + if (snappy_udev_init(inv->security_tag, &udev_s) == 0) + setup_devices_cgroup(inv->security_tag, &udev_s); + snappy_udev_cleanup(&udev_s); + /** * is_normal_mode controls if we should pivot into the base snap. * @@ -532,7 +606,7 @@ static void enter_non_classic_execution_environment(sc_invocation * inv, **/ sc_distro distro = sc_classify_distro(); inv->is_normal_mode = distro != SC_DISTRO_CORE16 || - !sc_streq(inv->orig_base_snap_name, "core"); + !sc_streq(inv->orig_base_snap_name, "core"); /* Stale mount namespace discarded or no mount namespace to join. We need to construct a new mount namespace ourselves. @@ -637,8 +711,4 @@ static void enter_non_classic_execution_environment(sc_invocation * inv, die("cannot set environment variable '%s'", tmpd[i]); } } - struct snappy_udev udev_s; - if (snappy_udev_init(inv->security_tag, &udev_s) == 0) - setup_devices_cgroup(inv->security_tag, &udev_s); - snappy_udev_cleanup(&udev_s); } diff --git a/cmd/snap-confine/snap-device-helper-test.c b/cmd/snap-confine/snap-device-helper-test.c index ca3ad40a15..3fccd0d87d 100644 --- a/cmd/snap-confine/snap-device-helper-test.c +++ b/cmd/snap-confine/snap-device-helper-test.c @@ -196,28 +196,43 @@ static struct sdh_test_data add_data = { "add", "snap.foo.bar", "snap_foo_bar", "devices.allow", "devices.deny" }; static struct sdh_test_data change_data = { "change", "snap.foo.bar", "snap_foo_bar", "devices.allow", -"devices.deny" }; + "devices.deny" +}; + static struct sdh_test_data remove_data = { "remove", "snap.foo.bar", "snap_foo_bar", "devices.deny", -"devices.allow" }; + "devices.allow" +}; + static struct sdh_test_data instance_add_data = { "add", "snap.foo_bar.baz", "snap_foo_bar_baz", "devices.allow", -"devices.deny" }; + "devices.deny" +}; + static struct sdh_test_data instance_change_data = { "change", "snap.foo_bar.baz", "snap_foo_bar_baz", "devices.allow", -"devices.deny" }; + "devices.deny" +}; + static struct sdh_test_data instance_remove_data = { "remove", "snap.foo_bar.baz", "snap_foo_bar_baz", "devices.deny", -"devices.allow" }; + "devices.allow" +}; + static struct sdh_test_data add_hook_data = { "add", "snap.foo.hook.configure", "snap_foo_hook_configure", -"devices.allow", "devices.deny" }; + "devices.allow", "devices.deny" +}; + static struct sdh_test_data instance_add_hook_data = { "add", "snap.foo_bar.hook.configure", "snap_foo_bar_hook_configure", -"devices.allow", "devices.deny" }; + "devices.allow", "devices.deny" +}; + static struct sdh_test_data instance_add_instance_name_is_hook_data = { "add", "snap.foo_hook.hook.configure", "snap_foo_hook_hook_configure", -"devices.allow", "devices.deny" }; + "devices.allow", "devices.deny" +}; static void __attribute__((constructor)) init(void) { diff --git a/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml b/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml index 00727918bb..a0c4760d2e 100644 --- a/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml +++ b/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml @@ -15,7 +15,7 @@ execute: | echo "We can now run busybox true and expect it to fail" orig_ratelimit=$(sysctl -n kernel.printk_ratelimit) sysctl -w kernel.printk_ratelimit=0 - ! /snap/bin/snapd-hacker-toolbelt.busybox true + not /snap/bin/snapd-hacker-toolbelt.busybox true sysctl -w kernel.printk_ratelimit=$orig_ratelimit echo "Not only the command failed because snap-confine failed, we see why!" dmesg --ctime | grep 'apparmor="DENIED" operation="mount" info="failed srcname match" error=-13 profile="/usr/lib/snapd/snap-confine" name="/snap/bin/" pid=[0-9]\+ comm="ubuntu-core-lau" srcname="/snap/snapd-hacker-toolbelt/[0-9]\+/mnt/" flags="rw, bind"' diff --git a/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml b/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml index 4eb3a66cdb..5748d0d1d5 100644 --- a/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml +++ b/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml @@ -15,7 +15,7 @@ execute: | echo "We can now run busybox true and expect it to fail" orig_ratelimit=$(sysctl -n kernel.printk_ratelimit) sysctl -w kernel.printk_ratelimit=0 - ! /snap/bin/snapd-hacker-toolbelt.busybox true + not /snap/bin/snapd-hacker-toolbelt.busybox true sysctl -w kernel.printk_ratelimit=$orig_ratelimit echo "Not only the command failed because snap-confine failed, we see why!" dmesg --ctime | grep 'apparmor="DENIED" operation="mount" info="failed srcname match" error=-13 profile="/usr/lib/snapd/snap-confine" name="/snap/snapd-hacker-toolbelt/[0-9]\+/mnt/" pid=[0-9]\+ comm="ubuntu-core-lau" srcname="/snap/bin/" flags="rw, bind"' diff --git a/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml b/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml index 3457952136..3ff9f7c96b 100644 --- a/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml +++ b/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml @@ -44,9 +44,9 @@ execute: | PATH=/foo:$PATH TESTVAR=bar hello-world.env | grep PATH cat /run/udev/spread-test.out echo "Ensure user-specified PATH is not used" - ! grep 'PATH=/foo' /run/udev/spread-test.out + not grep 'PATH=/foo' /run/udev/spread-test.out echo "Ensure environment is clean" - ! grep 'TESTVAR=bar' /run/udev/spread-test.out + not grep 'TESTVAR=bar' /run/udev/spread-test.out restore: | exit 0 echo "Remove hello-world" diff --git a/cmd/snap-confine/udev-support.c b/cmd/snap-confine/udev-support.c index 4781882d41..0c80de1557 100644 --- a/cmd/snap-confine/udev-support.c +++ b/cmd/snap-confine/udev-support.c @@ -73,6 +73,10 @@ _run_snappy_app_dev_add_majmin(struct snappy_udev *udev_s, execle("/usr/lib/snapd/snap-device-helper", "/usr/lib/snapd/snap-device-helper", "add", udev_s->tagname, path, buf, NULL, env); + else if (access("/usr/libexec/snapd/snap-device-helper", X_OK) == 0) + execle("/usr/libexec/snapd/snap-device-helper", + "/usr/libexec/snapd/snap-device-helper", "add", + udev_s->tagname, path, buf, NULL, env); else execle("/lib/udev/snappy-app-dev", "/lib/udev/snappy-app-dev", "add", diff --git a/cmd/snap-confine/user-support.c b/cmd/snap-confine/user-support.c index fee292ed0f..c9115af323 100644 --- a/cmd/snap-confine/user-support.c +++ b/cmd/snap-confine/user-support.c @@ -21,6 +21,7 @@ #include <stdlib.h> #include <sys/stat.h> +#include "../libsnap-confine-private/string-utils.h" #include "../libsnap-confine-private/utils.h" void setup_user_data(void) @@ -37,6 +38,11 @@ void setup_user_data(void) debug("creating user data directory: %s", user_data); if (sc_nonfatal_mkpath(user_data, 0755) < 0) { + if (errno == EROFS && !sc_startswith(user_data, "/home/")) { + // clear errno or it will be displayed in die() + errno = 0; + die("Sorry, home directories outside of /home are not currently supported. \nSee https://forum.snapcraft.io/t/11209 for details."); + } die("cannot create user data directory: %s", user_data); }; } |
