Browse Source

Jettisoned libseccomp which was not providing much cross-platform benefit

Caleb James DeLisle 10 years ago
parent
commit
e24f3c501a
8 changed files with 323 additions and 110 deletions
  1. 1 1
      debian/control
  2. 212 70
      util/Seccomp.c
  3. 6 34
      util/Seccomp.h
  4. 69 0
      util/Seccomp.js
  5. 31 0
      util/Seccomp_dummy.c
  6. 2 3
      util/Security.c
  7. 1 1
      util/Security.h
  8. 1 1
      util/Security_admin.c

+ 1 - 1
debian/control

@@ -2,7 +2,7 @@ Source: cjdns
 Section: comm
 Priority: extra
 Maintainer: Sergey "Shnatsel" Davidoff <shnatsel@gmail.com>
-Build-Depends: debhelper (>= 8.0.0), nodejs (>= 0.8.5) | wget, python2.7, libseccomp-dev
+Build-Depends: debhelper (>= 8.0.0), nodejs (>= 0.8.5) | wget, python2.7
 Standards-Version: 3.9.2
 Homepage: https://github.com/cjdelisle/cjdns/
 Vcs-Git: git://github.com/cjdelisle/cjdns.git

+ 212 - 70
util/Seccomp.c

@@ -12,28 +12,89 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#include "util/Seccomp.h"
 
 // SIG_UNBLOCK
 #define _POSIX_SOURCE
+
+#include "util/Seccomp.h"
+#include "util/Bits.h"
+#include "util/ArchInfo.h"
+
 #include <signal.h>
 
 // getpriority()
 #include <sys/resource.h>
+#include <sys/prctl.h>
 #include <errno.h>
-#include <seccomp.h>
+#include <string.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
 #include <stddef.h>
 
-void Seccomp_dropPermissions(struct Except* eh)
+#include <stdio.h>
+
+
+struct Filter {
+    int label;
+    int jt;
+    int jf;
+    struct sock_filter sf;
+};
+
+static struct sock_fprog* compile(struct Filter* input, int inputLen, struct Allocator* alloc)
 {
-    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRAP);
-    if (!ctx) {
-        seccomp_release(ctx);
-        Except_throw(eh, "Failed to initialize SECCOMP");
+    // compute gotos
+    int totalOut = 0;
+    for (int i = inputLen-1; i >= 0; i--) {
+        struct Filter* a = &input[i];
+        if (a->label == 0) {
+            // check for unresolved gotos...
+            Assert_true(a->jt == 0 && a->jf == 0);
+            totalOut++;
+            continue;
+        }
+        int diff = 0;
+        for (int j = i-1; j >= 0; j--) {
+            struct Filter* b = &input[j];
+            if (b->label != 0) { continue; }
+            if (b->jt == a->label) {
+                b->sf.jt = diff;
+                b->jt = 0;
+            }
+            if (b->jf == a->label) {
+                b->sf.jf = diff;
+                b->jf = 0;
+            }
+            diff++;
+        }
+    }
+
+    // copy into output filter array...
+    struct sock_filter* sf = Allocator_calloc(alloc, sizeof(struct sock_filter), totalOut);
+    int outI = 0;
+    for (int i = 0; i < inputLen; i++) {
+        if (input[i].label == 0) {
+            Bits_memcpyConst(&sf[outI++], &input[i].sf, sizeof(struct sock_filter));
+        }
+        Assert_true(outI <= totalOut);
+        Assert_true(i != inputLen-1 || outI == totalOut);
     }
 
-    int rc = 0;
+    struct sock_fprog* out = Allocator_malloc(alloc, sizeof(struct sock_fprog));
+    out->len = (unsigned short) totalOut;
+    out->filter = sf;
+
+    return out;
+}
+
+#define RET_TRAP      0x00030000u
+#define RET_ERRNO(x) (0x00050000u | ((x) & 0x0000ffffu))
+#define RET_SUCCESS   0x7fff0000u
 
+static struct sock_fprog* mkFilter(struct Allocator* alloc, struct Except* eh)
+{
     // Adding exceptions to the syscall filter:
     //
     // echo '#include <seccomp.h>' | gcc -E -dM - | grep 'define __NR_' | sort
@@ -58,90 +119,166 @@ void Seccomp_dropPermissions(struct Except* eh)
     //
     // Then add:
     //
-    //     rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
+    //     IFEQ(__NR_mmap, success),
     //
     // And add a comment documenting where you needed that syscall :)
 
-    // see seccomp.h:307 and "man seccomp_rule_add" for docs on this function
+    #define STMT(code, val) { .sf = BPF_STMT(code, val) }
+
+    #define JMPK(type, not, input, label) { \
+        .sf = BPF_JUMP(BPF_JMP+(type)+BPF_K, (input), 0, 0), \
+        .jt = (!(not) ? (label) : 0),                        \
+        .jf = ((not) ? (label) : 0)                          \
+    }
 
+    // Create a label for jumps, the label must be represented by a non-zero integer.
+    #define LABEL(lbl) { .label = (lbl) }
 
-    // libuv
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), 0);
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_wait), 0);
+    // Load offset into the register
+    #define LOAD(offset)    STMT(BPF_LD+BPF_W+BPF_ABS, (offset))
 
-    // logging to stdout/stderr, tun and eth0
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
+    // Return constant value
+    #define RET(val)        STMT(BPF_RET+BPF_K, (val))
 
-    // tun device and eth0
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
+    // If-equal if the currently loaded value equals input, jump to label.
+    #define IFEQ(input, label) JMPK(BPF_JEQ, 0, (input), (label))
 
-    // malloc()
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
+    // If-not-equal if the currently loaded value is not equal to input, jump to label.
+    #define IFNE(input, label) JMPK(BPF_JEQ, 1, (input), (label))
 
-    // udp
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0);
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvmsg), 0);
+    // If-greater-than
+    #define IFGT(input, label) JMPK(BPF_JGT, 0, (input), (label))
 
-    // ETHInterface
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendto), 0);
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 0);
+    // If-greater-than-or-equal-to
+    #define IFGE(input, label) JMPK(BPF_JGE, 0, (input), (label))
 
-    // abort()
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettid), 0);
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(tgkill), 0);
-    rc |= seccomp_rule_add(ctx,
-                           SCMP_ACT_ALLOW,
-                           SCMP_SYS(rt_sigprocmask),
-                           1,
-                           SCMP_CMP(0, SCMP_CMP_EQ, SIG_UNBLOCK, 0));
+    // If-less-than
+    #define IFLT(input, label) JMPK(BPF_JGE, 1, (input), (label))
 
-    // exit()
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
+    // If-less-than-or-equal-to
+    #define IFLE(input, label) JMPK(BPF_JGT, 1, (input), (label))
 
-    // Seccomp_isWorking()
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ERRNO(9000), SCMP_SYS(getpriority), 0);
 
-    // Securiy_checkPermissions() -> canOpenFiles()
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup), 0);
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
+    // labels are integers so they must be predefined
+    int success = 1;
+    int fail = 2;
+    int unmaskOnly = 3;
+    int isworking = 4;
 
-    // Security_checkPermissions() -> getMaxMem()
-    // x86 uses ugetrlimit and mmap2
-    #ifdef __NR_getrlimit
-        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrlimit), 0);
-    #endif
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ugetrlimit), 0);
-    #ifdef __NR_mmap
-        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
-    #endif
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), 0);
+    enum ArchInfo ai = ArchInfo_detect();
+    uint32_t auditArch = ArchInfo_toAuditArch(ai);
+    if (auditArch == UINT32_MAX) {
+        Except_throw(eh, "Could not detect system architecture");
+    }
 
-    // Seem to be used on i686 (Linux 3.12.18 SMP PREEMPT i686 GNU/Linux) from glibc-2.18's time()
-    #ifdef __NR_time
-        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(time), 0);
-    #endif
+    struct Filter seccompFilter[] = {
 
-    // printf
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0);
+        // verify the processor type is the same as what we're setup for.
+        LOAD(offsetof(struct seccomp_data, arch)),
+        IFNE(auditArch, fail),
 
-    // Some machines need this
-    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_gettime), 0);
+        // Get the syscall num.
+        LOAD(offsetof(struct seccomp_data, nr)),
 
-    /////
+        // udp
+        IFEQ(__NR_sendmsg, success),
+        IFEQ(__NR_recvmsg, success),
 
-    char* err = NULL;
-    if (rc) {
-        err = "Failed to add SECCOMP filters";
-    } else if (seccomp_load(ctx)) {
-        err = "Failed to load SECCOMP";
-    }
+        // ETHInterface
+        IFEQ(__NR_sendto, success),
+        IFEQ(__NR_recvfrom, success),
+
+        // libuv
+        IFEQ(__NR_epoll_ctl, success),
+        IFEQ(__NR_epoll_wait, success),
+
+        // TUN (and logging)
+        IFEQ(__NR_write, success),
+        IFEQ(__NR_read, success),
+
+        // modern librt reads a read-only mapped section of kernel space which contains the time
+        // older versions need system calls for getting the time.
+        // i686 glibc-2.18's time() uses __NR_time
+        IFEQ(__NR_clock_gettime, success),
+        IFEQ(__NR_time, success),
+
+        // malloc()
+        IFEQ(__NR_brk, success),
+
+        // abort()
+        IFEQ(__NR_gettid, success),
+        IFEQ(__NR_tgkill, success),
+        IFEQ(__NR_rt_sigprocmask, unmaskOnly),
 
-    seccomp_release(ctx);
+        // exit()
+        IFEQ(__NR_exit_group, success),
 
-    if (err) {
-        Except_throw(eh, "%s", err);
-    } else if (!Seccomp_isWorking()) {
-        Except_throw(eh, "SECCOMP is not working correctly");
+        // Seccomp_isWorking()
+        IFEQ(__NR_getpriority, isworking),
+
+        // Securiy_checkPermissions() -> canOpenFiles()
+        IFEQ(__NR_dup, success),
+        IFEQ(__NR_close, success),
+
+        // Security_checkPermissions() -> getMaxMem()
+        // x86/ARM use ugetrlimit and mmap2
+        // ARM does not even have __NR_getrlimit or __NR_mmap defined
+        // and AMD64 does not have __NR_ugetrlimit or __NR_mmap2 defined
+        #ifdef __NR_getrlimit
+            IFEQ(__NR_getrlimit, success),
+        #endif
+        #ifdef __NR_ugetrlimit
+            IFEQ(__NR_ugetrlimit, success),
+        #endif
+        #ifdef __NR_mmap
+            IFEQ(__NR_mmap, success),
+        #endif
+        #ifdef __NR_mmap2
+            IFEQ(__NR_mmap2, success),
+        #endif
+
+        // printf()
+        IFEQ(__NR_fstat, success),
+
+        RET(SECCOMP_RET_TRAP),
+
+
+        // We allow sigprocmask to *unmask* signals but we don't allow it to mask them.
+        LABEL(unmaskOnly),
+        LOAD(offsetof(struct seccomp_data, args[0])),
+        IFEQ(SIG_UNBLOCK, success),
+        RET(SECCOMP_RET_TRAP),
+
+        LABEL(isworking),
+        RET(RET_ERRNO(9000)),
+
+        LABEL(fail),
+        RET(SECCOMP_RET_TRAP),
+
+        LABEL(success),
+        RET(SECCOMP_RET_ALLOW),
+    };
+
+    return compile(seccompFilter, sizeof(seccompFilter)/sizeof(seccompFilter[0]), alloc);
+}
+
+static void installFilter(struct sock_fprog* filter, struct Log* logger, struct Except* eh)
+{
+    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+        // don't worry about it.
+        Log_warn(logger, "prctl(PR_SET_NO_NEW_PRIVS) -> [%s]\n", strerror(errno));
+    }
+    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filter) == -1) {
+        Except_throw(eh, "prctl(PR_SET_SECCOMP) -> [%s]\n", strerror(errno));
+    }
+}
+
+void Seccomp_dropPermissions(struct Allocator* tempAlloc, struct Log* logger, struct Except* eh)
+{
+    struct sock_fprog* filter = mkFilter(tempAlloc, eh);
+    installFilter(filter, logger, eh);
+    if (!Seccomp_isWorking()) {
+        Except_throw(eh, "Seccomp filter not installed properly, Seccomp_isWorking() -> false");
     }
 }
 
@@ -156,3 +293,8 @@ int Seccomp_isWorking()
     // we'll check for either case just in case this changes.
     return (ret == -1 && errno == 9000) || (ret == -9000 && errno == 0);
 }
+
+int Seccomp_exists()
+{
+    return 1;
+}

+ 6 - 34
util/Seccomp.h

@@ -16,43 +16,15 @@
 #define Seccomp_H
 
 #include "exception/Except.h"
+#include "memory/Allocator.h"
+#include "util/log/Log.h"
 
-<?js
-    var main = function (async) {
-        if (typeof(builder.config.HAS_SECCOMP) !== 'undefined') {
-            if (builder.config.HAS_SECCOMP) {
-                file.links.push("util/Seccomp.c");
-                return [
-                    "void Seccomp_dropPermissions(struct Except* eh);",
-                    "int Seccomp_isWorking();",
-                    "static inline int Seccomp_exists() { return 1; }",
-                ].join('\n');
-            } else {
-                return [
-                    "static inline void Seccomp_dropPermissions(struct Except* eh) { }",
-                    "static inline int Seccomp_isWorking() { return 0; }",
-                    "static inline int Seccomp_exists() { return 0; }",
-                ].join('\n');
-            }
-        }
+<?js require("../util/Seccomp.js").detect(this.async, file, builder); ?>
 
-        console.log("Searching for SECCOMP");
-        var done = async();
+void Seccomp_dropPermissions(struct Allocator* tempAlloc, struct Log* logger, struct Except* eh);
 
-        var HasFunction = require("./HasFunction");
+int Seccomp_isWorking();
 
-        HasFunction.check(builder, "seccomp_init", ["-lseccomp"], function (err, has) {
-            builder.config.HAS_SECCOMP = (!err && has);
-            if (has) {
-                console.log("Successfully found SECCOMP");
-                builder.config.libs.push("-lseccomp");
-            } else {
-                console.log("Could not find SECCOMP, skipping");
-            }
-            done(main());
-        });
-    };
-    return main(this.async);
-?>
+int Seccomp_exists();
 
 #endif

+ 69 - 0
util/Seccomp.js

@@ -0,0 +1,69 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var TEST_PROGRAM = [
+    "#include <sys/resource.h>",
+    "#include <sys/prctl.h>",
+    "#include <linux/filter.h>",
+    "#include <linux/seccomp.h>",
+    "#include <linux/audit.h>",
+    "#include <sys/syscall.h>",
+    "int main() {",
+    "    return __NR_read",
+    "        | PR_SET_NO_NEW_PRIVS | PR_SET_SECCOMP | AUDIT_ARCH_X86_64",
+    "        | BPF_K | SECCOMP_MODE_FILTER;",
+    "}"
+].join('\n');
+
+var pushLinks = function (file, builder) {
+    if (typeof(builder.config.HAS_SECCOMP) !== 'undefined') { 
+        if (builder.config.HAS_SECCOMP) {
+            file.links.push("util/Seccomp.c");
+        } else {
+            file.links.push("util/Seccomp_dummy.c");
+        }
+        return true;
+    }
+    return false;
+};
+
+var detect = module.exports.detect = function (async, file, builder) {
+
+    if (pushLinks(file, builder)) { return; }
+
+    console.log("Searching for SECCOMP");
+
+    var hasSeccomp = false;
+    if (builder.config.systemName !== 'linux') {
+        console.log("SECCOMP is only available on linux");
+    } else if (process.env['Seccomp_NO']) {
+        console.log("SECCOMP disabled");
+    } else {
+        var done = async();
+        var CanCompile = require('../node_build/CanCompile');
+        var cflags = [ builder.config.cflags, '-x', 'c' ];
+        CanCompile.check(builder, TEST_PROGRAM, cflags, function (err, can) {
+            builder.config.HAS_SECCOMP = !!can;
+            if (!can) {
+                console.log("Failed to get SECCOMP, compile failure: [" + err + "]");
+            }
+            pushLinks(file, builder);
+            done();
+        });
+        return;
+    }
+    builder.config.HAS_SECCOMP = hasSeccomp;
+    pushLinks(file, builder);
+};

+ 31 - 0
util/Seccomp_dummy.c

@@ -0,0 +1,31 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "util/Seccomp.h"
+
+// This file is for machines which do not support seccomp.
+
+void Seccomp_dropPermissions(struct Allocator* tempAlloc, struct Log* logger, struct Except* eh)
+{
+}
+
+int Seccomp_isWorking()
+{
+    return 0;
+}
+
+int Seccomp_exists()
+{
+    return 0;
+}

+ 2 - 3
util/Security.c

@@ -148,10 +148,9 @@ struct Security_Permissions* Security_checkPermissions(struct Allocator* alloc,
     return out;
 }
 
-void Security_dropPermissions(struct Except* eh)
+void Security_dropPermissions(struct Allocator* tempAlloc, struct Log* logger, struct Except* eh)
 {
     maxMemory(100000000, eh);
     noFiles(eh);
-    Seccomp_dropPermissions(eh);
-
+    Seccomp_dropPermissions(tempAlloc, logger, eh);
 }

+ 1 - 1
util/Security.h

@@ -39,7 +39,7 @@ struct Security_Permissions
 #define Security_setUser_PERMISSION -1
 int Security_setUser(char* userName, struct Log* logger, struct Except* eh);
 
-void Security_dropPermissions(struct Except* eh);
+void Security_dropPermissions(struct Allocator* tempAlloc, struct Log* logger, struct Except* eh);
 
 struct Security_Permissions* Security_checkPermissions(struct Allocator* alloc, struct Except* eh);
 

+ 1 - 1
util/Security_admin.c

@@ -54,7 +54,7 @@ static void dropPermissions(Dict* args, void* vctx, String* txid, struct Allocat
     struct Context* const ctx = (struct Context*) vctx;
     struct Jmp jmp;
     Jmp_try(jmp) {
-        Security_dropPermissions(&jmp.handler);
+        Security_dropPermissions(requestAlloc, ctx->logger, &jmp.handler);
     } Jmp_catch {
         sendError(jmp.message, txid, ctx->admin);
         return;