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
=== modified file 'debian/changelog'
--- debian/changelog 2016-03-09 07:45:08 +0000
+++ debian/changelog 2016-03-21 17:30:32 +0000
@@ -1,3 +1,13 @@
1ubuntu-core-launcher (1.0.20) UNRELEASED; urgency=medium
2
3 * don't set NO_NEW_PRIVS. This requires changing privilege dropping since
4 CAP_SYS_ADMIN is needed with seccomp_load(). This means temporarily
5 dropping until seccomp_load(), then raising before and permanently
6 dropping after the filter is applied. As a result, setuid/setgid is
7 required in all policy (but is still mediated by AppArmor)
8
9 -- Jamie Strandboge <jamie@ubuntu.com> Mon, 21 Mar 2016 12:23:41 -0500
10
1ubuntu-core-launcher (1.0.19) xenial; urgency=medium11ubuntu-core-launcher (1.0.19) xenial; urgency=medium
212
3 [Michael Vogt]13 [Michael Vogt]
414
=== modified file 'src/main.c'
--- src/main.c 2016-03-09 07:45:08 +0000
+++ src/main.c 2016-03-21 17:30:32 +0000
@@ -407,6 +407,8 @@
407 const char *appname = argv[1];407 const char *appname = argv[1];
408 const char *aa_profile = argv[2];408 const char *aa_profile = argv[2];
409 const char *binary = argv[3];409 const char *binary = argv[3];
410 unsigned real_uid = getuid();
411 unsigned real_gid = getgid();
410412
411 if(!verify_appname(appname))413 if(!verify_appname(appname))
412 die("appname %s not allowed", appname);414 die("appname %s not allowed", appname);
@@ -445,27 +447,23 @@
445 setup_udev_snappy_assign(appname);447 setup_udev_snappy_assign(appname);
446 }448 }
447449
448 // the rest does not so drop privs back to calling user450 // the rest does not so temporarily drop privs back to calling user
449 unsigned real_uid = getuid();451 // (we'll permanently drop after loading seccomp)
450 unsigned real_gid = getgid();452 if (setegid(real_gid) != 0)
451453 die("setegid failed");
452 // Note that we do not call setgroups() here because its ok454 if (seteuid(real_uid) != 0)
453 // that the user keeps the groups he already belongs to455 die("seteuid failed");
454 if (setgid(real_gid) != 0)456
455 die("setgid failed");457 if(real_gid != 0 && geteuid() == 0)
456 if (setuid(real_uid) != 0)
457 die("setuid failed");
458
459 if(real_gid != 0 && (getuid() == 0 || geteuid() == 0))
460 die("dropping privs did not work");458 die("dropping privs did not work");
461 if(real_uid != 0 && (getgid() == 0 || getegid() == 0))459 if(real_uid != 0 && getegid() == 0)
462 die("dropping privs did not work");460 die("dropping privs did not work");
463 }461 }
464462
465 // Ensure that the user data path exists.463 // Ensure that the user data path exists.
466 setup_user_data();464 setup_user_data();
467465
468 //https://wiki.ubuntu.com/SecurityTeam/Specifications/SnappyConfinement#ubuntu-snapp-launch466 // https://wiki.ubuntu.com/SecurityTeam/Specifications/SnappyConfinement
469467
470 int rc = 0;468 int rc = 0;
471 // set apparmor rules469 // set apparmor rules
@@ -480,6 +478,21 @@
480 if (rc != 0)478 if (rc != 0)
481 die("seccomp_load_filters failed with %i", rc);479 die("seccomp_load_filters failed with %i", rc);
482480
481 // Permanently drop if not root
482 if (geteuid() == 0) {
483 // Note that we do not call setgroups() here because its ok
484 // that the user keeps the groups he already belongs to
485 if (setgid(real_gid) != 0)
486 die("setgid failed");
487 if (setuid(real_uid) != 0)
488 die("setuid failed");
489
490 if(real_gid != 0 && (getuid() == 0 || geteuid() == 0))
491 die("permanently dropping privs did not work");
492 if(real_uid != 0 && (getgid() == 0 || getegid() == 0))
493 die("permanently dropping privs did not work");
494 }
495
483 // and exec the new binary496 // and exec the new binary
484 argv[NR_ARGS] = (char*)binary,497 argv[NR_ARGS] = (char*)binary,
485 execv(binary, (char *const*)&argv[NR_ARGS]);498 execv(binary, (char *const*)&argv[NR_ARGS]);
486499
=== modified file 'src/seccomp.c'
--- src/seccomp.c 2015-04-23 10:20:17 +0000
+++ src/seccomp.c 2016-03-21 17:30:32 +0000
@@ -49,6 +49,23 @@
49 if (ctx == NULL)49 if (ctx == NULL)
50 return ENOMEM;50 return ENOMEM;
5151
52 // Disable NO_NEW_PRIVS because it interferes with exec transitions in
53 // AppArmor. Unfortunately this means that security policies must be very
54 // careful to not allow the following otherwise apps can escape the snadbox:
55 // - seccomp syscall
56 // - prctl with PR_SET_SECCOMP
57 // - ptrace (trace) in AppArmor
58 // - capability sys_admin in AppArmor
59 // Note that with NO_NEW_PRIVS disabled, CAP_SYS_ADMIN is required to change
60 // the seccomp sandbox.
61 if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
62 rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0);
63 if (rc != 0) {
64 fprintf(stderr, "Cannot disable nnp\n");
65 return -1;
66 }
67 }
68
52 if (getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL)69 if (getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL)
53 filter_profile_dir = getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR");70 filter_profile_dir = getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR");
5471
@@ -110,15 +127,32 @@
110 }127 }
111 }128 }
112129
130 // raise privileges to load seccomp policy since we don't have nnp
131 if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) {
132 if (seteuid(0) != 0)
133 die("seteuid failed");
134 if (geteuid() != 0)
135 die("raising privs before seccomp_load did not work");
136 }
137
113 // load it into the kernel138 // load it into the kernel
114 rc = seccomp_load(ctx);139 rc = seccomp_load(ctx);
140
115 if (rc != 0) {141 if (rc != 0) {
116 fprintf(stderr, "seccomp_load failed with %i\n", rc);142 fprintf(stderr, "seccomp_load failed with %i\n", rc);
117 goto out;143 goto out;
118 }144 }
119145
120
121 out:146 out:
147 // drop privileges again
148 if (geteuid() == 0) {
149 unsigned real_uid = getuid();
150 if (seteuid(real_uid) != 0)
151 die("seteuid failed");
152 if (real_uid != 0 && geteuid() == 0)
153 die("dropping privs after seccomp_load did not work");
154 }
155
122 if (f != NULL) {156 if (f != NULL) {
123 fclose(f);157 fclose(f);
124 }158 }
125159
=== modified file 'tests/test_create_user_data'
--- tests/test_create_user_data 2016-02-09 20:13:51 +0000
+++ tests/test_create_user_data 2016-03-21 17:30:32 +0000
@@ -28,6 +28,10 @@
28arch_prctl28arch_prctl
29exit_group29exit_group
3030
31# required for not setting nnp
32geteuid
33getuid
34
31# unknown syscalls are ignore35# unknown syscalls are ignore
32i-dont-exit36i-dont-exit
33EOF37EOF
3438
=== modified file 'tests/test_restrictions_working'
--- tests/test_restrictions_working 2015-04-20 12:38:18 +0000
+++ tests/test_restrictions_working 2016-03-21 17:30:32 +0000
@@ -23,6 +23,9 @@
23arch_prctl23arch_prctl
24exit_group24exit_group
2525
26geteuid
27getuid
28
26# unknown syscalls are ignore29# unknown syscalls are ignore
27i-dont-exit30i-dont-exit
28EOF31EOF

Subscribers

People subscribed via source and target branches