helper/seccomp: improve error handling
All checks were successful
Test / Create distribution (push) Successful in 24s
Test / Fortify (push) Successful in 2m32s
Test / Fpkg (push) Successful in 3m18s
Test / Data race detector (push) Successful in 3m26s
Test / Flake checks (push) Successful in 47s

This passes both errno and libseccomp return value.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-03-12 15:52:48 +09:00
parent be16970e77
commit 29c3f8becb
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
5 changed files with 142 additions and 50 deletions

View File

@ -115,7 +115,7 @@ func TestExport(t *testing.T) {
t.Errorf("Read: error = %v", err) t.Errorf("Read: error = %v", err)
return return
} }
if err := e.Close(); err == nil || err.Error() != "seccomp_export_bpf failed: operation canceled" { if err := e.Close(); err == nil || !errors.Is(err, syscall.ECANCELED) || !errors.Is(err, syscall.EBADF) {
t.Errorf("Close: error = %v", err) t.Errorf("Close: error = %v", err)
return return
} }

View File

@ -27,28 +27,27 @@ struct f_syscall_act {
#define LEN(arr) (sizeof(arr) / sizeof((arr)[0])) #define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
#define SECCOMP_RULESET_ADD(ruleset) do { \ #define SECCOMP_RULESET_ADD(ruleset) do { \
if (opts & F_VERBOSE) F_println("adding seccomp ruleset \"" #ruleset "\""); \ if (opts & F_VERBOSE) F_println("adding seccomp ruleset \"" #ruleset "\""); \
for (int i = 0; i < LEN(ruleset); i++) { \ for (int i = 0; i < LEN(ruleset); i++) { \
assert(ruleset[i].m_errno == EPERM || ruleset[i].m_errno == ENOSYS); \ assert(ruleset[i].m_errno == EPERM || ruleset[i].m_errno == ENOSYS); \
\ \
if (ruleset[i].arg) \ if (ruleset[i].arg) \
ret = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 1, *ruleset[i].arg); \ *ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 1, *ruleset[i].arg); \
else \ else \
ret = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 0); \ *ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ruleset[i].m_errno), ruleset[i].syscall, 0); \
\ \
if (ret == -EFAULT) { \ if (*ret_p == -EFAULT) { \
res = 4; \ res = 4; \
goto out; \ goto out; \
} else if (ret < 0) { \ } else if (*ret_p < 0) { \
res = 5; \ res = 5; \
errno = -ret; \ goto out; \
goto out; \ } \
} \ } \
} \
} while (0) } while (0)
int32_t f_build_filter(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts) { int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts) {
int32_t res = 0; // refer to resErr for meaning int32_t res = 0; // refer to resErr for meaning
int allow_multiarch = opts & F_MULTIARCH; int allow_multiarch = opts & F_MULTIARCH;
int allowed_personality = PER_LINUX; int allowed_personality = PER_LINUX;
@ -229,8 +228,6 @@ int32_t f_build_filter(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts
} else } else
errno = 0; errno = 0;
int ret;
// We only really need to handle arches on multiarch systems. // We only really need to handle arches on multiarch systems.
// If only one arch is supported the default is fine // If only one arch is supported the default is fine
if (arch != 0) { if (arch != 0) {
@ -239,18 +236,16 @@ int32_t f_build_filter(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts
// allow the target arch, but we can't really disallow the // allow the target arch, but we can't really disallow the
// native arch at this point, because then bubblewrap // native arch at this point, because then bubblewrap
// couldn't continue running. // couldn't continue running.
ret = seccomp_arch_add(ctx, arch); *ret_p = seccomp_arch_add(ctx, arch);
if (ret < 0 && ret != -EEXIST) { if (*ret_p < 0 && *ret_p != -EEXIST) {
res = 2; res = 2;
errno = -ret;
goto out; goto out;
} }
if (allow_multiarch && multiarch != 0) { if (allow_multiarch && multiarch != 0) {
ret = seccomp_arch_add(ctx, multiarch); *ret_p = seccomp_arch_add(ctx, multiarch);
if (ret < 0 && ret != -EEXIST) { if (*ret_p < 0 && *ret_p != -EEXIST) {
res = 3; res = 3;
errno = -ret;
goto out; goto out;
} }
} }
@ -286,17 +281,15 @@ int32_t f_build_filter(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1)); seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1));
if (fd < 0) { if (fd < 0) {
ret = seccomp_load(ctx); *ret_p = seccomp_load(ctx);
if (ret != 0) { if (*ret_p != 0) {
res = 7; res = 7;
errno = -ret;
goto out; goto out;
} }
} else { } else {
ret = seccomp_export_bpf(ctx, fd); *ret_p = seccomp_export_bpf(ctx, fd);
if (ret != 0) { if (*ret_p != 0) {
res = 6; res = 6;
errno = -ret;
goto out; goto out;
} }
} }

View File

@ -20,4 +20,4 @@ typedef enum {
} f_syscall_opts; } f_syscall_opts;
extern void F_println(char *v); extern void F_println(char *v);
int32_t f_build_filter(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts); int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts);

View File

@ -10,19 +10,51 @@ import (
"errors" "errors"
"fmt" "fmt"
"runtime" "runtime"
"syscall"
) )
var CPrintln func(v ...any) var CPrintln func(v ...any)
var resErr = [...]error{ // LibraryError represents a libseccomp error.
0: nil, type LibraryError struct {
1: errors.New("seccomp_init failed"), Prefix string
2: errors.New("seccomp_arch_add failed"), Seccomp syscall.Errno
3: errors.New("seccomp_arch_add failed (multiarch)"), Errno error
4: errors.New("internal libseccomp failure"), }
5: errors.New("seccomp_rule_add failed"),
6: errors.New("seccomp_export_bpf failed"), func (e *LibraryError) Error() string {
7: errors.New("seccomp_load failed"), if e.Seccomp == 0 {
if e.Errno == nil {
panic("invalid libseccomp error")
}
return fmt.Sprintf("%s: %s", e.Prefix, e.Errno)
}
if e.Errno == nil {
return fmt.Sprintf("%s: %s", e.Prefix, e.Seccomp)
}
return fmt.Sprintf("%s: %s (%s)", e.Prefix, e.Seccomp, e.Errno)
}
func (e *LibraryError) Is(err error) bool {
if e == nil {
return err == nil
}
if ef, ok := err.(*LibraryError); ok {
return *e == *ef
}
return (e.Seccomp != 0 && errors.Is(err, e.Seccomp)) ||
(e.Errno != nil && errors.Is(err, e.Errno))
}
var resPrefix = [...]string{
0: "",
1: "seccomp_init failed",
2: "seccomp_arch_add failed",
3: "seccomp_arch_add failed (multiarch)",
4: "internal libseccomp failure",
5: "seccomp_rule_add failed",
6: "seccomp_export_bpf failed",
7: "seccomp_load failed",
} }
type SyscallOpts = C.f_syscall_opts type SyscallOpts = C.f_syscall_opts
@ -71,12 +103,14 @@ func buildFilter(fd int, opts SyscallOpts) error {
opts |= flagVerbose opts |= flagVerbose
} }
res, err := C.f_build_filter(C.int(fd), arch, multiarch, opts) var ret C.int
if re := resErr[res]; re != nil { res, err := C.f_build_filter(&ret, C.int(fd), arch, multiarch, opts)
if err == nil { if prefix := resPrefix[res]; prefix != "" {
return re return &LibraryError{
prefix,
-syscall.Errno(ret),
err,
} }
return fmt.Errorf("%s: %v", re.Error(), err)
} }
return err return err
} }

View File

@ -0,0 +1,65 @@
package seccomp_test
import (
"errors"
"runtime"
"syscall"
"testing"
"git.gensokyo.uk/security/fortify/helper/seccomp"
)
func TestLibraryError(t *testing.T) {
testCases := []struct {
name string
sample *seccomp.LibraryError
want string
wantIs bool
compare error
}{
{
"full",
&seccomp.LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF},
"seccomp_export_bpf failed: operation canceled (bad file descriptor)",
true,
&seccomp.LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF},
},
{
"errno only",
&seccomp.LibraryError{Prefix: "seccomp_init failed", Errno: syscall.ENOMEM},
"seccomp_init failed: cannot allocate memory",
false,
nil,
},
{
"seccomp only",
&seccomp.LibraryError{Prefix: "internal libseccomp failure", Seccomp: syscall.EFAULT},
"internal libseccomp failure: bad address",
true,
syscall.EFAULT,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if errors.Is(tc.sample, tc.compare) != tc.wantIs {
t.Errorf("errors.Is(%#v, %#v) did not return %v",
tc.sample, tc.compare, tc.wantIs)
}
if got := tc.sample.Error(); got != tc.want {
t.Errorf("Error: %q, want %q",
got, tc.want)
}
})
}
t.Run("invalid", func(t *testing.T) {
wantPanic := "invalid libseccomp error"
defer func() {
if r := recover(); r != wantPanic {
t.Errorf("panic: %q, want %q", r, wantPanic)
}
}()
runtime.KeepAlive(new(seccomp.LibraryError).Error())
})
}