Skip to content

fix: address CodeQL findings (zip-slip + implicit narrowing)#7576

Merged
karianna merged 3 commits into
PCGen:masterfrom
Vest:fix/codeql-zipslip-narrowing
Jun 3, 2026
Merged

fix: address CodeQL findings (zip-slip + implicit narrowing)#7576
karianna merged 3 commits into
PCGen:masterfrom
Vest:fix/codeql-zipslip-narrowing

Conversation

@Vest
Copy link
Copy Markdown
Contributor

@Vest Vest commented Jun 2, 2026

Summary

Closes a couple of CodeQL findings I noticed and adds a regression test for the security-relevant one.

  • Zip Slip in DataInstaller (java/zipslip, severity error). Archive extraction resolved entry names with plain string concat (destDir.getAbsolutePath() + suffix) and never normalised the result, so an entry like data/../../etc/whatever would have written outside the install directory. correctFileName now canonicalises both the base dir and the resolved entry path and refuses any entry that escapes the base. The two callers that didn't already throw IOException (checkOverwriteOK, createDirectories) catch the new exception and abort the install with the existing error-dialog pattern.

  • Implicit narrowing in SkillModifier (java/implicit-cast-in-compound-assignment, 12 alerts). int bonus += someDouble silently inserted a narrowing cast at every accumulation. The function returns Integer and the formula path already uses .intValue(), so the truncating intent was correct — the warning was about it being invisible. Casts are now written explicitly. No behaviour change; the bytecode already truncated each step.

  • Regression test for the zip-slip fix. Reflective unit test so the same test class can run against both the pre-fix and post-fix signatures of correctFileName. Verified by hand: with the production fix reverted, the test reports 1 pass + 2 failures (both ..-escape cases slip through silently); restoring the fix flips it to 3 passes. correctFileName goes from private to package-private so the in-package test can call it directly; reflection was only needed for the cross-state demonstration.

Test plan

  • :test (16,995 unit tests, 0 failures)
  • Targeted slowtests for code paths that exercise SkillModifier: pcgen.core.prereq.PreVarTest, pcgen.core.display.SkillCostDisplayTest (11 tests, 0 failures)
  • New DataInstallerZipSlipTest passes against the fixed code; verified by hand that it fails against the pre-fix code (see above)

Notes

  • Squash-merge is fine — the three commits are organised for review legibility, not bisectability.
  • I have not manually exercised the "install a benign data set" path through the GUI dialog, so a reviewer with a sample data set may want to spot-check that the new error-dialog path doesn't fire on legitimate archives.
  • 22 more java/implicit-cast-in-compound-assignment alerts remain in other files (SkillSitToken, WeaponToken, TotalWeightFacet, …). Out of scope for this PR.

Vest added 3 commits June 2, 2026 18:36
CodeQL java/zipslip flagged populateFileAndDirLists at lines 750/753;
the actual sink is createFiles at line 619 (FileOutputStream) and
createDirectories at line 617 (mkdirs), reached via a string-concat
correctFileName that didn't normalise '..' segments.

correctFileName now resolves each entry against its base directory and
rejects any path whose canonical form leaves that base. Both
canonicalisations happen on the resolved File, so symlinks, '.' and
'..' segments are all collapsed before comparison. The two callers
that didn't already throw IOException (checkOverwriteOK and
createDirectories) catch and abort the install with the existing
error-dialog pattern.

A hostile data set containing 'data/../../etc/whatever' would now be
refused before any file is written.
CodeQL java/implicit-cast-in-compound-assignment flagged 12 sites in
this file where a 'double' bonus was accumulated into an 'int' via +=,
which silently inserts (int) at every addition. The intent was always
truncating accumulation -- the function returns Integer and uses
.intValue() for the formula path -- so the warning is purely about
making the cast visible.

Fix: write the cast at every site. No behaviour change; the bytecode
already contained the same i2d/d2i pair.
Reflective unit test so the same class compiles against both the
pre-fix (`private`, no checked exception) and post-fix
(package-private, throws IOException) signatures of correctFileName.
Verified by hand: temporarily reverting DataInstaller to master and
re-running this test yields 1 pass + 2 failures (both '..'-escape
cases are silently accepted under the old logic), and restoring the
fix flips it to 3 passes -- which makes the test a real regression
guard rather than a tautology.

correctFileName goes from `private` to package-private to give the
test in the same package direct access; reflection was needed only
for the cross-state run.
@karianna karianna merged commit c4cbc43 into PCGen:master Jun 3, 2026
3 checks passed
@Vest Vest deleted the fix/codeql-zipslip-narrowing branch June 3, 2026 04:49
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.

2 participants