Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
Add support for bind profiles #43
Conversation
zyga
reviewed
Jun 20, 2016
| + die("snprintf returned unexpected value"); | ||
| + } | ||
| + | ||
| + f = fopen(profile_path, "r"); |
zyga
Jun 20, 2016
Collaborator
Instead of parsing this manually I'd recommend using a glibc function like getmntent. This way we can have a very familiar syntax and no parser to write.
mvo5
added some commits
Jun 20, 2016
zyga
reviewed
Jun 21, 2016
| + snap install hello-world | ||
| + hello-world.echo | grep Hello | ||
| + hello-world.env | grep SNAP_NAME=hello-world | ||
| + hello-world.evil && exit 1 || true |
zyga
reviewed
Jun 21, 2016
| @@ -0,0 +1,38 @@ | ||
| +project: snap-confine |
zyga
reviewed
Jun 21, 2016
| + | ||
| +prepare: | | ||
| + [ "$REUSE_PROJECT" != 1 ] || exit 0 | ||
| + |
mvo5
Jun 21, 2016
Contributor
I don't know, sorry, I think it will have to be something like if grep ubuntu /etc/os-release and similar.
zyga
reviewed
Jun 21, 2016
| @@ -293,3 +294,58 @@ void setup_slave_mount_namespace() | ||
| die("can not make make / rslave"); | ||
| } | ||
| } | ||
| + | ||
| +#define BIND_MAX_LINE_LENGTH 512 // arbitrary |
mvo5
Jun 21, 2016
Contributor
Thanks, this is actually a leftover from the previous iteration of the code, removed now.
zyga
reviewed
Jun 21, 2016
| + | ||
| +void setup_bind_mounts(const char *appname) | ||
| +{ | ||
| + debug("setup_bind_mounts %s", appname); |
zyga
reviewed
Jun 21, 2016
| + FILE *f = NULL; | ||
| + const char *bind_profile_dir = "/var/lib/snapd/bind/profiles/"; | ||
| + | ||
| + char profile_path[512]; // arbitrary path name limit |
zyga
reviewed
Jun 21, 2016
| + return; | ||
| + // however any other error is a real error | ||
| + if (f == NULL) { | ||
| + fprintf(stderr, "Can not open %s (%s)\n", profile_path, |
zyga
Jun 21, 2016
Collaborator
I guess you can just die("cannot open %s", profile_path) and let it do the rest
zyga
reviewed
Jun 21, 2016
| + if (strcmp(m->mnt_type, "") != 0) { | ||
| + die("only bind mounts are supported"); | ||
| + } | ||
| + if (strstr(m->mnt_opts, "bind") == NULL) { |
zyga
reviewed
Jun 21, 2016
| + if (strstr(m->mnt_opts, "bind") == NULL) { | ||
| + die("need bind mount flag"); | ||
| + } | ||
| + if (strstr(m->mnt_opts, "ro") != NULL) { |
zyga
reviewed
Jun 21, 2016
| + flags |= MS_RDONLY; | ||
| + } | ||
| + | ||
| + if (mount(m->mnt_fsname, m->mnt_dir, NULL, flags, NULL) != 0) { |
zyga
Jun 21, 2016
Collaborator
I'd pass all the data from m here, including m->mnt_type. While we don't use it yet we should validate all the values (and we do) so we should also just pass them here naturally. Not a strong opinion but please consider it.
mvo5
Jun 21, 2016
Contributor
I'm sitting a bit on the fence. It does make sense, OTOH given that this is suid and we don't need it seems ok to leave out.
zyga
reviewed
Jun 21, 2016
| + die("unable to bind private /tmp"); | ||
| + } | ||
| + } | ||
| + |
mvo5
Jun 21, 2016
Contributor
I took that from the seccomp code from jamie, I agree that it should be fine to just fclose()
zyga
reviewed
Jun 21, 2016
| +EOF | ||
| + | ||
| +printf "Test that bind mounts work " | ||
| +"$L" snap.name.app /bin/true |
zyga
Jun 21, 2016
Collaborator
It would be better to test that they really work by creating /src/foo and checking it exists in /dst/foo
mvo5
Jun 21, 2016
Contributor
Thanks, this test is actually bogus :/ I think we need a proper spread test here, its a bit tricky because snapd does not have the full support for writing the fstab file yet, so I will ponder a bit if I can fabricate stuff or if I will just wait for snapd.
zyga
reviewed
Jun 21, 2016
| @@ -318,8 +317,7 @@ void setup_bind_mounts(const char *appname) | ||
| return; | ||
| // however any other error is a real error | ||
| if (f == NULL) { | ||
| - fprintf(stderr, "Can not open %s (%s)\n", profile_path, | ||
| - strerror(errno)); | ||
| + fprintf(stderr, "cannot open %s\n", profile_path); |
zyga
Jun 21, 2016
Collaborator
I meat that die() does fprintf anyway so you can just do the die thing I listed and it will do the rest
mvo5
added some commits
Jun 22, 2016
zyga
reviewed
Jun 22, 2016
| @@ -317,8 +317,7 @@ void setup_bind_mounts(const char *appname) | ||
| return; | ||
| // however any other error is a real error | ||
| if (f == NULL) { | ||
| - fprintf(stderr, "cannot open %s\n", profile_path); | ||
| - die("aborting"); | ||
| + die("cannot open %s\n", profile_path); |
tyhicks
reviewed
Jun 22, 2016
| + char profile_path[PATH_MAX]; | ||
| + int snprintf_rc = | ||
| + snprintf(profile_path, sizeof(profile_path), "%s/%s.bind", | ||
| + bind_profile_dir, appname); |
tyhicks
Jun 22, 2016
Collaborator
Please switch to the helper function must_snprintf() so that you don't have to do your own error handling here.
tyhicks
reviewed
Jun 22, 2016
| + while ((m = getmntent(f)) != NULL) { | ||
| + int flags = MS_BIND; | ||
| + | ||
| + if (strcmp(m->mnt_type, "") != 0) { |
tyhicks
Jun 22, 2016
Collaborator
Is "none" for the fs_vfstype in the fstab file converted to an empty string? I think I remember that being the case but it isn't documented in the man page.
tyhicks
reviewed
Jun 22, 2016
| + if (hasmntopt(m, "ro") != NULL) { | ||
| + flags |= MS_RDONLY; | ||
| + } | ||
| + |
tyhicks
Jun 22, 2016
Collaborator
What about the other mount options that need to be converted from string options to flags before calling mount()? I think that we at least need to support the other security related ones. MS_NODEV, MS_NOEXEC, and MS_NOSUID.
tyhicks
Jun 22, 2016
Collaborator
Thinking about this some more, we should probably default to MS_NODEV, MS_NOEXEC, and MS_NOSUID and then require "dev", "exec", and/or "suid" to clear those bits.
mvo5
Jun 22, 2016
Contributor
I'm fine adding this, I'm not sure about MS_NOEXEC - that seems to be very little risk, no? Given that everything will run inside an apparmor confinement anyway?
tyhicks
Jun 22, 2016
Collaborator
You're correct that AppArmor will be confining everything that is bind mounted in so any binaries made available via the bind mount will still be confined. My suggestion was simply based on the idea to have the most secure defaults and then have the interface author have to specify "exec" if they expected binaries to be executed from the bind mount.
I'm ok either way but defaulting to nosuid,nodev while also defaulting to exec may be a bit confusing to interface authors.
tyhicks
reviewed
Jun 22, 2016
| + die("need bind mount flag"); | ||
| + } | ||
| + if (hasmntopt(m, "ro") != NULL) { | ||
| + flags |= MS_RDONLY; |
tyhicks
Jun 22, 2016
Collaborator
Won't the majority of bind mounts be ro? If so, I'd prefer that we initialize flags with (MS_BIND | MS_RDONLY) at the top of this function and then require "rw" to be present in the options in order to clear the MS_RDONLY bit and make the mount writable.
tyhicks
reviewed
Jun 22, 2016
| + flags |= MS_RDONLY; | ||
| + } | ||
| + | ||
| + if (mount(m->mnt_fsname, m->mnt_dir, NULL, flags, NULL) != 0) { |
tyhicks
Jun 22, 2016
Collaborator
This is where real vulnerabilities could creep in. I need a little more time to think about the source and destination mount points. There are two possible attacks that come to mind.
- An unprivileged user tricks the running-as-root setup_bind_mounts() to gain privileges on the system by setting up malicious symlinks in the source mount point path.
- A malicious snap modifies a rw bind mount and drops in a malicious symlink that is then followed by setup_bind_mounts() the next time the snap's bind mounts are set up.
We protect against the 2nd attack in LXC by very carefully chdir()'ing to the destination mount point, verifying inode ownership and not following symlinks along the way.
The 1st attack is a tough one. Hopefully we wouldn't need bind mount a directory owned by a non-root user and could simply enforce that in the code. What are your thoughts on that?
mvo5
Jun 22, 2016
Contributor
Thanks, this is interessting. We control the writing of the fstab file via snapd. The most common case is that we will have both src and dst in the a snap directory, which means that the directories will be both read-only. However there are other cases like when we allow bind mounting of fonts from the OS or when we allow bind mounting a shared data space. I think we can enforce the root requirement here, i.e. both src and dst must be root owned. Not sure if that is enough in a world where we give snaps root though. Suggestions for more defensiveness certainly welcome.
tyhicks
Jun 23, 2016
Collaborator
You say that, in most cases, they'll be read only but will the snap author have control of any components in the src or dst paths?
Hmm... good point about snaps having root. That adds a new twist that I hadn't thought about.
I think if we can prevent from following symlinks in the src and dst paths, we should be ok. As I mentioned previously, we had to solve this problem for LXC and we could reuse a lot of that code. The patch can be found at lxc/lxc@592fd47.
Are src and dst both expected to be absolute paths or can they be relative paths, also?
zyga
Jun 27, 2016
Collaborator
Both src and dst are what snapd is written to do. I can expect us to use absolute paths here.
I'll add a mount wrapper that does what you describe and ask for a re-review.
tyhicks
Jun 27, 2016
Collaborator
The question of whether or not the snap author have control of any components in the src or dst paths is still open. I think that question must be answered before we can decide on what the bind mount code needs to protect against.
Also, will the unprivileged user running the snap have control of any components in the src or dst paths?
mvo5
added some commits
Jun 22, 2016
zyga
changed the title from
add setup_bind_mounts()
to
Add support for bind profiles
Jun 24, 2016
zyga
reviewed
Jun 24, 2016
| @@ -69,6 +69,9 @@ | ||
| # reading seccomp filters | ||
| /{tmp/snap.rootfs_*/,}var/lib/snapd/seccomp/profiles/* r, | ||
| + | ||
| + # reading mount profiles | ||
| + /{tmp/snap.rootfs_*/,}var/lib/snapd/mount/profiles/* r, |
zyga
Jun 24, 2016
Collaborator
The profiles/ component was removed from the snapd side. Please update this here.
zyga
reviewed
Jun 24, 2016
| + debug("%s: %s", __FUNCTION__, appname); | ||
| + | ||
| + FILE *f = NULL; | ||
| + const char *bind_profile_dir = "/var/lib/snapd/bind/profiles/"; |
zyga
reviewed
Jun 24, 2016
| + const char *bind_profile_dir = "/var/lib/snapd/bind/profiles/"; | ||
| + | ||
| + char profile_path[PATH_MAX]; | ||
| + must_snprintf(profile_path, sizeof(profile_path), "%s/%s.bind", |
zyga
reviewed
Jun 24, 2016
| + must_snprintf(profile_path, sizeof(profile_path), "%s/%s.bind", | ||
| + bind_profile_dir, appname); | ||
| + | ||
| + f = fopen(profile_path, "r"); |
zyga
Jun 24, 2016
Collaborator
Can you please use __attribute__((cleanup(sc_cleanup_file))) and add sc_close_file to cleanup-funcs.[ch] with something like this inside:
void sc_cleanup_file(f **FILE) {
if (*f != NULL) {
fclose(*f);
}
}
This should simplify the logic below.
zyga
reviewed
Jun 24, 2016
| @@ -23,4 +23,6 @@ void setup_private_pts(); | ||
| void setup_snappy_os_mounts(); | ||
| void setup_slave_mount_namespace(); | ||
| +void setup_bind_mounts(const char *appname); |
zyga
Jun 24, 2016
Collaborator
I'd prefer this to be the security tag, the app name is not so true here AFAIK
|
As discussed with @mvo5 I'll continue this branch to address the issues. |
|
Closing, reopened as #51 |
mvo5 commentedJun 19, 2016
This adds support for the new "bind" security backend of snapd. It is used for content sharing between snaps and the format that snapd writes is very simple:
/src /dst (ro)or/src /dst (rw).