A directory descriptor and O_NOFOLLOW let a POSIX open reject a symlink in its final path component.
Python POSIX dir_fd and O_NOFOLLOW: reject a final symlink on an owned open
Operation contract
An owned temporary directory contains one receipt file and a symlink pointing at it. The fixture opens the real file relative to an already-open directory descriptor, reads its exact bytes, and sees an error when it tries to open the symlink with O_NOFOLLOW.
Failure boundary
This is a checked POSIX fixture; Windows requires another implementation. O_NOFOLLOW guards the final component only, and a directory descriptor does not by itself make every later operation race-free. Do not use string-prefix path checks as a substitute for descriptor-based ownership and a clear symlink policy.
Working program
import errno
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
receipt_path = Path(directory) / "receipt-47.txt"
receipt_path.write_bytes(b"paid:47")
(Path(directory) / "current.txt").symlink_to(receipt_path.name)
directory_fd = os.open(directory, os.O_RDONLY | os.O_DIRECTORY)
try:
receipt_fd = os.open("receipt-47.txt", os.O_RDONLY | os.O_NOFOLLOW,
dir_fd=directory_fd)
try:
print("receipt", os.read(receipt_fd, 7))
finally:
os.close(receipt_fd)
try:
link_fd = os.open("current.txt", os.O_RDONLY | os.O_NOFOLLOW,
dir_fd=directory_fd)
except OSError as failure:
print("link_rejected", failure.errno in (errno.ELOOP, errno.EMLINK))
else:
os.close(link_fd)
raise AssertionError("symlink unexpectedly opened")
finally:
os.close(directory_fd)Output
receipt b'paid:47'
link_rejected TrueCosts and limits
Descriptor-based opens cost system calls and require explicit close ownership. They avoid trusting an earlier resolved path name, but the full threat model still determines which components and operations need protection.
Common Mistakes
- O_NOFOLLOW covers the final component, not every ancestor in a path.
- A prior path check can become stale before a later open.
- Close both the opened file and directory descriptors on every path.
