以下的要求不是强制性的, 未按模板评论时对issue无任何影响
issue处理注意事项:
1. 当前issue受影响的分支提交pr时, 须在pr描述中填写当前issue编号进行关联, 否则无法关闭当前issue;
2. 模板内容需要填写完整, 无论是受影响或者不受影响都需要填写完整内容,未引入的分支不需要填写, 否则无法关闭当前issue;
3. 以下为模板中需要填写完整的内容, 请复制到评论区回复, 注: 内容的标题名称(影响性分析说明, 缺陷严重等级, 受影响版本排查(受影响/不受影响), 修复是否涉及abi变化(是/否))不能省略,省略后defect-manager将无法正常解析填写内容.
评论区可能使用到的指令说明:
| 指令 | 指令说明 | 使用权限 |
|---|---|---|
| /check-issue | 触发defect-manager校验 | 不限 |
| /reason xxx | /reason +挂起或取消条件 | 不限 |
影响性分析说明:
缺陷严重等级:(Critical/High/Moderate/Low)
缺陷根因说明:
受影响版本排查(受影响/不受影响):
- openEuler-20.03-LTS-SP4:
- openEuler-22.03-LTS-SP4:
- openEuler-24.03-LTS-SP1:
- openEuler-24.03-LTS-SP3:
- openEuler-24.03-LTS-SP4:
abi变化(是/否):
- openEuler-20.03-LTS-SP4:
- openEuler-22.03-LTS-SP4:
- openEuler-24.03-LTS-SP1:
- openEuler-24.03-LTS-SP3:
- openEuler-24.03-LTS-SP4:
缺陷issue处理具体操作请参考:
https://atomgit.com/openeuler/cve-manager/blob/master/cve-vulner-manager/doc/md/defect-manager-manual.md
pr关联issue具体操作请参考:
https://docs.atomgit.com/docs/help/home/org_project/pullrequests/pr-related-issue


Welcome To openEuler Community
Hey @yangna1310 , thanks for your contribution to the community.
Bot Usage Manual
I'm the Bot here serving you. You can find the instructions on how to interact with me at Here . That means you can comment below every pull request or issue to trigger Bot Commands. You can self-configure the PR merge rules for this repository. For more details, please refer to Here.
Contact Guide
If you have any questions, please contact the SIG: iSulad ,
and any of the maintainers: @Yekelu, @chantsztung, @haomintsai, @jingxiaolu ,


【缺陷描述】:请补充详细的缺陷问题现象描述
lxcfs-tools代码安全扫描问题
一、缺陷信息
发现 20 个问题(🔴 致命 0 · 🟡 重要 6 · 🟢 建议 11 · ⚠️ 疑似 3)
未发现可直接导致宿主机 root 命令执行或宿主机任意文件读写的 🔴 致命问题(工具无向宿主机路径写入用户可控内容的操作,所有
exec.Command均以参数数组形式调用、不经 shell,无命令注入)。但 6 个 🟡 重要问题中:①getRuncContainerIDAndPid的out[0]越界 panic 可在常见环境(docker/runc 主机无运行容器)下触发管理工具崩溃;②doMount以未净化的Rootfs(源自 OCI spec,容器创建者可控)做字符串拼接挂载路径,存在容器命名空间内挂载目标穿越;③ 日志文件符号链接跟随;④remount --all/umount --all超时后 goroutine 与 fork 子进程泄漏并可能造成挂载状态不一致;⑤ 容器 ID 子串匹配导致误操作容器(混淆代理);⑥execCommond在读流出错时os.Exit孤儿化子进程。建议优先修复 🟡 类问题。🟡 [重要] remountcmd.go:232 — getRuncContainerIDAndPid 未校验切片长度致越界 panic(DoS)
CWE: CWE-129(数组索引不当)/ CWE-754(对异常条件的不当检查)
规则: R01-内存安全(切片边界)/ R05-错误处理
描述:
getRuncContainerIDAndPid()调用execCommond("runc", ...)后直接访问out[0],未校验len(out) > 0。execCommond初始化res := []string{}(空切片),当runc list --format json输出为空(无运行容器、某些 runc 版本对空列表不输出[])或输出末行无换行符时,res保持为空,out[0]触发runtime error: index out of range [0]panic,工具进程崩溃。该路径在isula未安装(exec.LookPath("isula")失败)时作为回退被getContainerIDAndPid调用,是 docker/runc 主机的唯一路径。影响: 管理员执行
lxcfs-tools remount --all/umount --all(在无运行容器或 runc 输出异常时)触发 panic,工具崩溃,重挂/卸载操作中断(DoS)。CVSS: 4.0(Medium,AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)
数据来源:
runc list --format jsonstdout(受主机容器运行状态与 runc 版本影响)→execCommond返回[]string。风险传播路径:
remount --all→remountAll→getContainerIDAndPid(isula 缺失分支)→getRuncContainerIDAndPid→execCommond返回空切片 →out[0]panic。调用栈
另一入口:
umountcmd.go:88umountAll→getContainerIDAndPid→ 同链。问题代码
// 文件: remountcmd.go:227-234 func getRuncContainerIDAndPid() ([]string, error) { out, err := execCommond("runc", []string{"--root", runcStateDir, "list", "--format", "json"}) if err != nil { return nil ,err } if out[0] == "" { // 未校验 len(out) == 0 → panic return out, nil } ... }调用链分析
out[0]修复建议
func getRuncContainerIDAndPid() ([]string, error) { out, err := execCommond("runc", []string{"--root", runcStateDir, "list", "--format", "json"}) if err != nil { return nil, err } if len(out) == 0 || out[0] == "" { return out, nil // 先校验长度,再访问下标 } // ... }附带修复
execCommond末行丢失问题(见调用链同源 🟡-6):ReadString返回io.EOF且line != ""时应先 append 再 break。🟡 [重要] libmount/container_work.go:103,106 — Rootfs 字符串拼接挂载路径,未净化致挂载目标穿越
CWE: CWE-22(路径穿越)/ CWE-73(外部控制文件路径)
规则: R02-注入(路径遍历)/ R01-文件边界
描述:
doMount()以mount.Rootfs + lxcfsPath(字符串拼接,非filepath.Join)构造 bind mount 的源与目标,随后syscall.MS_BIND再MS_REMOUNT|MS_RDONLY。Rootfs经 JSON 管道来自父进程:hook 路径下源自 stdin 的 OCIState.Root(容器创建者/镜像配置可控),CLIremount路径下为""。lxcfsPath = "/var/lib/lxc/lxcfs/cgroup"。当Rootfs含..段(如/var/lib/isulad/engines/<id>/rootfs/../../../tmp),拼接结果经内核路径解析后逃逸预期 rootfs 边界,bind/remount 目标被重定向。同文件doMount循环syscall.Mount(mount.SrcPaths[i], mount.DestPaths[i], ...)中DestPaths[i]在 hook 路径下由execmount.go:49 fmt.Sprintf("%s/proc/%s", rootfs, ...)构造,同样以未净化的rootfs拼接。grep 全项目确认业务代码 0 处filepath.Join,路径构造系统性依赖字符串拼接。影响: 容器侧可控
Root/rootfs→ 容器 mount 命名空间内挂载目标被重定向至预期 rootfs 之外(如宿主机挂载进容器的卷路径、容器内任意路径),可能覆盖容器内关键文件(如/etc/crontab、二进制)为 lxcfs 只读 bind,造成容器内文件篡改/拒绝服务。因操作发生在容器自身 mount 命名空间内(nsenter 仅进入 mnt ns),不直接构成宿主机逃逸,故定为重要。CVSS: 5.0(Medium,AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L)
数据来源: hook 路径
state.Root(OCI spec stdin,utils.ParseHookState解析)→hooks/lxcfs-hook/main.go:71 rootfs = state.Root→prestartMountHook→libmount.NsExecMount(pid, rootfs, ...)→nsexec.execJSON 编码Mount.Rootfs→ 子进程doMount。风险传播路径: 容器 OCI spec
root→ hook stdin → ParseHookState → rootfs → NsExecMount → JSON 管道 → WorkInContainer/doMount →Rootfs + lxcfsPath(无净化)→ syscall.Mount 目标穿越。调用栈
问题代码
// 文件: libmount/container_work.go:102-113 // remount lxcfs cgroup path readonly if err := syscall.Mount(mount.Rootfs+lxcfsPath, mount.Rootfs+lxcfsPath, "none", syscall.MS_BIND, ""); err != nil { return err } if err := syscall.Mount(mount.Rootfs+lxcfsPath, mount.Rootfs+lxcfsPath, "none", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { return err } for i := 0; i < len(mount.SrcPaths) && i < len(mount.DestPaths); i++ { if err := syscall.Mount(mount.SrcPaths[i], mount.DestPaths[i], "none", syscall.MS_BIND, ""); err != nil { // DestPaths 源自 rootfs 拼接 return err } }// 文件: hooks/lxcfs-hook/execmount.go:48-50 for _, value := range lxcfssubpath { valuePaths = append(valuePaths, fmt.Sprintf("%s/proc/%s", rootfs, value.Name())) // 字符串拼接,非 filepath.Join valueMountPaths = append(valueMountPaths, fmt.Sprintf("/var/lib/lxc/lxcfs/proc/%s", value.Name())) }调用链分析
Root可控rootfs拼接 DestPathsmount.Rootfs = rootfsRootfs+lxcfsPath..未净化 → 挂载目标穿越修复建议
// 修复:1) 对 Rootfs 做净化(Clean + 拒绝 .. 且必须为绝对路径);2) 全部改用 filepath.Join。 import "path/filepath" func doMount(pipe *os.File) error { var mount nsexec.Mount if err := json.NewDecoder(pipe).Decode(&mount); err != nil { return err } // 净化 Rootfs:必须绝对、不含 .. rootfs := filepath.Clean(mount.Rootfs) if rootfs != "/" && !filepath.IsAbs(rootfs) { return fmt.Errorf("invalid rootfs: %q", mount.Rootfs) } if strings.Contains(rootfs, "..") { return fmt.Errorf("rootfs %q contains parent reference", mount.Rootfs) } cgrpPath := filepath.Join(rootfs, lxcfsPath) // 替代 rootfs+lxcfsPath if err := syscall.Mount(cgrpPath, cgrpPath, "none", syscall.MS_BIND, ""); err != nil { return err } if err := syscall.Mount(cgrpPath, cgrpPath, "none", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { return err } for i := 0; i < len(mount.SrcPaths) && i < len(mount.DestPaths); i++ { // 同样对 DestPaths 做 Clean + 边界校验 dst := filepath.Clean(mount.DestPaths[i]) if err := syscall.Mount(mount.SrcPaths[i], dst, "none", syscall.MS_BIND, ""); err != nil { return err } } return nil }同时在
execmount.go/remountcmd.go所有路径构造处统一改用filepath.Join,并在 hook 入口对state.Root做白名单校验。🟡 [重要] hooks/lxcfs-hook/main.go:38 — 日志文件符号链接跟随(root 追加写)
CWE: CWE-59(链接跟随不当)
规则: R06-隐私与数据 / R01-文件边界
描述:
setupLog()以os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0600)打开--log指定路径,OpenFile默认跟随符号链接。logfile来自 hookspec.json 的args(--log /var/log/lxcfs-hook.log,由 RPM%post脚本或管理员配置)。若该路径为指向敏感文件(如/etc/crontab、/etc/sudoers)的符号链接,或所在目录可被低权限用户写入而遭预置符号链接,hook 以 root 身份将日志内容以O_APPEND追加到该敏感文件。影响: 管理员误配置
--log指向符号链接,或共享目录符号链接攻击 → root 向敏感文件追加日志内容(日志内容非完全可控,但仍可污染文件结构,如 cron 文件追加非 cron 格式行致 cron 告警)。CVSS: 4.5(Medium,AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L)
数据来源: hookspec.json
args数组中的--log值 →flag.String("log", ...)→setupLog。风险传播路径: hookspec.json args →
lxcfs-hook --log <path>→flag.Parse→setupLog→os.OpenFile(O_APPEND, 跟随 symlink)→ root 追加写目标文件。调用栈
问题代码
// 文件: hooks/lxcfs-hook/main.go:34-44 func setupLog(logfile string) { logrus.SetLevel(logrus.DebugLevel) logrus.SetOutput(os.Stdout) if logfile != "" { f, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0600) if err != nil { return } logrus.SetOutput(f) return } // ... }修复建议
// 修复:使用 O_NOFOLLOW 拒绝符号链接,并校验父目录属主为 root。 fd, err := syscall.Open(logfile, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_APPEND|syscall.O_SYNC|syscall.O_NOFOLLOW, 0600) if err != nil { return fmt.Errorf("open log file %q failed: %v", logfile, err) } logfile_ := os.NewFile(uintptr(fd), logfile) // 进一步:os.Lstat 校验非 symlink、属主 root、父目录不可被其他用户写入 if fi, err := os.Lstat(logfile); err == nil && fi.Mode()&os.ModeSymlink != 0 { return fmt.Errorf("log file %q is a symlink", logfile) } logrus.SetOutput(logfile_)🟡 [重要] remountcmd.go:197-211 / umountcmd.go:100-114 — 超时后 goroutine 与 fork 子进程泄漏 + 挂载状态不一致
CWE: CWE-404(资源泄漏)/ CWE-662(同步不当)/ CWE-833(死锁/竞态)
规则: R04-并发安全 / R05-错误处理
描述:
remountAll()(umountAll()同构)对每个容器启动一个外层 goroutine,其内部再起一个内层 goroutine 调用remountToContainer,外层用select { case <-res: case <-time.After(30s): }等待。当 30s 超时命中time.After分支时,外层 goroutine 调用wg.Done()结束,但内层 goroutine 仍持续运行——remountToContainer内部经nsexec.execfork 出/proc/self/exe子进程并setns进入容器 mnt ns 执行挂载,该 fork 子进程与内层 goroutine 均不会被取消/回收。后果:① goroutine 与子进程泄漏,多次--all操作累积;② 超时后挂载操作可能延迟成功,工具已报告 timeout 失败但容器内挂载状态实际被改变,造成观测状态与实际状态不一致;③ 内层 goroutine 向已满的 buffered channelres(cap 1)写入虽不阻塞,但仍属无序收尾。影响: 长期运行或频繁
remount --all的节点上 goroutine/进程累积(资源耗尽);挂载状态不一致导致运维误判;理论上的并发挂载竞争。CVSS: 4.0(Medium,AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L)
数据来源:
getContainerIDAndPid返回的容器列表(来自isula ps/runc list,受主机容器状态影响)。风险传播路径:
remount --all→remountAll循环 → 每容器外层 goroutine → 内层 goroutineremountToContainer→nsexec.execfork 子进程 → 30s 超时 → 外层退出、内层与子进程泄漏 → 状态不一致。调用栈
问题代码
// 文件: remountcmd.go:196-211 wg.Add(1) go func() { defer wg.Done() res := make(chan struct{}, 1) go func() { // 内层 goroutine,无取消机制 if err := remountToContainer(initMountns, initUserns, containerslice[0], containerslice[1], true); err != nil { lxcfs_log.Errorf("remount lxcfs dir to container(%s) failed: %v", containerslice[0], err) } res <- struct{}{} }() select { case <-res: case <-time.After(30 * time.Second): // 超时:外层退出,内层 + fork 子进程泄漏 lxcfs_log.Errorf("remount lxcfs dir to container(%s) timeout", containerslice[0]) } }()修复建议
// 修复:使用 context.WithTimeout + cmd.Process.Kill 取消内层与子进程;或合并为单层 goroutine + context。 wg.Add(1) go func(cid, cpid string) { defer wg.Done() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // 将 ctx 传入 remountToContainer → NsExecMount → nsexec.exec, // 在 ctx.Done() 时 cmd.Process.Kill() 终止 fork 子进程。 if err := remountToContainerCtx(ctx, initMountns, initUserns, cid, cpid, true); err != nil { lxcfs_log.Errorf("remount container(%s) failed: %v", cid, err) } }(containerslice[0], containerslice[1]) // nsexec.exec 内:启动 cmd 后 select { case <-ctx.Done(): cmd.Process.Kill(); ... case <-cmdDone: }🟡 [重要] remountcmd.go:330 — isContainerExsit 子串匹配容器 ID(混淆代理/误操作容器)
CWE: CWE-863(授权不当)/ CWE-1023(不安全的比较)
规则: R02-注入(逻辑绕过)/ R07-代码质量
描述:
isContainerExsit()以strings.Contains(containerslice[0], containerid)判定目标容器——子串匹配而非精确/前缀匹配。containerid来自 CLI--container-id(管理员输入),containerslice[0]为isula ps/runc list输出的容器 ID。当多个容器 ID 共享子串时(如abc123与abc999共享abc,或短 ID 是长 ID 前缀),--container-id abc会命中列表中第一个含abc的容器,操作错误的目标容器。函数在首次匹配后即return,无歧义检测。影响: 管理员意图重挂容器 A,实际操作了容器 B(共享 ID 子串时),造成非预期容器的挂载/卸载状态变更(混淆代理)。在多租户或共享主机上可能跨租户误操作。
CVSS: 4.0(Medium,AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L)
数据来源: CLI
--container-id(remountcmd.go:79 / umountcmd.go:73)与isula ps/runc list输出。风险传播路径:
lxcfs-tools remount -c <id>→remountToContainer→isContainerExsit(<id>)→strings.Contains(实际ID, <id>)首次子串命中 → 返回错误容器的 PID → 操作错误容器。调用栈
问题代码
// 文件: remountcmd.go:325-333 for _, value := range out { containerslice := strings.Fields(value) if len(containerslice) < 2 { continue } if strings.Contains(containerslice[0], containerid) { // 子串匹配,可能误命中 return containerslice[1], nil } }修复建议
// 修复:使用精确匹配;若支持短 ID 前缀匹配,需校验唯一性。 var matched []string for _, value := range out { cs := strings.Fields(value) if len(cs) < 2 { continue } if cs[0] == containerid { // 精确匹配优先 return cs[1], nil } if strings.HasPrefix(cs[0], containerid) { matched = append(matched, cs[1]) } } if len(matched) == 1 { // 唯一前缀匹配 return matched[0], nil } if len(matched) > 1 { return "", fmt.Errorf("container id %q is ambiguous, matches: %v", containerid, matched) } return "", fmt.Errorf("no such container %s", containerid)🟡 [重要] remountcmd.go:360 — execCommond 读流出错调 os.Exit 孤儿化子进程 + 末行丢失
CWE: CWE-404(资源泄漏)/ CWE-252(未检查返回值)/ CWE-459(清理不完整)
规则: R05-错误处理 / R01-文件边界
描述:
execCommond()在bufio.Reader.ReadString('\n')循环中,遇到非 EOF 错误时调用onfail(err2),而onfail(main.go:35)执行os.Exit(1)。此时cmd.Start()已启动的子进程(isula/runc/mount)仍在运行,父进程直接退出 → 子进程孤儿化(由 init 回收为僵尸后再回收,但 stdout 管道已关闭,子进程写 stdout 可能 SIGPIPE 退出,行为不可控);管道 fd 未关闭。此外,ReadString('\n')在 EOF 时break而不 append 末尾部分行:当外部命令输出末行无换行符时(如isula ps最后一行无\n,或runc list输出[]无\n),该行被静默丢弃,导致remount --all漏处理最后一个容器、或getRuncContainerIDAndPid得到空切片(与 🟡-1 叠加)。影响: ① 读流出错时孤儿子进程 + fd 泄漏,极端情况子进程挂起占用资源;② 末行丢失致
--all操作静默漏处理容器(数据丢失/操作不完整)。CVSS: 3.5(Low,AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L)
数据来源:
isula ps/runc list/mount的 stdout 流。风险传播路径:
execCommond→cmd.Start→ReadString出错 →onfail→os.Exit(1)→ 子进程孤儿;或ReadStringEOF 末行无\n→ 末行丢弃。调用栈
问题代码
// 文件: remountcmd.go:353-363 reader := bufio.NewReader(stdout) for { line, err2 := reader.ReadString('\n') if err2 == io.EOF { break // 末尾部分行未 append → 丢失 } else if err2 != nil { onfail(err2) // os.Exit(1),子进程仍在运行 → 孤儿 } res = append(res, line) } if err := cmd.Wait(); err != nil { // onfail 后不会执行到此处 return res, err }修复建议
reader := bufio.NewReader(stdout) for { line, err2 := reader.ReadString('\n') // EOF 时若 line 非空,仍需收集(末行无换行) if err2 == io.EOF { if line != "" { res = append(res, line) } break } if err2 != nil { // 不再调 os.Exit;关闭管道、Kill 子进程、返回错误 _ = cmd.Process.Kill() _ = stdout.Close() return res, err2 } res = append(res, line) } if err := cmd.Wait(); err != nil { return res, err }并在
main.go的onfail中避免os.Exit,改为返回 error 由顶层统一处理。🟢 [建议] hooks/lxcfs-hook/utils/utils.go:48-64 — ParseSyslogService 变量遮蔽致解析地址丢失
规则: R07-代码质量 / R05-错误处理
描述:
ParseSyslogService在三个if/else if分支内使用serviceAddr :=(短变量声明)创建新的局部变量,遮蔽外层serviceAddr(第 46 行声明为"")。分支内对局部变量赋值后,外层serviceAddr仍为"",最终serv.Addr = serviceAddr始终为空字符串。setupLog("unix:///dev/log", ...)期望解析出/dev/log,实际serv.Addr = "",/dev/log被丢弃。调用栈:
hooks/lxcfs-hook/main.go:46 utils.SetupSyslog("unix:///dev/log", ...)→utils.go:78 ParseSyslogService→ 分支内serviceAddr :=(遮蔽)→utils.go:71 serv.Addr = ""→logrus_syslog.NewSyslogHook("", "", ...)。修复建议: 将分支内
serviceAddr :=改为serviceAddr =(赋值,非短声明)。🟢 [建议] hooks/lxcfs-hook/utils/utils.go:33-34 — 默认 syslog 端口错误(541 应为 514)
规则: R08-依赖与配置 / R07-代码质量
描述:
syslogDefaultUDPService = "localhost:541"、syslogDefaultTCPService = "localhost:541",标准 syslog 端口为 514,541 非标准。回退默认时 syslog 转发失效。(与 🟢-1 变量遮蔽叠加,该默认值当前实际不被使用,但仍为潜在配置错误。)调用栈:
utils.go:52,58在serviceAddr == ""时回退到syslogDefaultUDPService。修复建议: 改为
localhost:514。🟢 [建议] Makefile:57 — install 目标拼写错误"hakc"+ 残留"loacal:"目标 + 版本元数据不一致
规则: R07-代码质量 / R08-依赖与配置
描述: ① Makefile
install:目标cd hakc && ./install.sh,"hakc" 应为 "hack",make install必然失败(目录不存在);② 第 57 行loacal:为拼写错误的游离目标(无依赖、无配方,且与install:粘连在同一行install:\n\tcd hakc && ./install.shloacal:,实际install.shloacal:也是错误拼接);③GO_LDFLAGS含-X main.gitCommit=${COMMIT},但main.go仅声明var version,未声明gitCommit,链接器对不存在符号的-X为空操作(告警);④main.go:33 var version = "0.1"与VERSION文件v0.3、specVersion: 0.3不一致,CLI--version显示0.1(除非经 ldflags 覆盖)。调用栈:
Makefile:56-57 install:/Makefile:23 GO_LDFLAGS/main.go:33。修复建议: 修正
cd hack && ./install.sh;删除游离loacal:;main.go增加var gitCommit string或移除 ldflags 中-X main.gitCommit;统一version与VERSION文件。🟢 [建议] libmount/nsexec/nsexec.go:147,154,161-169 — pid 未校验为正整数即拼入 nsenter 路径
规则: R02-注入(路径)/ R01-输入校验
描述:
Mount(pid, ...)/Umount(pid, ...)接收字符串pid,buildNSString以fmt.Sprintf("%s:/proc/%s/ns/%s", ns, pid, ns)拼入传给 nsenter C 解析器的NsPathsAttr。pid在 CLI 路径来自isula ps/runc list输出字段(字符串),在 hook 路径来自strconv.Itoa(state.Pid)(int)。未校验pid为纯数字正整数,若isula ps输出异常字段(含,/:/..)可构造畸形 nsPaths,理论上可注入额外type:path命名空间条目(见 ⚠️-3)。当前因 isulad 为可信源而受限。调用栈:
remountcmd.go:304 libmount.NsExecMount(pid, ...)→nsexec.go:147 Mount→nsexec.go:149 buildNSString(pid, ...)→nsexec.go:167 fmt.Sprintf("mnt:/proc/%s/ns/mnt", pid)。修复建议: 在
Mount/Umount入口校验n, err := strconv.Atoi(pid); err != nil || n <= 0 { return error },并以strconv.Itoa(n)重建规范化 pid。🟢 [建议] libmount/container_work.go:124-125,131 — 基于错误字符串匹配判断(脆弱)
规则: R07-代码质量 / R05-错误处理
描述:
doUmount以strings.Contains(err.Error(), "invalid argument")与"no such file or directory"判定 umount 失败是否可忽略。该方式依赖 Gosyscall错误消息文本,若 Go 运行时未来变更错误字符串或返回包装错误,匹配失效,导致非可忽略错误被静默吞没或可忽略错误被错误返回。调用栈:
container_work.go:123 syscall.Unmount→container_work.go:124-125 strings.Contains(err.Error(), ...)。修复建议: 使用
errno, ok := err.(syscall.Errno); if ok && (errno == syscall.EINVAL || errno == syscall.ENOENT) { continue },或errors.Is(err, syscall.EINVAL)。🟢 [建议] hooks/lxcfs-hook/main.go:65-67 — state.Pid<=0 仅记日志未 return,以非法 pid 继续执行
规则: R05-错误处理 / R07-代码质量
描述:
if state.Pid <= 0 { logrus.Errorf(...) }后未 return,继续nsPID = state.Pid(≤0)、prestartMountHook(nsPID, rootfs)。下游strconv.Itoa(pid)→/proc/0/ns/mnt或/proc/-5/ns/mnt→os.Readlink失败返回错误(被上层捕获),但控制流不应放行非法 pid。调用栈:
main.go:65 if state.Pid <= 0→main.go:70 nsPID = state.Pid→main.go:73 prestartMountHook(nsPID, ...)。修复建议:
if state.Pid <= 0 { logrus.Errorf(...); return }。🟢 [建议] libmount/nsexec/nsexec.go:91,116,129 — parent 端 socketpair fd 未关闭(fd 泄漏)
规则: R04-并发安全(资源管理)/ R05-错误处理
描述:
exec()创建 socketpair 得parent, child,child经cmd.ExtraFiles传子进程(子进程退出后关闭),但parent在cmd.Wait()与读取响应后从未Close()。每次NsExecMount/NsExecUmount调用泄漏一个 fd;remount --all对 N 个容器调用 N 次,泄漏 N 个 fd,长期运行或大列表可耗尽进程 fd 表。调用栈:
nsexec.go:91 newPipe()→nsexec.go:116 io.Copy(parent, ...)→nsexec.go:120 WriteJSON(parent, ...)→nsexec.go:125 cmd.Wait()→nsexec.go:129-143 decoder.Decode(parent)→ 函数返回(无parent.Close())。修复建议:
defer parent.Close()置于newPipe成功之后。🟢 [建议] remountcmd.go:48-49 / umountcmd.go:42-43 — container-id 默认值" "(单空格)哨兵脆弱 + 仅剥离换行
规则: R07-代码质量 / R06-隐私(日志注入)
描述: ①
cli.StringFlag{Value: " "}以单空格作默认值,配合len(containerid) == 1判定"未提供",哨兵脆弱(用户若合法传入单字符 ID 会被误拒,且依赖长度魔法数);应默认""并== ""判定。②containerid = strings.Replace(containerid, "\n", "", -1)仅剥离\n,未处理\r、\t及其他控制字符,作为日志注入缓解不完整(containerid 经logrus.Infof记录)。调用栈:
remountcmd.go:79-82/umountcmd.go:73-76。修复建议: 默认值改
""、判定改== "";用strings.Map剥离所有unicode.IsControl字符或仅保留[A-Za-z0-9_.-]。🟢 [建议] hooks/lxcfs-hook/utils/utils.go:106 — ioutil.ReadAll 无界读取 stdin(潜在 OOM)
规则: R07-代码质量 / R01-资源管理
描述:
ParseHookState用ioutil.ReadAll(reader)读取全部 stdin 后json.Unmarshal。若 stdin 提供超大输入(异常容器运行时或恶意构造),内存无界增长。正常容器运行时仅发送小 JSON,风险低,但缺乏防御。调用栈:
utils.go:106 ioutil.ReadAll(reader)→utils.go:111 json.Unmarshal(stateBuf, &state)。修复建议:
stateBuf, err := ioutil.ReadAll(io.LimitReader(reader, 1<<20))(限制 1MiB),超限则报错。🟢 [建议] 全项目 — 拼写错误与命名不一致(可维护性)
规则: R07-代码质量
描述: 多处拼写错误影响可读性与可维护性:
isContainerExsit(→ Exist,remountcmd.go:315)、execCommond(→ Command,remountcmd.go:338)、recmountContainer(→ remount,remountcmd.go:36)、Unspported(→ Unsupported,utils.go:66)、perstart(→ prestart,remountcmd.go:115 Description)、Prase(→ Parse,execmount.go:30)、runing(→ running,remountcmd.go:184/umountcmd.go:87)。修复建议: 统一修正拼写;导出符号改名需评估外部兼容性(本项目为独立工具,可安全改)。
🟢 [建议] remountcmd.go:218-219 — runcStateDir 硬编码,docker/isulad 异构环境回退失败
规则: R08-依赖与配置 / R07-代码质量
描述:
runcStateDir = "/run/docker/runtime-runc/moby"硬编码。docker 默认 state 目录随版本/发行版不同(如/run/docker/runtime-runc/),isulad-only 主机无此目录。isula未安装时回退到 runc 读该目录,路径不匹配则runc list失败,remount --all无法工作。缺乏可配置性与错误提示。调用栈:
remountcmd.go:228 execCommond("runc", []string{"--root", runcStateDir, ...})。修复建议: 通过环境变量/配置文件/CLI flag 可配置
runcStateDir;或自动探测docker info获取实际根目录。⚠️ 疑似[重要] libmount/container_work.go:103-108 — Rootfs 可控下自绑定只读重挂的目标重定向
置信度: 50%
描述:
doMount先以Rootfs+lxcfsPath对自身做MS_BIND,再MS_BIND|MS_REMOUNT|MS_RDONLY改只读。若Rootfs(容器创建者可控的 OCIroot)含..,bind 与只读重挂的目标均被重定向至预期 rootfs 之外(容器 mnt ns 内)。因源==目标(自绑定),不产生跨位置数据移动,只读重挂仅改变目标挂载点属性。但若目标指向容器内已挂载的宿主机卷(如--volume /host:/data,构造Rootfs使Rootfs+lxcfsPath解析到/data/var/lib/lxc/lxcfs/cgroup),可能将容器内该路径误设为只读,影响容器对卷的写入。建议: 与 🟡-2 同源,按 🟡-2 修复方案净化
Rootfs即可缓解;另需评估自绑定只读重挂对容器内卷路径的副作用。⚠️ 疑似[建议] hack/lxcfs-tools.spec:53-70 — %post 脚本以 isula info 输出构造 hookspec 写入路径
置信度: 40%
描述: RPM
%post脚本以GRAPH=\isula info | grep -Eo "iSulad Root Dir:.+" | grep -Eo "/.*"`解析 isulad 根目录,并cat << EOF > ${HOOK_SPEC}/hookspec.json写入 hook 配置。GRAPH来自 isulad 运行时输出,若 isulad 配置被篡改以报告恶意根目录(如/etc),则hookspec.json被写到/etc/hookspec.json(覆盖或创建)。需先 compromise isulad 配置(已需 root),攻击面有限。 **建议**: 校验GRAPH必须为绝对路径且不含..`、属主 root;写 hookspec 前校验目标目录在预期 isulad 根下。⚠️ 疑似[建议] libmount/nsexec/nsexec.go:161-169 — nsPaths 字符串注入额外命名空间条目
置信度: 30%
描述:
buildNSString(pid, ["mnt"])产出mnt:/proc/<pid>/ns/mnt,作为NsPathsAttr经 netlink 传 nsenter C 解析器(runc vendor),解析器按type:path以,分隔多组。若pid含,或:(如1,ipc:/proc/1/ns/ipc),nsPaths 变为mnt:/proc/1,ipc:/proc/1/ns/ipc/ns/mnt,解析器行为依赖其实现(vendor runc nsenter.c,按规则排除未审)。pid在 CLI 路径来自 isulad 输出(可信),hook 路径为 int 转字符串(安全),故实际可利用性低。建议: 在
buildNSString前校验pid仅含数字(见 🟢-4);审查 vendor nsenter.c 对畸形 nsPaths 的处理(虽 vendor 排除审计,但此处为业务层数据流入点)。【缺陷所属的os版本】
24.03-LTS-next
【内核版本】
kernel6.6
【缺陷所属软件及版本号】
lxcfs-tools
【环境信息】
【问题复现步骤】
【实际结果】
【期望结果】
【其他相关附件信息】
【缺陷详情及分析指导参考链接】
二、缺陷分析结构反馈
影响性分析说明:
缺陷严重等级:(Critical/High/Moderate/Low)
缺陷根因说明:
受影响版本排查(受影响/不受影响):
openEuler-20.03-LTS-SP4
openEuler-22.03-LTS-SP4
openEuler-24.03-LTS-SP1
openEuler-24.03-LTS-SP3
openEuler-24.03-LTS-SP4
修复是否涉及abi变化(是/否):
openEuler-20.03-LTS-SP4
openEuler-22.03-LTS-SP4
openEuler-24.03-LTS-SP1
openEuler-24.03-LTS-SP3
openEuler-24.03-LTS-SP4