Skip to content

Commit f0af5af

Browse files
authored
Merge pull request #19015 from owen-mc/java/toctou-sync-methods
Java: Fix FP in "Time-of-check time-of-use race condition" (`java/toctou-race-condition`)
2 parents a56493c + 7702e9d commit f0af5af

File tree

7 files changed

+92
-29
lines changed

7 files changed

+92
-29
lines changed

java/ql/src/Security/CWE/CWE-367/TOCTOURace.ql

+13-9
Original file line numberDiff line numberDiff line change
@@ -54,30 +54,34 @@ class PossiblyConcurrentCallable extends Callable {
5454
}
5555
}
5656

57+
private VarAccess getANonInitializationAccess(Field f) {
58+
result = f.getAnAccess() and
59+
exists(Callable c | c = result.getEnclosingCallable() |
60+
not (
61+
c = f.getDeclaringType().getACallable() and
62+
(c instanceof Constructor or c instanceof InitializerMethod)
63+
)
64+
)
65+
}
66+
5767
/**
5868
* Holds if all accesses to `v` (outside of initializers) are locked in the same way.
5969
*/
6070
predicate alwaysLocked(Field f) {
6171
exists(Variable lock |
62-
forex(VarAccess access |
63-
access = f.getAnAccess() and not access.getEnclosingCallable() instanceof InitializerMethod
64-
|
72+
forex(VarAccess access | access = getANonInitializationAccess(f) |
6573
locallySynchronizedOn(access, _, lock)
6674
)
6775
)
6876
or
6977
exists(RefType thisType |
70-
forex(VarAccess access |
71-
access = f.getAnAccess() and not access.getEnclosingCallable() instanceof InitializerMethod
72-
|
78+
forex(VarAccess access | access = getANonInitializationAccess(f) |
7379
locallySynchronizedOnThis(access, thisType)
7480
)
7581
)
7682
or
7783
exists(RefType classType |
78-
forex(VarAccess access |
79-
access = f.getAnAccess() and not access.getEnclosingCallable() instanceof InitializerMethod
80-
|
84+
forex(VarAccess access | access = getANonInitializationAccess(f) |
8185
locallySynchronizedOnClass(access, classType)
8286
)
8387
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Fixed a false positive in "Time-of-check time-of-use race condition" (`java/toctou-race-condition`) where a field of a non-static class was not considered always-locked if it was accessed in a constructor.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package test.cwe367.semmle.tests;
2+
3+
import java.util.Enumeration;
4+
import java.util.Hashtable;
5+
6+
class FieldAlwaysLocked {
7+
8+
Hashtable field;
9+
10+
public FieldAlwaysLocked() {
11+
field = new Hashtable();
12+
}
13+
14+
protected synchronized void checkOut() {
15+
Object o;
16+
if (field.size() > 0) {
17+
Enumeration e = field.keys();
18+
while (e.hasMoreElements()) {
19+
o = e.nextElement();
20+
field.remove(o);
21+
}
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package test.cwe367.semmle.tests;
2+
3+
import java.util.Enumeration;
4+
import java.util.Hashtable;
5+
6+
class FieldNotAlwaysLocked {
7+
8+
Hashtable field;
9+
10+
public FieldNotAlwaysLocked() {
11+
field = new Hashtable();
12+
}
13+
14+
protected synchronized void checkOut() {
15+
Object o;
16+
if (field.size() > 0) {
17+
Enumeration e = field.keys(); // $ Alert
18+
while (e.hasMoreElements()) {
19+
o = e.nextElement();
20+
field.remove(o); // $ Alert
21+
}
22+
}
23+
}
24+
25+
protected void modifyUnlocked() {
26+
field = new Hashtable();
27+
}
28+
}

java/ql/test/query-tests/security/CWE-367/semmle/tests/TOCTOURace.expected

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
| FieldNotAlwaysLocked.java:17:41:17:52 | keys(...) | This uses the state of $@ which $@. But these are not jointly synchronized. | FieldNotAlwaysLocked.java:8:19:8:23 | field | field | FieldNotAlwaysLocked.java:16:21:16:32 | size(...) | is checked at a previous call |
2+
| FieldNotAlwaysLocked.java:20:33:20:47 | remove(...) | This uses the state of $@ which $@. But these are not jointly synchronized. | FieldNotAlwaysLocked.java:8:19:8:23 | field | field | FieldNotAlwaysLocked.java:16:21:16:32 | size(...) | is checked at a previous call |
13
| Test.java:13:4:13:10 | act(...) | This uses the state of $@ which $@. But these are not jointly synchronized. | Test.java:10:32:10:41 | r | r | Test.java:12:7:12:18 | getState(...) | is checked at a previous call |
24
| Test.java:20:4:20:10 | act(...) | This uses the state of $@ which $@. But these are not jointly synchronized. | Test.java:17:32:17:42 | r | r | Test.java:19:7:19:18 | getState(...) | is checked at a previous call |
35
| Test.java:27:4:27:10 | act(...) | This uses the state of $@ which $@. But these are not jointly synchronized. | Test.java:24:19:24:28 | r | r | Test.java:26:7:26:18 | getState(...) | is checked at a previous call |
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
Security/CWE/CWE-367/TOCTOURace.ql
1+
query: Security/CWE/CWE-367/TOCTOURace.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql

java/ql/test/query-tests/security/CWE-367/semmle/tests/Test.java

+19-19
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,39 @@
44

55
class Test {
66
public final Object lock = new Object();
7-
7+
88
public volatile boolean aField = true;
9-
9+
1010
public synchronized void bad1(Resource r) {
1111
// probably used concurrently due to synchronization
1212
if (r.getState()) {
13-
r.act();
13+
r.act(); // $ Alert
1414
}
1515
}
1616

1717
public synchronized void bad2(Resource2 r) {
1818
// probably used concurrently due to synchronization
1919
if (r.getState()) {
20-
r.act();
20+
r.act(); // $ Alert
2121
}
2222
}
2323

2424
public void bad3(Resource r) {
2525
// probably used concurrently due to use of volatile field
2626
if (r.getState() && aField) {
27-
r.act();
27+
r.act(); // $ Alert
2828
}
2929
}
3030

3131
public void bad4(Resource r) {
3232
// probably used concurrently due to synchronization
3333
synchronized(this) {
3434
if (r.getState() && aField) {
35-
r.act();
35+
r.act(); // $ Alert
3636
}
3737
}
3838
}
39-
39+
4040
public void good1(Resource r) {
4141
// synchronizes on the same monitor as the called methods
4242
synchronized(r) {
@@ -45,15 +45,15 @@ public void good1(Resource r) {
4545
}
4646
}
4747
}
48-
48+
4949
public Resource rField = new Resource();
50-
50+
5151
public void someOtherMethod() {
5252
synchronized(lock) {
5353
rField.act();
5454
}
5555
}
56-
56+
5757
public void good2() {
5858
// r is always guarded with the same lock, so okay
5959
synchronized(lock) {
@@ -77,43 +77,43 @@ public void good3(Resource r) {
7777
r.act();
7878
}
7979
}
80-
80+
8181
class Resource {
8282
boolean state;
83-
83+
8484
public synchronized void setState(boolean newState) {
8585
this.state = newState;
8686
}
87-
87+
8888
public synchronized boolean getState() {
8989
return state;
9090
}
91-
91+
9292
public synchronized void act() {
9393
if (state)
9494
sideEffect();
9595
else
9696
sideEffect();
9797
}
98-
98+
9999
public void sideEffect() { }
100100
}
101101

102102
class Resource2 {
103103
boolean state;
104-
104+
105105
public void setState(boolean newState) {
106106
synchronized(this) {
107107
this.state = newState;
108108
}
109109
}
110-
110+
111111
public boolean getState() {
112112
synchronized(this) {
113113
return state;
114114
}
115115
}
116-
116+
117117
public void act() {
118118
synchronized(this) {
119119
if (state)
@@ -122,7 +122,7 @@ public void act() {
122122
sideEffect();
123123
}
124124
}
125-
125+
126126
public void sideEffect() { }
127127
}
128128
}

0 commit comments

Comments
 (0)