From 954beb619a93764b6941f1f6f56b1e9f28c12ed7 Mon Sep 17 00:00:00 2001 From: Yukinari Toyota Date: Mon, 16 Dec 2024 21:52:06 +0900 Subject: [PATCH] Allow Nodejs async fetch --- internal/core/lib/nodejs/add_seccomp.go | 4 +- internal/core/lib/python/add_seccomp.go | 2 +- internal/core/lib/seccomp.go | 23 ++++-- internal/core/lib/seccomp_syscall_amd64.go | 1 + internal/core/lib/seccomp_syscall_arm64.go | 1 + .../static/nodejs_syscall/syscalls_amd64.go | 31 +++++++- .../static/nodejs_syscall/syscalls_arm64.go | 2 + .../integration_tests/nodejs_feature_test.go | 78 ++++++++++++++++++- 8 files changed, 131 insertions(+), 11 deletions(-) diff --git a/internal/core/lib/nodejs/add_seccomp.go b/internal/core/lib/nodejs/add_seccomp.go index 401a6ca2..e74d4d00 100644 --- a/internal/core/lib/nodejs/add_seccomp.go +++ b/internal/core/lib/nodejs/add_seccomp.go @@ -26,6 +26,7 @@ func InitSeccomp(uid int, gid int, enable_network bool) error { allowed_syscalls := []int{} allowed_not_kill_syscalls := []int{} + allowed_syscall_values := map[int]uint64{} allowed_syscall := os.Getenv("ALLOWED_SYSCALLS") if allowed_syscall != "" { @@ -43,10 +44,11 @@ func InitSeccomp(uid int, gid int, enable_network bool) error { if enable_network { allowed_syscalls = append(allowed_syscalls, nodejs_syscall.ALLOW_NETWORK_SYSCALLS...) + allowed_syscall_values = nodejs_syscall.ALLOW_NETWORK_SYSCALL_VALUES } } - err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls) + err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls, allowed_syscall_values) if err != nil { return err } diff --git a/internal/core/lib/python/add_seccomp.go b/internal/core/lib/python/add_seccomp.go index d06a62a4..0b90c5e1 100644 --- a/internal/core/lib/python/add_seccomp.go +++ b/internal/core/lib/python/add_seccomp.go @@ -45,7 +45,7 @@ func InitSeccomp(uid int, gid int, enable_network bool) error { } } - err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls) + err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls, nil) if err != nil { return err } diff --git a/internal/core/lib/seccomp.go b/internal/core/lib/seccomp.go index c4c982d5..a84eb400 100644 --- a/internal/core/lib/seccomp.go +++ b/internal/core/lib/seccomp.go @@ -10,7 +10,7 @@ import ( sg "github.com/seccomp/libseccomp-golang" ) -func Seccomp(allowed_syscalls []int, allowed_not_kill_syscalls []int) error { +func Seccomp(allowed_syscalls []int, allowed_not_kill_syscalls []int, allowed_syscall_values map[int]uint64) error { ctx, err := sg.NewFilter(sg.ActKillProcess) if err != nil { return err @@ -23,12 +23,25 @@ func Seccomp(allowed_syscalls []int, allowed_not_kill_syscalls []int) error { defer reader.Close() defer writer.Close() - for _, syscall := range allowed_syscalls { - ctx.AddRule(sg.ScmpSyscall(syscall), sg.ActAllow) + for _, sc := range allowed_syscalls { + val, ok := allowed_syscall_values[sc] + if ok { + ctx.AddRuleConditional(sg.ScmpSyscall(sc), sg.ActAllow, []sg.ScmpCondition{ + {Argument: 0, Op: sg.CompareEqual, Operand1: val}, + }) + } else { + ctx.AddRule(sg.ScmpSyscall(sc), sg.ActAllow) + } } - for _, syscall := range allowed_not_kill_syscalls { - ctx.AddRule(sg.ScmpSyscall(syscall), sg.ActErrno) + for _, sc := range allowed_not_kill_syscalls { + switch sc { + case SYS_CLONE3: + // use ENOSYS for CLONE3, see: https://github.com/moby/moby/issues/42680 + ctx.AddRule(sg.ScmpSyscall(sc), sg.ActErrno.SetReturnCode(int16(syscall.ENOSYS))) + default: + ctx.AddRule(sg.ScmpSyscall(sc), sg.ActErrno) + } } file := os.NewFile(uintptr(writer.Fd()), "pipe") diff --git a/internal/core/lib/seccomp_syscall_amd64.go b/internal/core/lib/seccomp_syscall_amd64.go index 5ebe4349..7828b0ad 100644 --- a/internal/core/lib/seccomp_syscall_amd64.go +++ b/internal/core/lib/seccomp_syscall_amd64.go @@ -4,4 +4,5 @@ package lib const ( SYS_SECCOMP = 317 + SYS_CLONE3 = 435 ) diff --git a/internal/core/lib/seccomp_syscall_arm64.go b/internal/core/lib/seccomp_syscall_arm64.go index 47a2befa..976cf40e 100644 --- a/internal/core/lib/seccomp_syscall_arm64.go +++ b/internal/core/lib/seccomp_syscall_arm64.go @@ -6,4 +6,5 @@ import "syscall" const ( SYS_SECCOMP = syscall.SYS_SECCOMP + SYS_CLONE3 = 435 ) diff --git a/internal/static/nodejs_syscall/syscalls_amd64.go b/internal/static/nodejs_syscall/syscalls_amd64.go index 2a40572d..baa08103 100644 --- a/internal/static/nodejs_syscall/syscalls_amd64.go +++ b/internal/static/nodejs_syscall/syscalls_amd64.go @@ -2,13 +2,24 @@ package nodejs_syscall -import "syscall" +import ( + "syscall" + + "golang.org/x/sys/unix" +) const ( //334 SYS_RSEQ = 334 // 435 SYS_CLONE3 = 435 + + SYS_SENDMMSG = 307 + SYS_GETRANDOM = 318 + SYS_PKEY_ALLOC = 329 + SYS_PKEY_MPROTECT = 330 + SYS_PKEY_FREE = 331 + SYS_STATX = 332 ) var ALLOW_SYSCALLS = []int{ @@ -30,7 +41,6 @@ var ALLOW_SYSCALLS = []int{ SYS_RSEQ, syscall.SYS_SETUID, syscall.SYS_SETGID, syscall.SYS_GETTID, - syscall.SYS_CLOCK_GETTIME, syscall.SYS_GETTIMEOFDAY, syscall.SYS_NANOSLEEP, syscall.SYS_TIME, @@ -38,10 +48,17 @@ var ALLOW_SYSCALLS = []int{ syscall.SYS_READLINK, syscall.SYS_DUP3, + + syscall.SYS_PIPE2, + syscall.SYS_SET_TID_ADDRESS, + syscall.SYS_PREAD64, syscall.SYS_PWRITE64, + + SYS_GETRANDOM, + syscall.SYS_EPOLL_CREATE1, syscall.SYS_EVENTFD2, } var ALLOW_ERROR_SYSCALLS = []int{ - syscall.SYS_CLONE, SYS_CLONE3, + SYS_CLONE3, /* return ENOSYS for glibc */ } var ALLOW_NETWORK_SYSCALLS = []int{ @@ -49,4 +66,12 @@ var ALLOW_NETWORK_SYSCALLS = []int{ syscall.SYS_GETSOCKNAME, syscall.SYS_RECVMSG, syscall.SYS_GETPEERNAME, syscall.SYS_SETSOCKOPT, syscall.SYS_PPOLL, syscall.SYS_UNAME, syscall.SYS_SENDMSG, syscall.SYS_GETSOCKOPT, syscall.SYS_FCNTL, syscall.SYS_FSTATFS, + SYS_SENDMMSG, syscall.SYS_POLL, + SYS_PKEY_ALLOC, SYS_PKEY_MPROTECT, SYS_PKEY_FREE, SYS_STATX, + syscall.SYS_CLONE, +} + +var ALLOW_NETWORK_SYSCALL_VALUES = map[int]uint64{ + // allow clone for nodejs networking + syscall.SYS_CLONE: unix.CLONE_VM | unix.CLONE_FS | unix.CLONE_FILES | unix.CLONE_SIGHAND | unix.CLONE_THREAD | unix.CLONE_SYSVSEM | unix.CLONE_SETTLS | unix.CLONE_PARENT_SETTID | unix.CLONE_CHILD_CLEARTID, } diff --git a/internal/static/nodejs_syscall/syscalls_arm64.go b/internal/static/nodejs_syscall/syscalls_arm64.go index aff900cc..c2e42c51 100644 --- a/internal/static/nodejs_syscall/syscalls_arm64.go +++ b/internal/static/nodejs_syscall/syscalls_arm64.go @@ -42,3 +42,5 @@ var ALLOW_NETWORK_SYSCALLS = []int{ syscall.SYS_FSTATAT, syscall.SYS_LSEEK, syscall.SYS_FSTATFS, } + +var ALLOW_NETWORK_SYSCALL_VALUES = map[int]uint64{} diff --git a/tests/integration_tests/nodejs_feature_test.go b/tests/integration_tests/nodejs_feature_test.go index 12aa3e62..37c35d52 100644 --- a/tests/integration_tests/nodejs_feature_test.go +++ b/tests/integration_tests/nodejs_feature_test.go @@ -67,7 +67,7 @@ console.log(JSON.stringify({"hello": "world"})); EnableNetwork: true, }) if resp.Code != 0 { - t.Error(resp) + t.Fatal(resp) } if resp.Data.(*service.RunCodeResponse).Stderr != "" { @@ -79,3 +79,79 @@ console.log(JSON.stringify({"hello": "world"})); } }) } + +func TestNodejsAsyncTemplate(t *testing.T) { + const code = `// declare main function +function main({a}) { + return {b: a} +} + +async function wrap() { + // decode and prepare input object + var inputs_obj = JSON.parse(Buffer.from('eyJhIjoiYSJ9', 'base64').toString('utf-8')) + + // execute main function + var output_obj = await main(inputs_obj) + + // convert output to json and print + var output_json = JSON.stringify(output_obj) + var result = ` + "`<>${output_json}<>`" + ` + console.log(result) +} +wrap() +` + + runMultipleTestings(t, 30, func(t *testing.T) { + resp := service.RunNodeJsCode(code, "", &types.RunnerOptions{ + EnableNetwork: false, + }) + if resp.Code != 0 { + t.Error(resp) + } + if resp.Data.(*service.RunCodeResponse).Stderr != "" { + t.Fatalf("unexpected error: %s\n", resp.Data.(*service.RunCodeResponse).Stderr) + } + if !strings.Contains(resp.Data.(*service.RunCodeResponse).Stdout, `{"b":"a"}`) { + t.Fatalf("unexpected output: %s\n", resp.Data.(*service.RunCodeResponse).Stdout) + } + }) +} + +func TestNodejsAsyncFetch(t *testing.T) { + // Test case for json + runMultipleTestings(t, 30, func(t *testing.T) { + const code = `// declare main function +async function main({a}) { + return {b: await (await fetch("https://www.bilibili.com")).text()} +} + +// decode and prepare input object +var inputs_obj = JSON.parse(Buffer.from('eyJhIjoiYSJ9', 'base64').toString('utf-8')) + +async function wrap() { + // execute main function + var output_obj = await main(inputs_obj) + + // convert output to json and print + var output_json = JSON.stringify(output_obj) + var result = ` + "`<>${output_json}<>`" + ` + console.log(result) +} +wrap() +` + resp := service.RunNodeJsCode(code, "", &types.RunnerOptions{ + EnableNetwork: true, + }) + if resp.Code != 0 { + t.Error(resp) + } + + if resp.Data.(*service.RunCodeResponse).Stderr != "" { + t.Fatalf("unexpected error: %s\n", resp.Data.(*service.RunCodeResponse).Stderr) + } + + if !strings.Contains(resp.Data.(*service.RunCodeResponse).Stdout, `bilibili`) { + t.Fatalf("unexpected output: %s\n", resp.Data.(*service.RunCodeResponse).Stdout) + } + }) +}