@@ -6,267 +6,10 @@ private import ruby
66private import codeql.ruby.Concepts
77private import codeql.ruby.ApiGraphs
88private import codeql.ruby.DataFlow
9- private import codeql.ruby.frameworks.Core
109private import codeql.ruby.dataflow.FlowSummary
11-
12- private DataFlow:: Node ioInstanceInstantiation ( ) {
13- result = API:: getTopLevelMember ( "IO" ) .getAnInstantiation ( ) or
14- result = API:: getTopLevelMember ( "IO" ) .getAMethodCall ( [ "for_fd" , "open" , "try_convert" ] )
15- }
16-
17- private DataFlow:: Node ioInstance ( ) {
18- result = ioInstanceInstantiation ( )
19- or
20- exists ( DataFlow:: Node inst |
21- inst = ioInstance ( ) and
22- inst .( DataFlow:: LocalSourceNode ) .flowsTo ( result )
23- )
24- }
25-
26- // Match some simple cases where a path argument specifies a shell command to
27- // be executed. For example, the `"|date"` argument in `IO.read("|date")`, which
28- // will execute a shell command and read its output rather than reading from the
29- // filesystem.
30- private predicate pathArgSpawnsSubprocess ( Expr arg ) {
31- arg .getConstantValue ( ) .getStringlikeValue ( ) .charAt ( 0 ) = "|"
32- }
33-
34- private DataFlow:: Node fileInstanceInstantiation ( ) {
35- result = API:: getTopLevelMember ( "File" ) .getAnInstantiation ( )
36- or
37- result = API:: getTopLevelMember ( "File" ) .getAMethodCall ( [ "open" , "try_convert" ] )
38- or
39- // Calls to `Kernel.open` can yield `File` instances
40- result .( KernelMethodCall ) .getMethodName ( ) = "open" and
41- // Assume that calls that don't invoke shell commands will instead open
42- // a file.
43- not pathArgSpawnsSubprocess ( result .( KernelMethodCall ) .getArgument ( 0 ) .asExpr ( ) .getExpr ( ) )
44- }
45-
46- private DataFlow:: Node fileInstance ( ) {
47- result = fileInstanceInstantiation ( )
48- or
49- exists ( DataFlow:: Node inst |
50- inst = fileInstance ( ) and
51- inst .( DataFlow:: LocalSourceNode ) .flowsTo ( result )
52- )
53- }
54-
55- abstract private class IOOrFileMethodCall extends DataFlow:: CallNode {
56- // TODO: Currently this only handles class method calls.
57- // Can we infer a path argument for instance method calls?
58- // e.g. by tracing back to the instantiation of that instance
59- DataFlow:: Node getAPathArgumentImpl ( ) {
60- result = this .getArgument ( 0 ) and this .getReceiverKind ( ) = "class"
61- }
62-
63- /**
64- * Holds if this call appears to read/write from/to a spawned subprocess,
65- * rather than to/from a file.
66- */
67- predicate spawnsSubprocess ( ) {
68- pathArgSpawnsSubprocess ( this .getAPathArgumentImpl ( ) .asExpr ( ) .getExpr ( ) )
69- }
70-
71- /** Gets the API used to perform this call, either "IO" or "File" */
72- abstract string getApi ( ) ;
73-
74- /** DEPRECATED: Alias for getApi */
75- deprecated string getAPI ( ) { result = this .getApi ( ) }
76-
77- /** Gets a node representing the data read or written by this call */
78- abstract DataFlow:: Node getADataNodeImpl ( ) ;
79-
80- /** Gets a string representation of the receiver kind, either "class" or "instance". */
81- abstract string getReceiverKind ( ) ;
82- }
83-
84- /**
85- * A method call that performs a read using either the `IO` or `File` classes.
86- */
87- private class IOOrFileReadMethodCall extends IOOrFileMethodCall {
88- private string api ;
89- private string receiverKind ;
90-
91- IOOrFileReadMethodCall ( ) {
92- exists ( string methodName | methodName = this .getMethodName ( ) |
93- // e.g. `{IO,File}.readlines("foo.txt")`
94- receiverKind = "class" and
95- methodName = [ "binread" , "foreach" , "read" , "readlines" ] and
96- api = [ "IO" , "File" ] and
97- this = API:: getTopLevelMember ( api ) .getAMethodCall ( methodName )
98- or
99- // e.g. `{IO,File}.new("foo.txt", "r").getc`
100- receiverKind = "interface" and
101- (
102- methodName =
103- [
104- "getbyte" , "getc" , "gets" , "pread" , "read" , "read_nonblock" , "readbyte" , "readchar" ,
105- "readline" , "readlines" , "readpartial" , "sysread"
106- ] and
107- (
108- this .getReceiver ( ) = ioInstance ( ) and api = "IO"
109- or
110- this .getReceiver ( ) = fileInstance ( ) and api = "File"
111- )
112- )
113- )
114- }
115-
116- override string getApi ( ) { result = api }
117-
118- /** DEPRECATED: Alias for getApi */
119- deprecated override string getAPI ( ) { result = this .getApi ( ) }
120-
121- override DataFlow:: Node getADataNodeImpl ( ) { result = this }
122-
123- override string getReceiverKind ( ) { result = receiverKind }
124- }
125-
126- /**
127- * A method call that performs a write using either the `IO` or `File` classes.
128- */
129- private class IOOrFileWriteMethodCall extends IOOrFileMethodCall {
130- private string api ;
131- private string receiverKind ;
132- private DataFlow:: Node dataNode ;
133-
134- IOOrFileWriteMethodCall ( ) {
135- exists ( string methodName | methodName = this .getMethodName ( ) |
136- // e.g. `{IO,File}.write("foo.txt", "hello\n")`
137- receiverKind = "class" and
138- api = [ "IO" , "File" ] and
139- this = API:: getTopLevelMember ( api ) .getAMethodCall ( methodName ) and
140- methodName = [ "binwrite" , "write" ] and
141- dataNode = this .getArgument ( 1 )
142- or
143- // e.g. `{IO,File}.new("foo.txt", "a+).puts("hello")`
144- receiverKind = "interface" and
145- (
146- this .getReceiver ( ) = ioInstance ( ) and api = "IO"
147- or
148- this .getReceiver ( ) = fileInstance ( ) and api = "File"
149- ) and
150- (
151- methodName = [ "<<" , "print" , "putc" , "puts" , "syswrite" , "pwrite" , "write_nonblock" ] and
152- dataNode = this .getArgument ( 0 )
153- or
154- // Any argument to these methods may be written as data
155- methodName = [ "printf" , "write" ] and dataNode = this .getArgument ( _)
156- )
157- )
158- }
159-
160- override string getApi ( ) { result = api }
161-
162- /** DEPRECATED: Alias for getApi */
163- deprecated override string getAPI ( ) { result = this .getApi ( ) }
164-
165- override DataFlow:: Node getADataNodeImpl ( ) { result = dataNode }
166-
167- override string getReceiverKind ( ) { result = receiverKind }
168- }
169-
170- /**
171- * Classes and predicates for modeling the core `IO` module.
172- */
173- module IO {
174- /**
175- * An instance of the `IO` class, for example in
176- *
177- * ```rb
178- * rand = IO.new(IO.sysopen("/dev/random", "r"), "r")
179- * rand_data = rand.read(32)
180- * ```
181- *
182- * there are 3 `IOInstance`s - the call to `IO.new`, the assignment
183- * `rand = ...`, and the read access to `rand` on the second line.
184- */
185- class IOInstance extends DataFlow:: Node {
186- IOInstance ( ) {
187- this = ioInstance ( ) or
188- this = fileInstance ( )
189- }
190- }
191-
192- /**
193- * A `DataFlow::CallNode` that reads data using the `IO` class. For example,
194- * the `read` and `readline` calls in:
195- *
196- * ```rb
197- * # invokes the `date` shell command as a subprocess, returning its output as a string
198- * IO.read("|date")
199- *
200- * # reads from the file `foo.txt`, returning its first line as a string
201- * IO.new(IO.sysopen("foo.txt")).readline
202- * ```
203- *
204- * This class includes only reads that use the `IO` class directly, not those
205- * that use a subclass of `IO` such as `File`.
206- */
207- class IOReader extends IOOrFileReadMethodCall {
208- IOReader ( ) { this .getApi ( ) = "IO" }
209- }
210-
211- /**
212- * A `DataFlow::CallNode` that writes data using the `IO` class. For example,
213- * the `write` and `puts` calls in:
214- *
215- * ```rb
216- * # writes the string `hello world` to the file `foo.txt`
217- * IO.write("foo.txt", "hello world")
218- *
219- * # appends the string `hello again\n` to the file `foo.txt`
220- * IO.new(IO.sysopen("foo.txt", "a")).puts("hello again")
221- * ```
222- *
223- * This class includes only writes that use the `IO` class directly, not those
224- * that use a subclass of `IO` such as `File`.
225- */
226- class IOWriter extends IOOrFileWriteMethodCall {
227- IOWriter ( ) { this .getApi ( ) = "IO" }
228- }
229-
230- /**
231- * A `DataFlow::CallNode` that reads data to the filesystem using the `IO`
232- * or `File` classes. For example, the `IO.read` and `File#readline` calls in:
233- *
234- * ```rb
235- * # reads the file `foo.txt` and returns its contents as a string.
236- * IO.read("foo.txt")
237- *
238- * # reads from the file `foo.txt`, returning its first line as a string
239- * File.new("foo.txt").readline
240- * ```
241- */
242- class FileReader extends IOOrFileReadMethodCall , FileSystemReadAccess:: Range {
243- FileReader ( ) { not this .spawnsSubprocess ( ) }
244-
245- override DataFlow:: Node getADataNode ( ) { result = this .getADataNodeImpl ( ) }
246-
247- override DataFlow:: Node getAPathArgument ( ) { result = this .getAPathArgumentImpl ( ) }
248- }
249-
250- /**
251- * A `DataFlow::CallNode` that reads data from the filesystem using the `IO`
252- * or `File` classes. For example, the `write` and `puts` calls in:
253- *
254- * ```rb
255- * # writes the string `hello world` to the file `foo.txt`
256- * IO.write("foo.txt", "hello world")
257- *
258- * # appends the string `hello again\n` to the file `foo.txt`
259- * File.new("foo.txt", "a").puts("hello again")
260- * ```
261- */
262- class FileWriter extends IOOrFileWriteMethodCall , FileSystemWriteAccess:: Range {
263- FileWriter ( ) { not this .spawnsSubprocess ( ) }
264-
265- override DataFlow:: Node getADataNode ( ) { result = this .getADataNodeImpl ( ) }
266-
267- override DataFlow:: Node getAPathArgument ( ) { result = this .getAPathArgumentImpl ( ) }
268- }
269- }
10+ private import core.IO
11+ private import core.Kernel:: Kernel
12+ private import core.internal.IOOrFile
27013
27114/**
27215 * Classes and predicates for modeling the core `File` module.
@@ -330,7 +73,7 @@ module File {
33073 ] )
33174 or
33275 // Instance methods
333- exists ( FileInstance fi |
76+ exists ( File :: FileInstance fi |
33477 this .getReceiver ( ) = fi and
33578 this .getMethodName ( ) = [ "path" , "to_path" ]
33679 )
0 commit comments