Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7e7a646

Browse files
committed
Python: FastAPI: Model extra-taint for pydantic models
It feels a bit strange to add it to `frameworks.rst` since we only support a little bit of it, but if I don't do it now, we will most likely forget to do it later on (since it has already been added to `frameworks.qll`).
1 parent f5464b7 commit 7e7a646

5 files changed

Lines changed: 122 additions & 6 deletions

File tree

docs/codeql/support/reusables/frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ Python built-in support
168168
invoke, Utility library
169169
jmespath, Utility library
170170
multidict, Utility library
171+
pydantic, Utility library
171172
yarl, Utility library
172173
aioch, Database
173174
clickhouse-driver, Database

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ private import semmle.python.frameworks.Mysql
2424
private import semmle.python.frameworks.MySQLdb
2525
private import semmle.python.frameworks.Peewee
2626
private import semmle.python.frameworks.Psycopg2
27+
private import semmle.python.frameworks.Pydantic
2728
private import semmle.python.frameworks.PyMySQL
2829
private import semmle.python.frameworks.Rsa
2930
private import semmle.python.frameworks.Simplejson

python/ql/lib/semmle/python/frameworks/FastApi.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.RemoteFlowSources
99
private import semmle.python.dataflow.new.TaintTracking
1010
private import semmle.python.Concepts
1111
private import semmle.python.ApiGraphs
12+
private import semmle.python.frameworks.Pydantic
1213

1314
/**
1415
* Provides models for the `fastapi` PyPI package.
@@ -78,6 +79,18 @@ private module FastApi {
7879
DataFlow::Node getResponseClassArg() { result = this.getArgByName("response_class") }
7980
}
8081

82+
/**
83+
* A parameter to a request handler that has a type-annotation with a class that is a
84+
* Pydantic model.
85+
*/
86+
private class PydanticModelRequestHandlerParam extends Pydantic::BaseModel::InstanceSource,
87+
DataFlow::ParameterNode {
88+
PydanticModelRequestHandlerParam() {
89+
this.getParameter().getAnnotation() = Pydantic::BaseModel::subclassRef().getAUse().asExpr() and
90+
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
91+
}
92+
}
93+
8194
// ---------------------------------------------------------------------------
8295
// Response modeling
8396
// ---------------------------------------------------------------------------
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `pydantic` PyPI package.
3+
*
4+
* See
5+
* - https://pypi.org/project/pydantic/
6+
* - https://pydantic-docs.helpmanual.io/
7+
*/
8+
9+
private import python
10+
private import semmle.python.dataflow.new.DataFlow
11+
private import semmle.python.dataflow.new.TaintTracking
12+
private import semmle.python.Concepts
13+
private import semmle.python.ApiGraphs
14+
15+
/**
16+
* INTERNAL: Do not use.
17+
*
18+
* Provides models for `pydantic` PyPI package.
19+
*
20+
* See
21+
* - https://pypi.org/project/pydantic/
22+
* - https://pydantic-docs.helpmanual.io/
23+
*/
24+
module Pydantic {
25+
/**
26+
* Provides models for `pydantic.BaseModel` subclasses (a pydantic model).
27+
*
28+
* See https://pydantic-docs.helpmanual.io/usage/models/.
29+
*/
30+
module BaseModel {
31+
/** Gets a reference to a `pydantic.BaseModel` subclass (a pydantic model). */
32+
API::Node subclassRef() {
33+
result = API::moduleImport("pydantic").getMember("BaseModel").getASubclass+()
34+
}
35+
36+
/**
37+
* A source of instances of `pydantic.BaseModel` subclasses, extend this class to model new instances.
38+
*
39+
* This can include instantiations of the class, return values from function
40+
* calls, or a special parameter that will be set when functions are called by an external
41+
* library.
42+
*
43+
* Use the predicate `BaseModel::instance()` to get references to instances of `pydantic.BaseModel`.
44+
*/
45+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
46+
47+
/** Gets a reference to an instance of a `pydantic.BaseModel` subclass. */
48+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
49+
t.start() and
50+
result instanceof InstanceSource
51+
or
52+
t.start() and
53+
instanceStepToPydanticModel(_, result)
54+
or
55+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
56+
}
57+
58+
/** Gets a reference to an instance of a `pydantic.BaseModel` subclass. */
59+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
60+
61+
/**
62+
* A step from an instance of a `pydantic.BaseModel` subclass, that might result in
63+
* an instance of a `pydantic.BaseModel` subclass.
64+
*
65+
* NOTE: We currently overapproximate, and treat all attributes as containing another
66+
* pydantic model. For the code below, we _could_ limit this to `main_foo` and
67+
* members of `other_foos`.
68+
*
69+
* ```py
70+
* class MyComplexModel(BaseModel):
71+
* field: str
72+
* main_foo: Foo
73+
* other_foos: List[Foo]
74+
* ```
75+
*/
76+
private predicate instanceStepToPydanticModel(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
77+
// attributes (such as `model.foo`)
78+
nodeFrom = instance() and
79+
nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom
80+
or
81+
// subscripts on attributes (such as `model.foo[0]`)
82+
nodeFrom.(DataFlow::AttrRead).getObject() = instance() and
83+
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
84+
}
85+
86+
/**
87+
* Extra taint propagation for `pydantic.BaseModel` subclasses. (note that these could also be `pydantic.BaseModel` subclasses)
88+
*/
89+
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
90+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
91+
// attributes (such as `model.foo`)
92+
nodeFrom = instance() and
93+
nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom
94+
or
95+
// subscripts on attributes (such as `model.foo[0]`)
96+
nodeFrom.(DataFlow::AttrRead).getObject() = instance() and
97+
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
98+
}
99+
}
100+
}
101+
}

python/ql/test/library-tests/frameworks/fastapi/taint_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ async def test_taint(name : str, number : int, also_input: MyComplexModel): # $
2929
number, # $ tainted
3030

3131
also_input, # $ tainted
32-
also_input.field, # $ MISSING: tainted
32+
also_input.field, # $ tainted
3333

34-
also_input.main_foo, # $ MISSING: tainted
35-
also_input.main_foo.foo, # $ MISSING: tainted
34+
also_input.main_foo, # $ tainted
35+
also_input.main_foo.foo, # $ tainted
3636

37-
also_input.other_foos, # $ MISSING: tainted
38-
also_input.other_foos[0], # $ MISSING: tainted
39-
also_input.other_foos[0].foo, # $ MISSING: tainted
37+
also_input.other_foos, # $ tainted
38+
also_input.other_foos[0], # $ tainted
39+
also_input.other_foos[0].foo, # $ tainted
4040
[f.foo for f in also_input.other_foos], # $ MISSING: tainted
4141
)
4242

0 commit comments

Comments
 (0)