苏ICP备网站建设中企动力无锡代理域名网站的公司
苏ICP备网站建设中企动力无锡,代理域名网站的公司,上传网站空间的建站程序怎么删除,成都好玩的地方1. Seccomp BPF 是什么#xff1f;为什么你需要它#xff1f;
想象一下#xff0c;你正在运行一个从网上下载的、或者处理不可信用户输入的程序。你心里是不是有点打鼓#xff0c;担心它会不会在你的系统里“胡作非为”#xff1f;比如#xff0c;偷偷读取你的私人文件 // 系统调用号 __u32 arch; // CPU 架构如 AUDIT_ARCH_X86_64 __u64 instruction_pointer; // 触发系统调用的指令地址 __u64 args[6]; // 系统调用的最多6个参数 };我们的 BPF 程序就像一个小法官它检查seccomp_data里的信息然后做出判决。判决结果通过一个返回值告诉内核。这个返回值的高 16 位表示动作低 16 位是附加数据。常见的动作有SECCOMP_RET_ALLOW允许这个系统调用执行。SECCOMP_RET_KILL_PROCESS / SECCOMP_RET_KILL_THREAD立刻杀死进程或线程。SECCOMP_RET_ERRNO拒绝系统调用并让进程收到一个指定的错误码如 EPERM。SECCOMP_RET_TRACE通知外部的跟踪器如 ptrace让跟踪器决定如何处理。SECCOMP_RET_LOG允许系统调用执行但在内核日志中记录一条信息用于审计和调试。SECCOMP_RET_USER_NOTIF这是高级功能通知一个用户空间的“监管者”进程来做决定我们后面会详细讲。BPF 程序本身由一系列指令构成每条指令的格式是struct sock_filter。不过别担心我们不需要手写这些晦涩的字节码。内核提供了两个非常方便的宏来帮助我们构造指令BPF_STMT用于声明操作和BPF_JUMP用于条件跳转。这就像用高级语言写汇编虽然还是底层但已经友好了很多。3. 手把手编写你的第一个 Seccomp BPF 过滤器理论说再多不如动手试一下。我们来写一个最简单的例子禁止当前进程使用fork系统调用来创建子进程。这个场景很常见比如你想确保一个服务进程不会意外地“分身”。首先我们需要一个关键的系统调用prctl来设置 Seccomp。在设置过滤器之前通常还需要设置PR_SET_NO_NEW_PRIVS标志。这个标志非常重要它确保了我们设置的 Seccomp 规则在进程执行execve加载新程序后依然有效并且防止子进程通过 setuid 等操作获得更高权限从而绕过我们的过滤器。#define _GNU_SOURCE #include stdio.h #include stdlib.h #include unistd.h #include sys/prctl.h #include linux/seccomp.h #include linux/filter.h #include linux/audit.h #include sys/syscall.h int main() { printf(开始配置 Seccomp BPF 过滤器禁止 fork...\n); // 第一步设置 NO_NEW_PRIVS这是启用过滤器的前提 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) -1) { perror(prctl(PR_SET_NO_NEW_PRIVS) 失败); exit(EXIT_FAILURE); } // 第二步定义我们的 BPF 过滤器程序 // 这个过滤器的逻辑是 // 1. 加载系统调用号 (nr) // 2. 检查是否是 __NR_fork (在 x86-64 上为 57) // 3. 如果是返回 KILL_THREAD (杀死线程) // 4. 否则返回 ALLOW (允许执行) struct sock_filter filter[] { // 将 seccomp_data.nr 的值加载到累加器 BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), // 比较累加器是否等于 __NR_fork如果不等跳转到下一条指令偏移0如果相等跳转到下一条指令偏移1 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fork, 1, 0), // 允许其他所有系统调用 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), // 拒绝 fork 系统调用 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_THREAD), }; // 第三步将过滤器程序打包成 sock_fprog 结构 struct sock_fprog prog { .len (unsigned short)(sizeof(filter) / sizeof(filter[0])), .filter filter, }; // 第四步应用过滤器 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog) -1) { perror(prctl(PR_SET_SECCOMP) 失败); exit(EXIT_FAILURE); } printf(Seccomp 过滤器已加载。现在尝试 fork...\n); // 尝试 fork这应该会被阻止 pid_t pid fork(); if (pid -1) { // fork 失败是我们期望的 perror(fork 失败 (符合预期)); printf(进程没有被杀死说明过滤器可能没生效等等我们检查下...\n); // 注意如果过滤器返回的是 KILL进程会直接终止不会执行到这里。 // 如果返回的是 ERRNOerrno 会被设置进程继续。 } else if (pid 0) { printf(子进程这行不应该被打印\n); exit(0); } else { printf(父进程fork 被成功阻止了。\n); } // 一个仍然被允许的系统调用 printf(write 系统调用仍然可以工作。\n); return 0; }把这段代码保存为seccomp_demo.c然后编译运行gcc -o seccomp_demo seccomp_demo.c ./seccomp_demo你会看到类似这样的输出开始配置 Seccomp BPF 过滤器禁止 fork... Seccomp 过滤器已加载。现在尝试 fork... fork 失败 (符合预期): Operation not permitted 进程没有被杀死说明过滤器可能没生效等等我们检查下... write 系统调用仍然可以工作。等等这里有个问题我们的程序没有像预期那样被杀死而是fork返回了错误。这是因为我们犯了一个新手常见错误我们过滤的是__NR_fork但在现代的 glibc 库中fork()函数可能实际上调用的是clone系统调用。而且我们的过滤器逻辑也有点小瑕疵。让我们修复它让它更健壮并且真正地“杀死”违规行为。3.1 修复和改进第一个例子我们需要同时过滤fork、clone、vfork这几个用于创建进程的系统调用。并且我们应该先检查系统架构确保过滤器只在我们预期的架构比如 x86_64上工作这是一个非常重要的安全实践。// ... 前面的头文件包含和 main 函数开始部分相同 ... // 改进后的 BPF 过滤器 struct sock_filter filter[] { // 首先检查架构必须是 x86_64 BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0), // 如果架构不匹配直接杀死进程因为过滤器可能不适用于其他架构 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), // 加载系统调用号 BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), // 检查是否是 fork (57), clone (56), vfork (58) BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fork, 5, 0), // 匹配则跳转到“拒绝”指令 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clone, 4, 0), // 不匹配则继续检查下一个 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_vfork, 3, 0), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clone3, 2, 0), // 也检查较新的 clone3 // 如果不是上述创建进程的系统调用则允许 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), // 如果是则拒绝并返回 EPERM 错误码而不是直接杀死 // SECCOMP_RET_ERRNO 的高16位是动作低16位是 errno 值 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM SECCOMP_RET_DATA)), }; struct sock_fprog prog { .len (unsigned short)(sizeof(filter) / sizeof(filter[0])), .filter filter, }; // 设置 NO_NEW_PRIVS if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) -1) { perror(prctl(PR_SET_NO_NEW_PRIVS)); exit(EXIT_FAILURE); } // 加载过滤器 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog) -1) { perror(prctl(PR_SET_SECCOMP)); exit(EXIT_FAILURE); } printf(改进版 Seccomp 过滤器已加载。\n); pid_t pid fork(); if (pid -1) { perror(fork 被阻止 (返回错误)); } else if (pid 0) { printf(子进程这行不应该被打印\n); _exit(0); // 子进程用 _exit } else { printf(父进程fork 调用失败子进程未被创建。\n); wait(NULL); // 等待子进程实际上不会有子进程 } // 测试一个仍然允许的系统调用 write(STDOUT_FILENO, write 调用成功。\n, 18); return 0;这次fork()会失败并设置errno为EPERM操作不允许。程序会继续运行并打印出write 调用成功。。这种方式比直接杀死进程更友好也便于调试。你可以通过strace命令来验证系统调用确实被拦截了strace -f ./seccomp_demo_improved。4. 使用 libseccomp更优雅、更安全的方式手写 BPF 指令就像用汇编语言编程虽然强大且高效但容易出错而且可读性差。在实际项目中我们几乎总是使用libseccomp这个库。它提供了一套高级 API让你可以用更直观的方式定义规则比如“允许所有以read开头的系统调用”或者“拒绝open但只拒绝以 O_WRONLY 模式打开/etc/passwd的情况”。libseccomp 帮你处理了底层 BPF 指令的生成、架构检查等繁琐且易错的工作。我们先用包管理器安装它以 Ubuntu 为例sudo apt-get install libseccomp-dev然后用 libseccomp 重写上面的“禁止创建进程”的例子#define _GNU_SOURCE #include stdio.h #include stdlib.h #include unistd.h #include seccomp.h int main() { printf(使用 libseccomp 配置过滤器...\n); // 初始化过滤器上下文默认动作是允许所有SCMP_ACT_ALLOW scmp_filter_ctx ctx seccomp_init(SCMP_ACT_ALLOW); if (ctx NULL) { perror(seccomp_init failed); exit(EXIT_FAILURE); } // 添加规则禁止 fork, clone, vfork, clone3 // 参数上下文动作系统调用号参数个数0表示不检查参数 if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fork), 0) ! 0) { perror(seccomp_rule_add for fork failed); seccomp_release(ctx); exit(EXIT_FAILURE); } if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(clone), 0) ! 0) { perror(seccomp_rule_add for clone failed); seccomp_release(ctx); exit(EXIT_FAILURE); } if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(vfork), 0) ! 0) { perror(seccomp_rule_add for vfork failed); seccomp_release(ctx); exit(EXIT_FAILURE); } // 注意clone3 可能需要较新的 libseccomp 和内核支持 // if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(clone3), 0) ! 0) { ... } // 加载过滤器到内核 if (seccomp_load(ctx) ! 0) { perror(seccomp_load failed); seccomp_release(ctx); exit(EXIT_FAILURE); } printf(libseccomp 过滤器已加载。\n); // 测试 fork pid_t pid fork(); if (pid -1) { perror(fork 被 libseccomp 阻止); } else if (pid 0) { printf(子进程不应出现。\n); _exit(0); } else { printf(父进程继续执行。\n); } // 释放过滤器上下文资源 seccomp_release(ctx); return 0; }编译时需要链接libseccompgcc -o seccomp_lib seccomp_lib.c -lseccomp ./seccomp_lib代码是不是清晰多了seccomp_init初始化一个过滤器上下文seccomp_rule_add添加一条条规则最后seccomp_load生效。SCMP_ACT_ERRNO(EPERM)表示匹配此规则时返回EPERM错误。4.1 进阶基于参数的过滤libseccomp 的强大之处在于可以检查系统调用的参数。比如我们想允许open系统调用但禁止以写入模式打开/etc/shadow这个敏感文件。// ... 省略初始化和错误处理 ... scmp_filter_ctx ctx seccomp_init(SCMP_ACT_ALLOW); // 添加一条针对 openat 的规则现代程序常用 openat // 规则如果 openat 的第二个参数文件路径指针指向的字符串是 /etc/shadow // 并且第三个参数flags包含 O_WRONLY、O_RDWR 或 O_APPEND 等写入标志 // 则返回 EACCES 错误。 // SCMP_A1 表示检查第二个参数 (const char *pathname) // SCMP_CMP_EQ 表示比较方式为相等 // SCMP_A2 表示检查第三个参数 (int flags) // SCMP_CMP_MASKED_EQ 表示进行掩码比较 (arg mask) value if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(openat), 3, SCMP_A0(SCMP_CMP_EQ, AT_FDCWD), // 第一个参数是 AT_FDCWD SCMP_A1(SCMP_CMP_EQ, (scmp_datum_t)/etc/shadow), // 注意这里比较的是指针值不是字符串内容实际应用需用更复杂的方法。 SCMP_A2(SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR | O_APPEND, O_WRONLY)) ! 0) { fprintf(stderr, 无法添加 openat 规则\n); seccomp_release(ctx); exit(EXIT_FAILURE); } // ... 加载和测试 ...注意上面的例子中SCMP_A1(SCMP_CMP_EQ, (scmp_datum_t)/etc/shadow)这行代码比较的是字符串常量的地址而不是字符串内容。在实际拦截中这通常不是我们想要的。基于指针值的比较在 Seccomp BPF 中是不安全的也是不推荐的因为攻击者可以传入其他指向相同字符串的指针。Seccomp BPF 的设计原则就是不能解引用用户空间指针以防止 TOCTOU检查时间/使用时间攻击。因此基于字符串路径的精细过滤通常需要在用户空间通过其他机制如 seccomp notify来实现或者依赖文件描述符fd和文件路径名前缀通过SCMP_CMP_STRNEQ等有限支持进行更安全的判断。libseccomp 提供了一些辅助函数来处理路径名参数但本质上仍有限制。更常见的做法是结合Capabilities和文件系统命名空间来限制对特定目录的访问Seccomp 则用来限制系统调用的类别。5. 实战案例为网络服务进程构建安全策略假设我们有一个简单的网络服务它只需要1) 监听套接字2) 接受连接3) 读写数据4) 管理内存brk,mmap5) 使用基本的文件描述符操作read,write,close,dup2。它不需要1) 创建新进程2) 执行其他程序3) 加载内核模块4) 修改系统时间5) 访问任意文件系统。我们可以利用 libseccomp 来构建这样一个白名单策略。Docker 的默认 Seccomp 配置文件就是一个极好的参考模板。我们可以从一个允许所有系统调用的默认策略开始然后逐步添加我们需要的规则但更常见的做法是从一个拒绝所有的默认策略开始然后显式地允许必要的系统调用。#include seccomp.h #include errno.h int install_syscall_filter() { scmp_filter_ctx ctx; int rc; // 初始化一个“白名单”过滤器默认拒绝所有然后逐个允许 ctx seccomp_init(SCMP_ACT_KILL_PROCESS); // 默认动作杀死进程 if (ctx NULL) { return -1; } // 允许进程终止相关的调用 rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0); // 允许内存管理 rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); // 允许基本文件/描述符操作 rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup2), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup3), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0); // 允许网络操作 (一个简单的 TCP 服务) rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(bind), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(listen), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept4), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(connect), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendto), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockname), 0); // 允许获取时间、获取随机数用于 TLS 等 rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_gettime), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettimeofday), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrandom), 0); // 允许一些必要的系统信息调用 rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(uname), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0); rc | seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettid), 0); if (rc ! 0) { seccomp_release(ctx); return -1; } // 加载过滤器 if (seccomp_load(ctx) ! 0) { seccomp_release(ctx); return -1; } seccomp_release(ctx); return 0; }在你的网络服务main函数开头调用install_syscall_filter()。这样即使服务代码存在漏洞被利用攻击者也很难执行execve来启动 shell或者调用ptrace进行调试因为根本不在白名单里。这极大地缩小了攻击面。6. 高级特性Seccomp Notify 与用户空间监管从 Linux 5.0 开始Seccomp 引入了一个强大的新特性Seccomp Notify (SECCOMP_RET_USER_NOTIF)。它允许内核将过滤决策“委托”给一个用户空间的“监管者”进程。这解决了传统 BPF 过滤器的两个主要限制无法检查指针指向的内存内容BPF 不能解引用指针所以无法基于字符串路径等复杂参数做决策。策略静态无法动态适应一旦加载BPF 程序就不能改变。有了 Seccomp Notify当目标进程触发一个被标记为SECCOMP_RET_USER_NOTIF的系统调用时内核会挂起目标进程并通过一个文件描述符通知监管者进程。监管者进程可以读取目标进程的内存通过/proc/[pid]/mem从而检查系统调用的字符串参数。根据复杂的逻辑做出决策。代表目标进程执行该系统调用或模拟其行为。将结果或错误返回给内核内核再恢复目标进程。这为构建灵活的沙箱、容器运行时和安全监控工具打开了新的大门。例如一个容器管理器可以监管容器内进程的所有mount或open调用只允许其访问特定的目录。下面是一个极简的示例框架展示监管者如何接收和处理一个open调用的通知// 目标进程 (target.c) - 简化版 #include linux/seccomp.h #include linux/filter.h #include sys/prctl.h #include sys/syscall.h #include stdio.h #include unistd.h int main() { 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_open, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF), // 将 open 调用通知用户空间 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), }; struct sock_fprog prog { .len 4, .filter filter }; prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); // 使用 seccomp 系统调用并获取一个通知 fd int notify_fd syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, prog); printf(Target PID: %d, Notify FD: %d\n, getpid(), notify_fd); // 尝试打开文件这会触发通知 open(/tmp/test.txt, O_RDONLY); return 0; }// 监管者进程 (supervisor.c) - 框架示例 #include linux/seccomp.h #include sys/ioctl.h #include stdio.h #include string.h #include errno.h void handle_notification(int notify_fd) { struct seccomp_notif req {}; struct seccomp_notif_resp resp {}; // 1. 接收通知 if (ioctl(notify_fd, SECCOMP_IOCTL_NOTIF_RECV, req) -1) { perror(接收通知失败); return; } printf(收到来自 PID %d 的系统调用通知调用号: %d\n, req.pid, req.data.nr); // 2. 这里可以检查 req.data.args[] 中的参数通过 /proc/[pid]/mem 读取内存等 // 3. 做出决策 resp.id req.id; resp.error 0; // 允许调用 resp.val 0; resp.flags SECCOMP_USER_NOTIF_FLAG_CONTINUE; // 让内核继续执行原系统调用 // 4. 发送决策回内核 if (ioctl(notify_fd, SECCOMP_IOCTL_NOTIF_SEND, resp) -1) { perror(发送响应失败); } } int main(int argc, char* argv[]) { if (argc ! 2) { printf(用法: %s 目标进程的 notify_fd\n, argv[0]); return 1; } int notify_fd atoi(argv[1]); handle_notification(notify_fd); return 0; }这个例子非常基础实际应用中监管者需要处理并发、超时、目标进程死亡等多种情况。但它的模式是清晰的内核将决策权交给了更强大、更灵活的用户空间程序。7. 调试与排查当 Seccomp 不按预期工作时给程序加上 Seccomp 过滤器后最头疼的就是它莫名其妙地崩溃或被杀死而你不知道是哪个系统调用触发了规则。这里有几个实用的调试技巧使用 SECCOMP_RET_LOG这是最友好的方式。在开发阶段你可以将默认动作或某些规则的动作设为SECCOMP_RET_LOGlibseccomp 中是SCMP_ACT_LOG。这样当系统调用被过滤时内核会在日志如dmesg或journalctl -k中记录一条信息而不是杀死进程。你可以看到是哪个系统调用、哪个线程触发了规则。// 使用 libseccomp 的日志模式 ctx seccomp_init(SCMP_ACT_LOG); // 默认记录所有系统调用可能很吵 // 或者针对特定规则记录 seccomp_rule_add(ctx, SCMP_ACT_LOG, SCMP_SYS(some_syscall), 0);使用strace工具strace可以跟踪进程所有的系统调用。在 Seccomp 杀死进程前strace能帮你看到最后一个系统调用是什么。strace -f -o trace.log ./your_program查看trace.log文件的最后几行。检查系统调用号不同 CPU 架构的系统调用号可能不同。确保你的过滤器是针对正确的架构编译和运行的。使用uname -m查看架构并在 BPF 程序开头用arch字段进行检查就像我们在 3.1 节做的那样。循序渐进不要一开始就上一个严格的白名单。可以先从一个宽松的策略开始比如只禁止一两个你知道不会用的系统调用确保程序能跑。然后利用SECCOMP_RET_LOG观察程序实际使用了哪些系统调用逐步收紧策略。Docker 的默认 Seccomp 配置文件就是一个经过千锤百炼的白名单直接拿来参考或修改是个好起点。理解 libc 与系统调用的映射你的 C 库函数如printf可能调用多个系统调用。禁止了其中一个就可能导致程序崩溃。多查手册了解底层机制。我在实际项目里整合 Seccomp 时经常先用SCMP_ACT_LOG跑一遍所有功能用例收集日志分析出必需的系统调用集合然后再生成正式的白名单。这个过程虽然繁琐但对理解程序的行为和构建坚实的安全边界至关重要。