os.stat不能替代真实访问检查,它仅确认路径存在及元数据可读,不保证可读写或执行;真正判断需按目标动作模拟,如用os.open读、os.openfile写、os.readdir进目录。

os.Stat 不能替代真实访问检查,别信它返回的“成功”
os.Stat 只告诉你路径是否存在、元数据能否读取,不等于你真能打开或写入。比如目录有读权限但缺执行(x)位,os.Stat 可能成功,但 os.Open 会直接报 permission denied;又比如符号链接指向不可达路径,os.Stat 报 no such file or directory,实际是权限不足而非路径不存在。
真正判断“能不能访问”,必须按目标动作模拟:
- 想读?用
os.Open或os.ReadFile,捕获fs.PathError中的errno - 想写?用
os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644) - 想进目录?用
os.ReadDir或os.Open后调dir.Readdir(0)
syscall.Access 是轻量预检手段,但只查最后一级且依赖绝对路径
syscall.Access 直接调内核 access(2),绕过 Go 文件抽象层,适合快速预判。但它只检查路径**最后一级**的 R_OK/W_OK/X_OK,不验证中间目录是否可遍历——例如 /a/b/c.txt,它只查 c.txt 权限,而进入 /a/b/ 还得靠父目录的 X_OK。
使用时注意三点:
- 必须传绝对路径:
filepath.Abs先转,否则相对路径会被解释为进程当前工作目录下 - Linux 受挂载选项影响(如
noexec),macOS 对目录 X_OK 检查更宽松 - 错误类型要区分:
err == syscall.EACCES才是明确权限拒绝,syscall.ENOENT是路径不存在
路径遍历防护不是加个 strings.Contains(filename, "..") 就完事
用户输入路径来自外部(如 HTTP query)时,仅过滤 ".." 完全不够。比如白名单是 /var/data,用户传 ../../etc/passwd,拼成 /var/data/../../etc/passwd,filepath.Clean 后变成 /etc/passwd——直接越界。
正确做法是双重校验:
- 先用
filepath.Abs转成绝对路径 - 再用
strings.HasPrefix(absPath, absWhitelistRoot+string(filepath.Separator))判断是否落在白名单根目录下 - 白名单路径本身也必须是
filepath.Abs后的结果,避免相对路径歧义 - Windows 路径大小写不敏感,Linux 敏感;跨平台建议统一转小写再比对(仅限 ASCII 路径)
os.Chmod 在 Windows 上基本无效,别指望它做权限控制
os.Chmod 在 Linux/macOS 上能设 POSIX 权限位(如 0444 让文件只读),但在 Windows 上只尝试设置只读标志(FILE_ATTRIBUTE_READONLY),其他位全被忽略。管理员或文件所有者仍可通过资源管理器随意改写。
这意味着:
- 跨平台服务中,不能依赖
os.Chmod实现“禁止写入”或“限制执行” - Windows 真正生效的权限需调用 ACL API(如
golang.org/x/sys/windows.SetNamedSecurityInfo) - Web 场景下,最可靠的权限控制始终在应用层:HTTP handler 显式校验用户身份 + 请求路径 + 动作类型
路径规范化和白名单约束必须在 http.ServeFile 前完成,且 path.Clean 要在拼接前调用,否则 ../../../ 可能绕过后续判断。符号链接攻击比路径遍历更隐蔽,strings.HasPrefix 校验比单纯检查 ".." 更防绕过。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











