子专栏:机制拆解

七个部件,从 waitpid 到 ELF loader,把「无特权 chroot」拆到系统调用这一层

归属:PRoot 解剖

  • PRoot 是什么:把 chroot 做进 ptrace 里

    PRoot 的官方定义只有一句话:chroot、 mount bind 和 binfmt misc 的用户态实现。它不需要 root,不需要内核模块,不需要 user namespace,甚至不需要容器运行时——只要一个 ptrace(2) 。代价同样干脆:被它接管的进程,每走一步都要停下来问它一次。

  • 事件循环:一个 waitpid 撑起的进程树

    PRoot 没有线程池,没有 epoll,没有协程。整个 tracer 就是一个 while (1) 加一次 waitpid( 1, ..., WALL) ——所有被追踪的进程、所有系统调用、所有信号,全都从这一个点流进来。

  • 路径翻译:canonicalize、binding 与 glue

    内核只认宿主路径。所以 PRoot 接到 openat(AT FDCWD, /etc/passwd , ...) 时,必须先把它变成 /home/u/rootfs/etc/passwd ,再把指针换掉——而 guest 里那个 /etc/passwd 可能根本不存在,也可能是三层符号链接套出来的。

  • 系统调用改写:在别人的地址空间里施工

    路径翻译只解决了「算」,还没解决「改」。要改一个正在运行的程序的内存,手段只有两个: process vm writev (或 PTRACE POKEDATA )和改寄存器。PRoot 两样都用,而且必须在系统调用的两个 stop 点之间完成。

  • execve:为什么 PRoot 要自带一个 ELF loader

    前四篇里,PRoot 做的都是「改参数、改返回值」。但 execve 是个硬边界:内核要打开 ELF、读 PT INTERP 、映射动态链接器——这些全在内核里完成,PRoot 插不进手。更要命的是,ELF 文件里写的 PT INTERP 是 guest 路径( /lib/ld linux x86 64.so.2 ),

  • 扩展机制:把「撒谎」做成插件

    PRoot 的三个招牌功能—— 0 (假装 root)、 l (硬链接转符号链接)、 k (假装旧内核)——都不是核心代码,而是插件。核心只提供一串事件回调,插件决定在这些点上改什么。

  • 边界:PRoot 拿不走什么,又偷走了什么

    前六篇讲的都是「怎么做到」。最后一篇讲代价:PRoot 给你一个看起来很完整的 guest 系统,但它既不是沙箱,也不是免费午餐。