1111 * external/cwe/cwe-409
1212 */
1313
14- import codeql.ruby.AST
15- import codeql.ruby.frameworks.Files
16- import codeql.ruby.ApiGraphs
17- import codeql.ruby.DataFlow
18- import codeql.ruby.dataflow.RemoteFlowSources
19- import codeql.ruby.TaintTracking
20- import DataFlow:: PathGraph
21-
22- module DecompressionBombs {
23- abstract class DecompressionBombSink extends DataFlow:: Node { }
24-
25- module Zlib {
26- /**
27- * `Zlib::GzipReader`
28- * > Note that if you use the lower level Zip::InputStream interface, rubyzip does not check the entry sizes.
29- *
30- * according to above warning from Doc we don't need to go forward after open()
31- * or new() methods, we just need the argument node of them
32- */
33- private API:: Node gzipReaderInstance ( ) {
34- result = API:: getTopLevelMember ( "Zlib" ) .getMember ( "GzipReader" )
35- }
36-
37- /**
38- * A return values of following methods
39- * `Zlib::GzipReader.open`
40- * `Zlib::GzipReader.zcat`
41- * `Zlib::GzipReader.new`
42- */
43- class ZipSink extends DecompressionBombSink {
44- ZipSink ( ) {
45- this = gzipReaderInstance ( ) .getMethod ( [ "open" , "new" , "zcat" ] ) .getReturn ( ) .asSource ( )
46- }
47- }
48-
49- predicate isAdditionalTaintStep ( DataFlow:: Node nodeFrom , DataFlow:: Node nodeTo ) {
50- exists ( API:: Node zipnode | zipnode = gzipReaderInstance ( ) .getMethod ( [ "open" , "new" , "zcat" ] ) |
51- nodeFrom = zipnode .getParameter ( 0 ) .asSink ( ) and
52- nodeTo = zipnode .getReturn ( ) .asSource ( )
53- )
54- }
55- }
56-
57- module ZipInputStream {
58- /**
59- * `Zip::InputStream`
60- * > Note that if you use the lower level Zip::InputStream interface, rubyzip does not check the entry sizes.
61- *
62- * according to above warning from Doc we don't need to go forward after open()
63- * or new() methods, we just need the argument node of them
64- */
65- private API:: Node zipInputStream ( ) {
66- result = API:: getTopLevelMember ( "Zip" ) .getMember ( "InputStream" )
67- }
68-
69- /**
70- * A return values of following methods
71- * `ZipIO.read`
72- * `ZipEntry.extract`
73- */
74- class ZipSink extends DecompressionBombSink {
75- ZipSink ( ) {
76- this = zipInputStream ( ) .getMethod ( [ "open" , "new" ] ) .getReturn ( ) .asSource ( ) and
77- not this .getLocation ( ) .getFile ( ) .getBaseName ( ) .matches ( "%spec%" )
78- }
79- }
80-
81- predicate isAdditionalTaintStep ( DataFlow:: Node nodeFrom , DataFlow:: Node nodeTo ) {
82- exists ( API:: Node zipnode | zipnode = zipInputStream ( ) .getMethod ( [ "open" , "new" ] ) |
83- nodeFrom = zipnode .getParameter ( 0 ) .asSink ( ) and
84- nodeTo = zipnode .getReturn ( ) .asSource ( )
85- )
86- }
87- }
88-
89- module ZipFile {
90- API:: Node rubyZipNode ( ) {
91- result = zipFile ( )
92- or
93- result = rubyZipNode ( ) .getMethod ( _)
94- or
95- result = rubyZipNode ( ) .getReturn ( )
96- or
97- result = rubyZipNode ( ) .getParameter ( _)
98- or
99- result = rubyZipNode ( ) .getAnElement ( )
100- or
101- result = rubyZipNode ( ) .getBlock ( )
102- }
103-
104- /**
105- * A return values of following methods
106- * `ZipIO.read`
107- * `ZipEntry.extract`
108- * sanitize the nodes which have `entry.size > someOBJ`
109- */
110- class ZipSink extends DecompressionBombSink {
111- ZipSink ( ) {
112- exists ( API:: Node zipnodes | zipnodes = rubyZipNode ( ) |
113- this = zipnodes .getMethod ( [ "extract" , "read" ] ) .getReturn ( ) .asSource ( ) and
114- not exists ( zipnodes .getMethod ( "size" ) .getReturn ( ) .getMethod ( ">" ) .getParameter ( 0 ) )
115- )
116- }
117- }
118-
119- predicate isAdditionalTaintStep ( DataFlow:: Node nodeFrom , DataFlow:: Node nodeTo ) {
120- exists ( API:: Node zipnodes | zipnodes = rubyZipNode ( ) |
121- nodeTo = zipnodes .getMethod ( [ "extract" , "read" ] ) .getReturn ( ) .asSource ( ) and
122- nodeFrom = zipnodes .getMethod ( [ "new" , "open" ] ) .getParameter ( 0 ) .asSink ( )
123- )
124- }
125-
126- /**
127- * `Zip::File`
128- */
129- API:: Node zipFile ( ) { result = API:: getTopLevelCall ( "Zip" ) .getMember ( "File" ) }
130- }
131- }
14+ private import codeql.ruby.Concepts
15+ private import codeql.ruby.DataFlow
16+ private import codeql.ruby.TaintTracking
17+ import DecompressionBombs
13218
13319/**
13420 * A call to `IO.copy_stream`
@@ -139,30 +25,17 @@ class IoCopyStream extends DataFlow::CallNode {
13925 DataFlow:: Node getAPathArgument ( ) { result = this .getArgument ( 0 ) }
14026}
14127
142- class Bombs extends TaintTracking:: Configuration {
143- Bombs ( ) { this = "Decompression Bombs" }
144-
145- override predicate isSanitizer ( DataFlow:: Node node ) {
146- not node .getLocation ( ) .hasLocationInfo ( "%spec%" , _, _, _, _)
28+ module BombsConfig implements DataFlow:: ConfigSig {
29+ predicate isBarrier ( DataFlow:: Node node ) {
30+ node .getLocation ( ) .hasLocationInfo ( "%spec%" , _, _, _, _)
14731 }
14832
149- override predicate isSource ( DataFlow:: Node source ) {
150- source instanceof RemoteFlowSource
151- // or
152- // source instanceof DataFlow::LocalSourceNode
153- // source = API::getTopLevelCall("Zip").getMember("InputStream").getASuccessor*()
154- }
33+ predicate isSource ( DataFlow:: Node source ) { source instanceof RemoteFlowSource }
15534
156- override predicate isSink ( DataFlow:: Node sink ) {
157- sink instanceof DecompressionBombs:: DecompressionBombSink
158- }
35+ predicate isSink ( DataFlow:: Node sink ) { sink instanceof DecompressionBomb:: Sink }
15936
160- override predicate isAdditionalTaintStep ( DataFlow:: Node nodeFrom , DataFlow:: Node nodeTo ) {
161- DecompressionBombs:: ZipFile:: isAdditionalTaintStep ( nodeFrom , nodeTo )
162- or
163- DecompressionBombs:: ZipInputStream:: isAdditionalTaintStep ( nodeFrom , nodeTo )
164- or
165- DecompressionBombs:: Zlib:: isAdditionalTaintStep ( nodeFrom , nodeTo )
37+ predicate isAdditionalFlowStep ( DataFlow:: Node nodeFrom , DataFlow:: Node nodeTo ) {
38+ any ( DecompressionBomb:: AdditionalTaintStep ats ) .isAdditionalTaintStep ( nodeFrom , nodeTo )
16639 or
16740 exists ( API:: Node n | n = API:: root ( ) .getMember ( "File" ) .getMethod ( "open" ) |
16841 nodeFrom = n .getParameter ( 0 ) .asSink ( ) and
@@ -194,7 +67,11 @@ class Bombs extends TaintTracking::Configuration {
19467 }
19568}
19669
197- from Bombs cfg , DataFlow:: PathNode source , DataFlow:: PathNode sink
198- where cfg .hasFlowPath ( source , sink )
199- select sink .getNode ( ) , source , sink , "This file extraction depends on a $@." , source .getNode ( ) ,
70+ module Bombs = TaintTracking:: Global< BombsConfig > ;
71+
72+ import Bombs:: PathGraph
73+
74+ from Bombs:: PathNode source , Bombs:: PathNode sink
75+ where Bombs:: flowPath ( source , sink )
76+ select sink .getNode ( ) , source , sink , "This file Decompression depends on a $@." , source .getNode ( ) ,
20077 "potentially untrusted source"
0 commit comments