CVE-2017-18367: Improper Input Validation (CWE-20)
value: libseccomp-golang 0.9.0 and earlier incorrectly generates BPFs that OR multiple arguments rather than ANDing them. A process running under a restrictive seccomp filter that specified multiple syscall arguments could bypass intended access restrictions by specifying a single matching argument.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 7.5
Published: 2019-04-24T21:29Z
Repository: https://github.com/seccomp/libseccomp-golang
Language: Go
Files changed: seccomp_internal.go
Commit date: 2017-04-24 13:34:20 -0400
Commit message: golang: Resolve bug with handling of multiple argument rules

In the upstream library, when added with a single API call,
multiple syscall argument rules should be matched with AND
logic - if all of them match, the rule matches.

At present, the Golang bindings apply OR logic to this case.
This commit resolves this and reverts to the behavior of the
main library.

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>

Diff: diff --git a/seccomp_internal.go b/seccomp_internal.go
index c9fd616..369f194 100644
--- a/seccomp_internal.go
+++ b/seccomp_internal.go
@@ -118,27 +118,31 @@ unsigned int get_micro_version()
 }
 #endif
 
 typedef struct scmp_arg_cmp* scmp_cast_t;
 
-// Wrapper to create an scmp_arg_cmp struct
-void*
-make_struct_arg_cmp(
-                    unsigned int arg,
-                    int compare,
-                    uint64_t a,
-                    uint64_t b
-                   )
+void* make_arg_cmp_array(unsigned int length)
 {
-	struct scmp_arg_cmp *s = malloc(sizeof(struct scmp_arg_cmp));
+        return calloc(length, sizeof(struct scmp_arg_cmp));
+}
 
-	s->arg = arg;
-	s->op = compare;
-	s->datum_a = a;
-	s->datum_b = b;
+// Wrapper to add an scmp_arg_cmp struct to an existing arg_cmp array
+void add_struct_arg_cmp(
+                        struct scmp_arg_cmp* arr,
+                        unsigned int pos,
+                        unsigned int arg,
+                        int compare,
+                        uint64_t a,
+                        uint64_t b
+                       )
+{
+        arr[pos].arg = arg;
+        arr[pos].op = compare;
+        arr[pos].datum_a = a;
+        arr[pos].datum_b = b;
 
-	return s;
+        return;
 }
 */
 import "C"
 
 // Nonexported types
@@ -237,16 +241,13 @@ func (f *ScmpFilter) setFilterAttr(attr scmpFilterAttr, value C.uint32_t) error
 }
 
 // DOES NOT LOCK OR CHECK VALIDITY
 // Assumes caller has already done this
 // Wrapper for seccomp_rule_add_... functions
-func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, cond C.scmp_cast_t) error {
-	var length C.uint
-	if cond != nil {
-		length = 1
-	} else {
-		length = 0
+func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, length C.uint, cond C.scmp_cast_t) error {
+	if length != 0 && cond == nil {
+		return fmt.Errorf("null conditions list, but length is nonzero")
 	}
 
 	var retCode C.int
 	if exact {
 		retCode = C.seccomp_rule_add_exact_array(f.filterCtx, action.toNative(), C.int(call), length, cond)
@@ -256,10 +257,12 @@ func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact b
 
 	if syscall.Errno(-1*retCode) == syscall.EFAULT {
 		return fmt.Errorf("unrecognized syscall")
 	} else if syscall.Errno(-1*retCode) == syscall.EPERM {
 		return fmt.Errorf("requested action matches default action of filter")
+	} else if syscall.Errno(-1*retCode) == syscall.EINVAL {
+		return fmt.Errorf("two checks on same syscall argument")
 	} else if retCode != 0 {
 		return syscall.Errno(-1 * retCode)
 	}
 
 	return nil
@@ -273,11 +276,11 @@ func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact b
 	if !f.valid {
 		return errBadFilter
 	}
 
 	if len(conds) == 0 {
-		if err := f.addRuleWrapper(call, action, exact, nil); err != nil {
+		if err := f.addRuleWrapper(call, action, exact, 0, nil); err != nil {
 			return err
 		}
 	} else {
 		// We don't support conditional filtering in library version v2.1
 		if !checkVersionAbove(2, 2, 1) {
@@ -285,17 +288,24 @@ func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact b
 				message: "conditional filtering is not supported",
 				minimum: "2.2.1",
 			}
 		}
 
-		for _, cond := range conds {
-			cmpStruct := C.make_struct_arg_cmp(C.uint(cond.Argument), cond.Op.toNative(), C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
-			defer C.free(cmpStruct)
+		argsArr := C.make_arg_cmp_array(C.uint(len(conds)))
+		if argsArr == nil {
+			return fmt.Errorf("error allocating memory for conditions")
+		}
+		defer C.free(argsArr)
+
+		for i, cond := range conds {
+			C.add_struct_arg_cmp(C.scmp_cast_t(argsArr), C.uint(i),
+				C.uint(cond.Argument), cond.Op.toNative(),
+				C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
+		}
 
-			if err := f.addRuleWrapper(call, action, exact, C.scmp_cast_t(cmpStruct)); err != nil {
-				return err
-			}
+		if err := f.addRuleWrapper(call, action, exact, C.uint(len(conds)), C.scmp_cast_t(argsArr)); err != nil {
+			return err
 		}
 	}
 
 	return nil
 }

Vulnerable code: // Wrapper to create an scmp_arg_cmp struct
void*
make_struct_arg_cmp(
                    unsigned int arg,
                    int compare,
                    uint64_t a,
                    uint64_t b
                   )
	struct scmp_arg_cmp *s = malloc(sizeof(struct scmp_arg_cmp));
	s->arg = arg;
	s->op = compare;
	s->datum_a = a;
	s->datum_b = b;
	return s;
func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, cond C.scmp_cast_t) error {
	var length C.uint
	if cond != nil {
		length = 1
	} else {
		length = 0
		if err := f.addRuleWrapper(call, action, exact, nil); err != nil {
		for _, cond := range conds {
			cmpStruct := C.make_struct_arg_cmp(C.uint(cond.Argument), cond.Op.toNative(), C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
			defer C.free(cmpStruct)
			if err := f.addRuleWrapper(call, action, exact, C.scmp_cast_t(cmpStruct)); err != nil {
				return err
			}
Fixed code: void* make_arg_cmp_array(unsigned int length)
        return calloc(length, sizeof(struct scmp_arg_cmp));
}
// Wrapper to add an scmp_arg_cmp struct to an existing arg_cmp array
void add_struct_arg_cmp(
                        struct scmp_arg_cmp* arr,
                        unsigned int pos,
                        unsigned int arg,
                        int compare,
                        uint64_t a,
                        uint64_t b
                       )
{
        arr[pos].arg = arg;
        arr[pos].op = compare;
        arr[pos].datum_a = a;
        arr[pos].datum_b = b;
        return;
func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, length C.uint, cond C.scmp_cast_t) error {
	if length != 0 && cond == nil {
		return fmt.Errorf("null conditions list, but length is nonzero")
	} else if syscall.Errno(-1*retCode) == syscall.EINVAL {
		return fmt.Errorf("two checks on same syscall argument")
		if err := f.addRuleWrapper(call, action, exact, 0, nil); err != nil {
		argsArr := C.make_arg_cmp_array(C.uint(len(conds)))
		if argsArr == nil {
			return fmt.Errorf("error allocating memory for conditions")
		}
		defer C.free(argsArr)

		for i, cond := range conds {
			C.add_struct_arg_cmp(C.scmp_cast_t(argsArr), C.uint(i),
				C.uint(cond.Argument), cond.Op.toNative(),
				C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
		}
		if err := f.addRuleWrapper(call, action, exact, C.uint(len(conds)), C.scmp_cast_t(argsArr)); err != nil {
			return err