Skip to content

Commit f1066c8

Browse files
committed
add simple seccomp setup
1 parent dfcd48f commit f1066c8

4 files changed

Lines changed: 69 additions & 18 deletions

File tree

.github/workflows/wheel.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
uses: actions/checkout@v4
4040

4141
- name: Install deps
42-
run: apt update && apt install -y git g++-13
42+
run: apt update && apt install -y git g++-13 libseccomp-dev pkg-config
4343

4444
- name: Install the latest version of uv
4545
uses: astral-sh/setup-uv@v7

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ set(CMAKE_CXX_STANDARD 17)
77
set(CMAKE_CXX_STANDARD_REQUIRED ON)
88
set(CMAKE_CUDA_ARCHITECTURES "80;90")
99

10+
find_package(PkgConfig REQUIRED)
11+
pkg_check_modules(LIBSECCOMP REQUIRED IMPORTED_TARGET libseccomp)
12+
1013
FetchContent_Declare(
1114
nanobind
1215
QUIET
@@ -28,7 +31,7 @@ nanobind_add_module(_pygpubench
2831
csrc/landlock.cpp
2932
csrc/obfuscate.cpp
3033
)
31-
target_link_libraries(_pygpubench PUBLIC Python::Module CUDA::cudart)
34+
target_link_libraries(_pygpubench PUBLIC Python::Module CUDA::cudart PkgConfig::LIBSECCOMP)
3235
# set a bunch of hardening options to make it harder to tamper with the executable
3336
target_compile_options(_pygpubench PUBLIC -fPIC -pie -fstack-protector-strong -fcf-protection=full -ftrivial-auto-var-init=zero -Wl,-z,relro,-z,now )
3437
target_compile_definitions(_pygpubench PUBLIC -D_GLIBCXX_ASSERTIONS -D_FORTIFY_SOURCE=3)

csrc/landlock.cpp

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <sys/syscall.h>
1717
#include <unistd.h>
1818
#include <linux/landlock.h>
19+
#include <seccomp.h>
1920
#include <system_error>
2021
#include <unordered_set>
2122
#include <utility>
@@ -114,21 +115,6 @@ void install_landlock() {
114115
allow_path(ruleset_fd, "/tmp", RW);
115116
allow_path(ruleset_fd, "/dev", RW); // needed for /dev/null etc, used e.g., by triton
116117

117-
// Prevent ptrace and /proc/self/mem tampering
118-
if (prctl(PR_SET_DUMPABLE, 0) < 0) {
119-
throw std::system_error(errno, std::system_category(), "prctl(PR_SET_DUMPABLE)");
120-
}
121-
122-
// Prevent gaining privileges (if attacker tries setuid exploits)
123-
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
124-
throw std::system_error(errno, std::system_category(), "prctl(PR_SET_NO_NEW_PRIVS)");
125-
};
126-
// no new executable code pages
127-
// note: this also prevents thread creating, which breaks torch.compile
128-
// workaround: run torch.compile once from trusted python code, then the thread already
129-
// exists at this point. does not seem reliable, so disabled for now
130-
// prctl(PR_SET_MDWE, PR_MDWE_REFUSE_EXEC_GAIN, 0, 0, 0);
131-
132118
landlock_restrict_self(ruleset_fd, 0);
133119
}
134120

@@ -192,4 +178,63 @@ void seal_executable_mappings() {
192178
for (auto& r : to_seal) {
193179
mseal(reinterpret_cast<void*>(r.start), r.end - r.start, r.src);
194180
}
195-
}
181+
}
182+
183+
static inline void check_seccomp(int rc, const char* what) {
184+
if (rc < 0)
185+
throw std::system_error(-rc, std::generic_category(), what);
186+
}
187+
188+
void setup_seccomp_filter(scmp_filter_ctx ctx) {
189+
check_seccomp(seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(ptrace), 0),
190+
"block ptrace");
191+
192+
check_seccomp(seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(prctl), 2,
193+
SCMP_A0(SCMP_CMP_EQ, PR_SET_DUMPABLE),
194+
SCMP_A1(SCMP_CMP_EQ, 1)),
195+
"block prctl(SET_DUMPABLE, 1)");
196+
197+
check_seccomp(seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(prctl), 1,
198+
SCMP_A0(SCMP_CMP_EQ, PR_SET_SECCOMP)),
199+
"block prctl(SET_SECCOMP)");
200+
// TODO figure out what else we can and should block
201+
/*
202+
check_seccomp(seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mprotect), 1,
203+
SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_WRITE, PROT_WRITE)),
204+
"block mprotect+WRITE");
205+
206+
check_seccomp(seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(pkey_mprotect), 1,
207+
SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_WRITE, PROT_WRITE)),
208+
"block pkey_mprotect+WRITE");
209+
*/
210+
}
211+
212+
void install_seccomp_filter() {
213+
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
214+
if (!ctx) throw std::runtime_error("seccomp_init failed");
215+
try {
216+
setup_seccomp_filter(ctx);
217+
} catch (...) {
218+
seccomp_release(ctx);
219+
throw;
220+
}
221+
222+
// Prevent ptrace and /proc/self/mem tampering
223+
if (prctl(PR_SET_DUMPABLE, 0) < 0) {
224+
throw std::system_error(errno, std::system_category(), "prctl(PR_SET_DUMPABLE)");
225+
}
226+
227+
// Prevent gaining privileges (if attacker tries setuid exploits)
228+
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
229+
throw std::system_error(errno, std::system_category(), "prctl(PR_SET_NO_NEW_PRIVS)");
230+
};
231+
// no new executable code pages
232+
// note: this also prevents thread creating, which breaks torch.compile
233+
// workaround: run torch.compile once from trusted python code, then the thread already
234+
// exists at this point. does not seem reliable, so disabled for now
235+
// prctl(PR_SET_MDWE, PR_MDWE_REFUSE_EXEC_GAIN, 0, 0, 0);
236+
237+
int rc = seccomp_load(ctx);
238+
seccomp_release(ctx);
239+
check_seccomp(rc, "seccomp_load");
240+
}

csrc/manager.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern void clear_cache(void* dummy_memory, int size, bool discard, cudaStream_t
2222
extern void install_landlock();
2323
extern bool mseal_supported();
2424
extern void seal_executable_mappings();
25+
extern void install_seccomp_filter();
2526

2627
static void check_check_approx_match_dispatch(unsigned* result, void* expected_data, nb::dlpack::dtype expected_type,
2728
const nb_cuda_array& received, float r_tol, float a_tol, unsigned seed, std::size_t n_bytes, cudaStream_t stream) {
@@ -293,6 +294,8 @@ void BenchmarkManager::do_bench_py(
293294
seal_executable_mappings();
294295
}
295296

297+
install_seccomp_filter();
298+
296299
// at this point, we call user code as we import the kernel (executing arbitrary top-level code)
297300
// after this, we cannot trust python anymore
298301
nb::callable kernel = kernel_from_qualname(kernel_qualname);

0 commit comments

Comments
 (0)