'unshare'在 C API 中无法按预期工作



这个命令序列工作:

unshare --fork --pid --mount 
umount /proc
mount -t proc proc /proc
umount /dev/pts
mount -t devpts devpts /dev/pts

但是,相应的 C 程序没有按预期工作(似乎它没有卸载以前的/proc,并且它还提供了尝试卸载 devpts 的 EBUS):

unshare(CLONE_NEWPID | CLONE_NEWNS );
int pid = fork();
if (pid != 0) {
    int status;
    waitpid(-1, &status, 0);
    return status;
}
printf("My pid: %in", getpid()); // It prints 1 as expected
umount("/proc"); // Returns 0
system("mount"); // Should print error on mtab, but it prints the previous mounted filesystems
mount("proc", "/proc", "proc",
      MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | MS_NODEV,
      NULL));  // Returns 0
umount("/dev/pts");  // Returns -1 errno = 0 (??)
mount("devpts", "/dev/pts", "devpts", 
      MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | MS_NODEV,
      NULL) ); // Returns -1 errno = EBUSY

我在这里省略了可读性的错误检查

我认为取消共享或卸载无法按预期工作:即使它返回零,似乎也不会卸载/proc(如果我尝试在那之后执行system("mount"),它会打印挂载的文件系统)。

尽管你的评论

"有时" umount 返回 0 "有时" -1,但最终它根本不卸载/proc

,在您的 pastebin 代码的 10000 次试用中,umount()对我来说总是失败,返回-1而不是卸载/proc. 我不愿意相信尽管未能执行请求的卸载,umount()会返回0,但如果确实如此,那将构成umount()中的错误。 如果您实际上可以证实这样的错误,那么社区意识的回应将是针对 glibc 提交错误报告。


那么问题就变成了您的bash脚本为什么以及如何表现不同。 然而,事实上,它似乎并没有这样做。

首先,您对unshare(1)命令有错误的期望。 与 unshare(2) 函数不同,unshare命令不会影响在其中执行它的 shell。 相反,它会启动一个单独的进程,该进程具有指定命名空间的专用副本。 通常,您会在unshare命令行上指定启动该进程的命令,实际上程序的手册页表明这样做是强制性的。

根据经验,我发现如果我未能像您一样指定这样的命令,那么unshare启动一个新的 shell 作为目标进程。 特别是,当我运行您的脚本(具有足够的权限使用 unshare )时,我立即收到一个新提示,但它是新 shell 的提示,在前台运行。 这对我来说是显而易见的,因为提示是不同的(但是,在这种情况下,您的提示可能没有任何不同)。 此时没有来自umount的错误消息等,因为它尚未运行。 如果我手动尝试在 (unshare d) 子壳中umount proc,它会失败并显示"设备繁忙"——这是您的 C 程序尝试执行的操作的模拟。

当我退出子外壳时,脚本的其余部分将运行,umount s 和两个 mount 都失败。 这是意料之中的,因为主脚本共享其挂载命名空间。


/proc确实很忙,因此无法卸载,即使对于具有挂载命名空间的私有副本的进程,也是完全合理的。 这样的过程很可能本身正在使用其/proc挂载的私人副本。 相比之下,我发现我可以在具有未共享的挂载命名空间的进程中成功卸载/dev/pts,但在共享该命名空间的系统副本的进程中则不能成功卸载

我发现检查取消共享命令的源代码时出现问题。/proc必须使用 MS_PRIVATE | MS_REC 卸载并在没有它们的情况下挂载,这本质上是为了确保挂载仅在当前(新)命名空间中有效。第二个问题是,如果不对全局命名空间产生影响,就无法卸载/dev/pts(这是由 devpts 驱动程序的内部例程引起的)。要拥有一个私有的/dev/pts,唯一的方法是使用专用的 -o newinstance 选项挂载它。最后/dev/ptmx也应绑定重新安装。

因此,这是预期的 C 工作代码:

unshare(CLONE_NEWPID | CLONE_NEWNS );
int pid = fork();
if (pid != 0) {
    int status;
    waitpid(-1, &status, 0);
    return status;
}
printf("New PID after unshare is %i", getpid());
if (mount("none", "/proc", NULL, MS_PRIVATE|MS_REC, NULL)) {
    printf("Cannot umount proc! errno=%i", errno);
    exit(1);
}
if (mount("proc", "/proc", "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL)) {
    printf("Cannot mount proc! errno=%i", errno);
    exit(1);
}

if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL | MS_NOSUID | MS_NOEXEC, "newinstance") ) {
    printf("Cannot mount pts! errno=%i", errno);
    exit(1);
}
if (mount("/dev/pts/ptmx", "/dev/ptmx", NULL, MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | MS_BIND, NULL) ) {
    printf("Cannot mount ptmx! errno=%i", errno);
    exit(1);
}
我认为

问题出在系统("mount")上,它生成了一个外壳并且不会承载umount。尝试在卸载后打开/proc/中的文件,看看它按预期工作。

看到这里 -

unshare(CLONE_NEWPID | CLONE_NEWNS );
int rc = 0;
int pid = fork();
if (pid != 0) {
        int status;
        waitpid(-1, &status, 0);
        return status;
}
printf(">>> My pid: %dn", getpid()); // It prints 1 as expected
rc = umount2("/proc", MNT_FORCE); // Returns 0
printf(">>> umount returned %d. errno = %d, desc = (%s)n", rc, errno, strerror(errno));
rc = open("/proc/cpuinfo", O_RDONLY);
printf(">>> open returned %d. errno = %d, desc = (%s)n", rc, errno, strerror(errno));

unshare bash != unshare c

取消共享 - 使用某些与父级取消共享的命名空间运行程序

所以基本上使用 --

fork 你可以使用 --pid 和 --mount 选项从/bin/bash 的/bin/sh 分叉(无论你用什么执行脚本)。"分叉"后跟"取消共享"

取消共享 - 取消关联部分流程执行上下文(当前进程)您正在从 init 取消共享,然后分叉。

CLONE_NEWPID"克隆"标志不是"取消共享"

因此,根据您要实现的目标 - 我假设您正在尝试使"/proc"和"/dev/pts"专用于儿童过程。

下面是一个挂载 --bind 本地文件夹的小例子:

# mkdir mnt point
# touch point/point.txt
# mount --bind point mnt
# ls mnt
point.txt
# ./unshare
My pid: 28377
Child:
point.txt
Parent:
# ls mnt

法典:

#define _GNU_SOURCE
#include <sched.h>
int main(int argc, char *argv[])
{
        /** umount global */
        system("umount mnt/");
        int pid = fork();
        if (pid != 0) {
                int status;
                waitpid(-1, &status, 0);
                printf("Parent:n");
                /* and here we don't */
                system("ls mnt/");
                return status;
        }
        /* unshare */
        unshare(CLONE_FS | CLONE_NEWNS);
        printf("My pid: %in", getpid()); // It prints 1 as expected
        /* mount exclusively */
        system("mount --bind point/ mnt/");
        printf("Child:n");
        /* here we see it */
        system("ls mnt/"); 
        return 0;
}

bash还有一个很好的例子:http://karelzak.blogspot.ru/2009/12/unshare1.html

延续:

mount

依赖于/etc/mtab,它并不总是指向/proc/mount 的符号链接

所以用 ls -la 检查/etc/mtab。

还要检查/dev/pts 上的 umount 代码:

int ret = umount("/dev/pts");
int errsv = errno;
if(ret == -1) {
  printf("Error on umount: %sn", strerror(errsv));
}

我很确定它被使用了 - 用热熔器/dev/pts/检查它

**编辑**

最后 - 我不确定您只能在命名空间中卸载 procfs(我认为这是不可能的)

但是您可以在命名空间中挂载自己的 procfs 副本:

# mount -t proc proc /proc/

现在只有您的过程可以通过 ps -e 看到。

相关内容

  • 没有找到相关文章

最新更新