Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added taint flow through `list.extend` and `list.insert`, matching the existing taint flow through `list.append`.
64 changes: 64 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Stdlib.qll
Original file line number Diff line number Diff line change
Expand Up @@ -4879,6 +4879,70 @@ module StdlibPrivate {
}
}

/**
* A flow summary for `list.extend`.
*
* See https://docs.python.org/3.10/library/stdtypes.html#typesseq-mutable
*/
class ListExtend extends SummarizedCallable::Range {
ListExtend() { this = "list.extend" }

override DataFlow::CallCfgNode getACall() {
result.(DataFlow::MethodCallNode).calls(_, "extend")
}

override DataFlow::ArgumentNode getACallback() {
result.(DataFlow::AttrRead).getAttributeName() = "extend"
}

override predicate propagatesFlow(string input, string output, boolean preservesValue) {
// elements of the newly added iterable are added to this
(
input = "Argument[0].ListElement"
or
input = "Argument[0].SetElement"
or
input = "Argument[0].AnyTupleElement"
) and
output = "Argument[self].ListElement" and
preservesValue = true
or
// transfer taint from new iterable to this (TODO: remove in future when taint-handling is more in line with other languages)
input = "Argument[0]" and
output = "Argument[self]" and
preservesValue = false
}
}

/**
* A flow summary for `list.insert`.
*
* See https://docs.python.org/3.10/library/stdtypes.html#typesseq-mutable
*/
class ListInsert extends SummarizedCallable::Range {
ListInsert() { this = "list.insert" }

override DataFlow::CallCfgNode getACall() {
result.(DataFlow::MethodCallNode).calls(_, "insert")
}

override DataFlow::ArgumentNode getACallback() {
result.(DataFlow::AttrRead).getAttributeName() = "insert"
}

override predicate propagatesFlow(string input, string output, boolean preservesValue) {
// newly added element added to this
input = "Argument[1]" and
output = "Argument[self].ListElement" and
preservesValue = true
or
// transfer taint from new element to this (TODO: remove in future when taint-handling is more in line with other languages)
input = "Argument[1]" and
output = "Argument[self]" and
preservesValue = false
}
}

/**
* A flow summary for `set.add`.
*
Expand Down
10 changes: 10 additions & 0 deletions python/ql/test/library-tests/dataflow/coverage/test_builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ def test_list_append():
l.append(SOURCE)
SINK(l[1]) # $ flow="SOURCE, l:-1 -> l[1]"

def test_list_extend():
l = [NONSOURCE]
l.extend([SOURCE])
SINK(l[1]) # $ flow="SOURCE, l:-1 -> l[1]"

def test_list_insert():
l = [NONSOURCE]
l.insert(0, SOURCE)
SINK(l[0]) # $ flow="SOURCE, l:-1 -> l[0]"

### Set

def test_set_pop():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,37 @@ def list_extend():
ensure_not_tainted(my_list)

my_list.extend(tainted_list)
ensure_tainted(my_list) # $ tainted


def list_extend_iteration():
my_list = ["safe"]
tainted_list = [TAINTED_STRING]

ensure_not_tainted(my_list)

my_list.extend(tainted_list)
for x in my_list:
ensure_tainted(x) # $ tainted


def list_insert():
tainted_string = TAINTED_STRING
my_list = ["safe"]

ensure_not_tainted(my_list)

my_list.insert(0, tainted_string)
ensure_tainted(my_list) # $ tainted


def list_iadd():
my_list = ["safe"]
tainted_list = [TAINTED_STRING]

ensure_not_tainted(my_list)

my_list += tainted_list
ensure_tainted(my_list) # $ MISSING: tainted


Expand Down Expand Up @@ -308,6 +339,9 @@ def set_add():
list_index_aug_assign()
list_append()
list_extend()
list_extend_iteration()
list_insert()
list_iadd()

dict_update_dict()
dict_update_kv_list()
Expand Down