rule.h: fix initialization failure for libstdcxx#13
Conversation
The libstdcxx uses TCGETS to get the current setting of terminal. This system call requires ioctl permission in seccomp. However, allowing all ioctl is so dangerous that can break the sandbox. Therefore, we use BPF to allow TCGETS only.
|
This should fix problem in https://noj.tw/course/Algorithm2025/submission/68f759d063cf4f3e2636faf7. |
There was a problem hiding this comment.
Pull Request Overview
This PR adds BPF filtering to securely allow only the TCGETS ioctl operation, which is required by libstdcxx for terminal initialization, while maintaining sandbox security by preventing unrestricted ioctl access.
Key Changes:
- Introduces a new
install_seccomp()function that uses BPF to filter ioctl syscalls, allowing only TCGETS - Adds
ioctl,getrandom, andfutexto the syscall whitelist to support libstdcxx initialization
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| int install_seccomp() { | ||
| struct sock_filter filter[] = { | ||
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), | ||
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 1, 0), | ||
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), | ||
|
|
||
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])), |
There was a problem hiding this comment.
The BPF filter checks args[1] (second argument) for the TCGETS value, but ioctl's request code is in args[1] as a 64-bit value on 64-bit systems. This should check args[1] as the lower 32 bits, but the current code loads a 32-bit word which may not correctly match the ioctl request on all architectures. Consider using the architecture-appropriate offset or verifying this works correctly on your target platform.
| int install_seccomp() { | |
| struct sock_filter filter[] = { | |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), | |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 1, 0), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), | |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])), | |
| // Macro to get the offset of the lower 32 bits of args[1] in struct seccomp_data | |
| #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
| #define ARGS1_LO_OFFSET (offsetof(struct seccomp_data, args[1])) | |
| #else | |
| #define ARGS1_LO_OFFSET (offsetof(struct seccomp_data, args[1]) + 4) | |
| #endif | |
| int install_seccomp() { | |
| struct sock_filter filter[] = { | |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), | |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 1, 0), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), | |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ARGS1_LO_OFFSET), |
| int c_cpp_rules(char *target, bool allow_write_file) | ||
| { | ||
| scmp_filter_ctx ctx; | ||
| install_seccomp(); |
There was a problem hiding this comment.
The return value of install_seccomp() is not checked. If the seccomp installation fails (returns -1), the function continues to set up additional seccomp rules via seccomp_init(), which could lead to an incomplete or inconsistent security policy. Should check the return value and handle failure appropriately.
| install_seccomp(); | |
| if (install_seccomp() != 0) | |
| { | |
| return LOAD_SECCOMP_FAILED; | |
| } |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), | ||
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 1, 0), | ||
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), | ||
|
|
||
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])), | ||
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, TCGETS, 0, 1), | ||
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), | ||
|
|
||
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), |
There was a problem hiding this comment.
The BPF logic is inverted: if the syscall is NOT ioctl (jump is taken when comparison fails), it proceeds to line 16 which allows it. If it IS ioctl (no jump), it falls through to check args[1]. This means all non-ioctl syscalls are allowed, which contradicts the intended behavior of a restrictive seccomp filter. The jump offsets should be adjusted to properly filter ioctl while denying other syscalls.
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), | |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 1, 0), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), | |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])), | |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, TCGETS, 0, 1), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), | |
| // Load syscall number | |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), | |
| // If syscall == __NR_ioctl, jump to check args[1] (next instruction); else, kill | |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 0, 1), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), | |
| // Load args[1] (the request argument to ioctl) | |
| BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])), | |
| // If args[1] == TCGETS, allow; else, kill | |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, TCGETS, 0, 1), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), | |
| BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), |
| SCMP_SYS(newfstatat), SCMP_SYS(set_tid_address), | ||
| SCMP_SYS(set_robust_list), SCMP_SYS(rseq), | ||
| SCMP_SYS(prlimit64)}; | ||
| SCMP_SYS(prlimit64), SCMP_SYS(getrandom), SCMP_SYS(futex), SCMP_SYS(ioctl)}; |
There was a problem hiding this comment.
Adding SCMP_SYS(ioctl) to the whitelist contradicts the PR's stated goal of restricting ioctl access to only TCGETS. This allows all ioctl operations through the libseccomp layer, effectively bypassing the BPF filter installed by install_seccomp(). If both filters are active, the behavior depends on their interaction order, which may not be what's intended.
The libstdcxx uses TCGETS to get the current setting of terminal. This system call requires ioctl permission in seccomp. However, allowing all ioctl is so dangerous that can break the sandbox. Therefore, we use BPF to allow TCGETS only.