setns对当前进程无效问题的排查(getpid获取值不变)

2023-11-03

1)复现流程及lxc的处理

demo1程序与执行结果如下,此时在容器内部看不到执行的程序。

int main()
{
	int ret, fd, pid;
	printf("father pid old:%d\n", getpid());
	fd = open("/dev/ns", O_RDWR);
	ret = ioctl(fd, 24635); // parm is dst ns process's pid
	printf("father pid old:%d\n", getpid());
	sleep(5);
	return 0;
}
# ./a.out 
father pid old:26169
father pid old:26169

demo2程序与执行结果如下,此时容器内还是看不到执行程序,但是这里getpid()获取到的值就为0了,对比上边,世界上没有这么玄乎的事,或许是缓存的问题?这是第一个疑问。

int main()
{
	int ret, fd, pid;
	fd = open("/dev/ns", O_RDWR);
	ret = ioctl(fd, 24635); // parm is dst ns process's pid
	printf("father pid old:%d\n", getpid());
	sleep(5);
	return 0;
}
# ./a.out 
father pid old:0

demo3程序与执行结果如下,这时在容器内部能看到子进程,为什么子进程实现了pid ns的切换,父进程却没有实现?这是第二个疑问。

int main()
{
	int ret, fd, pid;
	fd = open("/dev/ns", O_RDWR);
	ret = ioctl(fd, 24635); // parm is dst ns process's pid
	printf("father pid old:%d\n", getpid());
	pid = fork();
	if(0 == pid) {
		printf("son pid:%d\n", getpid());
		sleep(5);
	} else {
		printf("father pid:%d\n", getpid());
		wait();
	}
	return 0;
}
# ./a.out 
father pid old:0
father pid:0
son pid:87

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                         
    1 root      20   0   22036   2144   1624 S   0.0  0.1   0:00.01 bash                                            
   57 root      20   0   23672   1516   1160 R   0.0  0.1   0:00.66 top                                             
   87 root      20   0    3760    204    120 S   0.0  0.0   0:00.00 a.out

lxc attach流程如下,实现思路和我们上边的测试demo一致,看来这确实是linux机制的问题。

setns()
pid = fork();
if(!pid) { // son
execve(bash);
}

2)pid缓存问题

编写了demo4:

int main()
{
	int ret, fd, pid;
	printf("father pid:%d\n", getpid());
	printf("father pid:%d\n", getpid());
	printf("father pid:%d\n", getpid());
	printf("father pid:%d\n", getpid());
	fd = open("/dev/ns", O_RDWR);
	ret = ioctl(fd, 24635); // parm is dst ns process's pid
	printf("father pid old:%d\n", getpid());
	pid = fork();
	if(0 == pid) {
		printf("son pid:%d\n", getpid());
		sleep(5);
	} else {
		printf("father pid:%d\n", getpid());
		wait();
	}
	return 0;
}

看下结果,fork前后打印一致

# ./a.out 
father pid:23246
father pid:23246
father pid:23246
father pid:23246
father pid old:23246
father pid:23246
son pid:96

strace看下,果然和我们猜的一样,除了第一次getpid调用了syscall后,后边所有的返回值均是从缓存中获取的,所以这就能解释为什么不执行getpid并fork后执行getpid获取到的返回值是0,而执行了getpid并fork后再执行getpid获取到的返回值不变。

# strace ./a.out 
execve("./a.out", ["./a.out"], [/* 29 vars */]) = 0
brk(0)                                  = 0x18f4000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d79c000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=105026, ...}) = 0
mmap(NULL, 105026, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa04d782000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY)        = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@\356\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1478056, ...}) = 0
mmap(NULL, 3586120, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa04d215000
mprotect(0x7fa04d377000, 2097152, PROT_NONE) = 0
mmap(0x7fa04d577000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x162000) = 0x7fa04d577000
mmap(0x7fa04d57c000, 18504, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa04d57c000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d781000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d780000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d77f000
arch_prctl(ARCH_SET_FS, 0x7fa04d780700) = 0
mprotect(0x7fa04d577000, 16384, PROT_READ) = 0
mprotect(0x7fa04d79e000, 4096, PROT_READ) = 0
munmap(0x7fa04d782000, 105026)          = 0
getpid()                                = 23253
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 4), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d79b000
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
open("/dev/ns", O_RDWR)                 = 3
ioctl(3, 0x603b, 0x7fa04d57cdf0)        = 0
write(1, "father pid old:23253\n", 21father pid old:23253
)  = 21
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa04d7809d0) = 97
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
wait4(-1, son pid:97
0xffffffff, 0, NULL)          = -1 EFAULT (Bad address)
--- SIGCHLD (Child exited) @ 0 (0) ---

3)为什么子进程实现了pid ns的切换,父进程却没有实现?

先来看看setns的实现,在2.6.32中要支持setns很简单首先通过不创建新NS的方式调用copy_namespaces并传入dst ns,这会增加dst ns的引用,之后通过switch_task_namespaces传入current task与dst ns,在函数中会首先进行nsproxy指针的交换,将当前task切换到dst ns中,之后src ns减引用,这样就能保证引用数量的正确。因此这里实际上切换了current task的nsproxy。

copy_namespaces->switch_task_namespaces
int copy_namespaces(unsigned long flags, struct task_struct *tsk)
{
    struct nsproxy *old_ns = tsk->nsproxy;
    struct nsproxy *new_ns;
    int err = 0;

    if (!old_ns)
        return 0;

    get_nsproxy(old_ns);

    if (!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
                CLONE_NEWPID | CLONE_NEWNET)))
        return 0;
。。。
}

void switch_task_namespaces(struct task_struct *p, struct nsproxy *new)
{
    struct nsproxy *ns;

    might_sleep();

    ns = p->nsproxy;

    rcu_assign_pointer(p->nsproxy, new);

    if (ns && atomic_dec_and_test(&ns->count)) {
        /*
         * wait for others to get what they want from this nsproxy.
         *
         * cannot release this nsproxy via the call_rcu() since
         * put_mnt_ns() will want to sleep
         */
        synchronize_rcu();
        free_nsproxy(ns);
    }
}

再看看getpid的调用流程(sys_getpid–>task_tgid_vnr->pid_vnr->pid_nr_ns),这里考虑线程问题通过task_tgid获取该PID的tgid,因为应用层和内核对PID的定义不同,内核中进程与线程都拥有相同的结构体描述struct task_struct,因此进程与线程在内核中均拥有自己独立的PID,因此这时候找到“用户态PID”的关键是找到根进程,因为父根进程的pid与tgid是一致如下,因此在这里传入tgid来代替父根进程的PID。在pid_nr_ns中会进行两个判断,一是该pid ns的level应大于指定pid ns的level,这里的pid ns level如下图所示是一个树状结构,这里default pid ns中的pid level为0,而基于某进程创建的CLONE_NEWPID的进程,其pid ns level + 1,因此这里的判断会出现问题,因为setns只修改了current->nsproxy,如果是在default pid ns中执行的demo程序以及lxc程序,那么current->nsproxy->pid_ns->level = 1,而pid->level = 0,这会导致判断失败直接返回0,这也是我们在demo程序中通过getpid()得到0的原因。

 <-- PID 43 --> <----------------- PID 42 ----------------->
                     +---------+
                     | process |
                    _| pid=42  |_
                  _/ | tgid=42 | \_ (new thread) _
       _ (fork) _/   +---------+                  \
      /                                        +---------+
+---------+                                    | process |
| process |                                    | pid=44  |
| pid=43  |                                    | tgid=42 |
| tgid=43 |                                    +---------+
+---------+
 <-- PID 43 --> <--------- PID 42 --------> <--- PID 44 --->

在这里插入图片描述

SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current);
}

static inline pid_t task_tgid_vnr(struct task_struct *tsk)
{
    return pid_vnr(task_tgid(tsk));
}

pid_t pid_vnr(struct pid *pid)
{
    return pid_nr_ns(pid, current->nsproxy->pid_ns);
}

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
    struct upid *upid;
    pid_t nr = 0;

    if (pid && ns->level <= pid->level) {
        upid = &pid->numbers[ns->level];
        if (upid->ns == ns)
            nr = upid->nr;
    }
    return nr;
}

如下,这里有对获取到的upid->nr的一个很好的解释,也就是说upid是pid ns有效的,另外这个实验是在2.6.32.5中实现的,而2.6.32.5中不支持setns(我对它进行了移植),或许在3.x的某些支持setns的版本中也能复现,但4.9.0中pid_nr_ns的不同实现导致了实验效果的不同,在4.9.0中demo程序会返回default ns中的pid。

/*
 * struct upid is used to get the id of the struct pid, as it is
 * seen in particular namespace. Later the struct pid is found with
 * find_pid_ns() using the int nr and struct pid_namespace *ns.
 */
struct upid {
    /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
    int nr;
    struct pid_namespace *ns;
    struct hlist_node pid_chain;
};

最后再补充下,前面可以看到setns实现其实只修改了task_struct中的nsproxy,因此fork会通过调用链do_fork->copy_process->alloc_pid(p->nsproxy->pid_ns)也就是利用父进程的nsproxy->pid_ns来创建自己的pid,这样就说明了为什么lxc中必须fork出子进程来执行execve。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

setns对当前进程无效问题的排查(getpid获取值不变) 的相关文章

  • 在哪里可以找到并安装 pygame 的依赖项?

    我对 Linux 比较陌生 正在尝试安装 python 的 pygame 开发环境 当我运行 setup py 时 它说我需要安装以下依赖项 我找到并安装了其中之一 SDL 然而 其他人则更加难以捉摸 Hunting dependencie
  • 无法连接到 macOS 上的 Docker 守护进程

    我通常更喜欢使用brew 来管理我的OSX 上的应用程序 我能够安装 docker docker compose 和 docker machine docker version Docker version 17 05 0 ce build
  • fopen 不返回

    我在 C 程序中使用 fopen 以只读模式 r 打开文件 但就我而言 我观察到 fopen 调用没有返回 它不返回 NULL 或有效指针 执行在 fopen 调用时被阻止 文件补丁绝对正确 我已经验证过 并且不存在与权限相关的问题 任何人
  • 从镜像创建 Docker 容器而不启动它

    作为我的部署策略的一部分 我使用 Upstart 管理 Docker 容器 为此 我需要从注册表中提取图像并创建一个命名容器 如建议的那样 用于运行容器的新贵脚本不会管理生命周期 https stackoverflow com questi
  • NSQ Docker Swarm

    我尝试在 Docker Swarm 中使用 NSQ 但没有成功 mhlg rpi nsq 是为 Raspberry Pi ARM7 板构建的 Docker 映像 如果作为普通 Docker 容器运行 我可以确认其工作正常 在 Docker
  • 容器中的 JVM 计算处理器错误?

    最近我又做了一些研究 偶然发现了这一点 在向 OpenJDK 团队抱怨之前 我想看看是否有其他人观察到这一点 或者不同意我的结论 因此 众所周知 JVM 长期以来忽略了应用于 cgroup 的内存限制 众所周知 现在从 Java 8 更新某
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成
  • Docker 中的 Python 日志记录

    我正在 Ubuntu Web 服务器上的 Docker 容器中测试运行 python 脚本 我正在尝试查找由 Python Logger 模块生成的日志文件 下面是我的Python脚本 import time import logging
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • PHP 从命令行启动 gui 程序,但 apache 不启动

    首先 我阅读了有类似问题的人的一些帖子 但所有答案都没有超出导出 DISPLAY 0 0 和 xauth cookies 这是我的问题 提前感谢您的宝贵时间 我开发了一个小库 它使用 OpenGL 和 GLSL 渲染货架 过去几天我将它包装
  • 如何在apache 2.4.6上安装apxs模块

    我刚刚用过apt get update我的 apache 已更新为2 4 6 我想安装 apxs 来编译模块 但收到此错误 The following packages have unmet dependencies apache2 pre
  • jpegtran 优化而不更改文件名

    我需要优化一些图像 但不更改它们的名称 jpegtran copy none optimize image jpg gt image jpg 但是 这似乎创建了 0 的文件大小 当我对不同的文件名执行此操作时 大小仍然完全相同 怎么样 jp
  • ASP .NET Core 在 Heroku 上出现 System.Net.Sockets.SocketException 错误

    我正在尝试将 NET core Web API 部署到 Heroku 下面是我的Dockerfile FROM mcr microsoft com dotnet core aspnet 2 1 AS runtime WORKDIR app
  • ubuntu:升级软件(cmake)-版本消歧(本地编译)[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我的机器上安装了 cmake 2 8 0 来自 ubuntu 软件包 二进制文件放置在 usr bin cmake 中 我需要将 cmake 版本至少
  • 为什么我可以直接从 bash 执行 JAR?

    我是一个长期从事 Java 工作的人 并且知道运行带有主类的 JAR 的方法MANIFEST MFJar 中的文件很简单 java jar theJar jar 我用它来启动 Fabric3 服务器 包含在bin server jar在其标
  • docker容器大小远大于实际大小

    我正在尝试从中构建图像debian latest 构建后 报告的图像虚拟大小来自docker images命令为 1 917 GB 我登录查看尺寸 du sh 大小为 573 MB 我很确定这么大的尺寸通常是不可能的 这里发生了什么 如何获
  • 我的线程图像生成应用程序如何将其数据传输到 GUI?

    Mandelbrot 生成器的缓慢多精度实现 线程化 使用 POSIX 线程 Gtk 图形用户界面 我有点失落了 这是我第一次尝试编写线程程序 我实际上并没有尝试转换它的单线程版本 只是尝试实现基本框架 到目前为止它是如何工作的简要描述 M
  • 查找哪个程序运行另一个程序

    我有一个 NAS 运行在 Redhat Linux 的有限版本上 我按照指示破解了它 这样我就可以访问 shell 这很有帮助 我还做了一些修改 其他人也做过修改 除了一个问题之外 它们似乎都工作得很好 不知何故 每隔 22 天 系统就会关
  • 什么会阻止 Docker 容器中运行的代码连接到单独服务器上的数据库?

    我有一个在 Ubuntu 14 04 上的 Docker 容器中运行的 NET Core 1 1 应用程序 它无法连接到在单独服务器上运行的 SQL Server 数据库 错误是 未处理的异常 System Data SqlClient S
  • 如何确保应用程序在 Linux 上持续运行

    我试图确保脚本在开发服务器上保持运行 它会整理统计数据并提供网络服务 因此它应该会持续存在 但一天中有几次 它会因未知原因而消失 当我们注意到时 我们只需再次启动它 但这很麻烦 并且某些用户没有权限 或专有技术 来启动它 作为一名程序员 我

随机推荐

  • PCL分割方法:区域生长分割算法(RegionGrowing)

    转载 有梦想的田园犬 https blog csdn net AmbitiousRuralDog article details 80267519
  • 数字信号处理基础----傅里叶级数

    1 傅里叶级数的余弦形式 1 1 正交的三角函数集 三角函数集 1 2 2 1 2 3 内的函数在区间 上彼此正交 也即 任意两个不同的函数的内积为0 函数和自身的内积不为零 因此 函数可以由该正交函数集唯一的表示 1 2 傅里叶级数的定义
  • vite+vue3打包部署问题

    最近使用vite vue3写了个小的demo 发现打包部署后页面出不来 如果是正常把包放在服务器的根目录中 项目页面是可以打开的 但是我要部署的是根目录dist包里面 外面多了一层文件夹 解决 vite config ts文件 base z
  • 横向手风琴效果

    html
  • 华硕a501lb5200加内存和固盘并装上win7系统并设置固盘为第一启动

    华硕a501lb5200加内存和固盘并装上win7系统并设置固盘为第一启动 最近入手一只华硕a501lb5200 然后某宝上买内存 固盘 接着拆机加内存和固盘并装上win7系统 于是想分享下自己的经验 大家多多补充 1 拆机加内存和固盘 内
  • Redis集群主从复制不生效的问题分析及解决

    一 集群信息 Redis版本 5 0 集群规模 三主三从 二 基本情况 在项目中为了达到高可用的目的 使用了Redis集群 搭建过程同Redis Cluster集群原理 三主三从交叉复制实战 故障切换 但是在实际使用中发现 集群方式比单点模
  • discuz未登录情况下首页tdk显示“首页”

    问题 discuz未登录状态下首页tdk与后台设置的不符 如图问题keywords和description变成了门户 也有部分变成了首页 解决办法 用编辑器打开 source class helper下的helper seo php文件 找
  • LeetCode#88. 合并两个有序数组(Python)

    题目 来源力扣 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2 另有两个整数 m 和 n 分别表示 nums1 和 nums2 中的元素数目 请你 合并 nums2 到 nums1 中 使合并后的数组同样按 非递减顺序
  • 很全的 Java 权限认证框架

    今天给大家推荐的这个开源项目超级棒 可能是史上功能最全的 Java 权限认证框架 这个开源项目就是 sa token Sa Token是什么 sa token是一个轻量级Java权限认证框架 主要解决 登录认证 权限认证 Session会话
  • FATFS:一个兼容windows的嵌入式文件系统API使用详解

    FATFS 一个兼容windows的嵌入式文件系统API使用详解 目录 FATFS 一个兼容windows的嵌入式文件系统API使用详解 1 API分类 2 常用API说明 2 1 挂载文件系统与解除挂载 2 2 文件操作 2 2 1 文件
  • arduino 1 读取电机编码器值

    define BAUDRATE 115200 define LEFT 0 左轮 define RIGHT 1 右轮 define FORWARDS true define BACKWARDS false 如果一个变量所在的代码段可能会意外地
  • vue3项目打开本地pdf文件实现方法

    vue3项目打开本地pdf文件实现方法 效果图 引入pdf插件 pdf页面封装 pdf存放目录 结语 效果图 引入pdf插件 注意一定要这个版本 不然会报错key split at is not a function npm install
  • 深度学习入门笔记之VggNet网络

    VGGNet是由牛津大学的视觉几何组 Visual Geometry Group 和谷歌旗下DeepMind团队的研究员共同研发提出的 获得了ILSVRC 2014 2014年ImageNet图像分类竞赛 的第二名 将 Top 5错误率降到
  • 数据库事务的四大特性以及事务的隔离级别

    本篇讲诉数据库中事务的四大特性 ACID 并且将会详细地说明事务的隔离级别 1 数据库事务的四大特性 如果一个数据库声称支持事务的操作 那么该数据库必须要具备以下四个特性 1 1 原子性 Atomicity 原子性是指事务包含的所有操作要么
  • lvgl实现动态切换横竖屏

    有两种方式 一种是通过lvgl自带的软件选择 但是这个效率很慢 而且只支持90度 180度 270度的旋转 不一定达到想要的效果 我需要实现的是这种效果 软件旋转没有办法实现 旋转后会镜像过去 而且如果你的屏幕不是等比例的 比如240 24
  • upload-labs pass02-05攻略(详细)

    pass 02 进入关卡 查看提示和源码 根据源代码我们可以发现 这一关是对文件类型验证 也就是验证MIME信息 接下来我们进行文件上传 使用burpsuit抓包 将Content Type修改为允许上传的类型 image jpeg ima
  • ERROR - Connection is read-only.

    今天在serviceImpl的查询中 调用了一样更新的操作 结果出现如下错误 ERROR Connection is read only Queries leading to data modification are not allowe
  • vi笔记3——vi之快速移动

    vi笔记3 vi之快速移动 VI快速移动主要包含以下内容 This chapter covers Movement by screens Movement by text blocks Movement by searches for pa
  • NDIS的NDIS_PROTOCOL_BLOCK和NDIS_OPEN_BLOCK的介绍

    转载自 http blog sina com cn s blog 4de78d5901000bfd html 本人简单的介绍一种更有效的基于NDIS包拦截技术 大家都知道 NDIS协议驱动程序是通过填写一张NDIS PROTOCOL CHA
  • setns对当前进程无效问题的排查(getpid获取值不变)

    1 复现流程及lxc的处理 demo1程序与执行结果如下 此时在容器内部看不到执行的程序 int main int ret fd pid printf father pid old d n getpid fd open dev ns O R