Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
preserve TMPDIR and HOSTALIASES across snap-confine invocation (LP: #1682308) #3872
Conversation
mwhudson
changed the title from
preserve TMPDIR and HOSTALIASES across snap-confine invocation (LP: #…
to
preserve TMPDIR and HOSTALIASES across snap-confine invocation (LP: #1682308)
Sep 7, 2017
|
I'm not sure but perhaps this is only something one would want for classic snaps? |
codecov-io
commented
Sep 7, 2017
•
Codecov Report
@@ Coverage Diff @@
## master #3872 +/- ##
==========================================
- Coverage 75.8% 75.72% -0.08%
==========================================
Files 424 428 +4
Lines 36580 36689 +109
==========================================
+ Hits 27729 27784 +55
- Misses 6905 6951 +46
- Partials 1946 1954 +8
Continue to review full report at Codecov.
|
zyga
reviewed
Sep 7, 2017
Two questions, code looks good but not sure about what the motivation is and semantics should be exactly.
| -// envMap creates a map from the given environment string list, e.g. the | ||
| -// list returned from os.Environ() | ||
| +var unsafeEnv = map[string]bool{ | ||
| + "HOSTALIASES": true, |
zyga
Sep 7, 2017
Contributor
If the user sets HOSTALIASES to a directory that is not readable due to the sandbox shoud this be still preserved?
| -// list returned from os.Environ() | ||
| +var unsafeEnv = map[string]bool{ | ||
| + "HOSTALIASES": true, | ||
| + "TMPDIR": true, |
zyga
Sep 7, 2017
Contributor
I'm not sure TMPDIR should be preserved. The designated directory may not exist inside the execution environment. What is your motivation for preserving it?
|
The motivation is that this keeps confusing users of my Go snap: https://bugs.launchpad.net/snapd/+bug/1682308 I think your questions indicate the answer to my question: this should only be done for a classic snap, and it turns out that that's really easy (pushed). Here is the full list of variables glibc strips out: https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/generic/unsecvars.h;hb=HEAD -- I guess it would make sense to save some more of these, maybe even all of them? |
|
LGTM, but, should we ask Jamie to review this? |
|
I fixed the conflicts by amending the second commit to not touch the changelog (it was a mistake to commit it in the first place) |
niemeyer
requested changes
Sep 13, 2017
Idea looks good, as long as it's only for classic confinement indeed.
Just some details please:
| -func envMap(env []string) map[string]string { | ||
| +var unsafeEnv = map[string]bool{ | ||
| + "HOSTALIASES": true, | ||
| + "TMPDIR": true, |
| +// rename some variables that will be stripped out by the dynamic | ||
| +// linker executing the setuid snap-confine by prepending their names | ||
| +// with PreservedUnsafePrefix. | ||
| +func envMap(env []string, preserveUnsafeVars bool) map[string]string { | ||
| envMap := map[string]string{} | ||
| for _, kv := range env { |
mwhudson
Sep 13, 2017
Contributor
It took me a moment to realize why you wanted this so I added a comment too.
mwhudson
added some commits
Sep 13, 2017
| +// rename variables that will be stripped out by the dynamic linker | ||
| +// executing the setuid snap-confine by prepending their names with | ||
| +// PreservedUnsafePrefix. | ||
| +func envMap(env []string, preserveUnsafeVars bool) map[string]string { |
mvo5
Sep 19, 2017
Collaborator
nitpick - I'm not a fan of this boolean here. It makes it hard to reason about what the function is doing without looking at the signature. I.e.: what does envMap(env, true) mean? I would prefer a flag argument and a name, maybe something like envMap(env, {0, preserveUnsafeVars]) ?
mvo5
Sep 20, 2017
Collaborator
Yeah, this is what I had in mind, thanks for this! Sorry that I was hard to parse.
|
Code looks nice, thank you! I would love to see some sort of test for this, ideally both unit and spread. Please let us know if you would prefer help with this. |
mwhudson
added some commits
Sep 20, 2017
|
I've pushed some unit tests and a spread test. I haven't run the spread test locally so if it doesn't work I guess I'd like some help with that :) |
| @@ -17,6 +17,8 @@ execute: | | ||
| run_install --classic | ||
| $SNAPMOUNTDIR/bin/test-snapd-hello-classic | MATCH 'Hello Classic!' | ||
| + TMPDIR=/tmpdir $SNAPMOUNTDIR/bin/test-snapd-hello-classic t | MATCH TMPDIR=/tmpdir |
mwhudson
Sep 21, 2017
Contributor
This fails with "/bin/bash: line 65: SNAPMOUNTDIR: unbound variable" which doesn't make any sense to me. Also the instructions in HACKING.md for running the spread tests don't work. Help?
zyga
Sep 22, 2017
Contributor
If you have the a key for linode API access (aka spread key) you can run this test with:
spread -debug -v linode:ubuntu-16.04-64:tests/main/confinement-classic
(substitute target environment if you want other OS)
I would also suggest to cut on the number of workers to just one (see spread.yaml) as otherwise debugging is weird and you waste machines that do nothing useful.
mwhudson
added some commits
Sep 29, 2017
| @@ -21,7 +21,7 @@ execute: | | ||
| run_install --classic | ||
| $SNAP_MOUNT_DIR/bin/test-snapd-hello-classic | MATCH 'Hello Classic!' | ||
| - TMPDIR=/tmpdir $SNAPMOUNTDIR/bin/test-snapd-hello-classic t | MATCH TMPDIR=/tmpdir | ||
| + TMPDIR=/tmpdir $SNAP_MOUNT_DIR/bin/test-snapd-hello-classic t | MATCH TMPDIR=/tmpdir |
mwhudson
added some commits
Oct 3, 2017
|
This fails in the unit tests:
failure looks real. |
zyga
approved these changes
Oct 5, 2017
LGTM, some comments inline for your consideration.
| +type preserveUnsafeEnvFlag int8 | ||
| + | ||
| +const ( | ||
| + noPreserve preserveUnsafeEnvFlag = iota |
zyga
Oct 5, 2017
Contributor
noPreserve and doPreserve differ by one character. With my ageing eyes I find it hard to distinguish easily. How about simply preserve and notPreserve (other ideas welcome)
| -func envMap(env []string) map[string]string { | ||
| +// Environment variables glibc strips out when running a setuid binary. | ||
| +// Taken from https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/generic/unsecvars.h;hb=HEAD | ||
| +var unsafeEnv = map[string]bool{ |
zyga
Oct 5, 2017
Contributor
This would be a nice thing to go generate eventually. No need to do it now but maybe add a comment.
zyga
added some commits
Oct 5, 2017
jdstrand
requested changes
Oct 5, 2017
•
This looks ok to me overall, but please address my comment on renaming the unit tests. Also, while we test in spread that TMPDIR makes it through to the classic snap, we should also test that TMPDIR doesn't make it through for non-classic.
In terms of the approach, I think it is ok because the actual setuid binary (snap-confine) will still have these variables stripped (ie, we aren't adding to the attack surface for snap-confine). If the classic snap shipped a setuid binary, it would still be stripped at exec().
Furthermore, a classic snap is intended to work more with the system settings so preserving (at least some of these) variables for classic makes sense. That said, it seems to me since we are preserving the various LD_* variables, this PR may add confusion for users and/or may complicate matters related to https://forum.snapcraft.io/t/classic-snaps-failing-on-ubuntu-17-10/2324/7. I've not studied that issue closely so will not block on this point, but please consider it before merging.
For completeness and posterity, it is also worth noting that AppArmor looks at AT_SECURE with the profile transitions for Px, Cx and Ux. This PR will not affect that, since any exec transitions will still have the variables stripped at the time of the exec(). Specifically for snappy, classic snap policy intentionally has 'pix' for profile transitions (ie, to not strip these variables when the snap executes other binaries), and a non-snap application that has an apparmor profile that is executing a classic snap that has a Px/Cx/Ux rule for it will correctly still have these vars cleared on profile transition (just as the local admin specified).
| } | ||
| +} | ||
| + | ||
| +func (s *HTestSuite) TestExecEnvRenameTMPDIRForClassic(c *C) { |
jdstrand
Oct 5, 2017
•
Contributor
This should be named TestExecEnvNoRenameTMPDIRForNonClassic() and the next test should be named TestExecEnvRenameTMPDIRForClassic(), no? (in this function you are mocking a non-classic snap and the next a classic snap)
Ack.
OK, but I think I would also argue that if non-preservation of TMPDIR etc is important, we should probably filter them out ourselves (which would be easy to do in this branch, I guess) rather than relying on the dynamic loader doing it, as e.g. it doesn't look like musl does the same stripping or if for some reason we shipped a statically linked snap-confine there wouldn't be a dynamic loader at all. Do you think I should change envMap to filter out "unsafe" variables in the non-classic case?
Although the only variable my users actually seem to care about is TMPDIR, I think I would argue that not allowing these variables through is more confusing than blocking them. My sympathy for people who set LD_LIBRARY_PATH without knowing what they are doing is a bit limited.
|
mwhudson
added some commits
Oct 8, 2017
In addition, now that I've read that thread, the solution being proposed there in effect means that LD_LIBRARY_PATH will be ignored for binaries in classic snaps anyway. |
mwhudson commentedSep 7, 2017
•
Edited 1 time
-
mwhudson
Sep 7, 2017
Because snap-confine is setuid, the dynamic loader strips out certain
environment variables when starting the process. Work around this by renaming
them before invoking snap-confine and renaming them back in snap-exec.