@@ -10,46 +10,110 @@ 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 ) {
1616 exists ( ArrayInit ai , int i |
1717 ex = ai .getInit ( i ) and
1818 i > 0 and
19- not isShell ( ai .getInit ( 0 ) )
19+ not isShell ( ai .getInit ( 0 ) ) and
20+ (
21+ not exists ( delegatedCommandIndex ( ai .getInit ( 0 ) ) )
22+ or
23+ exists ( int commandIndex |
24+ commandIndex = delegatedCommandIndex ( ai .getInit ( 0 ) ) and
25+ (
26+ i < commandIndex
27+ or
28+ i > commandIndex and
29+ not isShell ( ai .getInit ( commandIndex ) ) and
30+ not exists ( delegatedCommandIndex ( ai .getInit ( commandIndex ) ) )
31+ )
32+ )
33+ )
2034 )
2135 or
22- exists ( CommandArgumentList cal |
36+ exists ( CommandArgumentList cal , int i |
2337 not cal .isShell ( ) and
24- ex = cal .getASubsequentAdd ( ) .getArgument ( 0 )
38+ i > 0 and
39+ ex = cal .getArgument ( i ) and
40+ (
41+ not exists ( delegatedCommandIndex ( cal .getArgument ( 0 ) ) )
42+ or
43+ exists ( int commandIndex |
44+ commandIndex = delegatedCommandIndex ( cal .getArgument ( 0 ) ) and
45+ (
46+ i < commandIndex
47+ or
48+ i > commandIndex and
49+ exists ( cal .getArgument ( commandIndex ) ) and
50+ not isShell ( cal .getArgument ( commandIndex ) ) and
51+ not exists ( delegatedCommandIndex ( cal .getArgument ( commandIndex ) ) )
52+ )
53+ )
54+ )
2555 )
2656 or
27- exists ( CommandArgArrayImmutableFirst caa |
57+ exists ( CommandArgArrayImmutableFirst caa , int i |
2858 not caa .isShell ( ) and
29- ex = caa .getAWrite ( any ( int i | i > 0 ) )
59+ i > 0 and
60+ ex = caa .getAWrite ( i ) and
61+ (
62+ not exists ( delegatedCommandIndex ( caa .getFirstElement ( ) ) )
63+ or
64+ exists ( int commandIndex |
65+ commandIndex = delegatedCommandIndex ( caa .getFirstElement ( ) ) and
66+ (
67+ i < commandIndex
68+ or
69+ i > commandIndex and
70+ exists ( caa .getAnElement ( commandIndex ) ) and
71+ not isShell ( caa .getAnElement ( commandIndex ) ) and
72+ not exists ( delegatedCommandIndex ( caa .getAnElement ( commandIndex ) ) )
73+ )
74+ )
75+ )
3076 )
3177}
3278
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- )
79+ private string commandName ( Expr ex ) {
80+ result = ex .( StringLiteral ) .getValue ( )
4081 or
4182 exists ( SsaDefinition ssa , SsaExplicitWrite def |
4283 ex = ssa .getARead ( ) and
43- isShell ( def .getDefiningExpr ( ) )
84+ result = commandName ( def .getDefiningExpr ( ) )
4485 |
4586 ssa .getAnUltimateDefinition ( ) = def
4687 or
4788 ssa .( SsaCapturedDefinition ) .getAnUltimateCapturedDefinition ( ) = def
4889 )
4990 or
50- isShell ( ex .( Assignment ) .getRhs ( ) )
91+ result = commandName ( ex .( Assignment ) .getRhs ( ) )
5192 or
52- isShell ( ex .( LocalVariableDeclExpr ) .getInit ( ) )
93+ result = commandName ( ex .( LocalVariableDeclExpr ) .getInit ( ) )
94+ }
95+
96+ /**
97+ * Holds if the given expression is the name of a shell command such as bash or python
98+ */
99+ private predicate isShell ( Expr ex ) {
100+ commandName ( ex ) .regexpMatch ( ".*(sh|javac?|python[23]?|osascript|cmd)(\\.exe)?$" )
101+ }
102+
103+ /**
104+ * Gets the fixed position of the command executed by a supported delegating launcher.
105+ *
106+ * Only simple invocation forms are recognized. Launcher options are intentionally not parsed.
107+ */
108+ private int delegatedCommandIndex ( Expr ex ) {
109+ exists ( string launcher |
110+ launcher = commandName ( ex ) .regexpCapture ( "(.*/)?([^/.]+)(\\.exe)?$" , 2 ) and
111+ (
112+ launcher = [ "env" , "nice" , "nohup" , "setsid" , "sudo" , "xargs" ] and result = 1
113+ or
114+ launcher = [ "chroot" , "stdbuf" , "timeout" ] and result = 2
115+ )
116+ )
53117}
54118
55119/**
@@ -101,16 +165,28 @@ private class CommandArgumentList extends SsaExplicitWrite {
101165 result .getQualifier ( ) = this .getAUseBeforeFirstAdd ( )
102166 }
103167
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 ( )
168+ /** Gets a single-element addition at the given index. */
169+ private MethodCall getAnAddAtIndex ( int index ) {
170+ index = 0 and
171+ result = this .getAFirstAdd ( ) and
172+ result .getMethod ( ) .hasName ( "add" ) and
173+ result .getNumArgument ( ) = 1
174+ or
175+ index > 0 and
176+ exists ( MethodCall previous |
177+ previous = this .getAnAddAtIndex ( index - 1 ) and
178+ result = this .getAnAdd ( ) and
179+ result .getMethod ( ) .hasName ( "add" ) and
180+ result .getNumArgument ( ) = 1 and
181+ adjacentUseUse ( previous .getQualifier ( ) , result .getQualifier ( ) )
182+ )
108183 }
109184
185+ /** Gets the argument added at the given index. */
186+ Expr getArgument ( int index ) { result = this .getAnAddAtIndex ( index ) .getArgument ( 0 ) }
187+
110188 /** 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+ predicate isShell ( ) { isShell ( this .getArgument ( 0 ) ) }
114190}
115191
116192/**
@@ -153,7 +229,7 @@ private class CommandArgumentArray extends SsaExplicitWrite {
153229 */
154230private class CommandArgArrayImmutableFirst extends CommandArgumentArray {
155231 CommandArgArrayImmutableFirst ( ) {
156- ( exists ( this .getAWrite ( 0 ) ) or exists ( firstElementOf ( this .getDefiningExpr ( ) ) ) ) and
232+ ( exists ( this .getAWrite ( 0 ) ) or exists ( arrayElementOf ( this .getDefiningExpr ( ) , 0 ) ) ) and
157233 forall ( VarRead use | exists ( this .getAWrite ( 0 , use ) ) | use = ssaGetAFirstUse ( this ) )
158234 }
159235
@@ -162,41 +238,53 @@ private class CommandArgArrayImmutableFirst extends CommandArgumentArray {
162238 result = this .getAWrite ( 0 )
163239 or
164240 not exists ( this .getAWrite ( 0 ) ) and
165- result = firstElementOf ( this .getDefiningExpr ( ) )
241+ result = arrayElementOf ( this .getDefiningExpr ( ) , 0 )
242+ }
243+
244+ /** Gets a possible value for the element at the given index. */
245+ Expr getAnElement ( int index ) {
246+ index = 0 and result = this .getFirstElement ( )
247+ or
248+ index > 0 and
249+ (
250+ result = this .getAWrite ( index )
251+ or
252+ result = arrayElementOf ( this .getDefiningExpr ( ) , index )
253+ )
166254 }
167255
168256 /** Holds if the first element of this array is a shell command. */
169257 predicate isShell ( ) { isShell ( this .getFirstElement ( ) ) }
170258}
171259
172- /** Gets the first element of an imutable array of strings */
173- private Expr firstElementOf ( Expr arr ) {
260+ /** Gets an element of an immutable array of strings. */
261+ private Expr arrayElementOf ( Expr arr , int index ) {
174262 arr .getType ( ) instanceof ArrayOfStringType and
175263 (
176- result = firstElementOf ( arr .( Assignment ) .getRhs ( ) )
264+ result = arrayElementOf ( arr .( Assignment ) .getRhs ( ) , index )
177265 or
178- result = firstElementOf ( arr .( LocalVariableDeclExpr ) .getInit ( ) )
266+ result = arrayElementOf ( arr .( LocalVariableDeclExpr ) .getInit ( ) , index )
179267 or
180268 exists ( CommandArgArrayImmutableFirst caa | arr = caa .getARead ( ) |
181- result = caa .getFirstElement ( )
269+ result = caa .getAnElement ( index )
182270 )
183271 or
184272 exists ( MethodCall ma , Method m |
185273 arr = ma and
186274 ma .getMethod ( ) = m and
187275 m .getDeclaringType ( ) .hasQualifiedName ( "java.util" , "Arrays" ) and
188276 m .hasName ( "copyOf" ) and
189- result = firstElementOf ( ma .getArgument ( 0 ) )
277+ result = arrayElementOf ( ma .getArgument ( 0 ) , index )
190278 )
191279 or
192280 exists ( Field f |
193281 f .isStatic ( ) and
194282 arr .( FieldRead ) .getField ( ) = f and
195- result = firstElementOf ( f .getInitializer ( ) )
283+ result = arrayElementOf ( f .getInitializer ( ) , index )
196284 )
197285 or
198- result = arr .( ArrayInit ) .getInit ( 0 )
286+ result = arr .( ArrayInit ) .getInit ( index )
199287 or
200- result = arr .( ArrayCreationExpr ) .getInit ( ) .getInit ( 0 )
288+ result = arr .( ArrayCreationExpr ) .getInit ( ) .getInit ( index )
201289 )
202290}
0 commit comments