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

snap-confine: bind mount /usr/lib/snapd relative to snap-confine #3956

Merged
merged 8 commits into from
Sep 25, 2017

Conversation

mvo5
Copy link
Contributor

@mvo5 mvo5 commented Sep 22, 2017

Use the basedir of snap-confine to bind mount as /usr/lib/snapd
when using bases. This ensures we use the re-execed tools if we
are re-execing. This fixes the bug that busybox-static does not
work with the "bare" snap when using a snapd that re-execs from
e.g. beta.

Use the basedir of snap-confine to bind mount as /usr/lib/snapd
when using bases. This ensures we use the re-execed tools if we
are re-execing. This fixes the bug that busybox-static does not
work with the "bare" snap when using a snapd that re-execs from
e.g. beta.
@mvo5 mvo5 added this to the 2.28 milestone Sep 22, 2017
Copy link
Contributor

@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.

Looks good. I think we can remove one of the calls to sc_must_snprintf but it is just an optimization. Technically dirname can return the . directory but that would require the attacker to be able to spoof /proc/self/exe already. I don't think we need more defense there. CC @jdstrand

@mvo5 mvo5 requested a review from jdstrand September 22, 2017 10:11
Copy link
Contributor

@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.

Thank you for the improvement :)

char self[PATH_MAX + 1] = { 0, };
if (readlink("/proc/self/exe", self, sizeof self) > 0) {
char *dir = dirname(self);
sc_must_snprintf(src, sizeof src, "%s", dir);
src = dirname(self);
Copy link
Contributor

Choose a reason for hiding this comment

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

❤️

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.

Comments inline. I think in a follow-up PR it would make sense to look for other instances of readlink() and dirname() in snap-confine and make sure we are being consistent. A small refactor to call a helper function might be in order.

// that we are re-execing from
char *src = LIBEXECDIR;
char self[PATH_MAX + 1] = { 0, };
if (readlink("/proc/self/exe", self, sizeof self) > 0) {

Choose a reason for hiding this comment

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

Can we use sizeof(self) - 1 here? readlink() doesn't append a NULL byte so if for some reason the kernel returned too much, we are safe (since you initialized 'self' to all NULLs). That seems to be your intent for using PATH_MAX + 1, but let's not trust there are no kernel bugs either.

Choose a reason for hiding this comment

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

I might also point out that the not all /proc/*/exes are symlinks, but they will be for /proc/self/exe for snap-confine (ie, kernel processes won't be a symlink). If we are guarding against kernel bugs, we could:

if (strstr(self, "/snap-confine") == NULL) {
    die(...);
}

This is a harder kernel bug to imagine happening, but it's a cheap check. @zyga, I didn't think of this one in the 3621 review. Follow-up PR would be fine there.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jdstrand ack, will do.

char self[PATH_MAX + 1] = { 0, };
if (readlink("/proc/self/exe", self, sizeof self) > 0) {
src = dirname(self);
}

Choose a reason for hiding this comment

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

I think we should } else { die(...) }. no? If the kernel is failing to give what we need, we probably don't want to continue using whatever LIBEXECDIR is set to, but maybe I am missing something? Not to mention, we are initializing src to LIBEXECDIR, but then we immediately reassign it to dirname so, barring a kernel bug, we are using extra memory on the stack for no reason.

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 was initially thinking that LIBEXECDIR is a valid fallback if for whatever reason we cannot read /proc - however if you think we should fail hard in this case I'm happy to add code for this.

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 updated the code to die() if /proc/self/exe cannot be read.

Choose a reason for hiding this comment

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

I think it would make the most sense to use char *src = NULL; and require readlink() to succeed and when it does, src = dirname(self).

char *src = LIBEXECDIR;
char self[PATH_MAX + 1] = { 0, };
if (readlink("/proc/self/exe", self, sizeof self) > 0) {
src = dirname(self);
Copy link

@jdstrand jdstrand Sep 22, 2017

Choose a reason for hiding this comment

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

Since we are guarding against kernel bugs, and we know that dirname will always return a NULL-terminated string, we can do a cheap check on the first character (we are doing this in PR 3621's bootstrap.c, for example). We should probably check for other occurrences in the snap-confine sources):

// dirname(path) might return '.' depending on path. /proc/self/exe should always point
// to an absolute path, but let's guarantee that.
if (src[0] != '/') {
    die(...)
}

@@ -363,9 +363,19 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config)
// where $ROOT is either "/" or the "/snap/core/current"
// that we are re-execing from
char *src = LIBEXECDIR;
char self[PATH_MAX + 1] = { 0, };
if (readlink("/proc/self/exe", self, sizeof self) > 0) {
char self[PATH_MAX] = { 0, };

Choose a reason for hiding this comment

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

Note I didn't mean for you to remove PATH_MAX+1 here. Your change for sizeof(self) -1 guards against the issue I was referring to (so this code is safe), but now it means it will fail in the unlikely event that a valid path has length of PATH_MAX.

}
src = dirname(self);
// dirname(path) might return '.' depending on path. /proc/self/exe should always point
// to an absolute path, but let's guarantee that.

Choose a reason for hiding this comment

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

Think you need a go fmt here.

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.

Thanks for the updates! Approving since the code here is hardened and robust, but please consider my follow on comment regarding PATH_MAX+1.

@mvo5
Copy link
Contributor Author

mvo5 commented Sep 22, 2017

Thanks! Sorry that I misunderstood the earlier comment. This is fixed now.

@zyga
Copy link
Contributor

zyga commented Sep 25, 2017

@mvo5 the test failures look real. I think some apparmor tweak is needed for snap-confine's profile.

mvo5 and others added 2 commits September 25, 2017 15:38
When app snap uses a custom base snap we bind-mount the aforementioned
directory from the core snap but that bind mount can be safely made to
be read-only.

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
@codecov-io
Copy link

Codecov Report

Merging #3956 into master will increase coverage by 0.03%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3956      +/-   ##
==========================================
+ Coverage   75.93%   75.96%   +0.03%     
==========================================
  Files         420      423       +3     
  Lines       36310    36525     +215     
==========================================
+ Hits        27571    27746     +175     
- Misses       6807     6838      +31     
- Partials     1932     1941       +9
Impacted Files Coverage Δ
interfaces/apparmor/backend.go 70.73% <0%> (-1.32%) ⬇️
cmd/snap-update-ns/main.go 0% <0%> (ø) ⬆️
interfaces/builtin/system_observe.go 100% <0%> (ø) ⬆️
interfaces/builtin/opengl.go 100% <0%> (ø) ⬆️
cmd/snap-repair/trace.go 81.31% <0%> (ø)
cmd/snap-repair/cmd_show.go 82.22% <0%> (ø)
cmd/snap-repair/cmd_list.go 87.17% <0%> (ø)
dirs/dirs.go 98.09% <0%> (+0.01%) ⬆️
snap/validate.go 95.75% <0%> (+0.05%) ⬆️
cmd/snap-repair/runner.go 82.59% <0%> (+0.18%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f6a3064...11c4a5e. Read the comment docs.

@mvo5 mvo5 merged commit 9c330f8 into canonical:master Sep 25, 2017
mvo5 added a commit that referenced this pull request Sep 25, 2017
* snap-confine: bind mount /usr/lib/snapd relative to snap-confine

Use the basedir of snap-confine to bind mount as /usr/lib/snapd
when using bases. This ensures we use the re-execed tools if we
are re-execing. This fixes the bug that busybox-static does not
work with the "bare" snap when using a snapd that re-execs from
e.g. beta.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants