- commit
- 0c69d0cf2ecf58fe7de8fe68ac87452b03e8ba81
- parent
- 6cf5c614e5be2d8939f13486f0d8cc7e5d8bb33c
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2026-05-24 10:53
read store from caller's mount namespace
Diffstat
| M | xikeyring/keyring.py | 18 | ++++++++++++++---- |
| M | xikeyring/pidfd.py | 24 | ++++++++++++++++++++++++ |
2 files changed, 38 insertions, 4 deletions
diff --git a/xikeyring/keyring.py b/xikeyring/keyring.py
@@ -27,9 +27,11 @@ class Item: 27 27 attributes: dict[str, str] 28 28 29 2930 -1 def write_bytes(path: Path, data: bytes) -> int:-1 30 def write_bytes(path: Path, data: bytes, pid: PID | None = None) -> int: 31 31 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC 32 32 fd = os.open(path, flags, mode=0o600) -1 33 if pid: -1 34 pid.check_active() 33 35 try: 34 36 return os.write(fd, data) 35 37 finally: @@ -88,10 +90,12 @@ class Keyring: 88 90 return KernelKey(key) 89 91 90 92 def _read(self, pid: PID) -> dict[int, Item]:91 -1 if not self.path.exists():-1 93 path = pid.path(self.path) -1 94 if not path.exists(): 92 95 return {} 93 9694 -1 encrypted = self.path.read_bytes()-1 97 encrypted = path.read_bytes() -1 98 pid.check_active() 95 99 decrypted = Fernet(self.key.value).decrypt(encrypted) 96 100 raw = json.loads(decrypted) 97 101 return { @@ -100,6 +104,12 @@ class Keyring: 100 104 } 101 105 102 106 def _write(self, pid: PID, items: dict[int, Item]): -1 107 path = pid.path(self.path) -1 108 if not path.parent.exists(): -1 109 # Raise an error instead of creating the directory because this -1 110 # might be a tmpfs. -1 111 raise NotFoundError -1 112 103 113 raw = [ 104 114 ( 105 115 id, @@ -110,7 +120,7 @@ class Keyring: 110 120 ] 111 121 decrypted = json.dumps(raw).encode('utf-8') 112 122 encrypted = Fernet(self.key.value).encrypt(decrypted)113 -1 write_bytes(self.path, encrypted)-1 123 write_bytes(path, encrypted, pid) 114 124 115 125 def confirm_access(self) -> None: 116 126 if not self.prompt.confirm('Allow access to a secret from your keyring?'):
diff --git a/xikeyring/pidfd.py b/xikeyring/pidfd.py
@@ -1,4 +1,28 @@ -1 1 import selectors -1 2 from pathlib import Path -1 3 -1 4 1 5 class PID: 2 6 def __init__(self, pid: int, pidfd: int): 3 7 self.pid = pid 4 8 self.pidfd = pidfd -1 9 -1 10 def check_active(self) -> None: -1 11 with selectors.DefaultSelector() as sel: -1 12 sel.register(self.pidfd, selectors.EVENT_READ) -1 13 if sel.select(0) != []: -1 14 raise ValueError('Calling process has quit') -1 15 -1 16 def path(self, path: str | Path) -> Path: -1 17 root = (Path('/proc') / str(self.pid) / 'root').resolve() -1 18 rel_path = Path(path).absolute().relative_to('/') -1 19 result = (root / rel_path).resolve() -1 20 -1 21 # FIXME: symlinks are resoled relative to the host. -1 22 # -1 23 # A proper fix would involve openat2() -1 24 # (see https://github.com/python/cpython/issues/141878). -1 25 if root not in result.parents: -1 26 raise ValueError('path escapes mount namespace') -1 27 -1 28 return result