Fix CodeQL note-severity alerts: array logging and uncaught NumberFormatException - #817
Conversation
…matException * HostPort and BrowserController logged an InetAddress[]/String[] through string concatenation, which printed "[Ljava.net.InetAddress;@1a2b3c" instead of the addresses (java/print-array). * Two trace messages were missing a space between the concatenated literals (java/missing-space-in-concatenation). * Numbers coming from clients, replication peers, on-disk state and system properties are now parsed with a proper error instead of letting NumberFormatException escape (java/uncaught-number-format-exception): - MemoryBackend rejects a paged results cookie it did not create with a protocol error instead of failing the search with a runtime exception. - Both GSER parsers report an integer which matches the GSER integer pattern but does not fit in an int as a decoding error. - CSN validates the length and the hexadecimal fields of its string representation, and ChangelogBackend reports an invalid replicationCSN assertion value as an attribute syntax error. - ByteArrayScanner reports a malformed number in a replication message as a DataFormatException, like every other parsing error of that scanner. - SubtreeSpecification, SchemaUtils.parseRuleID and GeneralizedTimeSyntax report an out of range number as a syntax error. - ReplicationEnvironment reports a corrupted domains state file, BackupManager a base backup descriptor without a usable last log file size, and the JDBC backend falls back to the default connection TTL instead of failing its class initialization when the TTL system property is not a number. - backendstat reports an invalid entry ID instead of a stack trace. The remaining alerts of this rule in server code are left as they are: the time-of-day values of FixedTimeRotationPolicy are constrained to HHmm by the configuration definition, CtsAccessTokenResolver already converts runtime exceptions into an access token exception, and ProductInformation reads build-generated properties. The alerts in the command line and GUI tools are left for a separate change.
maximthomas
left a comment
There was a problem hiding this comment.
Solid batch — the "route the parse failure into the error channel the surrounding code already uses" pattern is applied faithfully, and the triage is accurate. Verified: both modules compile clean; every new checked exception reaches a caller that already declares it; no test depends on the old exception types; the alert inventory matches exactly (5 print-array + 2 missing-space + 17 uncaught-number-format = 24), and all three "left open" false-positive justifications hold (time-of-day really is regex-constrained to 4 digits, CtsAccessTokenResolver really does end in thenCatchRuntimeException, ProductInformation really does read build-generated properties).
Requesting changes on two one-liners.
ByteArrayScanner.nextCSNUTF8() no longer converts truncated CSNs (blocker)
opendj-server-legacy/src/main/java/org/opends/server/replication/protocol/ByteArrayScanner.java:264-272 converts only IndexOutOfBoundsException. Before this PR, a truncated CSN hit str.substring(0, 16) → StringIndexOutOfBoundsException, which that catch converted. Now CSN throws LocalizedIllegalArgumentException (extends IllegalArgumentException), so it escapes unchecked:
pre-PR CSN this PR
truncated "00000" → DataFormatException: begin 0, end 16, length 5 → LocalizedIllegalArgumentException: Invalid CSN: "00000"
28-char non-hex → NumberFormatException (already escaped) → LocalizedIllegalArgumentException (still escapes)
Reachable from AckMsg:164, UpdateMsg.decodeHeader:245, LDAPUpdateMsg:353/361, ByteArrayScanner:362 — peer-supplied bytes on legacy protocol versions. (ChangeTimeHeartbeatMsg is fine, it wraps in catch (RuntimeException).)
Runtime impact is low — Session.receive() catches RuntimeException and ServerReader/ReplicationBroker catch Exception, so the session is torn down either way — but it inverts this PR's own goal in the file it's otherwise hardening. The next method down (nextDN(), line 290) already has the right pattern:
catch (LocalizedIllegalArgumentException | IndexOutOfBoundsException e)
{
throw new DataFormatException(e.getMessage());
}CachedConnection still dies on a negative TTL (blocker)
opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java guards non-numeric values but not negatives. Against the bundled Caffeine:
ttl="-1" -> IllegalArgumentException: duration cannot be negative: -1000000 NANOSECONDS
So -Dorg.openidentityplatform.opendj.jdbc.ttl=-1 still produces ExceptionInInitializerError — the exact failure mode the change removes, via a different bad value:
final long millis = Long.parseLong(ttl.trim());
if (millis >= 0) {
return millis;
}
// fall through to the existing warning + DEFAULT_TTL_MSTwo claims in the description are inaccurate (docs)
GeneralizedTimeSyntaxis listed under real fixes, but the flaggedDouble.parseDoublecannot throw:finishDecodingFractionseeds the buffer with"0."and the switch appends only ASCII digits.Double.parseDouble("0." + 10000 nines)returns1.0. Alert 1203 is a false positive; moving the two statements into the existingcatch (Exception e)silences it without fixing anything. It belongs next toFixedTimeRotationPolicy, not next toSubtreeSpecification/SchemaUtils.parseRuleID— those two are genuinely reachable (INT_TOKENis\d+;parseOIDreturns an unvalidated token thatSchemaBackend:1340feeds from a client modification value).backendstatdoes not "report an invalid entry ID instead of a stack trace" —dumpBackendTree/dumpStorageTreecatchExceptionand printERR_BACKEND_TOOL_ERROR_READING_TREE.get(stackTraceToSingleLineString(e)). Only the message inside improves (Invalid entry ID: "abc"vsFor input string: "abc").
No tests (minor)
None of the new error branches are covered. The test classes already have the hooks:
CSNTest— the new length/hex contract. Highest value: this is exactly the class of breakage thenextCSNUTF8issue above is.opendj-core/src/test/java/org/forgerock/opendj/ldap/GSERParserTestCase.java:109— already has an invalid-integer block, add"99999999999".opendj-core/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java:506— already builds a paged-results search, add a forged-cookiePROTOCOL_ERRORassertion.
Nits
- Cookie echoed raw:
MemoryBackend.decodePagedResultsCookieputs the opaque client cookie into the diagnostic message. The server precedent handles this defensively —EntryContainer.java:1135usespageRequest.getCookie().toHexString()withUNWILLING_TO_PERFORM. SuggestpagedResults.getCookie().toHexString(). The catch also drops the causee. - Half-hardened
ReplicationEnvironment: now thatCSNhas a documented contract, two on-disk-data sites are one line each from complete —ReplicationEnvironment.readOfflineStateFile:699(catches onlyIOExceptionthough it declaresthrows ChangelogException) andFileReplicaDB.decodeKeyFromString:418. - Silent fallback:
BackupManager.parseSuffixNumbermaps an unparseable suffix to0. The reasoning is sound (regex is\d*, so a failure means overflow), but a silent0in a method that picks the next file number deserves alogger.trace. SubtreeSpecification.nextInt:new InputMismatchException(e.getMessage())drops the cause, and the message is discarded by theisValid = falsepath anyway. Every other throw site in that class uses the no-arg form.ChangelogBackend.decodeCSN: returns aCSNboth callers discard, and drops the cause. MirrorsdecodeChangeNumberso it's consistent — butvoid validateCSNwould read better.- Dead API surface:
GSERParser.nextInteger()has zero callers in either module (CertificateExactMatchingRuleImplusesnextBigInteger), and the whole server-sideorg.opends.server.protocols.asn1.GSERParserhas no consumers in main or test. Correct fixes, but API hardening rather than reachable defects — worth not reading the "external input" framing too broadly. CSN.valueOf(String)javadoc wasn't updated with the new@throws, though the constructor it delegates to was.- Lazy validation:
ReplicationEnvironmentvalidates domain IDs infindNextDomainIdrather thanreadDomainsStateFile, so a corrupt state file only surfaces when a new domain is created. The catch-all inreadDomainsStateFilealready uses the same message. Judgment call, not a defect. - Pre-existing, adjacent:
getHighestSuffixNumberForPathbuildsPattern.compile(baseFile + "\\d*")with an unescaped path — regex metacharacters in a backup path would misbehave. Not introduced here; flagging only because the PR touches the method.
…JDBC TTL - ByteArrayScanner.nextCSNUTF8() converts LocalizedIllegalArgumentException into the DataFormatException its callers expect (fixes the CI test failure) - CachedConnection falls back to the default TTL on a negative value, which used to reach Caffeine and raise ExceptionInInitializerError - ReplicationEnvironment.readOfflineStateFile and FileReplicaDB .decodeKeyFromString report a corrupted on-disk CSN as ChangelogException - MemoryBackend reports a forged paged results cookie in hexadecimal form and keeps the cause, following the EntryContainer precedent - ChangelogBackend validates the replicationCSN assertion with void validateCSN and keeps the cause - BackupManager traces an ignored backup file suffix and quotes the base path before building the suffix regex - CSN.valueOf(String) documents the thrown exception; SubtreeSpecification uses the no-arg InputMismatchException like the rest of the class - New tests: invalid CSN strings, a non-hexadecimal CSN in a replication message, GSER integer overflow, a forged paged results cookie
|
Thanks for the thorough review — every point checked out. Addressed in d05cb26: Blockers
Description — reworded as suggested: Nits applied
Tests — all three suggested hooks plus one more: six invalid-string cases in Full runs locally: |
Second batch of note-severity code scanning fixes (after #814), covering 24 alerts of three rules.
Arrays logged as
[Ljava.net.InetAddress;@1a2b3c(java/print-array, 5 alerts)HostPort.isEquivalentTo()concatenated theInetAddress[]arrays into its trace message, andBrowserControllerconcatenated the referralString[]into the displayed node description, so both printed the array identity instead of its content. Both now useArrays.toString().Missing space between concatenated literals (
java/missing-space-in-concatenation, 2 alerts)Two trace messages read "…has been definedin the associated password policy" and "…because readingits attributes failed".
Uncaught
NumberFormatExceptionon external input (java/uncaught-number-format-exception, 17 alerts)Numbers coming from clients, replication peers, on-disk state and system properties are now parsed with the error channel the surrounding code already uses:
MemoryBackend— the simple paged results cookie comes straight from the client, so a forged cookie failed the search with an unchecked exception. It is now rejected with a protocol error; the diagnostic message reports the cookie in hexadecimal form, following theEntryContainerprecedent.GSERParser(both the SDK and the server copy) — a value which matches the GSER integer pattern but does not fit in anintis now reported as a decoding error rather than escaping the parser.CSN— the string constructor now validates the length and the hexadecimal fields; a short value previously raisedStringIndexOutOfBoundsException.ByteArrayScanner.nextCSNUTF8()converts the newLocalizedIllegalArgumentExceptioninto theDataFormatExceptionits callers expect, so a malformed CSN in a legacy replication message is handled like every other parsing error of that scanner (previously a 28-character non-hexadecimal CSN escaped asNumberFormatException).ChangelogBackendturns an invalid(replicationCSN=…)assertion value intoinvalidAttributeSyntax, mirroring the existing handling ofchangeNumber. The changelog also validates CSNs it reads back from disk:ReplicationEnvironment.readOfflineStateFileandFileReplicaDB.decodeKeyFromStringreport a corrupted value as aChangelogException, the error both already declare. The ECL cookie path inMultiDomainServerStatewas already protected.SubtreeSpecification,SchemaUtils.parseRuleID— an out of range number in a client provided value is reported as a syntax error instead of an unchecked exception.ReplicationEnvironmentreports a corrupted domains state file as aChangelogException;BackupManagerreports a base backup descriptor whose last log file size is missing or malformed (the previous code also raised NPE on a missing property), and traces and ignores a file suffix which is too large to have been generated by it (the suffix scan also quotes the base path before building its regex, so metacharacters in a backup path no longer misbehave); the JDBC backend falls back to the default connection TTL with a warning instead of failing class initialization whenorg.openidentityplatform.opendj.jdbc.ttldoes not hold a non-negative number (a negative value used to reach Caffeine and raiseExceptionInInitializerError).backendstat(ID2Entry,ID2ChildrenCount) — the tool still prints the exception summary either way, but it now names the invalid entry ID (Invalid entry ID: "abc") instead of the rawFor input string: "abc".GeneralizedTimeSyntax— false positive: the fraction buffer is seeded with"0."and the parse loop appends only ASCII digits, so the flaggedDouble.parseDoublecannot throw. The two statements are moved inside the existingtryblock only to close the alert; no behavior changes.Alerts deliberately left open
FixedTimeRotationPolicy(2) — false positive: thetime-of-dayproperty is constrained by the configuration definition to^(([0-1][0-9])|([2][0-3]))([0-5][0-9])$, always four digits.CtsAccessTokenResolver(1) — false positive: the promise chain already hasthenCatchRuntimeExceptionwhich converts it into an access token exception.ProductInformation(4) — the values are read from a properties file generated by the build.Testing
New tests cover the new error branches:
CSNTest(invalid string representations),ByteArrayTest(truncated and non-hexadecimal CSN in a replication message),GSERParserTestCase(integer overflow values),MemoryBackendTestCase(forged paged results cookie →protocolError).opendj-core: full test suite — 8176 tests, all passing.opendj-server-legacy(-Pprecommit):CSNTest(1253),SchemaBackendTestCase(165),GeneralizedTimeSyntaxTest(72),HostPortTest(37),TestSubtreeSpecification(35),ChangelogBackendTestCase(30),MultiDomainServerStateTest(20),ByteArrayTest(16),TestBackupAndRestore(12),BackupManagerTestCase(11),CSNGeneratorTest(4),ServerStateTest(4) — 1659 tests, all passing.