The sticky bit on a Linux directory adds an ownership restriction to removing or renaming entries. It is commonly combined with broad directory write and search permissions so several users can create temporary files without freely removing one another’s entries. The directory permission check and the sticky ownership check both matter; sticky is an additional condition, not a replacement for normal access control.
When it breaks
Without sticky, a user with the necessary write and search permissions on a directory can often remove another user’s file even when that file’s mode grants no access. Unlink changes the directory’s name-to-object relationship. It does not require permission to read or overwrite the file contents, so making the file itself mode 000 does not protect its pathname in a broadly writable directory.
With sticky enabled, ordinary users must additionally own the entry or the containing directory to perform the relevant removal. A privileged process with the appropriate CAP_FOWNER capability can bypass that ownership restriction. Sticky therefore limits peers under a specific permissions model; it does not protect against every administrator or the directory’s own owner.
Sticky does not stop permitted content access. Another user may still read or overwrite a mode-0666 file while being unable to unlink it. The mode also does not automatically propagate to newly created subdirectories. Confusing pathname protection, content privacy, and inheritance can leave a shared tree with materially different behavior from its apparently protected top level.
Check it quickly
Save this as sticky.py and run python3 sticky.py as root in a disposable Linux container. The controller needs permission to assign three numeric identities and launch unprivileged children; it creates no accounts. The recorded environment used Linux 6.12.76 and Python 3.11.2. All paths are inside a temporary fixture directory:
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')
The first case uses a 0777 directory owned by one identity and a mode-000 file owned by another. A third user successfully unlinks that file. This demonstrates that directory write/search permissions can allow removal independently of the file’s content permissions. Root sets up the fixture, but the tested removal occurs under the third, unprivileged identity.
After setting the directory to 1777, the third user’s unlink and rename attempts fail with EPERM. The same user successfully overwrites the file when its mode is 0666. The write opens an existing file without O_CREAT, avoiding a different Linux protected-regular-file policy that could otherwise obscure which permission rule the exercise is measuring.
Changing the file to 0600 causes the third user’s read attempt to fail with EACCES. Sticky remained enabled throughout, so it is the file-permission change that altered content access. The two error classes arise from different checks and should not be collapsed into a claim that sticky makes each temporary file private.
The file’s owner successfully removes its entry, and a newly recreated file can also be removed by the directory owner. Both outcomes are checked using ordinary child identities. These cases demonstrate the two ownership exceptions directly instead of relying on root, whose capabilities would make the result ambiguous.
Next, the third user creates a subdirectory. It has no sticky bit automatically inherited from the parent. Changing the parent to 1766 produces an uppercase T in its permission display, and another creation attempt fails because directory search permission is missing. Restoring 1777 produces lowercase t, which combines sticky with the other-execute bit.
Design shared storage deliberately
Use sticky when the intended policy allows shared creation but restricts peer removal. Set file permissions or ACLs separately for the intended readers and writers. If each user’s data must remain confidential, verify access under another identity; the presence of a sticky directory alone supplies no evidence that its files are unreadable.
Treat directory ownership as authority over its entries. A directory owner who can remove files may disrupt other users even while unable to read their contents. In a hierarchy, inspect the permissions of the actual parent containing each entry, because a protected ancestor does not automatically apply its sticky behavior to descendants.
For temporary-file creation, use an API that creates a fresh, suitably restricted object without trusting a predictable preexisting name. Sticky helps with a particular shared-directory removal problem, but it does not solve symbolic-link substitution or every unsafe open pattern. Verify creation, subsequent content access, and cleanup under the actual identities and filesystem used by the application.
Sources
- inode(7), sticky directories, file ownership, and displayed permission bits, checked 2026-09-12.
- unlink(2), directory write/search permissions and sticky removal restrictions, checked 2026-09-12.
- capabilities(7), CAP_FOWNER exceptions to sticky-directory ownership checks, checked 2026-09-12.
- rename(2), rename permission checks in sticky directories, checked 2026-09-12.