@@ -10,46 +10,114 @@ import semmle.code.java.Collections
1010
1111/**
1212 * Holds if `ex` is used safely as an argument to a command;
13- * i.e. it's not in the first position and it's not a shell command.
13+ * i.e. it's not in a position that selects a command to execute .
1414 */
1515predicate isSafeCommandArgument ( Expr ex ) {
16- exists ( ArrayInit ai , int i |
17- ex = ai .getInit ( i ) and
16+ exists ( CommandArguments arguments , int i |
1817 i > 0 and
19- not isShell ( ai .getInit ( 0 ) )
20- )
21- or
22- exists ( CommandArgumentList cal |
23- not cal .isShell ( ) and
24- ex = cal .getASubsequentAdd ( ) .getArgument ( 0 )
25- )
26- or
27- exists ( CommandArgArrayImmutableFirst caa |
28- not caa .isShell ( ) and
29- ex = caa .getAWrite ( any ( int i | i > 0 ) )
18+ ex = arguments .getArgumentUse ( i ) and
19+ arguments .hasSafeArgument ( i )
3020 )
3121}
3222
33- /**
34- * Holds if the given expression is the name of a shell command such as bash or python
35- */
36- private predicate isShell ( Expr ex ) {
37- exists ( string cmd | cmd = ex .( StringLiteral ) .getValue ( ) |
38- cmd .regexpMatch ( ".*(sh|javac?|python[23]?|osascript|cmd)(\\.exe)?$" )
39- )
23+ private string commandName ( Expr ex ) {
24+ result = ex .( StringLiteral ) .getValue ( )
4025 or
4126 exists ( SsaDefinition ssa , SsaExplicitWrite def |
4227 ex = ssa .getARead ( ) and
43- isShell ( def .getDefiningExpr ( ) )
28+ result = commandName ( def .getDefiningExpr ( ) )
4429 |
4530 ssa .getAnUltimateDefinition ( ) = def
4631 or
4732 ssa .( SsaCapturedDefinition ) .getAnUltimateCapturedDefinition ( ) = def
4833 )
4934 or
50- isShell ( ex .( Assignment ) .getRhs ( ) )
35+ result = commandName ( ex .( Assignment ) .getRhs ( ) )
5136 or
52- isShell ( ex .( LocalVariableDeclExpr ) .getInit ( ) )
37+ result = commandName ( ex .( LocalVariableDeclExpr ) .getInit ( ) )
38+ }
39+
40+ /**
41+ * Holds if the given expression is the name of a shell command such as bash or python
42+ */
43+ private predicate isShell ( Expr ex ) {
44+ commandName ( ex ) .regexpMatch ( ".*(sh|javac?|python[23]?|osascript|cmd)(\\.exe)?$" )
45+ }
46+
47+ /**
48+ * Gets the fixed position of the command executed by a supported delegating launcher.
49+ *
50+ * Only simple invocation forms are recognized. Launcher options are intentionally not parsed.
51+ */
52+ private int delegatedCommandIndex ( Expr ex ) {
53+ exists ( string launcher |
54+ launcher = commandName ( ex ) .regexpCapture ( "(.*/)?([^/.]+)(\\.exe)?$" , 2 ) and
55+ (
56+ launcher = [ "env" , "nice" , "nohup" , "setsid" , "sudo" , "xargs" ] and result = 1
57+ or
58+ launcher = [ "chroot" , "stdbuf" , "timeout" ] and result = 2
59+ )
60+ )
61+ }
62+
63+ private newtype TCommandArguments =
64+ TLiteralArguments ( ArrayInit init ) or
65+ TListArguments ( CommandArgumentList list ) or
66+ TMutableArguments ( CommandArgArrayImmutableFirst array )
67+
68+ /** An ordered sequence of command arguments. */
69+ private class CommandArguments extends TCommandArguments {
70+ string toString ( ) { result = this .getArgument ( 0 ) .toString ( ) }
71+
72+ /** Gets a possible value at the given index. */
73+ private Expr getArgument ( int index ) {
74+ exists ( ArrayInit init | this = TLiteralArguments ( init ) | result = init .getInit ( index ) )
75+ or
76+ exists ( CommandArgumentList list |
77+ this = TListArguments ( list ) and
78+ result = list .getArgument ( index )
79+ )
80+ or
81+ exists ( CommandArgArrayImmutableFirst array |
82+ this = TMutableArguments ( array ) and
83+ result = array .getAnElement ( index )
84+ )
85+ }
86+
87+ /** Gets an expression whose value is stored at the given index. */
88+ Expr getArgumentUse ( int index ) {
89+ exists ( ArrayInit init | this = TLiteralArguments ( init ) | result = init .getInit ( index ) )
90+ or
91+ exists ( CommandArgumentList list |
92+ this = TListArguments ( list ) and
93+ result = list .getArgument ( index )
94+ )
95+ or
96+ exists ( CommandArgArrayImmutableFirst array |
97+ this = TMutableArguments ( array ) and
98+ result = array .getAWrite ( index )
99+ )
100+ }
101+
102+ predicate hasSafeArgument ( int index ) {
103+ exists ( this .getArgumentUse ( index ) ) and
104+ not isShell ( this .getArgument ( 0 ) ) and
105+ (
106+ not exists ( delegatedCommandIndex ( this .getArgument ( 0 ) ) )
107+ or
108+ exists ( int commandIndex |
109+ commandIndex = delegatedCommandIndex ( this .getArgument ( 0 ) ) and
110+ (
111+ index < commandIndex
112+ or
113+ index > commandIndex and
114+ exists ( this .getArgument ( commandIndex ) ) and
115+ not isShell ( this .getArgument ( commandIndex ) ) and
116+ not exists ( delegatedCommandIndex ( this .getArgument ( commandIndex ) ) )
117+ )
118+ )
119+ )
120+ }
53121}
54122
55123/**
@@ -101,16 +169,25 @@ private class CommandArgumentList extends SsaExplicitWrite {
101169 result .getQualifier ( ) = this .getAUseBeforeFirstAdd ( )
102170 }
103171
104- /** Gets an addition to this list which is not the first element. */
105- MethodCall getASubsequentAdd ( ) {
106- result = this .getAnAdd ( ) and
107- not result = this .getAFirstAdd ( )
172+ /** Gets a single-element addition at the given index. */
173+ private MethodCall getAnAddAtIndex ( int index ) {
174+ index = 0 and
175+ result = this .getAFirstAdd ( ) and
176+ result .getMethod ( ) .hasName ( "add" ) and
177+ result .getNumArgument ( ) = 1
178+ or
179+ index > 0 and
180+ exists ( MethodCall previous |
181+ previous = this .getAnAddAtIndex ( index - 1 ) and
182+ result = this .getAnAdd ( ) and
183+ result .getMethod ( ) .hasName ( "add" ) and
184+ result .getNumArgument ( ) = 1 and
185+ adjacentUseUse ( previous .getQualifier ( ) , result .getQualifier ( ) )
186+ )
108187 }
109188
110- /** Holds if the first element of this list is a shell command. */
111- predicate isShell ( ) {
112- exists ( MethodCall ma | ma = this .getAFirstAdd ( ) and isShell ( ma .getArgument ( 0 ) ) )
113- }
189+ /** Gets the argument added at the given index. */
190+ Expr getArgument ( int index ) { result = this .getAnAddAtIndex ( index ) .getArgument ( 0 ) }
114191}
115192
116193/**
@@ -153,7 +230,7 @@ private class CommandArgumentArray extends SsaExplicitWrite {
153230 */
154231private class CommandArgArrayImmutableFirst extends CommandArgumentArray {
155232 CommandArgArrayImmutableFirst ( ) {
156- ( exists ( this .getAWrite ( 0 ) ) or exists ( firstElementOf ( this .getDefiningExpr ( ) ) ) ) and
233+ ( exists ( this .getAWrite ( 0 ) ) or exists ( arrayElementOf ( this .getDefiningExpr ( ) , 0 ) ) ) and
157234 forall ( VarRead use | exists ( this .getAWrite ( 0 , use ) ) | use = ssaGetAFirstUse ( this ) )
158235 }
159236
@@ -162,41 +239,50 @@ private class CommandArgArrayImmutableFirst extends CommandArgumentArray {
162239 result = this .getAWrite ( 0 )
163240 or
164241 not exists ( this .getAWrite ( 0 ) ) and
165- result = firstElementOf ( this .getDefiningExpr ( ) )
242+ result = arrayElementOf ( this .getDefiningExpr ( ) , 0 )
166243 }
167244
168- /** Holds if the first element of this array is a shell command. */
169- predicate isShell ( ) { isShell ( this .getFirstElement ( ) ) }
245+ /** Gets a possible value for the element at the given index. */
246+ Expr getAnElement ( int index ) {
247+ index = 0 and result = this .getFirstElement ( )
248+ or
249+ index > 0 and
250+ (
251+ result = this .getAWrite ( index )
252+ or
253+ result = arrayElementOf ( this .getDefiningExpr ( ) , index )
254+ )
255+ }
170256}
171257
172- /** Gets the first element of an imutable array of strings */
173- private Expr firstElementOf ( Expr arr ) {
258+ /** Gets an element of an immutable array of strings. */
259+ private Expr arrayElementOf ( Expr arr , int index ) {
174260 arr .getType ( ) instanceof ArrayOfStringType and
175261 (
176- result = firstElementOf ( arr .( Assignment ) .getRhs ( ) )
262+ result = arrayElementOf ( arr .( Assignment ) .getRhs ( ) , index )
177263 or
178- result = firstElementOf ( arr .( LocalVariableDeclExpr ) .getInit ( ) )
264+ result = arrayElementOf ( arr .( LocalVariableDeclExpr ) .getInit ( ) , index )
179265 or
180266 exists ( CommandArgArrayImmutableFirst caa | arr = caa .getARead ( ) |
181- result = caa .getFirstElement ( )
267+ result = caa .getAnElement ( index )
182268 )
183269 or
184270 exists ( MethodCall ma , Method m |
185271 arr = ma and
186272 ma .getMethod ( ) = m and
187273 m .getDeclaringType ( ) .hasQualifiedName ( "java.util" , "Arrays" ) and
188274 m .hasName ( "copyOf" ) and
189- result = firstElementOf ( ma .getArgument ( 0 ) )
275+ result = arrayElementOf ( ma .getArgument ( 0 ) , index )
190276 )
191277 or
192278 exists ( Field f |
193279 f .isStatic ( ) and
194280 arr .( FieldRead ) .getField ( ) = f and
195- result = firstElementOf ( f .getInitializer ( ) )
281+ result = arrayElementOf ( f .getInitializer ( ) , index )
196282 )
197283 or
198- result = arr .( ArrayInit ) .getInit ( 0 )
284+ result = arr .( ArrayInit ) .getInit ( index )
199285 or
200- result = arr .( ArrayCreationExpr ) .getInit ( ) .getInit ( 0 )
286+ result = arr .( ArrayCreationExpr ) .getInit ( ) .getInit ( index )
201287 )
202288}
0 commit comments