Skip to content

Commit 4216d66

Browse files
authored
Add lossy-IO-write.ql
1 parent 5ef14bc commit 4216d66

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Finds calls to methods of `java.io.DataOutput`, `java.io.OutputStream` and `java.io.Writer`
3+
* which discard bits of the provided value to be written.
4+
*
5+
* For example `OutputStream#write(int)` has a parameter of type `int` but actually only
6+
* writes the lower 8 bits. That means when accidentally passing int values outside of
7+
* the byte value range, some of their bits will be silently ignored.
8+
*
9+
* @kind problem
10+
* @id TODO
11+
*/
12+
13+
import java
14+
import semmle.code.java.dataflow.DataFlow
15+
16+
abstract class LossyIntegralMethod extends Method {
17+
/** min value, inclusive */
18+
abstract int minValue();
19+
20+
/** max value, inclusive */
21+
abstract int maxValue();
22+
}
23+
24+
class TypeDataOutput extends Interface {
25+
TypeDataOutput() { hasQualifiedName("java.io", "DataOutput") }
26+
}
27+
28+
abstract class LossyIntegralDataOutputMethod extends LossyIntegralMethod {
29+
LossyIntegralDataOutputMethod() { getDeclaringType() instanceof TypeDataOutput }
30+
}
31+
32+
class DataOutputWriteByte extends LossyIntegralDataOutputMethod {
33+
DataOutputWriteByte() { hasStringSignature(["write(int)", "writeByte(int)"]) }
34+
35+
override int minValue() { result = -128 }
36+
37+
override int maxValue() { result = 127 }
38+
}
39+
40+
class DataOutputWriteChar extends LossyIntegralDataOutputMethod {
41+
DataOutputWriteChar() { hasName("writeChar") }
42+
43+
override int minValue() { result = 0 }
44+
45+
override int maxValue() { result = 65535 }
46+
}
47+
48+
class DataOutputWriteShort extends LossyIntegralDataOutputMethod {
49+
DataOutputWriteShort() { hasName("writeShort") }
50+
51+
override int minValue() { result = -32768 }
52+
53+
override int maxValue() { result = 32767 }
54+
}
55+
56+
class OutputStreamWriteByte extends LossyIntegralMethod {
57+
OutputStreamWriteByte() {
58+
getDeclaringType().hasQualifiedName("java.io", "OutputStream") and
59+
hasStringSignature("write(int)")
60+
}
61+
62+
override int minValue() { result = -128 }
63+
64+
override int maxValue() { result = 127 }
65+
}
66+
67+
class WriterWriteChar extends LossyIntegralMethod {
68+
WriterWriteChar() {
69+
getDeclaringType().hasQualifiedName("java.io", "Writer") and
70+
hasStringSignature("write(int)")
71+
}
72+
73+
override int minValue() { result = 0 }
74+
75+
override int maxValue() { result = 65535 }
76+
}
77+
78+
class DataOutputWriteBytes extends Method {
79+
DataOutputWriteBytes() {
80+
getDeclaringType() instanceof TypeDataOutput and
81+
hasStringSignature("writeBytes(String)")
82+
}
83+
}
84+
85+
from Expr value, MethodCall lossyCall, Method m
86+
where
87+
m = lossyCall.getMethod() and
88+
DataFlow::localExprFlow(value, lossyCall.getAnArgument()) and
89+
(
90+
exists(LossyIntegralMethod lossyMethod, int intValue |
91+
m.getASourceOverriddenMethod*() = lossyMethod and
92+
intValue = value.(CompileTimeConstantExpr).getIntValue()
93+
|
94+
(intValue < lossyMethod.minValue() or intValue > lossyMethod.maxValue()) and
95+
// Ignore if value is within unsigned value range
96+
not (
97+
intValue >= 0 and
98+
intValue <= (lossyMethod.maxValue() - lossyMethod.minValue())
99+
)
100+
)
101+
or
102+
exists(string stringValue |
103+
m.getASourceOverriddenMethod*() instanceof DataOutputWriteBytes and
104+
stringValue = value.(CompileTimeConstantExpr).getStringValue()
105+
|
106+
// `writeBytes` only uses lower byte of each char
107+
stringValue.codePointAt(_) > 255
108+
)
109+
)
110+
select lossyCall, "Lossy method call for $@", value, "this value"

0 commit comments

Comments
 (0)