From 6b037b23f4b6903b1e364942bb20f581304daa1e Mon Sep 17 00:00:00 2001 From: ai-anant Date: Fri, 4 Sep 2026 21:53:43 +0530 Subject: [PATCH 1/2] feat(java): detect direct positive-index access into split() result without length check --- java/indexing/unchecked-split-index.yaml | 43 +++++++++++++++++++ .../indexing/unchecked-split-index-neg.java | 14 ++++++ .../indexing/unchecked-split-index-pos.java | 14 ++++++ 3 files changed, 71 insertions(+) create mode 100644 java/indexing/unchecked-split-index.yaml create mode 100644 testcases/java/indexing/unchecked-split-index-neg.java create mode 100644 testcases/java/indexing/unchecked-split-index-pos.java diff --git a/java/indexing/unchecked-split-index.yaml b/java/indexing/unchecked-split-index.yaml new file mode 100644 index 0000000..4be4de4 --- /dev/null +++ b/java/indexing/unchecked-split-index.yaml @@ -0,0 +1,43 @@ +rules: + - id: codevigilant.java.indexing.unchecked-split-index + mode: search + severity: MEDIUM + message: >- + A String.split(...) result is indexed at a positive literal offset + ($N) in the same expression, without first verifying the split-array + length. If the input does not contain enough separator-delimited + segments (e.g. a single-line payload split by a line-break regex, or a + value with fewer comma-separated fields than expected), the access + throws an uncaught ArrayIndexOutOfBoundsException. When the split input + is even partially attacker-influenced (file contents, log/console lines, + request data, structured report payloads), this is an uncaught-exception + denial-of-service (CWE-248 / CWE-754) that aborts the enclosing build, + request, or worker thread. Assign the split result to a variable, check + its length before indexing (and validate each field's format before + parsing it), so malformed input degrades gracefully instead of throwing. + languages: + - java + patterns: + - pattern-either: + - pattern: $S.split($SEP)[$N] + - pattern: $S.split($SEP, $LIMIT)[$N] + - pattern: $S.split($SEP)[$N].$METHOD($ARGS) + - metavariable-regex: + metavariable: $N + regex: '[1-9][0-9]*' + metadata: + cwe: + - "CWE-248: Uncaught Exception" + - "CWE-754: Improper Check for Unusual or Exceptional Conditions" + owasp: + - "A05:2021 - Security Misconfiguration" + technology: + - java + confidence: MEDIUM + category: security + references: + - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ArrayIndexOutOfBoundsException.html + - https://owasp.org/www-community/vulnerabilities/Denial_of_Service + source: + - semgrep-rule-gap + license: MIT \ No newline at end of file diff --git a/testcases/java/indexing/unchecked-split-index-neg.java b/testcases/java/indexing/unchecked-split-index-neg.java new file mode 100644 index 0000000..803a647 --- /dev/null +++ b/testcases/java/indexing/unchecked-split-index-neg.java @@ -0,0 +1,14 @@ +public class UncheckedSplitIndexNeg { + // guarded: length checked before indexing, index 0 only + public String guarded(String csvResults) { + String[] lines = csvResults.split("[\\r\\n]+"); + if (lines.length < 2) { + return ""; + } + String[] fields = lines[1].split(","); + if (fields.length >= 4) { + return fields[0].trim(); + } + return ""; + } +} \ No newline at end of file diff --git a/testcases/java/indexing/unchecked-split-index-pos.java b/testcases/java/indexing/unchecked-split-index-pos.java new file mode 100644 index 0000000..f645a99 --- /dev/null +++ b/testcases/java/indexing/unchecked-split-index-pos.java @@ -0,0 +1,14 @@ +public class UncheckedSplitIndexPos { + // a single-line file: no second line -> split("[\\r\\n]+")[1] throws AIOOBE + public String parseResults(String csvResults) { + return csvResults.split("[\\r\\n]+")[1]; + } + + public int field(String row) { + return Integer.parseInt(row.split(",")[2].trim()); + } + + public String fieldChained(String row) { + return row.split(",")[3].toUpperCase(); + } +} \ No newline at end of file From 92ca67e74d21edd26e306ca6076d690bd9a0b4c1 Mon Sep 17 00:00:00 2001 From: ai-anant Date: Sat, 19 Sep 2026 09:15:05 +0530 Subject: [PATCH 2/2] feat(java): also match split() result stored in a variable then indexed --- java/indexing/unchecked-split-index.yaml | 58 +++++++++++++++---- .../indexing/unchecked-split-index-pos.java | 6 ++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/java/indexing/unchecked-split-index.yaml b/java/indexing/unchecked-split-index.yaml index 4be4de4..e2262d1 100644 --- a/java/indexing/unchecked-split-index.yaml +++ b/java/indexing/unchecked-split-index.yaml @@ -4,17 +4,20 @@ rules: severity: MEDIUM message: >- A String.split(...) result is indexed at a positive literal offset - ($N) in the same expression, without first verifying the split-array - length. If the input does not contain enough separator-delimited - segments (e.g. a single-line payload split by a line-break regex, or a - value with fewer comma-separated fields than expected), the access - throws an uncaught ArrayIndexOutOfBoundsException. When the split input - is even partially attacker-influenced (file contents, log/console lines, - request data, structured report payloads), this is an uncaught-exception - denial-of-service (CWE-248 / CWE-754) that aborts the enclosing build, - request, or worker thread. Assign the split result to a variable, check - its length before indexing (and validate each field's format before - parsing it), so malformed input degrades gracefully instead of throwing. + ($N) without first verifying the split-array length - either directly on + the split call, or through a local variable that holds the split result. + If the input does not contain enough separator-delimited segments (e.g. a + single-line payload split by a line-break regex, a structured record with + fewer fields than the code assumes, or a value with fewer + comma-separated fields than expected), the access throws an uncaught + ArrayIndexOutOfBoundsException. When the split input is even partially + attacker-influenced (record/field values parsed out of a structured + message, file contents, log/console lines, request data), this is an + uncaught-exception denial-of-service (CWE-248 / CWE-754) that aborts the + enclosing build, request, or - when the code runs in a polling, listener + or callback thread - the whole worker loop. Check the array length before + indexing (and validate each field's format before parsing it), so + malformed input degrades gracefully instead of throwing. languages: - java patterns: @@ -22,6 +25,37 @@ rules: - pattern: $S.split($SEP)[$N] - pattern: $S.split($SEP, $LIMIT)[$N] - pattern: $S.split($SEP)[$N].$METHOD($ARGS) + - pattern: $S.split($SEP, $LIMIT)[$N].$METHOD($ARGS) + - patterns: + - pattern-inside: | + $ARR = $S.split($SEP, $LIMIT); + ... + - pattern: $ARR[$N] + - pattern-not-inside: | + if ($ARR.length < $M) { + ... + } + ... + - pattern-not-inside: | + if ($ARR.length <= $M) { + ... + } + ... + - patterns: + - pattern-inside: | + $ARR = $S.split($SEP); + ... + - pattern: $ARR[$N] + - pattern-not-inside: | + if ($ARR.length < $M) { + ... + } + ... + - pattern-not-inside: | + if ($ARR.length <= $M) { + ... + } + ... - metavariable-regex: metavariable: $N regex: '[1-9][0-9]*' @@ -40,4 +74,4 @@ rules: - https://owasp.org/www-community/vulnerabilities/Denial_of_Service source: - semgrep-rule-gap - license: MIT \ No newline at end of file + license: MIT diff --git a/testcases/java/indexing/unchecked-split-index-pos.java b/testcases/java/indexing/unchecked-split-index-pos.java index f645a99..ae7bf76 100644 --- a/testcases/java/indexing/unchecked-split-index-pos.java +++ b/testcases/java/indexing/unchecked-split-index-pos.java @@ -11,4 +11,10 @@ public int field(String row) { public String fieldChained(String row) { return row.split(",")[3].toUpperCase(); } + + // split result stored in a local array, then indexed without a length check + public String twoStep(String arn, String host, String path) { + String[] tokens = arn.split(":", 6); + return String.format(host, tokens[3]) + String.format(path, tokens[5]); + } } \ No newline at end of file