Skip to content

Commit 9a0dac1

Browse files
committed
fix(azure): use safe git after privilege drop
1 parent 5655adb commit 9a0dac1

2 files changed

Lines changed: 35 additions & 37 deletions

File tree

bin/fm-worker-supervisor.py

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -313,28 +313,16 @@ def stage_payload(request, worktree, account_home):
313313
repo = worktree / "repo"
314314
if repo.is_symlink() or not repo.is_dir():
315315
raise SupervisorError("existing task-disk repository is unavailable or redirected")
316-
top = subprocess.run(
317-
["git", "-C", str(repo), "rev-parse", "--show-toplevel"],
318-
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
319-
timeout=GIT_HEAD_TIMEOUT, check=False,
320-
)
316+
top = git_in(repo, "rev-parse", "--show-toplevel", timeout=GIT_HEAD_TIMEOUT)
321317
if top.returncode != 0 or Path(top.stdout.decode().strip()).resolve() != repo.resolve():
322318
raise SupervisorError("existing task-disk repository is not the exact repository root")
323-
lineage = subprocess.run(
324-
[
325-
"git", "-C", str(repo), "merge-base", "--is-ancestor",
326-
request["repository_generation"], "HEAD",
327-
],
328-
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
329-
timeout=GIT_HEAD_TIMEOUT, check=False,
319+
lineage = git_in(
320+
repo, "merge-base", "--is-ancestor",
321+
request["repository_generation"], "HEAD", timeout=GIT_HEAD_TIMEOUT,
330322
)
331323
if lineage.returncode != 0:
332324
raise SupervisorError("existing task-disk repository lost its dispatched lineage")
333-
readable = subprocess.run(
334-
["git", "-C", str(repo), "status", "--porcelain"],
335-
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
336-
timeout=GIT_STATUS_TIMEOUT, check=False,
337-
)
325+
readable = git_in(repo, "status", "--porcelain", timeout=GIT_STATUS_TIMEOUT)
338326
if readable.returncode != 0:
339327
raise SupervisorError("existing task-disk working tree is unreadable")
340328
return repo
@@ -368,11 +356,7 @@ def stage_payload(request, worktree, account_home):
368356
clone.stderr.decode("utf-8", errors="replace")[-500:]
369357
)
370358
)
371-
head = subprocess.run(
372-
["git", "-C", str(repo), "rev-parse", "HEAD"],
373-
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
374-
timeout=GIT_HEAD_TIMEOUT, check=False,
375-
)
359+
head = git_in(repo, "rev-parse", "HEAD", timeout=GIT_HEAD_TIMEOUT)
376360
if head.returncode != 0 or head.stdout.decode().strip() != request["repository_generation"]:
377361
raise SupervisorError("staged repository head differs from the bound repository generation")
378362
if request.get("worker_role") == "no-mistakes":
@@ -541,11 +525,12 @@ def prepare_no_mistakes_execution(worktree, worktree_root, account_home, brief):
541525
return {}
542526

543527

544-
def git_in(repo, *arguments, timeout=BUNDLE_CREATE_TIMEOUT):
528+
def git_in(repo, *arguments, timeout=BUNDLE_CREATE_TIMEOUT, input_bytes=None, env=None):
545529
return subprocess.run(
546530
["git", "-c", "safe.directory={}".format(repo), "-C", str(repo), *arguments],
547-
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
548-
timeout=timeout, check=False,
531+
input=input_bytes, stdin=subprocess.DEVNULL if input_bytes is None else None,
532+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout, check=False,
533+
env=env,
549534
)
550535

551536

@@ -646,9 +631,9 @@ def _scratch_artifacts(repo):
646631

647632

648633
def _hash_blob(repo, body):
649-
result = subprocess.run(
650-
["git", "-C", str(repo), "hash-object", "-w", "--stdin"], input=body,
651-
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=GIT_HEAD_TIMEOUT, check=False,
634+
result = git_in(
635+
repo, "hash-object", "-w", "--stdin", input_bytes=body,
636+
timeout=GIT_HEAD_TIMEOUT,
652637
)
653638
if result.returncode != 0:
654639
raise SupervisorError("returned artifact could not be stored in the repository")
@@ -659,9 +644,8 @@ def _return_commit(repo, base, artifacts, request):
659644
entries = []
660645
for name, body in sorted(artifacts.items()):
661646
entries.append("100644 blob {}\t{}\n".format(_hash_blob(repo, body), name))
662-
tree = subprocess.run(
663-
["git", "-C", str(repo), "mktree"], input="".join(entries).encode(),
664-
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=GIT_HEAD_TIMEOUT, check=False,
647+
tree = git_in(
648+
repo, "mktree", input_bytes="".join(entries).encode(), timeout=GIT_HEAD_TIMEOUT,
665649
)
666650
if tree.returncode != 0:
667651
raise SupervisorError("returned artifact tree could not be created")
@@ -674,11 +658,10 @@ def _return_commit(repo, base, artifacts, request):
674658
"GIT_AUTHOR_DATE": "@0 +0000",
675659
"GIT_COMMITTER_DATE": "@0 +0000",
676660
})
677-
committed = subprocess.run(
678-
["git", "-C", str(repo), "commit-tree", tree.stdout.decode().strip(), "-p", base],
679-
input=("Firstmate worker return {}\n".format(request["request_digest"])).encode(),
680-
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=GIT_HEAD_TIMEOUT,
681-
check=False, env=environment,
661+
committed = git_in(
662+
repo, "commit-tree", tree.stdout.decode().strip(), "-p", base,
663+
input_bytes=("Firstmate worker return {}\n".format(request["request_digest"])).encode(),
664+
timeout=GIT_HEAD_TIMEOUT, env=environment,
682665
)
683666
if committed.returncode != 0:
684667
raise SupervisorError("returned artifact commit could not be created")

tests/fm-worker-supervisor.test.sh

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,21 @@ run_supervisor_existing_task_disk_recovery() {
725725
fm_git_init_commit "$repo"
726726
base=$(git -C "$repo" rev-parse HEAD)
727727
printf 'uncommitted scout evidence\n' > "$repo/scratch.txt"
728+
python3 - "$repo" <<'PY' \
729+
|| fail "could not prepare the cross-owner retained repository fixture"
730+
import os
731+
from pathlib import Path
732+
import pwd
733+
import sys
734+
735+
root = Path(sys.argv[1])
736+
if os.geteuid() == 0:
737+
identity = pwd.getpwnam("nobody")
738+
for directory, directories, files in os.walk(root):
739+
os.chown(directory, identity.pw_uid, identity.pw_gid)
740+
for name in directories + files:
741+
os.chown(Path(directory) / name, identity.pw_uid, identity.pw_gid)
742+
PY
728743
cat > "$work/.fm-return/data/recover-existing/report.md" <<'REPORT'
729744
## Summary
730745
@@ -841,7 +856,7 @@ PY
841856
expect_code 2 "$status" "existing task-disk recovery with foreign lineage should refuse: $out"
842857
assert_contains "$out" "lost its dispatched lineage" "existing task-disk lineage refusal was not explicit"
843858
assert_present "$repo/scratch.txt" "a refused existing task-disk recovery removed scout work"
844-
pass "existing task-disk recovery returns reports and scratch without restaging or lineage drift"
859+
pass "existing task-disk recovery and return Git work across ownership without lineage drift"
845860
}
846861

847862
run_no_mistakes_privilege_contract() {

0 commit comments

Comments
 (0)