Skip to content

Commit 954cb2a

Browse files
martinpittclaude
andcommitted
Don't leak gpg-agent when signing with gpg
mkosi signs SHA256SUMS by running gpg, which autostarts a gpg-agent if none is running. As mkosi's sandbox has no PID namespace, that agent daemonizes and is leaked when the sandbox goes away. Unprivileged this is worse, as the leaked agents hold systemd-nsresourced dynamic UID ranges and eventually exhaust the pool (builds then fail with io.systemd.NamespaceResource.NoDynamicRange), but the process is leaked either way. Shut the agent down after signing, but only if we started it: an agent the user already had running for their GNUPGHOME (e.g. their own ~/.gnupg) is left untouched. gpg-connect-agent exits 0 even with no agent, so detect a running agent by its GETINFO output rather than the exit code. The gpg signing integration test also starts its own agent via gpg --quick-gen-key; kill that too (its GNUPGHOME is a throwaway directory). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent db3e38e commit 954cb2a

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

‎mkosi/__init__.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2565,12 +2565,35 @@ def calculate_signature_gpg(context: Context) -> None:
25652565
] # fmt: skip
25662566

25672567
with complete_step("Signing SHA256SUMS…"):
2568+
# gpg autostarts a gpg-agent to sign and, as the sandbox has no PID namespace, that agent is
2569+
# leaked when the sandbox goes away. If we started it (no agent was running yet), shut it down
2570+
# again afterwards; leave an agent the user already had running (e.g. for ~/.gnupg) alone.
2571+
# gpg-connect-agent exits 0 even with no agent, so detect one via its GETINFO output: a running
2572+
# agent prints "D <version>\nOK" on stdout, otherwise stdout stays empty.
2573+
agent_running = bool(
2574+
run(
2575+
["gpg-connect-agent", "--no-autostart", "GETINFO version", "/bye"],
2576+
env=env,
2577+
sandbox=context.sandbox(options=options),
2578+
stdout=subprocess.PIPE,
2579+
check=False,
2580+
).stdout.strip()
2581+
)
2582+
25682583
run(
25692584
cmdline,
25702585
env=env,
25712586
sandbox=context.sandbox(options=options),
25722587
)
25732588

2589+
if not agent_running:
2590+
run(
2591+
["gpgconf", "--kill", "gpg-agent"],
2592+
env=env,
2593+
sandbox=context.sandbox(options=options),
2594+
check=False,
2595+
)
2596+
25742597

25752598
def calculate_signature_sop(context: Context) -> None:
25762599
if context.config.key is None:

‎tests/integration_signing.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ async def test_signing_checksums_with_gpg(self) -> None:
6666
stdout=o,
6767
)
6868

69+
# gpg --quick-gen-key started a gpg-agent that we're done with now (the build manages its own).
70+
# Shut it down so it doesn't linger past the test. GNUPGHOME is a throwaway directory, so this
71+
# only affects this test's agent, never one running for the user's real GNUPGHOME.
72+
run(cmdline=["gpgconf", "--kill", "gpg-agent"], env=env, check=False)
73+
6974
image.build(options=["--checksum=true", "--sign=true", f"--key={signing_key}"], env=env)
7075

7176
signed_file = image.output_dir / "image.SHA256SUMS"

0 commit comments

Comments
 (0)