Merge lp:~jdstrand/snap-confine/security-review-fixes into lp:~snappy-dev/snap-confine/trunk

Proposed by Jamie Strandboge
Status: Merged
Merged at revision: 110
Proposed branch: lp:~jdstrand/snap-confine/security-review-fixes
Merge into: lp:~snappy-dev/snap-confine/trunk
Diff against target: 492 lines (+150/-110)
8 files modified
debian/changelog (+3/-0)
src/main.c (+25/-25)
src/seccomp.c (+46/-33)
src/utils.c (+2/-1)
tests/test_bad_seccomp_filter_length (+20/-0)
tests/test_bad_seccomp_filter_missing_trailing_newline (+17/-0)
tests/test_create_user_data (+7/-51)
tests/test_unrestricted_missed (+30/-0)
To merge this branch: bzr merge lp:~jdstrand/snap-confine/security-review-fixes
Reviewer Review Type Date Requested Status
Tyler Hicks (community) Needs Fixing
Snappy Developers Pending
Review via email: mp+290156@code.launchpad.net

Description of the change

Various fixes to address Seth's feedback:
- 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

To post a comment you must log in.
122. By Jamie Strandboge

merge from trunk

123. By Jamie Strandboge

rename recently add tests

124. By Jamie Strandboge

merge from trunk

125. By Jamie Strandboge

fix debian/changelog which had improper syntax

126. By Jamie Strandboge

merge from trunk

127. By Jamie Strandboge

merge from trunk

128. By Jamie Strandboge

merge from trunk

129. By Jamie Strandboge

merge from trunk

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

I've completed a partial review. I still haven't gotten through all of the seccomp and test related changes. I'll complete that later this afternoon.

review: Needs Fixing
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
130. By Jamie Strandboge

update a few comments based on tyhicks feedback

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

I've completed my review now. I only had one additional comment regarding another fclose() error check.

131. By Jamie Strandboge

simplify seccomp_load_filters() and always die() on error instead of trying to
return non-zero sometimes

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

Thanks for the review! Yes, the error checking on the (f)close()s are not needed since they are read only, but I added them to help futureproof the codebase. Leaving those in.

I cleaned up seccomp_load_filters() error handling in r131.

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-30 12:46:25 +0000
3+++ debian/changelog 2016-03-31 19:41:16 +0000
4@@ -2,6 +2,9 @@
5
6 * update README for recent commits
7 * add README.syscalls
8+ * src/seccomp.c: various cleanups from security team audit (also add
9+ additional tests)
10+ * don't support obsoleted SNAP_APP_TMPDIR and SNAP_APP_USER_DATA_PATH
11
12 -- Jamie Strandboge <jamie@ubuntu.com> Tue, 29 Mar 2016 16:07:13 -0500
13
14
15=== modified file 'src/main.c'
16--- src/main.c 2016-03-22 19:42:30 +0000
17+++ src/main.c 2016-03-31 19:41:16 +0000
18@@ -67,6 +67,9 @@
19
20 int status = 0;
21 pid_t pid = fork();
22+ if (pid < 0) {
23+ die("could not fork");
24+ }
25 if (pid == 0) {
26 char buf[64];
27 unsigned major = MAJOR(devnum);
28@@ -198,7 +201,8 @@
29 if (fd < 0)
30 return false;
31 int n = read(fd, content, sizeof(content));
32- close(fd);
33+ if (close(fd) != 0)
34+ die("could not close override");
35 if (n < sizeof(content))
36 return false;
37
38@@ -259,7 +263,7 @@
39 die("unable to chown tmpdir");
40 }
41 // ensure we set the various TMPDIRs to our newly created tmpdir
42- const char *tmpd[] = { "TMPDIR", "TEMPDIR", "SNAP_APP_TMPDIR", NULL };
43+ const char *tmpd[] = { "TMPDIR", "TEMPDIR", NULL };
44 int i;
45 for (i = 0; tmpd[i] != NULL; i++) {
46 if (setenv(tmpd[i], "/tmp", 1) != 0) {
47@@ -395,7 +399,7 @@
48 // Try to create the directory. It's okay if it already
49 // existed, but any other error is fatal.
50 if (mkdirat(fd, path_segment, 0755) < 0 && errno != EEXIST) {
51- close(fd);
52+ close(fd); // we die regardless of return code
53 free(path_copy);
54 die("failed to create user data directory");
55 }
56@@ -403,7 +407,10 @@
57 // previous one) so we can continue down the path.
58 int previous_fd = fd;
59 fd = openat(fd, path_segment, open_flags);
60- close(previous_fd);
61+ if (close(previous_fd) != 0) {
62+ free(path_copy);
63+ die("could not close path segment");
64+ }
65 if (fd < 0) {
66 free(path_copy);
67 die("failed to create user data directory");
68@@ -413,7 +420,10 @@
69 }
70
71 // Close the descriptor for the final directory in the path.
72- close(fd);
73+ if (close(fd) != 0) {
74+ free(path_copy);
75+ die("could not close final directory");
76+ }
77
78 free(path_copy);
79 }
80@@ -422,16 +432,8 @@
81 {
82 const char *user_data = getenv("SNAP_USER_DATA");
83
84- // If $SNAP_USER_DATA wasn't defined, check the deprecated
85- // $SNAP_APP_USER_DATA_PATH.
86- if (user_data == NULL) {
87- user_data = getenv("SNAP_APP_USER_DATA_PATH");
88- // If it's still not defined, there's nothing to do. No need to
89- // die, there's simply no directory to create.
90- if (user_data == NULL) {
91- return;
92- }
93- }
94+ if (user_data == NULL)
95+ return;
96 // Only support absolute paths.
97 if (user_data[0] != '/') {
98 die("user data directory must be an absolute path");
99@@ -449,15 +451,16 @@
100 const char *appname = argv[1];
101 const char *aa_profile = argv[2];
102 const char *binary = argv[3];
103- unsigned real_uid = getuid();
104- unsigned real_gid = getgid();
105+ uid_t real_uid = getuid();
106+ gid_t real_gid = getgid();
107
108 if (!verify_appname(appname))
109 die("appname %s not allowed", appname);
110
111 // this code always needs to run as root for the cgroup/udev setup,
112 // however for the tests we allow it to run as non-root
113- if (geteuid() != 0 && getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
114+ if (geteuid() != 0
115+ && secure_getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
116 die("need to run as root or suid");
117 }
118
119@@ -511,13 +514,11 @@
120 // set apparmor rules
121 rc = aa_change_onexec(aa_profile);
122 if (rc != 0) {
123- if (getenv("SNAPPY_LAUNCHER_INSIDE_TESTS") == NULL)
124+ if (secure_getenv("SNAPPY_LAUNCHER_INSIDE_TESTS") == NULL)
125 die("aa_change_onexec failed with %i", rc);
126 }
127- // set seccomp
128- rc = seccomp_load_filters(aa_profile);
129- if (rc != 0)
130- die("seccomp_load_filters failed with %i", rc);
131+ // set seccomp (note: seccomp_load_filters die()s on all failures)
132+ seccomp_load_filters(aa_profile);
133
134 // Permanently drop if not root
135 if (geteuid() == 0) {
136@@ -534,8 +535,7 @@
137 die("permanently dropping privs did not work");
138 }
139 // and exec the new binary
140- argv[NR_ARGS] = (char *)binary,
141- execv(binary, (char *const *)&argv[NR_ARGS]);
142+ execv(binary, (char *const *)&argv[NR_ARGS]);
143 perror("execv failed");
144 return 1;
145 }
146
147=== modified file 'src/seccomp.c'
148--- src/seccomp.c 2016-03-22 19:42:30 +0000
149+++ src/seccomp.c 2016-03-31 19:41:16 +0000
150@@ -14,6 +14,10 @@
151 * along with this program. If not, see <http://www.gnu.org/licenses/>.
152 *
153 */
154+#ifndef _GNU_SOURCE
155+#define _GNU_SOURCE
156+#endif
157+
158 #include <errno.h>
159 #include <stdio.h>
160 #include <unistd.h>
161@@ -37,7 +41,7 @@
162 return slen;
163 }
164
165-int seccomp_load_filters(const char *filter_profile)
166+void seccomp_load_filters(const char *filter_profile)
167 {
168 debug("seccomp_load_filters %s", filter_profile);
169 int rc = 0;
170@@ -45,45 +49,51 @@
171 scmp_filter_ctx ctx = NULL;
172 FILE *f = NULL;
173 size_t lineno = 0;
174+ uid_t real_uid, effective_uid, saved_uid;
175
176 ctx = seccomp_init(SCMP_ACT_KILL);
177- if (ctx == NULL)
178- return ENOMEM;
179-
180+ if (ctx == NULL) {
181+ errno = ENOMEM;
182+ die("seccomp_init() failed");
183+ }
184 // Disable NO_NEW_PRIVS because it interferes with exec transitions in
185 // AppArmor. Unfortunately this means that security policies must be
186 // very careful to not allow the following otherwise apps can escape
187- // the snadbox:
188+ // the sandbox:
189 // - seccomp syscall
190 // - prctl with PR_SET_SECCOMP
191 // - ptrace (trace) in AppArmor
192 // - capability sys_admin in AppArmor
193 // Note that with NO_NEW_PRIVS disabled, CAP_SYS_ADMIN is required to
194 // change the seccomp sandbox.
195- if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
196- rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0);
197- if (rc != 0) {
198- fprintf(stderr, "Cannot disable nnp\n");
199- return -1;
200- }
201- }
202-
203- if (getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL)
204+
205+ if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0)
206+ die("could not find user IDs");
207+
208+ // If running privileged or capable of raising, disable nnp
209+ if (real_uid == 0 || effective_uid == 0 || saved_uid == 0)
210+ if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0) != 0)
211+ die("Cannot disable nnp");
212+
213+ // Note that secure_gettenv will always return NULL when suid, so
214+ // SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR can't be (ab)used in that case.
215+ if (secure_getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL)
216 filter_profile_dir =
217- getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR");
218+ secure_getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR");
219
220- char profile_path[128];
221- if (snprintf
222- (profile_path, sizeof(profile_path), "%s/%s", filter_profile_dir,
223- filter_profile) < 0) {
224- goto out;
225+ char profile_path[512]; // arbitrary path name limit
226+ int snprintf_rc = snprintf(profile_path, sizeof(profile_path), "%s/%s",
227+ filter_profile_dir, filter_profile);
228+ if (snprintf_rc < 0 || snprintf_rc >= 512) {
229+ errno = 0;
230+ die("snprintf returned unexpected value");
231 }
232
233 f = fopen(profile_path, "r");
234 if (f == NULL) {
235 fprintf(stderr, "Can not open %s (%s)\n", profile_path,
236 strerror(errno));
237- return -1;
238+ die("aborting");
239 }
240 // 80 characters + '\n' + '\0'
241 char buf[82];
242@@ -104,16 +114,17 @@
243 fprintf(stderr,
244 "seccomp filter line %zu was too long (%zu characters max)\n",
245 lineno, sizeof(buf) - 2);
246- rc = -1;
247- goto out;
248+ errno = 0;
249+ die("aborting");
250 }
251 // kill final newline
252 len = trim_right(buf, len);
253 if (len == 0)
254 continue;
255
256- // check for special "@unrestricted" command
257- if (strncmp(buf, "@unrestricted", sizeof(buf)) == 0)
258+ // check for special "@unrestricted" rule which short-circuits
259+ // seccomp sandbox
260+ if (strcmp(buf, "@unrestricted") == 0)
261 goto out;
262
263 // syscall not available on this arch/kernel
264@@ -132,13 +143,15 @@
265 fprintf(stderr,
266 "seccomp_rule_add failed with %i for '%s'\n",
267 rc, buf);
268- goto out;
269+ errno = 0;
270+ die("aborting");
271 }
272 }
273 }
274
275- // raise privileges to load seccomp policy since we don't have nnp
276- if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
277+ // If not root but can raise, then raise privileges to load seccomp
278+ // policy since we don't have nnp
279+ if (effective_uid != 0 && saved_uid == 0) {
280 if (seteuid(0) != 0)
281 die("seteuid failed");
282 if (geteuid() != 0)
283@@ -149,10 +162,8 @@
284
285 if (rc != 0) {
286 fprintf(stderr, "seccomp_load failed with %i\n", rc);
287- goto out;
288+ die("aborting");
289 }
290-
291- out:
292 // drop privileges again
293 if (geteuid() == 0) {
294 unsigned real_uid = getuid();
295@@ -162,9 +173,11 @@
296 die("dropping privs after seccomp_load did not work");
297 }
298
299+ out:
300 if (f != NULL) {
301- fclose(f);
302+ if (fclose(f) != 0)
303+ die("could not close seccomp file");
304 }
305 seccomp_release(ctx);
306- return rc;
307+ return;
308 }
309
310=== modified file 'src/utils.c'
311--- src/utils.c 2016-03-22 12:17:03 +0000
312+++ src/utils.c 2016-03-31 19:41:16 +0000
313@@ -70,7 +70,8 @@
314 die("fwrite failed");
315 if (fflush(f) != 0)
316 die("fflush failed");
317- fclose(f);
318+ if (fclose(f) != 0)
319+ die("fclose failed");
320 }
321
322 int must_snprintf(char *str, size_t size, const char *format, ...)
323
324=== added file 'tests/test_bad_seccomp_filter_length'
325--- tests/test_bad_seccomp_filter_length 1970-01-01 00:00:00 +0000
326+++ tests/test_bad_seccomp_filter_length 2016-03-31 19:41:16 +0000
327@@ -0,0 +1,20 @@
328+#!/bin/sh
329+
330+set -e
331+
332+. $(pwd)/common.sh
333+
334+printf "Test seccomp filter (bad - too long)"
335+
336+cat >$TMP/aaprofile <<EOF
337+# some comment
338+baddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
339+EOF
340+
341+if $L appname aaprofile /bin/true 2>/dev/null; then
342+ # true returned successfully, length test failed
343+ FAIL
344+fi
345+
346+# all good
347+PASS
348
349=== added file 'tests/test_bad_seccomp_filter_missing_trailing_newline'
350--- tests/test_bad_seccomp_filter_missing_trailing_newline 1970-01-01 00:00:00 +0000
351+++ tests/test_bad_seccomp_filter_missing_trailing_newline 2016-03-31 19:41:16 +0000
352@@ -0,0 +1,17 @@
353+#!/bin/sh
354+
355+set -e
356+
357+. $(pwd)/common.sh
358+
359+printf "Test seccomp filter (bad - no trailing newline)"
360+
361+echo -n "missingnewline" > $TMP/aaprofile
362+
363+if $L appname aaprofile /bin/true 2>/dev/null; then
364+ # true returned successfully, length test failed
365+ FAIL
366+fi
367+
368+# all good
369+PASS
370
371=== modified file 'tests/test_create_user_data'
372--- tests/test_create_user_data 2016-03-19 02:32:43 +0000
373+++ tests/test_create_user_data 2016-03-31 19:41:16 +0000
374@@ -82,56 +82,28 @@
375 return $pass
376 }
377
378-# $1 = $SNAP_APP_USER_DATA_PATH definition
379-# $2 = Profile name
380-# $3 = True if success is expected, false otherwise
381-run_deprecated() {
382- export SNAP_APP_USER_DATA_PATH=$1
383- run_launcher $1 $2 $3
384- pass=$?
385- unset SNAP_APP_USER_DATA_PATH
386-
387- return $pass
388-}
389-
390-# $1 = User data path
391-# $2 = Profile name
392-# $3 = True if success is expected, false otherwise
393-run_both() {
394- run_current $1 $2 $3
395- current_pass=$?
396- run_deprecated $1 $2 $3
397- deprecated_pass=$?
398-
399- if [ $current_pass -a $deprecated_pass ]; then
400- PASS
401- else
402- FAIL
403- fi
404-}
405-
406 printf "Test that an unrestricted launcher creates user data"
407-run_both $TMP/user_data unrestricted true
408+run_current $TMP/user_data unrestricted true
409
410 printf "Test that a restricted launcher creates user data"
411-run_both $TMP/user_data restricted true
412+run_current $TMP/user_data restricted true
413
414 printf "Test that an unrestricted launcher creates user data with parent directory"
415-run_both $TMP/parent/user_data unrestricted true
416+run_current $TMP/parent/user_data unrestricted true
417
418 printf "Test that a restricted launcher creates user data with parent directory"
419-run_both $TMP/parent/user_data restricted true
420+run_current $TMP/parent/user_data restricted true
421
422 printf "Test that user data can contain multiple path separators"
423-run_both $TMP//user_data unrestricted true
424+run_current $TMP//user_data unrestricted true
425
426 printf "Test that user data must be absolute"
427-run_both "../foo" unrestricted false
428+run_current "../foo" unrestricted false
429
430 printf "Testing that user data cannot be contained within a symlink"
431 mkdir $TMP/nefarious_parent
432 ln -s $TMP/nefarious_parent $TMP/parent
433-run_both $TMP/parent/user_data unrestricted, false
434+run_current $TMP/parent/user_data unrestricted, false
435
436 printf "Test that an unrestricted launcher works when user data exists (current)"
437 mkdir $TMP/user_data
438@@ -148,19 +120,3 @@
439 else
440 FAIL
441 fi
442-
443-printf "Test that an unrestricted launcher works when user data exists (deprecated)"
444-mkdir $TMP/user_data
445-if run_deprecated $TMP/user_data unrestricted true; then
446- PASS
447-else
448- FAIL
449-fi
450-
451-printf "Test that an restricted launcher works when user data exists (deprecated)"
452-mkdir $TMP/user_data
453-if run_deprecated $TMP/user_data restricted true; then
454- PASS
455-else
456- FAIL
457-fi
458
459=== added file 'tests/test_unrestricted_missed'
460--- tests/test_unrestricted_missed 1970-01-01 00:00:00 +0000
461+++ tests/test_unrestricted_missed 2016-03-31 19:41:16 +0000
462@@ -0,0 +1,30 @@
463+#!/bin/sh
464+
465+set -e
466+
467+. $(pwd)/common.sh
468+
469+cat >$TMP/myprofile <<EOF
470+# super strict filter
471+@unrestricte
472+@unrestrictes
473+@nrestricted
474+@UNRESTRICTED
475+unrestricted
476+EOF
477+
478+# ensure that the command "true" can not run due to impossible
479+# filtering
480+
481+printf "Test that near misses of unrestricted fail"
482+if $L appid myprofile /bin/true 2>/dev/null; then
483+ # true returned successfully, our filtering is broken!
484+ FAIL
485+else
486+ # true returend a error code, check dmesg
487+ if dmesg|tail -n1|grep -q "audit"; then
488+ PASS
489+ else
490+ FAIL
491+ fi
492+fi

Subscribers

People subscribed via source and target branches