github / github/codeql

[Python] False Negative: Taint flow fails to penetrate function-local class definitions

Đang mở
#20,947 4 bình luận 0 reaction 0 người được giao Xem trên GitHub
question
Ngôn ngữ chính
CodeQL
Star
10.1k
Fork
2.1k
Merge trung bình
2 ngày 15 giờ
Pull request đã merge (30 ngày)
141

Mô tả

### Description

I have identified a false negative in Python DataFlow analysis where taint tracking is lost when a class is defined **inside a function**.

If a tainted variable is passed as an argument to a function, and that argument is subsequently used inside a class defined within that function (a function-local class), CodeQL fails to track the data flow to the class instance's attributes.

However, if a similar logic is applied using a top-level (module-level) class, the data flow is detected correctly. This suggests an issue with how data flow is handled across the scope boundary of locally defined classes.

### Reproduction Case (False Negative)

In this example, `taint_src` is passed to `constructor_field_001_T`. The class `A` is defined inside the function and captures `taint_src`. The flow to `os.system` is **NOT detected**.

```python
import os

def constructor_field_001_T(taint_src):
# Class defined inside the function scope
class A:
def __init__(self):
# ISSUE: The analyzer fails to track 'taint_src' from the
# outer function argument into this local class scope.
self.data = taint_src
self.sani = '_'

obj = A()
taint_sink(obj.data)

def taint_sink(o):
os.system(o)

if __name__ == "__main__":
taint_src = "taint_src_value"
constructor_field_001_T(taint_src)
```

### Control Case (Working)

In this example, the class `A` is defined at the module level. The flow to `os.system` **IS detected** correctly.

```python
import os

# Class defined at module level
class A:
def __init__(self):
# Accessing taint_src (as a global/captured in this context) works fine
self.data = taint_src
self.sani = '_'

def constructor_field_001_T(taint_src):
obj = A()
taint_sink(obj.data)

def taint_sink(o):
os.system(o)

if __name__ == "__main__":
taint_src = "taint_src_value"
constructor_field_001_T(taint_src)
```

### Additional Control Case (Working: Explicit Argument)

Significantly, if I keep the class inside the function but pass `taint_src` as an **explicit argument** to `__init__`, the flow **IS detected**.
```python
import os

def constructor_field_explicit_arg(taint_src):
# Class defined inside function
class A:
# Explicit argument instead of capture
def __init__(self, val):
self.data = val

# Passing taint explicitly
obj = A(taint_src)
taint_sink(obj.data)

def taint_sink(o):
os.system(o)

if __name__ == "__main__":
taint_src = "taint_src_value"
constructor_field_explicit_arg(taint_src)
```

### CodeQL Query Used

I am using a standard `DataFlow::Global` configuration looking for the specific string literal flowing to `os.system`.

Click to view query

```ql
/**
* @name Python Taint Reproduction
* @kind path-problem
* @problem.severity error
* @id py/taint-reproduction
*/

import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking

class TaintSource extends DataFlow::Node {
TaintSource() {
exists(StrConst str |
str.getText() = "taint_src_value" and
this.asExpr() = str
)
}
}

class DangerousSink extends DataFlow::Node {
DangerousSink() {
exists(Call call |
(
call.getFunc().(Attribute).getName() = "system" and
call.getFunc().(Attribute).getObject().(Name).getId() = "os"
) and
this.asExpr() = call.getAnArg()
)
}
}

module TaintConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof TaintSource
}

predicate isSink(DataFlow::Node sink) {
sink instanceof DangerousSink
}
}

module TaintFlow = TaintTracking::Global;
import TaintFlow::PathGraph

from TaintFlow::PathNode source, TaintFlow::PathNode sink
where TaintFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Taint flow detected"
```

### Expected Behavior

CodeQL should be able to track the `taint_src` argument into the `__init__` method of the locally defined class `A`, eventually leading to the `os.system` sink, just as it does for top-level classes.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.