fix(firecracker): keep the snapshot partial private even if one was left behind
test / unit (pull_request) Successful in 1m9s
test / integration (pull_request) Successful in 21s
test / coverage (pull_request) Successful in 1m14s
lint / lint (push) Successful in 2m21s
test / unit (push) Successful in 1m18s
test / integration (push) Successful in 30s
test / coverage (push) Successful in 1m27s
Update Quality Badges / update-badges (push) Successful in 1m17s
test / unit (pull_request) Successful in 1m9s
test / integration (pull_request) Successful in 21s
test / coverage (pull_request) Successful in 1m14s
lint / lint (push) Successful in 2m21s
test / unit (push) Successful in 1m18s
test / integration (push) Successful in 30s
test / coverage (push) Successful in 1m27s
Update Quality Badges / update-badges (push) Successful in 1m17s
Follow-up to the codex review on #398. os.open's mode arg only applies on creation, so a committed-rootfs.tar.partial left 0644 by an interrupted run would be opened/truncated (not re-moded) and stay world-readable for the whole SSH stream. Unlink any leftover and exclusively recreate it (O_EXCL|O_NOFOLLOW), then fchmod 0600 immediately so umask can't loosen it. Test pre-creates a 0644 partial and asserts the fd is 0600 mid-stream. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UoEZHDjv84ChoZbozQERhJ
This commit was merged in pull request #398.
This commit is contained in:
@@ -64,9 +64,17 @@ def _commit_rootfs_via_ssh(private_key: Path, guest_ip: str, tar_path: Path) ->
|
|||||||
partial = tar_path.with_name(tar_path.name + ".partial")
|
partial = tar_path.with_name(tar_path.name + ".partial")
|
||||||
ssh = util.ssh_base_argv(private_key, guest_ip)
|
ssh = util.ssh_base_argv(private_key, guest_ip)
|
||||||
# The snapshot can contain the bottle's private workspace, so keep it
|
# The snapshot can contain the bottle's private workspace, so keep it
|
||||||
# owner-only (0600) — create it that way and re-assert after the rename
|
# owner-only (0600) for the whole stream. The `os.open` mode only applies
|
||||||
# (os.replace carries the source mode, but be explicit).
|
# on *creation*, so unlink any leftover partial (a prior interrupted run
|
||||||
fd = os.open(partial, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
# could have left it world-readable, or something could swap in a symlink
|
||||||
|
# at this predictable name) and exclusively recreate it — O_EXCL|O_NOFOLLOW
|
||||||
|
# — then fchmod immediately so umask can't loosen it. Re-assert after the
|
||||||
|
# rename too (os.replace carries the source mode, but be explicit).
|
||||||
|
partial.unlink(missing_ok=True)
|
||||||
|
fd = os.open(
|
||||||
|
partial, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600
|
||||||
|
)
|
||||||
|
os.fchmod(fd, 0o600)
|
||||||
with os.fdopen(fd, "wb") as tar_out:
|
with os.fdopen(fd, "wb") as tar_out:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[*ssh, "--", "tar", "--create", "--one-file-system",
|
[*ssh, "--", "tar", "--create", "--one-file-system",
|
||||||
|
|||||||
@@ -253,22 +253,47 @@ class TestCommitRootfsPermissions(unittest.TestCase):
|
|||||||
"""The snapshot tar can hold the bottle's private workspace, so the
|
"""The snapshot tar can hold the bottle's private workspace, so the
|
||||||
freezer must write it owner-only (0600)."""
|
freezer must write it owner-only (0600)."""
|
||||||
|
|
||||||
|
def _commit(self, tar_path: Path) -> int:
|
||||||
|
"""Run _commit_rootfs_via_ssh with a stubbed ssh|tar pipe; return the
|
||||||
|
mode of the open partial observed mid-stream (from subprocess.run)."""
|
||||||
|
key = tar_path.parent.parent / "key"
|
||||||
|
key.write_text("K")
|
||||||
|
seen: dict[str, int] = {}
|
||||||
|
|
||||||
|
def fake_run(argv: list[str], *a: Any, **k: Any) -> Any:
|
||||||
|
out = k["stdout"]
|
||||||
|
seen["mode"] = stat.S_IMODE(os.fstat(out.fileno()).st_mode)
|
||||||
|
out.write(b"TARDATA")
|
||||||
|
return subprocess.CompletedProcess(argv, 0, b"", b"")
|
||||||
|
|
||||||
|
with patch.object(freezer.util, "ssh_base_argv", return_value=["ssh"]), \
|
||||||
|
patch.object(freezer.subprocess, "run", side_effect=fake_run):
|
||||||
|
freezer._commit_rootfs_via_ssh(key, "10.0.0.1", tar_path)
|
||||||
|
return seen["mode"]
|
||||||
|
|
||||||
def test_snapshot_created_owner_only(self):
|
def test_snapshot_created_owner_only(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="fc-freeze.") as d:
|
with tempfile.TemporaryDirectory(prefix="fc-freeze.") as d:
|
||||||
tmp = Path(d)
|
tar_path = Path(d) / "state" / "committed-rootfs.tar"
|
||||||
key = tmp / "key"
|
tar_path.parent.mkdir()
|
||||||
key.write_text("K")
|
stream_mode = self._commit(tar_path)
|
||||||
tar_path = tmp / "state" / "committed-rootfs.tar"
|
|
||||||
|
|
||||||
def fake_run(argv: list[str], *a: Any, **k: Any) -> Any:
|
self.assertEqual(0o600, stream_mode) # private during the stream
|
||||||
# Emulate the ssh|tar pipe streaming the snapshot to stdout.
|
self.assertEqual(b"TARDATA", tar_path.read_bytes())
|
||||||
k["stdout"].write(b"TARDATA")
|
self.assertEqual(0o600, stat.S_IMODE(tar_path.stat().st_mode))
|
||||||
return subprocess.CompletedProcess(argv, 0, b"", b"")
|
|
||||||
|
|
||||||
with patch.object(freezer.util, "ssh_base_argv", return_value=["ssh"]), \
|
def test_leftover_world_readable_partial_is_recreated_private(self):
|
||||||
patch.object(freezer.subprocess, "run", side_effect=fake_run):
|
"""A partial left 0644 by an interrupted prior run must not keep the
|
||||||
freezer._commit_rootfs_via_ssh(key, "10.0.0.1", tar_path)
|
new snapshot world-readable while it streams."""
|
||||||
|
with tempfile.TemporaryDirectory(prefix="fc-freeze.") as d:
|
||||||
|
tar_path = Path(d) / "state" / "committed-rootfs.tar"
|
||||||
|
tar_path.parent.mkdir()
|
||||||
|
partial = tar_path.with_name(tar_path.name + ".partial")
|
||||||
|
partial.write_bytes(b"stale")
|
||||||
|
os.chmod(partial, 0o644)
|
||||||
|
|
||||||
|
stream_mode = self._commit(tar_path)
|
||||||
|
|
||||||
|
self.assertEqual(0o600, stream_mode)
|
||||||
self.assertEqual(b"TARDATA", tar_path.read_bytes())
|
self.assertEqual(b"TARDATA", tar_path.read_bytes())
|
||||||
self.assertEqual(0o600, stat.S_IMODE(tar_path.stat().st_mode))
|
self.assertEqual(0o600, stat.S_IMODE(tar_path.stat().st_mode))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user