近期,大量用户在 LXC 中运行 Docker 或 Podman 时,突然遭遇 permission denied 错误,提示无法访问 net.ipv4.ip_unprivileged_port_start

这并非配置错误,而是一个由安全增强引发的兼容性“蝴蝶效应”——涉及 runc、AppArmor、LXC 与内核路径解析机制。本文将带你从现象出发,层层剖析问题根源,并提供安全、有效的解决方案。


一、问题现象:熟悉的命令,陌生的报错

你在 PVE 的 LXC 容器中执行:

1
docker run --rm hello-world

却意外收到如下错误:

1
2
Error response from daemon: 
open sysctl net.ipv4.ip_unprivileged_port_start file: reopen fd 8: permission denied

更奇怪的是:

  • 之前一直正常;
  • 没有修改容器配置;
  • 唯一变化是 最近升级了 Docker

这是怎么回事?


二、溯源:从 Docker 升级说起

1. Docker 依赖 runc

Docker 并不直接管理容器,而是调用底层 OCI 运行时 runc 来创建和启动容器。因此,升级 Docker 往往会连带升级 runc

2. runc 的安全增强(v1.2.8+)

为修复一个严重的 mount race condition 漏洞(攻击者可篡改挂载内容),runc 在 v1.2.8 和 v1.3.3 中引入了一项关键变更:

使用“脱离挂载”(detached mount)的方式访问 /proc/sys/... 下的 sysctl 文件

这种做法更安全,但带来了一个副作用:内核在脱离挂载中生成的路径名不再包含 /proc 前缀

例如,原本访问:

1
/proc/sys/net/ipv4/ip_unprivileged_port_start

在脱离挂载中被简化为:

1
/sys/net/ipv4/ip_unprivileged_port_start

注意:这只是内核内部路径表示,实际仍是 procfs,而非真正的 sysfs。


三、AppArmor 的“误判”:路径即正义?

AppArmor 是 Linux 的强制访问控制(MAC)模块,其核心逻辑是:根据进程尝试访问的路径字符串决定是否放行

当它看到 runc 尝试写入 /sys/net/... 时:

  • 它不知道这是在“脱离挂载的 procfs”中;
  • 它只认路径以 /sys/ 开头;
  • LXC 默认的 AppArmor 策略明确禁止写入 /sys(除少数例外)。

具体规则如下(位于 /etc/apparmor.d/lxc/container-base):

1
deny /sys/[^fdc]*{,/**} wklx,
  • f/sys/fs/(如 cgroup)
  • d/sys/devices/
  • c/sys/class/
  • n(net)不在允许列表中 → 被拒绝!

于是,一场“误会”酿成权限错误。

🔍 此问题已被分配 CVE 编号:CVE-2025-52881,并记录于 runc GitHub issue #4968


四、为什么 Proxmox VE 用户特别“受伤”?

Proxmox VE 使用 LXC 作为容器方案,且默认启用 AppArmor。更重要的是:

  • PVE 的 LXC 容器若启用了 nesting=1(用于运行 Docker),通常会使用 自动生成的 AppArmor 策略lxc.apparmor.profile = generated);
  • 该策略无法通过修改 /etc/apparmor.d/ 文件覆盖
  • 用户既不能降级 runc(会引入更严重漏洞),又无法绕过策略。

结果:大量 PVE 用户在升级 Docker 后“中招”。


五、解决方案:安全、有效、无需升级 PVE

好消息是:你不需要升级 Proxmox VE,也不必降级 Docker。以下是官方推荐的修复方式。

✅ 方案一:为容器禁用 AppArmor(推荐)

编辑你的 LXC 容器配置文件(如 /etc/pve/lxc/101.conf),添加:

1
2
3
4
5
# 禁用 AppArmor 策略
lxc.apparmor.profile: unconfined

# 欺骗 Docker:让它认为系统未启用 AppArmor
lxc.mount.entry: /dev/null sys/module/apparmor/parameters/enabled none bind,ro 0 0

然后重启容器:

1
pct stop 101 && pct start 101

💡 第二行至关重要!否则 Docker 会因无法读取 /sys/kernel/security/apparmor/profiles 而启动失败。

✅ 方案二:放宽全局 AppArmor 策略(适用于多容器场景)

如果你有多个容器需运行 Docker,可修改 LXC 策略模板:

1
2
3
4
5
6
7
8
# 备份原文件
sudo cp /etc/apparmor.d/lxc/container-base{,.bak}

# 允许 /sys/net/ 写入(增加 'n')
sudo sed -i 's/deny \/sys\/\[\^fdc\]\*/deny \/sys\/\[\^fdcn\]\*/g' /etc/apparmor.d/lxc/container-base

# 重载策略
sudo apparmor_parser -r /etc/apparmor.d/lxc-containers

⚠️ 注意:此操作影响所有 LXC 容器,请评估安全风险。

❌ 方案三:降级Docker版本(可能带来更多安全风险)

以Debian13为例

1
apt install -y --allow-downgrades containerd.io=1.7.28-1~debian.13~trixie

六、总结

关键点 说明
问题根源 runc 安全增强 + AppArmor 路径误判 + LXC 严格策略
触发条件 PVE LXC 容器中运行新版 Docker/Podman
是否需升级 PVE ❌ 不需要
推荐方案 容器配置中设置 unconfined + 绑定 /dev/null
长期展望 等待 PVE 集成 Incus 或改进 LXC 策略生成逻辑

许多用户试图通过降级 runc 到 1.2.7 以下来“绕过”问题。但 runc 团队明确警告:

降级会重新暴露已知高危漏洞,包括可绕过 AppArmor 的攻击(如 CVE-2024-XXXXX)

安全增强带来的兼容性问题,应通过策略调整解决,而非放弃安全。