fix: read the umask under one lock

plain_file_mode and safe_extract_zip set the umask to 0 to read it. Two threads doing so could restore 0 last, leaving every later file world-writable. Both now read it through process_umask, which holds a lock.
This commit is contained in:
Abdessamad Derraz committed 2026-11-05 19:10:57 +01:00
1 parent a1d5e7c13d
commit 80f736c167
3 files changed
+53 -8

No files matched your search

+19 -4
View File
@@ -12,6 +12,7 @@ import contextlib
import json
import os
import tempfile
import threading
import re
@@ -62,12 +63,26 @@ def write_if_changed(
return True
_UMASK_LOCK = threading.Lock()
def process_umask() -> int:
"""The process umask, read without letting a second thread see it at 0.
Reading the umask sets it, so it is put back at once. Two threads doing
that unguarded interleave as A sets 0 and reads 022, B sets 0 and reads 0,
A restores 022, B restores 0: the process keeps umask 0 and every file it
creates afterwards is world-writable.
"""
with _UMASK_LOCK:
umask = os.umask(0)
os.umask(umask)
return umask
def plain_file_mode() -> int:
"""The mode open() gives a new file: 0o666 less the process umask."""
# Reading the umask sets it, so it is put back at once.
umask = os.umask(0)
os.umask(umask)
return 0o666 & ~umask
return 0o666 & ~process_umask()
def set_plain_mode(path: str | os.PathLike) -> None:
+2 -4
View File
@@ -14,7 +14,7 @@ import sys
import tempfile
import zipfile
from artifacts import set_plain_mode
from artifacts import process_umask, set_plain_mode
from hashing import md5sum
@@ -251,9 +251,7 @@ def safe_extract_zip(
"""
dest = os.path.realpath(dest_dir)
os.makedirs(dest, exist_ok=True)
# Reading the umask sets it, so it is put back at once.
umask = os.umask(0)
os.umask(umask)
umask = process_umask()
with zipfile.ZipFile(zip_path, "r") as zf:
members = zf.infolist()
if len(members) > max_members: