windows 上 cgroups 不可用,.net 应用无法使用;io 隔离需应用层路径校验、容器层资源限制与 os 层权限控制协同实现。

Windows 上 cgroups 不可用,别白费劲
C# 运行在 Windows 时,cgroups 根本不存在——它是 Linux 内核的机制,Windows 没有原生支持。试图在 .NET 中调用 cgroups 接口(比如通过 libcgroup 或直接写 /sys/fs/cgroup)会直接失败,或仅在 WSL2 里“看起来有效”,但和宿主 Windows 的 .NET 进程完全无关。
常见错误现象:DirectoryNotFoundException 或 UnauthorizedAccessException 出现在尝试访问 /sys/fs/cgroup 路径时;或在容器外硬编码 cgroup 路径导致部署即崩。
- Windows 容器用的是 Windows Server Containers / Hyper-V Containers,资源隔离靠
job objects和storage QoS,不是 cgroups - Linux 容器中跑 .NET(如 Alpine + dotnet-runtime)才可能用到 cgroups,但那是容器运行时(dockerd / containerd)配置的,.NET 应用层不参与控制
- 不要在 C# 代码里试图读写
/sys/fs/cgroup—— 权限、路径、生命周期都不可控
.NET 自身不提供文件 IO 隔离 API
System.IO 所有类型(FileStream、Directory、Path)默认共享整个进程的文件句柄和权限上下文,没有租户维度的沙箱开关。你不能靠改某个 FileStreamOptions 参数就让 A 租户只能读 /data/tenant-a、B 租户只能读 /data/tenant-b。
使用场景:多租户 SaaS 后端需防止租户越权访问他人上传的附件、配置文件或缓存目录。
- 必须自己实现路径白名单校验,例如拦截所有
File.ReadAllText(path)前检查path是否落在租户根目录下 - 避免用
Path.Combine(baseDir, userInput)直接拼接——userInput是"../etc/passwd"就完蛋 - 推荐用
Path.GetRelativePath(tenantRoot, fullPath)判断是否为子路径,再配合Path.IsPathFullyQualified()拦截绝对路径
真正可行的 IO 隔离靠三层边界
不是靠某一个函数或配置项,而是操作系统层、容器层、应用层协同划界。C# 只能管好最上面一层:应用级路径裁决。
性能与兼容性影响:过度校验路径(比如每读一次都做完整归一化 + 白名单比对)会影响吞吐,尤其高频小文件操作;但比起租户数据泄露,这点开销必须承担。
- OS 层:Linux 用
chroot(不推荐)或容器 rootfs +mount --bind绑定租户专属目录;Windows 用 NTFS 权限 +SetNamedSecurityInfo锁定目录 ACL - 容器层:Docker run 时用
--read-only、--tmpfs、--storage-opt size=限制磁盘配额,cgroup v2 的io.max控制 IObps/iops(仅 Linux) - 应用层(C#):所有 IO 入口统一走封装方法,例如
TenantFileSystem.ReadText(tenantId, "config.json"),内部自动拼路径 + 校验 + 记录审计日志
租户路径校验的最小可靠示例
别信网上那些只做 Contains("..") 的“防护”——绕过太容易。下面这段是生产可用的底线逻辑:
public static bool IsUnderTenantRoot(string tenantRoot, string candidatePath)
{
var normalized = Path.GetFullPath(candidatePath);
var root = Path.GetFullPath(tenantRoot) + Path.DirectorySeparatorChar;
return normalized.StartsWith(root, StringComparison.Ordinal);
}注意:Path.GetFullPath 会解析 .. 和 .,但不会处理符号链接(symlink)。如果租户目录下允许软链,还得额外调用 File.GetAttributes 检查 FileAttributes.ReparsePoint 并拒绝。
容易被忽略的地方:Windows 路径大小写不敏感,但 StartsWith 默认敏感;Linux 下挂载点可能跨文件系统,GetFullPath 无法识别绑定挂载导致误判。这些边界情况不出问题时没人查,一出就是数据混访。










