一处谁都能写的临时空间,文件名可能相撞。

临时目录是公用空间

/tmp 是名字的共享命名空间,不是私有的暂存区。任何本地用户都能创建你的进程即将使用的那个名字,所以名字并不构成对文件的占有;而 hier(7) 明确说,里面的内容可能在任何时候被删除,而且不会通知你。 粘滞位让这个目录可以安全地被共享,却没有让它能被可预测地使用。因此修法不是改权限,而是改系统调用的形状:用一次调用同时生成名字并创建文件,中间不留任何空隙给别人抢占。 1777 才让共享成为可能 模式 1777 是「人人可写」加上粘滞位,而在这台主机上这不是假设:/tmp 是指向 private/tmp 的符号链接,后者是 drwxrwxrwt。没有粘滞位,目录里任何能写的人都能删除或重命名别人的文件;有了它,只有文件属主、目录属主或 root 可以。正是这一个位,让 /tmp 能被许多互不信任的用户同时使用。 粘滞位做不到的事,是在文件存在之前保护那个名字。open("/tmp/svc.log", "w") 会跟随一个已经存在的符号链接,于是写入落在链接指向的地方,而不是一个新文件里。实验用真实的 open(2) 验证了这一点,随后验证了两次拒绝:O_CREAT|O_EXCL 因名字已被占用而返回 EEXIST,O_CREAT|O_NOFOLLOW 因最后一段是符号链接而返回 ELOOP。 不是你创建的文件,权限不属于你 同一个缺陷的另一半更隐蔽:既没有符号链接,也没有可见的竞争。O_CREAT 只在它真正创建文件时才应用模式参数,所以一个被预先创建、人人可读的文件,在你用模式 0600 打开它之后仍然保持 0666,你写进去的秘密也就一直人人可读。实验用真实的 stat(2) 复现了这一点。 修法是让一次调用同时完成命名与创建。mkstemp(3) 把契约写得很清楚:模板的最后六个字符是 XXXXXX,文件以权限 0600 创建,并以 O_EXCL 打开,从而保证创建者就是调用者。mktemp(1) 在 shell 里做同样的事,会遵循 TMPDIR,默认模板为 tmp.XXXXXXXXXX。它的 -u 只打印名字而不创建任何东西,手册直接把它标为 “(unsafe)";实验确认那个被打印出来的名字之后仍然空闲,而这正是 mkstemp 要关掉的那扇窗。如果工作单元是目录而不是文件,mkdtemp(3) 应用同一条规则:以模式 0700 创建它,别人进不去。这一步同样是一次调用完成命名与创建,没有留给别人的窗口。 ...

九月 26, 2027 · 1 分钟 · XB Field Notes