Skip to content

Commit 852eab9

Browse files
committed
C#: Improve format logic to take CompositeFormat and generics into account.
1 parent 286ae79 commit 852eab9

File tree

4 files changed

+98
-15
lines changed

4 files changed

+98
-15
lines changed

Diff for: csharp/ql/lib/semmle/code/csharp/frameworks/Format.qll

+19-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,19 @@ abstract class FormatMethod extends Method {
1616
abstract int getFormatArgument();
1717
}
1818

19+
/** A class of types used for formatting. */
20+
private class FormatType extends Type {
21+
FormatType() {
22+
this instanceof StringType or
23+
this instanceof SystemTextCompositeFormatClass
24+
}
25+
}
26+
1927
private class StringAndStringBuilderFormatMethods extends FormatMethod {
2028
StringAndStringBuilderFormatMethods() {
2129
(
2230
this.getParameter(0).getType() instanceof SystemIFormatProviderInterface and
23-
this.getParameter(1).getType() instanceof StringType
31+
this.getParameter(1).getType() instanceof FormatType
2432
or
2533
this.getParameter(0).getType() instanceof StringType
2634
) and
@@ -38,6 +46,16 @@ private class StringAndStringBuilderFormatMethods extends FormatMethod {
3846
}
3947
}
4048

49+
private class SystemMemoryExtensionsFormatMethods extends FormatMethod {
50+
SystemMemoryExtensionsFormatMethods() {
51+
this = any(SystemMemoryExtensionsClass c).getTryWriteMethod() and
52+
this.getParameter(1).getType() instanceof SystemIFormatProviderInterface and
53+
this.getParameter(2).getType() instanceof SystemTextCompositeFormatClass
54+
}
55+
56+
override int getFormatArgument() { result = 2 }
57+
}
58+
4159
private class SystemConsoleAndSystemIoTextWriterFormatMethods extends FormatMethod {
4260
SystemConsoleAndSystemIoTextWriterFormatMethods() {
4361
this.getParameter(0).getType() instanceof StringType and

Diff for: csharp/ql/lib/semmle/code/csharp/frameworks/System.qll

+13-1
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ class SystemStringClass extends StringType {
365365
/** Gets a `Format(...)` method. */
366366
Method getFormatMethod() {
367367
result.getDeclaringType() = this and
368-
result.hasName("Format") and
368+
result.getName().regexpMatch("Format(<.*>)?") and
369369
result.getNumberOfParameters() in [2 .. 5] and
370370
result.getReturnType() instanceof StringType
371371
}
@@ -751,6 +751,18 @@ class SystemNotImplementedExceptionClass extends SystemClass {
751751
SystemNotImplementedExceptionClass() { this.hasName("NotImplementedException") }
752752
}
753753

754+
/** The `System.MemoryExtensions` class. */
755+
class SystemMemoryExtensionsClass extends SystemClass {
756+
SystemMemoryExtensionsClass() { this.hasName("MemoryExtensions") }
757+
758+
/** Gets a `TryWrite` method. */
759+
Method getTryWriteMethod() {
760+
result.getDeclaringType() = this and
761+
result.getName().regexpMatch("TryWrite(<.*>)?") and
762+
result.getParameter(0).getType().getUnboundDeclaration() instanceof SystemSpanStruct
763+
}
764+
}
765+
754766
/** The `System.DateTime` struct. */
755767
class SystemDateTimeStruct extends SystemStruct {
756768
SystemDateTimeStruct() { this.hasName("DateTime") }

Diff for: csharp/ql/lib/semmle/code/csharp/frameworks/system/Text.qll

+14-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ class SystemTextStringBuilderClass extends SystemTextClass {
2222
SystemTextStringBuilderClass() { this.hasName("StringBuilder") }
2323

2424
/** Gets the `AppendFormat` method. */
25-
Method getAppendFormatMethod() { result = this.getAMethod("AppendFormat") }
25+
Method getAppendFormatMethod() {
26+
exists(string name |
27+
name.regexpMatch("AppendFormat(<.*>)?") and
28+
result = this.getAMethod(name)
29+
)
30+
}
2631
}
2732

2833
/** The `System.Text.Encoding` class. */
@@ -38,3 +43,11 @@ class SystemTextEncodingClass extends SystemTextClass {
3843
/** Gets the `GetChars` method. */
3944
Method getGetCharsMethod() { result = this.getAMethod("GetChars") }
4045
}
46+
47+
/** The `System.Text.CompositeFormat` class */
48+
class SystemTextCompositeFormatClass extends SystemTextClass {
49+
SystemTextCompositeFormatClass() { this.hasName("CompositeFormat") }
50+
51+
/** Gets the `Parse` method. */
52+
Method getParseMethod() { result = this.getAMethod("Parse") }
53+
}

Diff for: csharp/ql/src/API Abuse/FormatInvalid.ql

+52-12
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,59 @@
1111
*/
1212

1313
import csharp
14+
import semmle.code.csharp.frameworks.system.Text
1415
import semmle.code.csharp.frameworks.Format
15-
import FormatInvalid::PathGraph
16+
import FormatFlow::PathGraph
17+
18+
abstract private class FormatStringParseCall extends MethodCall {
19+
abstract Expr getFormatExpr();
20+
}
21+
22+
private class OrdinaryFormatCall extends FormatStringParseCall instanceof FormatCall {
23+
override Expr getFormatExpr() { result = FormatCall.super.getFormatExpr() }
24+
}
25+
26+
private class ParseFormatStringCall extends FormatStringParseCall {
27+
ParseFormatStringCall() {
28+
this.getTarget() = any(SystemTextCompositeFormatClass x).getParseMethod()
29+
}
30+
31+
override Expr getFormatExpr() { result = this.getArgument(0) }
32+
}
1633

1734
module FormatInvalidConfig implements DataFlow::ConfigSig {
1835
predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
1936

20-
predicate isSink(DataFlow::Node n) { exists(FormatCall c | n.asExpr() = c.getFormatExpr()) }
37+
predicate isSink(DataFlow::Node n) {
38+
exists(FormatStringParseCall c | n.asExpr() = c.getFormatExpr())
39+
}
2140
}
2241

2342
module FormatInvalid = DataFlow::Global<FormatInvalidConfig>;
2443

44+
module FormatLiteralConfig implements DataFlow::ConfigSig {
45+
predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
46+
47+
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
48+
// Add flow via `System.Text.CompositeFormat.Parse`.
49+
exists(ParseFormatStringCall call |
50+
pred.asExpr() = call.getFormatExpr() and
51+
succ.asExpr() = call
52+
)
53+
}
54+
55+
predicate isSink(DataFlow::Node n) { exists(FormatCall c | n.asExpr() = c.getFormatExpr()) }
56+
}
57+
58+
module FormatLiteral = DataFlow::Global<FormatLiteralConfig>;
59+
60+
module FormatFlow =
61+
DataFlow::MergePathGraph<FormatInvalid::PathNode, FormatLiteral::PathNode,
62+
FormatInvalid::PathGraph, FormatLiteral::PathGraph>;
63+
2564
private predicate invalidFormatString(
2665
InvalidFormatString src, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
27-
FormatCall call, string callString
66+
FormatStringParseCall call, string callString
2867
) {
2968
source.getNode().asExpr() = src and
3069
sink.getNode().asExpr() = call.getFormatExpr() and
@@ -34,13 +73,13 @@ private predicate invalidFormatString(
3473
}
3574

3675
private predicate unusedArgument(
37-
FormatCall call, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
76+
FormatCall call, FormatLiteral::PathNode source, FormatLiteral::PathNode sink, string msg,
3877
ValidFormatString src, string srcString, Expr unusedExpr, string unusedString
3978
) {
4079
exists(int unused |
4180
source.getNode().asExpr() = src and
4281
sink.getNode().asExpr() = call.getFormatExpr() and
43-
FormatInvalid::flowPath(source, sink) and
82+
FormatLiteral::flowPath(source, sink) and
4483
unused = call.getASuppliedArgument() and
4584
not unused = src.getAnInsert() and
4685
not src.getValue() = "" and
@@ -52,13 +91,13 @@ private predicate unusedArgument(
5291
}
5392

5493
private predicate missingArgument(
55-
FormatCall call, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
94+
FormatCall call, FormatLiteral::PathNode source, FormatLiteral::PathNode sink, string msg,
5695
ValidFormatString src, string srcString
5796
) {
5897
exists(int used, int supplied |
5998
source.getNode().asExpr() = src and
6099
sink.getNode().asExpr() = call.getFormatExpr() and
61-
FormatInvalid::flowPath(source, sink) and
100+
FormatLiteral::flowPath(source, sink) and
62101
used = src.getAnInsert() and
63102
supplied = call.getSuppliedArguments() and
64103
used >= supplied and
@@ -68,16 +107,17 @@ private predicate missingArgument(
68107
}
69108

70109
from
71-
Element alert, FormatInvalid::PathNode source, FormatInvalid::PathNode sink, string msg,
72-
Element extra1, string extra1String, Element extra2, string extra2String
110+
Element alert, FormatFlow::PathNode source, FormatFlow::PathNode sink, string msg, Element extra1,
111+
string extra1String, Element extra2, string extra2String
73112
where
74-
invalidFormatString(alert, source, sink, msg, extra1, extra1String) and
113+
invalidFormatString(alert, source.asPathNode1(), sink.asPathNode1(), msg, extra1, extra1String) and
75114
extra2 = extra1 and
76115
extra2String = extra1String
77116
or
78-
unusedArgument(alert, source, sink, msg, extra1, extra1String, extra2, extra2String)
117+
unusedArgument(alert, source.asPathNode2(), sink.asPathNode2(), msg, extra1, extra1String, extra2,
118+
extra2String)
79119
or
80-
missingArgument(alert, source, sink, msg, extra1, extra1String) and
120+
missingArgument(alert, source.asPathNode2(), sink.asPathNode2(), msg, extra1, extra1String) and
81121
extra2 = extra1 and
82122
extra2String = extra1String
83123
select alert, source, sink, msg, extra1, extra1String, extra2, extra2String

0 commit comments

Comments
 (0)