Skip to content

Refuse archive entries that escape their target directory and drop dead ECIES code - #126

Open
vharseko wants to merge 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:codeql-high-zipslip-dead-crypto
Open

vharseko wants to merge 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:codeql-high-zipslip-dead-crypto

Conversation

@vharseko

Copy link
Copy Markdown
Member

Closes 11 of the 21 open high-severity CodeQL alerts (the ones that can be fixed without a compatibility impact): java/zipslip #7 #8 #9 #10, java/weak-cryptographic-algorithm #3 #4, java/comparison-with-wider-type #16 #17 #18 #19, java/unreachable-exit-in-loop #23.

Zip slip

new File(dir, entry.getName()) let an archive entry such as lib/../../x land outside of the directory it is extracted into:

  • LocalConnectorInfoManagerImpl.copyStreamToFile(stream, name) — the lib/* and native/* entries of a connector bundle are expanded into java.io.tmpdir/bundle-<random>/. A crafted bundle wrote outside of that directory (the new test reproduced it: the file showed up in java.io.tmpdir). Whoever can drop a bundle into the bundle directory can run code as the server anyway, so this is defence in depth rather than a privilege gain, but it costs one line.
  • IOUtil.unjar — public utility, no callers left in this repository.
  • ConnectorInfoReportMojo / DocBookResourceMojo — extract the plugin's own jar at build time; guarded for uniformity.

All four go through the new IOUtil.resolveEntry(dir, entryName), which throws IOException when the entry's canonical path leaves dir. The directory itself passes: the plugin strips the shared/ prefix, so its directory entry resolves to an empty name — the first cut rejected that and would have aborted the resource copy; covered by resolveEntryAcceptsTheDirectoryItself.

Dead ECIES code

ECIESEncryptor (AES/CBC/PKCS5 with the IV taken from the ECDH secret) was only referenced from OpenICFServerAdapter.initialiseEncryptor(), which nothing calls and which starts with HandshakeMessage message = null; message.getPublicKey(). Both are removed, together with the round-trip test in SecurityUtilTest. keyPair stays (it goes into the handshake message); SecurityUtil.doECDH stays as public API.

Small ones

  • AuthenticationApiOpTests: for (int i …; i < getLongTestParam(MAX_ITERATIONS, 1); …) compared an int with a long; the counter is long now.
  • ObjectPool.borrowObjectNoTest: do { … } while (nanos > 0) — the body throws on nanos <= 0 before the condition is ever evaluated, so the loop only left by returning or throwing; now while (true) with a comment saying so. Same behaviour, ObjectPoolTests green.

Tests

  • IOUtilsTests: resolveEntry inside / directory itself / escaping (IOException); unjar refuses ../escaped.txt and does not write it.
  • LocalConnectorInfoManagerTests.testRejectsBundleEntryEscapingTempDirectory: bundle with lib/ok.jar followed by lib/../../escaped-<uuid>.jar (the regular entry first, so that lib/ exists and the escape resolves) → ConfigurationException, nothing written to java.io.tmpdir.
  • The maven plugin sites and the long counters have no tests: the plugin has no test harness and extracts its own artifact, and the counter change needs MAX_ITERATIONS > Integer.MAX_VALUE.

Local runs: connector-framework 189, connector-framework-internal 470, connector-framework-server 28, all green; contract module and maven plugin compile.

Left open on purpose (separate decisions)

…ad ECIES code

Entries of connector bundles (lib/*, native/*), of IOUtil.unjar and of the
maven plugin's own resources were resolved with new File(dir, name), so an
entry such as lib/../../x was written outside of the target directory
(CodeQL java/zipslip). IOUtil.resolveEntry now rejects any entry whose
canonical path leaves the directory, and the four extraction sites use it.

ECIESEncryptor (AES/CBC with an IV taken from the ECDH secret) was only
reachable through OpenICFServerAdapter.initialiseEncryptor(), which nothing
calls and which dereferences a null HandshakeMessage; both are removed.

Also widens the contract tests' loop counters to long to match the long
MAX_ITERATIONS parameter, and makes the ObjectPool wait loop's exits
explicit - it only ever left by returning or throwing.
@vharseko vharseko added security Security fix / CVE remediation java Pull requests that update java code tests Test additions or fixes framework OpenICF-java-framework maven-plugin OpenICF-maven-plugin labels Sep 18, 2026
…izer

The check compared the canonical path with "equals the directory or starts
with its prefix"; CodeQL's path-injection sanitizer only credits a lone
startsWith on a normalised path that guards the use. A trailing separator
on both sides makes one startsWith cover the directory itself as well.
resolveEntry now returns the canonical file, so the bundle temp directory
is canonicalised too before its parent directories are walked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework OpenICF-java-framework java Pull requests that update java code maven-plugin OpenICF-maven-plugin security Security fix / CVE remediation tests Test additions or fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants