浅谈 Linux 中 namespace 相关系统调用

文章目录

  • [1. 简介](#1. 简介)
  • [2. 相关系统调用介绍](#2. 相关系统调用介绍)
    • [2.1 clone](#2.1 clone)
    • [2.2 unshare](#2.2 unshare)
    • [2.3 setns](#2.3 setns)
    • [2.4 mount](#2.4 mount)
    • [2.5 umount2](#2.5 umount2)
    • [2.6 OJ 系统 sand_box 中 mount namespace 构建全流程](#2.6 OJ 系统 sand_box 中 mount namespace 构建全流程)
  • [3. 总结](#3. 总结)

1. 简介

Linux 中,namespace 是有关资源的一个重要概念,有点类似 cpp 中的 namespace , 本质就是划定一个区域,区域内的事物仅可见该区域,区域外的资源均不可见。

Linux 中,进程处在特定namespace 中,仅可见该 namespace 中的相关资源,namespace 外均不可见,因此实现了对进程的特定隔离。

Linux 中一共有 8 种 namespace, 较为常见的是mount, pid, network, user, ipc等

关于namespace ,有三个核心系统调用clone, unshare, setns, 当然,考虑到存在 mount namespace, 因此讲解 mountumount2 也是必要的。

2. 相关系统调用介绍

2.1 clone

复制代码
int clone(int (*fn)(void *), void *stack, int flags, void* arg)

clone 本质是 fork 的超集,fork 本质就是再调用 clone 。同时,pthread_create 线程创建,本质也是调用 clone

关于上述参数,逐个介绍:

  • fn : 子进程的入口函数。相较于 fork, 在父子进程中,各返回 1 次,而 clone 直接指定子进程执行函数,clone 本身仅在 father process 中返回。
  • stack : 子进程的栈。在 clone 中,需要手动分配 clone 的栈空间。特别注意的是,如果是创建子进程,那么 COW , 写时拷贝,会自动分配新的栈空间;但如果创建线程,必须手动分配。一般传入栈顶指针,因为大多数架构栈向下增长。
  • flags : 关键参数。主要讲两类 flags。一类是 namespace 相关 flags:
    • CLONE_NEWPID: 创建新的 PID NAMESPACE, 子进程进入该新的 PID NAMESPACE 中。此时,child process 自身成为 PID 1 .
    • CLONE_NEWNS: 新的 mount namespace . 子进程拿到父进程挂载树的一份拷贝,之后独立操作。这是在进程隔离中,隔离目录树,即 dentry 结构的重要参数。
    • CLONE_NEWNET: 新 network namespace 。 子进程看到一个干净的 network, 只有 loopback, 没有外部网络接口,实现网络隔离。
    • CLONE_NEWUSER: 新的 user namespace . 这个特殊,因为是唯一一个非特权用户能够创建的 namespace 。 创建后,子进程在新的 user namespace 中,可拥有全部能力。
      第二类是资源共享相关 flags, 主要用于区别创建进程和线程上:
    • CLONE_VM: share 地址空间,否则 COW。前者 thread, 后者进程。
    • CLONE_FILES: 共享文件描述表,否则则拷贝一份。
    • CLONE_FS: 共享 fs_struct .
    • CLONE_SIGNALED: 信号处理表共享
    • CLONE_THEAD: 放入同一个线程组,即创建线程。

2.2 unshare

复制代码
int unshare(int flags);

unshare 是用于操作当前进程,即让当前进程脱离指定的 namespace, 进入新创建的 namespace

相较于 clone, 二者均会创建新的namespace ,但是 unshare 不创建新进程。

关于PID NAMESPACE, unshare 中,这个具备延迟语义,因为已存在进程,无法切换 PID namespace。因此,如果使用该 flags:那么相应子进程会进入新的 PID NAMESPACE 。
flags 中相关参数,和 clone 中的 namespace 参数完全相同。

2.3 setns

复制代码
int setns(int fd, int nstype);

简而言之,让当前进程加入一个已存在的 namespace
setns 不创建新 namespace, 而是加入到其它进程已有的 namespace 中。

参数解析:

  • fd: 文件描述符,**来自 /proc/pid/ns/ ** 下的 namespace 文件,比如 /proc/pid/ns/pid/proc/pid/ns/net 等。这些文件与普通文件不同, open 后得到的 fd 是内核 namespace 对象的引用。
  • nstype: 指定期望的 namespace 类型,本质做安全校验 。传 0 为不校验,一般建议显示传,防止误入错误的namespace 中。

实际上,就是将当前进程放入 fd 所对应的 namespace 中。
与 unshare 相同,对于 PID NAMESAPCE 同样具备延迟语义,得到的子进程才能进入目标 pid namespace 。

2.4 mount

关于 mount, 本质非 namespace 核心系统调用,主要是进入新的 mount namespace 后,需要频繁使用 mount 操作进行挂载。

复制代码
int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags,  const void *data);

本质:在当前 mount namespace 的挂载树上,将一个文件系统,或者源目录路径(可以是块设备) 挂到指定目录节点上,本质就是 映射。

下面逐一介绍参数:

  • source: 挂载源头。一般是 块设备tmpfs, 即虚拟内存文件系统 ,或者更常见的 bind mount, 即源目录路径
  • target: 将 source 挂载到的 路径。必须是已存在的目录或文件。
  • filesystemtype: 当挂载 source 是 tmpfs, proc, ext4时,与相应的 source 对应,bind amount 时,即映射具体文件路劲时,传 NULL 即可。
  • mountflags: 控制 mount 具体行为。
    • MS_BIND: bind amout 。把一个目录映射到另一个位置,并非新文件系统,而是两个路径指向同一个 dentry。
    • MS_REC: 递归操作,配合 MS_BIND 或 MS_PRIVATE 使用,对子挂载点生效,通常用于目录,文件无必要
    • MS_RDONLY:只读挂载。
    • MS_NOSUID: 忽略 SUID/SGID 位,防止提权。
    • MS_PRIVATE: 设置挂载传播类型为 private, 父子之间 mount namespace 挂载事件完全隔离。
  • data: 挂载文件系统时参数,bind amount 传 NULL 即可。例如,"size=16m,nr_inodes=32,mode=0755" 限制该路径下,写入文件总大小,创建文件总数目,以及创建权限等。

2.5 umount2

复制代码
int umount2(const char *target, int flags)

mount 用于将 source 挂载到 target, umount2 用于解除 target 上的挂载。

参数解析:

  • target: 相应解除挂载的目录。
  • flags: 限制挂载接触行为,通常两种:MNT_DETACHMNT_FORCE , 前者是惰性卸载,后者为强制卸载 ,二者都将挂载点从 target 移除,但是 MNT_DETACH 内核资源等到合适再释放,一般使用这个,若强行 MNT_FORCE, 可能会失败。

2.6 OJ 系统 sand_box 中 mount namespace 构建全流程

下面过程,均在 child process 中完成。

第一步:设置传播类型为 private

复制代码
mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL) 

第二步:bind mount 相关目录

第三步:挂载相关文件系统等,如 tmpfsproc

复制代码
mount("tmpfs", target, "tmpfs", MS_NOSUID | MS_NODEV, "size=16m");
mount("proc", target, "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL);

第四步:pivot_root ------ 切换根目录,最关键的一步,上述所有挂载处理完后,再切换根目录

复制代码
mount(sandbox_root, sandbox_root, NULL, MS_BIND | MS_REC, NULL); // new_root 必须是一个挂载点
mkdir(put_old,0700); // 在 new_root 下创建一个 put_old 目录,用来临时存放旧根
syscall(SYS_pivot_root, sandbox_root, put_old);
chdir("/"); // 切换 cwd, 此时根目录解析起点已经改变
umount2("/,old_root", MNT_DETACH); // 卸除旧根目录挂载
rmdir("/.old_root"); // 删除无用目录

上述便是 OJ sandbox 中,完整 mount namespace 构建流程。上述构建后,child process 便处在一个 隔离的 mount namespace 中,无法看到宿主机上未经过bind mount 的其余文件。

3. 总结

简而言之,namespace 三个核心 syscall : clone, unshare, setns 。 clone 创建新进程+新NS,unshare 仅创建新 NS,setns 什么都不新建。clone 对 pid namespace 无延迟,unshare 和 setns 对 pid namespace 存在延迟。

相关推荐
zb200641202 小时前
CVE-2024-38819:Spring 框架路径遍历 PoC 漏洞复现
java·后端·spring
2401_895521342 小时前
spring-ai 下载不了依赖spring-ai-openai-spring-boot-starter
java·人工智能·spring
YMWM_2 小时前
【问题】thor上的cubLas
linux·python·thor
何仙鸟2 小时前
GarmageSet下载和处理
java·开发语言
wefly20172 小时前
免安装!m3u8live.cn在线 M3U8 播放器,小白也能快速上手
java·开发语言·python·json·php·m3u8·m3u8在线转换
yuweiade3 小时前
springboot和springframework版本依赖关系
java·spring boot·后端
ywf12153 小时前
springboot设置多环境配置文件
java·spring boot·后端
小马爱打代码3 小时前
SpringBoot + 消息生产链路追踪 + 耗时分析:从创建到发送,全链路性能可视化
java·spring boot·后端
虾..3 小时前
多路复用 --- select系统调用
服务器·数据库·sql