8000 security: reject path traversal and non-regular entries when extracting remote backup archives by tpq2030 · Pull Request #1626 · Altinity/clickhouse-backup · GitHub
[go: up one dir, main page]

Skip to content

security: reject path traversal and non-regular entries when extracting remote backup archives - #1626

Merged
Slach merged 3 commits into
Altinity:masterfrom
tpq2030:fix/tar-extraction-path-traversal
Oct 20, 2026
Merged

Slach merged 3 commits into
Altinity:masterfrom
tpq2030:fix/tar-extraction-path-traversal

Conversation

@tpq2030
@tpq2030 tpq2030 commented Oct 19, 2026
Copy link
Copy Markdown
Contributor

Problem

DownloadCompressedStream extracts remote backup archives with:

extractFile := filepath.Join(localPath, header.Name)
...
dst, createErr := os.Create(extractFile)

The archive entry name is used without any validation, so an entry crafted with ../ segments (tar-slip / CWE-22) resolves outside localPath, and os.Create then writes attacker-chosen paths on the ClickHouse host. Non-regular entries (symlinks/hardlinks) are also honored, which opens the usual link-following variant of the same attack.

Threat model

This code path runs during restore / download (incl. the API server's /backup/download/{name} and /backup/restore/{name}), extracting archives that come from the configured remote storage. Anyone who can tamper with the backup bucket — compromised storage credentials, a malicious insider, or a MITM when the remote is plaintext FTP — can plant a crafted archive and get arbitrary file writes on the database server, which typically runs as root in the container image.

Archive integrity is exactly the trust boundary an extractor should defend, so this hardens the extractor instead of relying on the storage being benign.

Fix

  • Resolve entry names through a new safeExtractPath() helper that rejects any entry whose cleaned path escapes localPath.
  • Refuse non-regular entries (tar.TypeSymlink, tar.TypeLink, etc.) instead of extracting them.
  • Table-driven unit tests for both benign names (including store/some db/...-style names used for data parts) and traversal payloads.

Notes

  • Behavior for legitimate archives is unchanged: entries are relative file paths, which pass the check.
  • Compile/test verified logically; local Go toolchain was unavailable, so I'd appreciate CI confirming. Happy to adjust anything.

Older archives may contain tar directory entries; create the directory
(under the same containment check) and skip, instead of aborting.
@Slach Slach added this to the 2.8.2 milestone Oct 19, 2026
@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 39281711786

Coverage decreased (-2.6%) to 67.798%

Details

  • Coverage decreased (-2.6%) from the base build.
  • Patch coverage: 12 uncovered changes across 1 file (10 of 22 lines covered, 45.45%).
  • 776 coverage regressions across 21 files.

Uncovered Changes

File Changed Covered %
pkg/storage/general.go 22 10 45.45%

Coverage Regressions

776 previously-covered lines in 21 files lost coverage.

Top 10 Files by Coverage Loss Lines Losing Coverage Coverage
pkg/backup/restore_cloud.go 240 33.84%
pkg/storage/cos.go 161 3.56%
pkg/server/server.go 114 56.79%
pkg/storage/gcs.go 95 42.62%
pkg/storage/general.go 35 66.54%
pkg/storage/s3.go 31 61.27%
cmd/clickhouse-backup/main.go 22 77.73%
pkg/backup/backuper.go 19 75.0%
pkg/backup/restore_remote.go 12 56.73%
pkg/backup/download.go 6 77.98%

Coverage Stats

Coverage Status
Relevant Lines: 29399
Covered Lines: 19932
Line Coverage: 67.8%
Coverage Strength: 26635.71 hits per line

💛 - Coveralls

@Slach
Slach merged commit 78f849d into Altinity:master Oct 20, 2026
59 of 60 checks passed
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.

3 participants

0