T09 · Insecure Skill Coding Practices
- Location
milb_email/fetcher.py:313- Finding
Predictable Temporary Lock File Allows Symlink-Based File Truncation
- Content
View full analysis
Vulnerability Details
File Location:
milb_email/fetcher.py, lines 313–317 and 335–338
Vulnerability Type: Unsafe temporary lock-file handling
Risk Level: MediumVulnerable Code
python # Lock file prevents concurrent execution LOCK_FILE = '/tmp/bidding_email.lock' try: lock_fd = open(LOCK_FILE, 'w') fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except (IOError, OSError): print("[INFO] Another task is already running; skipping this execution") sys.exit(0)python finally: fcntl.flock(lock_fd, fcntl.LOCK_UN) lock_fd.close() try: os.remove(LOCK_FILE) except: passTechnical Analysis
The application uses a fixed, predictable filename in the globally writable
/tmpdirectory. It opens that path with modew, which truncates the referenced file and follows symbolic links before the advisory lock is acquired.A local attacker can create
/tmp/bidding_email.lockas a symbolic link to another file writable by the account that subsequently runsmilb-email. When the application opens the lock path, Python follows the symbolic link and truncates the target file.The later pathname-based removal is also subject to a race: another process could replace the lock pathname after it is opened but before
os.remove()executes. The bareexceptsuppresses errors that could otherwise reveal unsafe lock cleanup.Attack Path
- An attacker with local access creates a symbolic link at the predictable path:
text /tmp/bidding_email.lock -> /path/to/user-writable/target - A victim runs
milb-emailunder an account that can write to the target. open('/tmp/bidding_email.lock', 'w')follows the symbolic link.- The target is truncated before
flock()provides any protection. - During cleanup, pathname replacement could additionally cause the program to remove an unintended replacement entry.
Impact Assessment
Successful exploitation can corrupt or er ...[truncated 362 chars]
- An attacker with local access creates a symbolic link at the predictable path:
- Remediation
View remediation
Remediation Suggestions
- Store the lock in a private runtime directory owned by the executing user, with permissions such as
0700, rather than directly in shared/tmp. - Create the lock atomically with low-level flags such as
O_CREAT | O_EXCL | O_NOFOLLOWand mode0600. - Verify with
fstat()that the opened object is a regular file and that its owner matches the effective user. - Prefer a maintained lock-file library that safely handles creation, ownership, stale locks, and cleanup.
- If a stable lock inode is used with
flock(), avoid unlinking it after every execution. Otherwise, verify that the pathname still identifies the originally opened inode before removal. - Replace the bare
exceptduring cleanup with specific exception handling and appropriate logging.
Example hardening approach:
python runtime_dir = Path(os.environ.get("XDG_RUNTIME_DIR", Path.home() / ".cache")) / "milb-email" runtime_dir.mkdir(mode=0o700, parents=True, exist_ok=True) lock_path = runtime_dir / "bidding_email.lock" flags = os.O_RDWR | os.O_CREAT if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW lock_fd_raw = os.open(lock_path, flags, 0o600) lock_fd = os.fdopen(lock_fd_raw, "r+") fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)- Store the lock in a private runtime directory owned by the executing user, with permissions such as
