Merge lp:~jdstrand/snap-confine/ubuntu-core-launcher.nnp-off into lp:~snappy-dev/snap-confine/trunk

Proposed by Jamie Strandboge
Status: Merged
Merged at revision: 99
Proposed branch: lp:~jdstrand/snap-confine/ubuntu-core-launcher.nnp-off
Merge into: lp:~snappy-dev/snap-confine/trunk
Diff against target: 181 lines (+79/-15)
5 files modified
debian/changelog (+10/-0)
src/main.c (+27/-14)
src/seccomp.c (+35/-1)
tests/test_create_user_data (+4/-0)
tests/test_restrictions_working (+3/-0)
To merge this branch: bzr merge lp:~jdstrand/snap-confine/ubuntu-core-launcher.nnp-off
Reviewer Review Type Date Requested Status
Tyler Hicks (community) Abstain
Seth Arnold Needs Fixing
Jamie Strandboge (community) Approve
Review via email: mp+289683@code.launchpad.net

Description of the change

Don't set NO_NEW_PRIVS. This requires changing privilege dropping since CAP_SYS_ADMIN is needed with seccomp_load(). This means temporarily dropping until seccomp_load(), then raising before and permanently dropping after the filter is applied. As a result, setuid/setgid is required in all policy (but is still mediated by AppArmor).

To post a comment you must log in.
Revision history for this message
Jamie Strandboge (jdstrand) wrote :

One point I want to mention is that I could have dropped privs permanently after seccomp_load() in seccomp.c but instead dropped temporarily there and permanently after seccomp_load_filters() in main.c. I did this because I felt the code was easier to follow which I thought outweighed being permanently dropped for the file close and seccomp_release().

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

Another point to mention is the handling of UBUNTU_CORE_LAUNCHER_NO_ROOT in two places. The following should help with the logic there and impact on racing the env with the suid launcher:

UBUNTU_CORE_LAUNCHER_NO_ROOT unset in both (root): disable nnp then raise to load_seccomp (correct)
UBUNTU_CORE_LAUNCHER_NO_ROOT set in both (non-root): nnp stays then no raise to load_seccomp (correct)
UBUNTU_CORE_LAUNCHER_NO_ROOT unset first (root) and set second (non-root): disable nnp then load_seccomp fails closed
UBUNTU_CORE_LAUNCHER_NO_ROOT set first (non-root) and unset second (root): nnp stays then load_seccomp. This is more strict than the intended behavior and will trigger and apparmor denial

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

Note in both cases where nnp stays is the current behavior where we aren't disabling nnp, which again, is more strict and will trigger an apparmor denial.

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

Yikes, the kernel is live which means snappy dimension on classic is busted. Per Tyler, uploaded this as is but I'll respond to any comments from Seth in a subsequent upload.

review: Approve
Revision history for this message
Seth Arnold (seth-arnold) wrote :

Most of these changes look good in isolation but the "out:" label in seccomp_load_filters() is asked to do too many things. It is currently trying to handle:

- error returns
- normal code flow
- run as a 'real root'
- run via setuid execution

seccomp_load_filters() should be adjusted to die immediately on any error and avoid use of "out" for that case.

Furthermore the extensive efforts to allow testing in non-production environments is very complicated. The calls to getenv() for UBUNTU_CORE_LAUNCHER_DEBUG and UBUNTU_CORE_LAUNCHER_NO_ROOT should instead use secure_getenv().

The other environment variables should probably also be converted to use secure_getenv() as well but they have far less effect on the execution pathways, only on the correctness of the resulting policy. (secure_getenv() may break the current system but this may simply be a reflection that environment variables are being asked to do tasks they shouldn't.)

Here's the full notes I collected; not everything is direct result of this merge proposal, but this seems as good a place as any to put them:

- run_snappy_app_dev_add() does not check fork() error return
- write_string_to_file() doesn't check fclose() error return
  - close(2) manpage describes why this is important
- snappy_udev_setup_required() seems very brittle
  - why don't all snaps require this anyway?
- main() argv[NR_ARGS] = (char*)binary -- very odd, argv[NR_ARGS]
  should not have been modified at any point during the execution, and the
  statement uses a comma rather than ; to separate it from the next
  statement, exec().
- main() extra arguments passed through argv[] are passed to the executed
  program; is this intentional?

- seccomp_load_filters() "snadbox"
- seccomp_load_filters() snprintf() does not check for >= 128 error return
- seccomp_load_filters() line-too-long error handling is awkward; rather
  than jumping to out: it should probably just die immediately.
- seccomp_load_filters() line-too-long test could be written simpler
  without line length checks and use strchr('\n') instead. (I think both
  will fail if the file doesn't end with a new line character.)
- seccomp_load_filters() out: error-handling is far too complicated.
  Errors should probably just die() and allow out: for the "normal" exit
  path.
- seccomp_load_filters() "@unrestricted" check is awkward -- strncmp()
  isn't needed and only confuses the matter -- and hiding "@unrestricted"
  several hundred lines into a file means the rules that were processed to
  that point are just dropped entirely. IF we're going to allow an
  "@unrestricted" escape hatch we should enforce that it is the entire
  contents of the file, not just one line in a file.

Thanks

review: Needs Fixing
Revision history for this message
Jamie Strandboge (jdstrand) wrote :

I've addressed all of the coding issues in lp:~jdstrand/ubuntu-core-launcher/security-review-fixes/. Specifically I:
- cleaned up 'out' handling and die instead
- fixed the typo
- verify snprint() >= 512 (I increased this length) and added tests
- add test for filter missing trailing newline (I didn't change the line-too-long handling, but did add tests and verify we must end with trailing newline
- use strcmp() with "@unrestricted". add @unrestricted near miss tests
- use getresuid() instead of UBUNTU_CORE_LAUNCHER_NO_ROOT
- replace getenv() with secure_getenv() everywhere we can
- check return code of fork()
- verify return code of fclose()
- simplified final execv()

In addition:
- don't support obsoleted SNAP_APP_TMPDIR and SNAP_APP_USER_DATA_PATH
- use uid_t and gid_t instead of unsigned
- check return codes of other (f)close()s to help futureproof

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

And for these two questions:

"- snappy_udev_setup_required() seems very brittle
  - why don't all snaps require this anyway?"

this is about to go away.

"- main() extra arguments passed through argv[] are passed to the executed
  program; is this intentional?"

yes

Revision history for this message
Tyler Hicks (tyhicks) wrote :

The seccomp_load_filters() error handling is still very confusing since there are a mix of die()'s and non-zero return values which ultimately end in a die(). If seccomp_load_filters() is going to internally call die() upon error in some conditions, it should be changed to have a void return and simply call die() in all error conditions. I believe that was what Seth was suggesting, as well.

review: Needs Fixing
Revision history for this message
Tyler Hicks (tyhicks) wrote :

Sorry, I entered the last comment in the wrong MP. Please ignore it.

Revision history for this message
Tyler Hicks (tyhicks) :
review: Abstain

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2016-03-09 07:45:08 +0000
3+++ debian/changelog 2016-03-21 17:30:32 +0000
4@@ -1,3 +1,13 @@
5+ubuntu-core-launcher (1.0.20) UNRELEASED; urgency=medium
6+
7+ * don't set NO_NEW_PRIVS. This requires changing privilege dropping since
8+ CAP_SYS_ADMIN is needed with seccomp_load(). This means temporarily
9+ dropping until seccomp_load(), then raising before and permanently
10+ dropping after the filter is applied. As a result, setuid/setgid is
11+ required in all policy (but is still mediated by AppArmor)
12+
13+ -- Jamie Strandboge <jamie@ubuntu.com> Mon, 21 Mar 2016 12:23:41 -0500
14+
15 ubuntu-core-launcher (1.0.19) xenial; urgency=medium
16
17 [Michael Vogt]
18
19=== modified file 'src/main.c'
20--- src/main.c 2016-03-09 07:45:08 +0000
21+++ src/main.c 2016-03-21 17:30:32 +0000
22@@ -407,6 +407,8 @@
23 const char *appname = argv[1];
24 const char *aa_profile = argv[2];
25 const char *binary = argv[3];
26+ unsigned real_uid = getuid();
27+ unsigned real_gid = getgid();
28
29 if(!verify_appname(appname))
30 die("appname %s not allowed", appname);
31@@ -445,27 +447,23 @@
32 setup_udev_snappy_assign(appname);
33 }
34
35- // the rest does not so drop privs back to calling user
36- unsigned real_uid = getuid();
37- unsigned real_gid = getgid();
38-
39- // Note that we do not call setgroups() here because its ok
40- // that the user keeps the groups he already belongs to
41- if (setgid(real_gid) != 0)
42- die("setgid failed");
43- if (setuid(real_uid) != 0)
44- die("setuid failed");
45-
46- if(real_gid != 0 && (getuid() == 0 || geteuid() == 0))
47+ // the rest does not so temporarily drop privs back to calling user
48+ // (we'll permanently drop after loading seccomp)
49+ if (setegid(real_gid) != 0)
50+ die("setegid failed");
51+ if (seteuid(real_uid) != 0)
52+ die("seteuid failed");
53+
54+ if(real_gid != 0 && geteuid() == 0)
55 die("dropping privs did not work");
56- if(real_uid != 0 && (getgid() == 0 || getegid() == 0))
57+ if(real_uid != 0 && getegid() == 0)
58 die("dropping privs did not work");
59 }
60
61 // Ensure that the user data path exists.
62 setup_user_data();
63
64- //https://wiki.ubuntu.com/SecurityTeam/Specifications/SnappyConfinement#ubuntu-snapp-launch
65+ // https://wiki.ubuntu.com/SecurityTeam/Specifications/SnappyConfinement
66
67 int rc = 0;
68 // set apparmor rules
69@@ -480,6 +478,21 @@
70 if (rc != 0)
71 die("seccomp_load_filters failed with %i", rc);
72
73+ // Permanently drop if not root
74+ if (geteuid() == 0) {
75+ // Note that we do not call setgroups() here because its ok
76+ // that the user keeps the groups he already belongs to
77+ if (setgid(real_gid) != 0)
78+ die("setgid failed");
79+ if (setuid(real_uid) != 0)
80+ die("setuid failed");
81+
82+ if(real_gid != 0 && (getuid() == 0 || geteuid() == 0))
83+ die("permanently dropping privs did not work");
84+ if(real_uid != 0 && (getgid() == 0 || getegid() == 0))
85+ die("permanently dropping privs did not work");
86+ }
87+
88 // and exec the new binary
89 argv[NR_ARGS] = (char*)binary,
90 execv(binary, (char *const*)&argv[NR_ARGS]);
91
92=== modified file 'src/seccomp.c'
93--- src/seccomp.c 2015-04-23 10:20:17 +0000
94+++ src/seccomp.c 2016-03-21 17:30:32 +0000
95@@ -49,6 +49,23 @@
96 if (ctx == NULL)
97 return ENOMEM;
98
99+ // Disable NO_NEW_PRIVS because it interferes with exec transitions in
100+ // AppArmor. Unfortunately this means that security policies must be very
101+ // careful to not allow the following otherwise apps can escape the snadbox:
102+ // - seccomp syscall
103+ // - prctl with PR_SET_SECCOMP
104+ // - ptrace (trace) in AppArmor
105+ // - capability sys_admin in AppArmor
106+ // Note that with NO_NEW_PRIVS disabled, CAP_SYS_ADMIN is required to change
107+ // the seccomp sandbox.
108+ if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
109+ rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0);
110+ if (rc != 0) {
111+ fprintf(stderr, "Cannot disable nnp\n");
112+ return -1;
113+ }
114+ }
115+
116 if (getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL)
117 filter_profile_dir = getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR");
118
119@@ -110,15 +127,32 @@
120 }
121 }
122
123+ // raise privileges to load seccomp policy since we don't have nnp
124+ if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
125+ if (seteuid(0) != 0)
126+ die("seteuid failed");
127+ if (geteuid() != 0)
128+ die("raising privs before seccomp_load did not work");
129+ }
130+
131 // load it into the kernel
132 rc = seccomp_load(ctx);
133+
134 if (rc != 0) {
135 fprintf(stderr, "seccomp_load failed with %i\n", rc);
136 goto out;
137 }
138
139-
140 out:
141+ // drop privileges again
142+ if (geteuid() == 0) {
143+ unsigned real_uid = getuid();
144+ if (seteuid(real_uid) != 0)
145+ die("seteuid failed");
146+ if (real_uid != 0 && geteuid() == 0)
147+ die("dropping privs after seccomp_load did not work");
148+ }
149+
150 if (f != NULL) {
151 fclose(f);
152 }
153
154=== modified file 'tests/test_create_user_data'
155--- tests/test_create_user_data 2016-02-09 20:13:51 +0000
156+++ tests/test_create_user_data 2016-03-21 17:30:32 +0000
157@@ -28,6 +28,10 @@
158 arch_prctl
159 exit_group
160
161+# required for not setting nnp
162+geteuid
163+getuid
164+
165 # unknown syscalls are ignore
166 i-dont-exit
167 EOF
168
169=== modified file 'tests/test_restrictions_working'
170--- tests/test_restrictions_working 2015-04-20 12:38:18 +0000
171+++ tests/test_restrictions_working 2016-03-21 17:30:32 +0000
172@@ -23,6 +23,9 @@
173 arch_prctl
174 exit_group
175
176+geteuid
177+getuid
178+
179 # unknown syscalls are ignore
180 i-dont-exit
181 EOF

Subscribers

People subscribed via source and target branches