Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package issues.iChain;

/**
* Stub mirroring `java.net.http.HttpClient` — the sink consumes a
* built {@link HttpRequest}.
*/
public class HttpClient {
public static HttpClient newHttpClient() {
return new HttpClient();
}

public String send(HttpRequest req) {
return "response";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package issues.iChain;

/**
* Stub mirroring `java.net.http.HttpRequest` for the chained-builder
* pattern repro. The static `newBuilder()` returns a {@link Builder};
* the builder chain is `.uri(URI).GET().build()`.
*/
public class HttpRequest {
public static Builder newBuilder() {
return new Builder();
}

public static class Builder {
public Builder uri(URI uri) {
return this;
}

public Builder GET() {
return this;
}

public HttpRequest build() {
return new HttpRequest();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package issues.iChain;

/**
* Trivial tainted-source helper. The pattern-source in the rule is
* `String $UNTRUSTED = Source.taint();`, so any call to `taint()`
* produces an untrusted string.
*/
public class Source {
public static String taint() {
return "tainted";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package issues.iChain;

/**
* Stub mirroring `java.net.URI` for the chained-builder pattern repro.
* Real `java.net.URI` would also work but using a stub keeps the test
* self-contained.
*/
public class URI {
public final String value;

private URI(String value) {
this.value = value;
}

public static URI create(String s) {
return new URI(s);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package issues;

import base.RuleSample;
import base.RuleSet;
import issues.iChain.HttpClient;
import issues.iChain.HttpRequest;
import issues.iChain.Source;
import issues.iChain.URI;

@RuleSet("issues/issueChain.yaml")
public abstract class issueChain implements RuleSample {

// Carries real taint, but the rule's sub-pattern order (URL bound before the
// builder) doesn't line up with the code's call order (builder created first),
// so the sink is unreachable and the rule does not fire — a Negative case.
static class NegativeTaintOrderSensitive extends issueChain {
@Override
public void entrypoint() {
String t = Source.taint();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(t))
.GET()
.build();
HttpClient client = HttpClient.newHttpClient();
client.send(req);
}
}

static class NegativeTaint extends issueChain {
@Override
public void entrypoint() {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.GET()
.build();
HttpClient client = HttpClient.newHttpClient();
client.send(req);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package issues;

import base.RuleSample;
import base.RuleSet;
import issues.iChain.HttpClient;
import issues.iChain.HttpRequest;
import issues.iChain.Source;
import issues.iChain.URI;

@RuleSet("issues/issueChainSplitBuilder.yaml")
public abstract class issueChainSplitBuilder implements RuleSample {

static class PositiveTaint extends issueChainSplitBuilder {
@Override
public void entrypoint() {
String t = Source.taint();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(t))
.GET()
.build();
HttpClient client = HttpClient.newHttpClient();
client.send(req);
}
}

static class NegativeTaint extends issueChainSplitBuilder {
@Override
public void entrypoint() {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.GET()
.build();
HttpClient client = HttpClient.newHttpClient();
client.send(req);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
rules:
- id: issue-chain
message: "Reproducer (order-sensitive match): rule sub-pattern order (URL before builder) doesn't line up with the code's call order, so the sink is unreachable"
severity: WARNING
languages: [java]
mode: taint

pattern-sources:
- pattern: $UNTRUSTED = issues.iChain.Source.taint();

pattern-sinks:
- patterns:
- pattern-either:
- pattern-inside: |
$URL = issues.iChain.URI.create($UNTRUSTED);
...
- pattern: |
$BUILDER = issues.iChain.HttpRequest.newBuilder().uri($URL);
...;
$TYPE $REQ = $BUILDER.build();
...;
(issues.iChain.HttpClient $C).send($REQ, ...);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
rules:
- id: issue-chain-split-builder
message: "Working form: bind newBuilder() in its own pattern-inside, then match .uri()/.build() call-by-call"
severity: WARNING
languages: [java]
mode: taint

pattern-sources:
- pattern: $UNTRUSTED = issues.iChain.Source.taint();

pattern-sinks:
- patterns:
- pattern-either:
- pattern-inside: |
$URL = issues.iChain.URI.create($UNTRUSTED);
...
- pattern-inside: |
$NEW_BUILDER = issues.iChain.HttpRequest.newBuilder();
...
- pattern: |
$BUILDER = $NEW_BUILDER.uri($URL);
...;
$TYPE $REQ = $BUILDER.build();
...;
(issues.iChain.HttpClient $C).send($REQ, ...);
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import issues.issue94
import issues.issue95
import issues.issue96
import issues.issue97
import issues.issueChain
import issues.issueChainSplitBuilder
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.TestInstance
Expand Down Expand Up @@ -105,6 +107,12 @@ class IssuesTest : SampleBasedTest() {
@Test
fun `issue 97`() = runTest<issue97>()

@Test
fun `issue chain-pattern order-sensitive match`() = runTest<issueChain>(EXPECT_STATE_VAR)

@Test
fun `issue chain-pattern split builder`() = runTest<issueChainSplitBuilder>(EXPECT_STATE_VAR)

@AfterAll
fun close() {
closeRunner()
Expand Down
Loading