1111 */
1212
1313import csharp
14+ import semmle.code.csharp.frameworks.system.Text
1415import semmle.code.csharp.frameworks.Format
15- import FormatInvalid::PathGraph
16+ import FormatFlow::PathGraph
17+
18+ abstract class FormatStringParseCall extends MethodCall {
19+ abstract Expr getFormatExpr();
20+ }
21+
22+ class OrdinaryFormatCall extends FormatStringParseCall instanceof FormatCall {
23+ override Expr getFormatExpr() { result = FormatCall.super.getFormatExpr() }
24+ }
25+
26+ 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
1734module 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
2342module 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+
2564private 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
3675private 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
5493private 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
70109from
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
73112where
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
83123select alert, source, sink, msg, extra1, extra1String, extra2, extra2String
0 commit comments