Skip to content

Support caller-owned extraction roots - #127

Draft
vvoland wants to merge 5 commits into
moby:mainfrom
vvoland:extraction-root2
Draft

vvoland wants to merge 5 commits into
moby:mainfrom
vvoland:extraction-root2

Conversation

@vvoland

@vvoland vvoland commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add TarOptions.DestinationRoot so callers can extract relative to an already-open *os.Root.

Existing callers are unaffected. When DestinationRoot is nil, extraction continues to open dest directly as it does today.

Motivation

For the general case, passing a root is equivalent to allowing Unpack to call os.OpenRoot(dest) itself. The distinction matters when a caller already has a vetted directory handle.

If the caller converts that handle back into a pathname, the pathname can be renamed or replaced before Unpack opens it. Extraction may then operate on a different directory from the one the caller selected. Passing the existing os.Root preserves that directory identity.

This keeps authorization with the application while allowing go-archive to perform extraction through the boundary the application established.

@codecov-commenter

codecov-commenter commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.05128% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.98%. Comparing base (953c7df) to head (ebb6313).
⚠️ Report is 18 commits behind head on main.

Files with missing lines Patch % Lines
xattr_root_unix.go 68.96% 9 Missing ⚠️
archive.go 95.34% 2 Missing ⚠️
chrootarchive/diff.go 0.00% 2 Missing ⚠️
rootpath.go 90.47% 2 Missing ⚠️
xattr_root_linux.go 77.77% 2 Missing ⚠️
xattr_unsupported.go 0.00% 2 Missing ⚠️
chrootarchive/archive.go 50.00% 1 Missing ⚠️
dev_darwin.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #127      +/-   ##
==========================================
+ Coverage   65.48%   74.98%   +9.50%     
==========================================
  Files          46       48       +2     
  Lines        2393     2439      +46     
==========================================
+ Hits         1567     1829     +262     
- Misses        605      607       +2     
+ Partials      221        3     -218     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Empty destinations can unexpectedly target the working directory, and Unix xattr handling has a special-file race.

2 open findings
What changed in this PR

Adds caller-owned os.Root support for extraction while preserving directory identity across renames.

Changes:

  • Adds TarOptions.DestinationRoot and root-relative extraction.
  • Adds platform-specific xattr handling and unsupported-API validation.
  • Adds contract, identity, and platform regression tests.
File Description
archive.go Implements caller-owned extraction roots.
rootpath.go Resolves paths through root handles.
diff.go Rejects roots for layer extraction.
chrootarchive/​archive.go Rejects roots for chroot extraction.
chrootarchive/​diff.go Rejects roots for chroot layers.
xattr_root_linux.go Adds root-safe Linux xattrs.
xattr_root_unix.go Adds descriptor-based Unix xattrs.
xattr_unsupported.go Adds unsupported-platform stub.
destination_root_test.go Tests API contracts and containment.
destination_root_darwin_test.go Tests macOS node restrictions.
archive_test.go Tests root identity preservation.
archive_unix_test.go Updates root-path tests.
xattr_root_linux_test.go Tests Linux xattr identity.
xattr_root_unix_test.go Tests Unix xattr behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread xattr_root_unix.go Resolved
Comment thread archive.go Resolved
@vvoland
vvoland force-pushed the extraction-root2 branch 3 times, most recently from 1d0015e to 0fd79b2 Compare October 13, 2026 14:29
@vvoland
vvoland requested a balanced review from Copilot October 13, 2026 14:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Linux xattrs fail beneath unreadable directories, and unsupported layer requests can still alter the process-wide umask.

2 open findings
2 resolved since last review

🧠 Review effort: Balanced

Comment thread diff.go Resolved
Comment thread xattr_root_linux.go Resolved
vvoland and others added 4 commits October 13, 2026 16:34
Extraction opens its destination as an os.Root, but resolved parents
of missing or escaping paths through root.Name().
If the destination was renamed and replaced during extraction, that
fallback resolved symlinks in the replacement directory.

resolveRootPath now walks symlinks through the os.Root handle and
returns root-relative paths.

The opaque whiteout walk now uses fs.WalkDir over root.FS(), so it no
longer needs a host path.

xattrs and darwin mknod have no portable handle-relative API, so they
still use a host path from fsRootPath.
fsRootPath now takes the caller's *os.Root, resolves through it, and
returns a breakout error if the host path's parent is not the same
file as the parent resolved through the handle.
The remaining window between that check and the host-path operation
is the same one that existed before.

Signed-off-by: Paweł Gronowski <git@grono.dev>
Add DestinationRoot to TarOptions so callers can extract relative to an
already-open os.Root. This preserves a caller-vetted directory identity when
its pathname is renamed or replaced before extraction begins.

Keep the existing behavior when DestinationRoot is unset. Treat dest as
root-relative when it is set, retain caller ownership of the supplied root,
and close only roots derived for nested destinations.

Add coverage that replaces the original destination path after its root is
opened and verifies that extraction still writes to the opened directory and
does not close the caller-owned root.

Signed-off-by: Remy Suen <remy.suen@docker.com>
Assited-by: ChatGPT <noreply@openai.com>
macOS created device nodes and FIFOs through a pathname derived from
root.Name().
When a caller supplied a renamed DestinationRoot, that path could
identify a replacement directory instead.

Reject these entries with ENOTSUP when DestinationRoot is set because
macOS has no mknodat operation to preserve directory identity.

Signed-off-by: Paweł Gronowski <git@grono.dev>
Extraction with a caller-owned root could apply metadata to a
replacement directory after the selected directory was renamed.

Applied extended attributes without using the root's original
pathname.
Kept dest relative to DestinationRoot, retained caller ownership, and
closed only roots opened for nested destinations.
An empty dest or "." extracts directly into the supplied root.

Outside Linux, extended attributes are set through a descriptor.
Reject them with ENOTSUP on symlinks, devices, and FIFOs, since
opening a device node can trigger driver side effects, and granted
owner read access only while opening entries the owner cannot read.
On Linux, they are set through /proc/self/fd, which must be mounted.

Signed-off-by: Paweł Gronowski <git@grono.dev>
@vvoland
vvoland force-pushed the extraction-root2 branch 2 times, most recently from 0fd79b2 to 990e5c1 Compare October 13, 2026 14:36
Layer extraction silently ignored DestinationRoot. Chroot extraction
could not preserve the supplied root consistently across platforms.
Callers could therefore mistake the option for an enforced directory
boundary.

Reject DestinationRoot in those extraction entry points instead of
ignoring it or passing it into chroot machinery.

Existing callers without DestinationRoot remain unaffected.

Signed-off-by: Paweł Gronowski <git@grono.dev>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes security-sensitive filesystem behavior across several operating systems and requires final cross-platform human validation.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants