Skip to content

SOLR-18178: Fix configset archive path handling - #4968

Open
nick-boss-tech wants to merge 9 commits into
apache:mainfrom
nick-boss-tech:solr-18178-verify
Open

nick-boss-tech wants to merge 9 commits into
apache:mainfrom
nick-boss-tech:solr-18178-verify

Conversation

@nick-boss-tech

@nick-boss-tech nick-boss-tech commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖 (posted on behalf of Nick Shanin)

https://issues.apache.org/jira/browse/SOLR-18178

What happens today

Configset archive handling built ZIP entry names with OS-native path separators, so an archive produced on Windows carried backslash entry names that broke on extraction in UploadConfigSet. Normalizing those names creates a second problem: a Windows-style traversal name such as ..\evil.txt, inert as a literal filename on Linux, becomes live ../ syntax on every platform once backslashes become slashes. The ZooKeeper configset backend had no traversal guard of its own, so an escaping entry name went straight into the znode path and aborted the upload partway through the archive.

What this change does

Archive entry names are normalized to forward slashes everywhere: a new DownloadConfigSet.toZipEntryName builds ZIP-legal names (/ separators, no leading slash, directories get a trailing slash), and UploadConfigSet extraction plus FileSystemConfigSetService directory listing use the same normalization. Before any entry is dispatched to a backend, UploadConfigSet now skips unsafe paths: after normalization, an entry with a leading /, a . or .. path segment, a drive-qualified Windows path (C:/..., either slash spelling), or an empty name is logged and skipped, and the upload of the remaining entries proceeds. The filesystem backend's child-of-root check stays in place as a second layer.

Proof

Verified at head 61b8f76 on 2026-10-04 (tidy clean, Error Prone compile clean, :solr:core:check -x test green).

  • The new entry-path tests fail on the base code and pass here (run locally): UploadConfigSetAPITest 15/15, including testSafeZipEntryPathRules, testZipUploadRejectsPathTraversalEntries, testZipUploadSkipsUnsafeEntryPathsBeforeBackendDispatch (a ZIP mixing unsafe entries with conf/good.txt dispatches only the good entry), and testZipUploadRejectsDriveQualifiedEntries; DownloadConfigSetAPITest 5/5 covers archives containing subdirectories.
  • Also at head (run on the GitHub Actions fork runner): TestFileSystemConfigSetService 3/3 and TestSchemaDesignerConfigSetHelper 8/8 pass, covering the export-side directory listing and the schema-designer ZIP entry assertions.
  • Windows run (2026-10-04, at c3b6ded): the three export test methods pass, 3 tests, 0 failed. Against the base code with the branch's test files overlaid, 2 of the 3 fail with backslash entry names: DownloadConfigSetAPITest.testZipConfigSetUsesForwardSlashEntryNames (lang\extra0) and TestSchemaDesignerConfigSetHelper.testDownloadAndZip (lang\contractions_ca.txt). The test files for those two classes are unchanged since that run.
  • Windows run for the strengthened directory test (2026-10-04, at 61b8f76): TestFileSystemConfigSetService passes 3/3 at head. Against the base code with the branch's test file overlaid, testGetAllConfigFilesUsesForwardSlashesForDirectories fails (3 tests, 1 failure): expected:<[lang/, lang/nested/, lang/nested/deep.txt, lang/stopwords_en.txt]> but was:<[lang/, lang/nested/deep.txt, lang/stopwords_en.txt, lang\nested/]>.

A choice to check

An archive containing an unsafe entry could instead fail as a whole. This change skips the unsafe entries, logs each one, and uploads the rest, so one bad entry cannot block a configset whose other entries are fine. Was skipping the right call, or should the upload fail closed?

Limits

Found while testing and not fixed here: uploadConfigSet(..., cleanup=true) can delete newly uploaded files. The deletion set is computed before the upload and directories in it are deleted recursively afterwards, so a ZIP that adds files under a pre-existing directory without an explicit directory entry loses those files to the cleanup. Pre-existing behavior; a separate ticket follows.

The export-side separator tests were first run on the Linux fork runner, where Path.toString already uses forward slashes, so they pass on base and head alike and cannot discriminate there. The Windows run for the ZIP export tests is now done (see Proof). The directory-listing test in TestFileSystemConfigSetService passed on base even on Windows as originally written, because it built only a one-level directory, whose name contains no separator. The test now also covers a nested directory path, and the Windows comparison for that nested case is done (2026-10-04, at 61b8f76): the strengthened test passes at head and fails against the base code with the branch's test file overlaid, where the listing returns lang\nested/ instead of lang/nested/.

The backslash replacement is unconditional on both sides: on Linux, a file legitimately named with a backslash (for example a\b.txt) is exported as a/b.txt, and an upload entry of that name lands in a subdirectory, rather than keeping the literal filename.

Changelog: changelog/unreleased/SOLR-18178.yml (fixed)

AI assistance

AI agents assisted with research, implementation, review, and drafting. Nick Shanin directed the work and takes responsibility for this contribution.

@github-actions github-actions Bot added the tests label Sep 30, 2026
UploadConfigSet now normalizes backslashes before handing entry names to
the config-set backends, which turns Windows-style traversal entries into
live '../' syntax on every platform. Pin that both '../evil.txt' and
'..\evil2.txt' are contained: neither lands in the configset nor escapes
next to it on disk, while legitimate entries still upload.
…spatch

UploadConfigSet normalizes backslashes in zip entry names and hands the
result to whichever ConfigSetService backend is configured. The
filesystem backend contains traversal entries on its own, but the
ZooKeeper backend builds a znode path from the entry name, so a single
'../' entry aborts the whole upload in SolrCloud instead of being
contained. Skip entries whose normalized path is absolute or contains
'.' or '..' segments at dispatch, before either backend sees them.

Adds a backend-agnostic test that pins exactly which entry names reach
a ConfigSetService: only the legitimate entry is dispatched. The test
fails against the unpatched dispatch.
isSafeZipEntryPath accepted a normalized entry such as
C:/outside/evil.txt: it does not start with a slash and has no dot
segments, yet it is absolute on Windows and reached the config-set
backend dispatch, outside the guard this change added. Empty entry
names passed the same check. The guard now rejects both on every
platform (a colon cannot appear in a Windows file name, so a leading
drive designator is never a legitimate entry name). Tests pin the
guard directly and cover both spellings through the filesystem
backend and a mock backend dispatch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant