fix(chv): cell 映射改由父进程写,中间进程先 setuid(1000)
两个真机教训(2026-11-07): - 子进程自己写不进 uid_map:unshare 后 euid 变 overflow, 连 /proc/self/uid_map 的 open 都 EPERM——改由父进程 (仍在初始 ns 的 root)经 /proc/<pid>/uid_map 写。 - 映射按外层 uid 翻译:中间进程外层是 root(0) 时 “0 1000 1” 盖不住它、新 ns 里仍是 overflow——中间进程 须先 setgroups/setgid/setuid(1000) 再 unshare(实测 chv 身份 unshare 在客机上可行,AppArmor 不挡)。 另:中间进程异常加 traceback(调试用);userns 单测改测 父写映射真实路径(映射行按 split 比,内核会多空格对齐)。
This commit is contained in:
1 parent
a2a5624736
commit
afbb7772c5
2 files changed
+117
-16
No files matched your search
@@ -155,23 +155,55 @@ class CellSeccompTest(unittest.TestCase):
|
||||
|
||||
class CellUsernsTest(unittest.TestCase):
|
||||
def test_write_id_maps_live(self):
|
||||
"""子进程里 unshare 用户 ns 并写映射:内 0 → 外 1000。
|
||||
环境建不了 userns(容器/AppArmor 限制)则跳过。"""
|
||||
"""父进程给已 unshare 的子进程写映射:内 0 → 子进程
|
||||
的外层 uid。子进程自己写不进(unshare 后 euid 变
|
||||
overflow,连 /proc/self/uid_map 的 open 都 EPERM,
|
||||
2026-11-07 真机实测)。父进程没 CAP_SETUID 时跳过。
|
||||
注:真实路径里中间进程先 setuid(1000) 再 unshare,
|
||||
映射的外层 uid 就是 1000;单测里子进程不改 uid,
|
||||
映射它自己的 euid/egid,测的是同一套父写映射机制。"""
|
||||
me_r, me_w = os.pipe()
|
||||
go_r, go_w = os.pipe()
|
||||
uid = os.geteuid()
|
||||
gid = os.getegid()
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
try:
|
||||
os.close(me_r)
|
||||
os.close(go_w)
|
||||
os.unshare(CELL.CLONE_NEWUSER)
|
||||
CELL._write_id_maps()
|
||||
os.write(me_w, b"1")
|
||||
os.close(me_w)
|
||||
got = os.read(go_r, 1)
|
||||
if not got:
|
||||
os._exit(2)
|
||||
with open("/proc/self/uid_map") as fh:
|
||||
uid_map = fh.read().strip()
|
||||
with open("/proc/self/gid_map") as fh:
|
||||
gid_map = fh.read().strip()
|
||||
ok = (uid_map == "0 1000 1"
|
||||
and gid_map == "0 1000 1"
|
||||
ok = (uid_map.split() == ["0", str(uid), "1"]
|
||||
and gid_map.split() == ["0", str(gid), "1"]
|
||||
and os.getuid() == 0)
|
||||
os._exit(0 if ok else 1)
|
||||
except Exception:
|
||||
os._exit(2)
|
||||
os.close(me_w)
|
||||
os.close(go_r)
|
||||
try:
|
||||
got = os.read(me_r, 1)
|
||||
if not got:
|
||||
self.skipTest("子进程 unshare 失败,跳过 live 验证")
|
||||
try:
|
||||
CELL._write_id_maps_for_pid(pid, uid=uid, gid=gid)
|
||||
except Exception:
|
||||
self.skipTest("父进程写映射无权限,跳过 live 验证")
|
||||
os.write(go_w, b"1")
|
||||
finally:
|
||||
for fd in (me_r, go_w):
|
||||
try:
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
pass
|
||||
_, status = os.waitpid(pid, 0)
|
||||
code = os.waitstatus_to_exitcode(status)
|
||||
if code == 2:
|
||||
|
||||
Reference in new issue
Block a user