Linux 目录上的 sticky bit,为删除或重命名目录项增加所有权限制。它常与宽松的目录写入、搜索权限一起使用,让多个用户能够创建临时文件,又不能任意删除彼此的条目。普通目录权限检查和 sticky 所有权检查都需要满足;sticky 是额外条件,不会替代正常访问控制,也不能只凭这一位判断整个目录里的数据是否安全。
坏掉的样子
没有 sticky 时,用户只要具备目录所需的写入与搜索权限,通常就能删除另一个用户的文件,即使文件模式完全不允许内容访问。unlink 改变的是目录里的名字与对象关系,不要求调用者能够读取或覆盖文件内容。因此,在广泛可写目录中把文件设为 000,并不能单独保护该路径不被他人删除;应分别检查父目录和文件自身的规则。
启用 sticky 后,普通用户还需要拥有该条目或包含它的目录,才能执行相关删除。具有适当 CAP_FOWNER 能力的特权进程,可以绕过这项所有权限制。因此 sticky 约束的是具体权限模型下的同级用户,并不抵御所有管理员,也不保护文件免受目录所有者删除。把它描述为绝对隔离,会夸大真实保证。
sticky 不阻止本来被允许的内容访问。一个用户可能无法 unlink 某个 0666 文件,却仍然可以读取或覆盖它。新建子目录也不会自动继承父目录的 sticky 位。混淆路径保护、内容隐私和继承,会让共享目录树表现出与顶层看起来完全不同的行为,需要沿实际数据位置检查,而不是只看根部权限字符串。
快速验证
把脚本保存为 sticky.py,在一次性 Linux 容器中以 root 运行 python3 sticky.py。控制进程需要分配三个数字身份并启动普通子进程的权限,不会创建账号。实测使用 Linux 6.12.76 和 Python 3.11.2,所有操作都发生在临时样本目录内:
from pathlib import Path
from tempfile import TemporaryDirectory
import errno,os,stat,subprocess,sys
directory_owner=61000;file_owner=61001;other_user=61002
probe_code=r'''
import os,sys
path=sys.argv[1];operation=sys.argv[2]
try:
if operation=='unlink':os.unlink(path)
elif operation=='rename':os.rename(path,path+'-renamed')
elif operation=='write':
fd=os.open(path,os.O_WRONLY|os.O_TRUNC)
try:os.write(fd,b'other-user-write')
finally:os.close(fd)
elif operation=='read':
fd=os.open(path,os.O_RDONLY);os.close(fd)
elif operation=='mkdir':os.mkdir(path,0o777)
else:raise AssertionError('unknown operation')
except OSError as error:print(error.errno)
else:print(0)
'''
def probe(uid,path,operation):
result=subprocess.run([sys.executable,'-c',probe_code,str(path),operation],user=uid,group=uid,extra_groups=[],capture_output=True,text=True,timeout=5)
assert result.returncode==0,result.stderr
return int(result.stdout)
with TemporaryDirectory(prefix='xbcatm-sticky-') as directory:
root=Path(directory);root.chmod(0o755);shared=root/'shared';shared.mkdir();shared.chmod(0o777)
os.chown(shared,directory_owner,directory_owner);victim=shared/'victim'
def make_victim(mode):
victim.write_bytes(b'fixture');os.chown(victim,file_owner,file_owner);victim.chmod(mode)
make_victim(0o000)
assert probe(other_user,victim,'unlink')==0
print('without sticky, directory write/search allows removing another owner’s mode-000 file')
shared.chmod(0o1777);make_victim(0o666)
assert probe(other_user,victim,'unlink')==errno.EPERM
assert probe(other_user,victim,'rename')==errno.EPERM
assert probe(other_user,victim,'write')==0 and victim.read_bytes()==b'other-user-write'
victim.chmod(0o600);assert probe(other_user,victim,'read')==errno.EACCES
print('sticky blocks outsider unlink/rename, but file permissions still govern reading and writing')
assert probe(file_owner,victim,'unlink')==0
make_victim(0o600);assert probe(directory_owner,victim,'unlink')==0
print('both the entry owner and the directory owner may remove it')
child=shared/'child';assert probe(other_user,child,'mkdir')==0
assert not child.stat().st_mode&stat.S_ISVTX
shared.chmod(0o1766);assert stat.filemode(shared.stat().st_mode).endswith('T')
assert probe(other_user,shared/'blocked','mkdir')==errno.EACCES
shared.chmod(0o1777);assert stat.filemode(shared.stat().st_mode).endswith('t')
print('sticky is not inherited by a new subdirectory; uppercase T means other-execute is absent')
第一组使用属于一个身份的 0777 目录,以及属于另一个身份的 000 文件。第三个用户成功 unlink 该文件,证明目录写入与搜索权限可以独立于内容权限允许删除。root 只负责准备样本,真正被检查的删除动作由第三个普通身份执行,避免特权绕过使结果失去解释价值。
把目录设为 1777 后,第三个用户的 unlink 与 rename 都得到 EPERM。同一用户在文件为 0666 时,却成功覆盖内容。写入使用不含 O_CREAT 的打开方式,明确操作既有文件,避免 Linux 另一项针对共享目录普通文件的保护策略干扰结果。实验需要区分正在测量的权限规则,而不是把所有拒绝都归因于 sticky。
文件改为 0600 后,第三个用户的读取得到 EACCES。此过程中 sticky 始终启用,所以改变内容访问结果的是文件权限。两种错误来自不同检查,不能合并成“sticky 让每个临时文件私有”的结论。共享目录中的名字不容易被别人删除,与其中内容只有本人能读写,是两条需要分别成立的条件。
文件所有者随后成功删除自己的条目,重新创建的文件也能由目录所有者删除。两项操作都通过普通子进程身份检查,直接验证所有权例外,没有依赖可能具备绕过能力的 root。目录所有者对条目的管理权,与文件所有者对内容的访问权存在区别,应在共享空间设计中明确这种关系。
第三个用户接着创建子目录,后者没有自动继承 sticky。父目录改为 1766 后,权限显示以大写 T 结束,再次创建失败,因为缺少目录搜索权限。恢复 1777,则显示小写 t,表示 sticky 与其他用户执行位同时存在。大写字母不表示保护更强,而是提醒相应执行位缺失,必须结合目录遍历语义理解。
有意识地设计共享存储
当策略允许共享创建、但限制同级用户删除时,可以使用 sticky。预期读写者则需要通过文件权限或 ACL 单独配置。如果每位用户的数据必须保密,应使用另一身份实际尝试访问;只有目录带 sticky,无法证明其中的文件不可读。权限验证要针对需要保护的动作,而不是只确认配置位已经存在。
应把目录所有权理解为对目录项的管理权限。目录所有者可能无法读取某文件,却能够删除它并中断其他用户工作。在多层目录中,需要检查每个条目真正所在父目录的权限,因为受保护的祖先不会自动把 sticky 行为传给后代。顶层正确不等于整棵树已经具有相同删除边界。
创建临时文件时,应使用能够新建合适受限对象、且不信任可预测既有名字的接口。sticky 解决共享目录中一类删除问题,却不解决符号链接替换或所有不安全打开方式。应按应用实际身份与文件系统,分别验证创建、后续内容访问和清理行为,才能确认完整工作流程符合预期。
来源
- inode(7),sticky 目录、文件所有权与权限位显示,已核验 2026-09-12。
- unlink(2),目录写入搜索权限与 sticky 删除限制,已核验 2026-09-12。
- capabilities(7),CAP_FOWNER 对 sticky 目录所有权检查的例外,已核验 2026-09-12。
- rename(2),sticky 目录内重命名的权限检查,已核验 2026-09-12。