Skip to content

Commit df67028

Browse files
committed
Python: Model aiohttp.StreamReader
1 parent 2d31ef7 commit df67028

File tree

2 files changed

+76
-8
lines changed

2 files changed

+76
-8
lines changed

python/ql/src/semmle/python/frameworks/Aiohttp.qll

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,66 @@ module AiohttpWebModel {
295295
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
296296
}
297297

298+
/**
299+
* Provides models for the `aiohttp.StreamReader` class
300+
*
301+
* See https://docs.aiohttp.org/en/stable/streams.html#aiohttp.StreamReader
302+
*/
303+
module StreamReader {
304+
/**
305+
* A source of instances of `aiohttp.StreamReader`, extend this class to model new instances.
306+
*
307+
* This can include instantiations of the class, return values from function
308+
* calls, or a special parameter that will be set when functions are called by an external
309+
* library.
310+
*
311+
* Use `StreamReader::instance()` predicate to get
312+
* references to instances of `aiohttp.StreamReader`.
313+
*/
314+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
315+
316+
/** Gets a reference to an instance of `aiohttp.StreamReader`. */
317+
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
318+
t.start() and
319+
result instanceof InstanceSource
320+
or
321+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
322+
}
323+
324+
/** Gets a reference to an instance of `aiohttp.StreamReader`. */
325+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
326+
327+
/**
328+
* Taint propagation for `aiohttp.StreamReader`.
329+
*/
330+
private class AiohttpStreamReaderAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
331+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
332+
// Methods
333+
//
334+
// TODO: When we have tools that make it easy, model these properly to handle
335+
// `meth = obj.meth; meth()`. Until then, we'll use this more syntactic approach
336+
// (since it allows us to at least capture the most common cases).
337+
nodeFrom = StreamReader::instance() and
338+
exists(DataFlow::AttrRead attr | attr.getObject() = nodeFrom |
339+
// normal methods
340+
attr.getAttributeName() in ["read_nowait"] and
341+
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
342+
or
343+
// async methods
344+
exists(Await await, DataFlow::CallCfgNode call |
345+
attr.getAttributeName() in [
346+
"read", "readany", "readexactly", "readline", "readchunk", "iter_chunked",
347+
"iter_any", "iter_chunks"
348+
] and
349+
call.getFunction() = attr and
350+
await.getValue() = call.asExpr() and
351+
nodeTo.asExpr() = await
352+
)
353+
)
354+
}
355+
}
356+
}
357+
298358
/**
299359
* A parameter that will receive an `aiohttp.web.Request` instance when a request
300360
* handler is invoked.
@@ -395,6 +455,14 @@ module AiohttpWebModel {
395455
}
396456
}
397457

458+
/** An attribute read on an `aiohttp.web.Request` that is a `aiohttp.StreamReader` instance. */
459+
class AiohttpRequestStreamReaderInstances extends StreamReader::InstanceSource {
460+
AiohttpRequestStreamReaderInstances() {
461+
this.(DataFlow::AttrRead).getObject() = Request::instance() and
462+
this.(DataFlow::AttrRead).getAttributeName() in ["content", "_payload"]
463+
}
464+
}
465+
398466
// ---------------------------------------------------------------------------
399467
// aiohttp.web Response modeling
400468
// ---------------------------------------------------------------------------

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,21 @@ async def test_taint(request: web.Request): # $ requestHandler
4949
# aiohttp.StreamReader
5050
# see https://docs.aiohttp.org/en/stable/streams.html#aiohttp.StreamReader
5151
request.content, # $ tainted
52-
await request.content.read(), # $ MISSING: tainted
53-
await request.content.readany(), # $ MISSING: tainted
54-
await request.content.readexactly(42), # $ MISSING: tainted
55-
await request.content.readline(), # $ MISSING: tainted
56-
await request.content.readchunk(), # $ MISSING: tainted
57-
(await request.content.readchunk())[0], # $ MISSING: tainted
52+
await request.content.read(), # $ tainted
53+
await request.content.readany(), # $ tainted
54+
await request.content.readexactly(42), # $ tainted
55+
await request.content.readline(), # $ tainted
56+
await request.content.readchunk(), # $ tainted
57+
(await request.content.readchunk())[0], # $ tainted
5858
[line async for line in request.content], # $ MISSING: tainted
5959
[data async for data in request.content.iter_chunked(1024)], # $ MISSING: tainted
6060
[data async for data in request.content.iter_any()], # $ MISSING: tainted
6161
[data async for data, _ in request.content.iter_chunks()], # $ MISSING: tainted
62-
request.content.read_nowait(), # $ MISSING: tainted
62+
request.content.read_nowait(), # $ tainted
6363

6464
# aiohttp.StreamReader
6565
request._payload, # $ tainted
66-
await request._payload.readany(), # $ MISSING: tainted
66+
await request._payload.readany(), # $ tainted
6767

6868
request.content_type, # $ tainted
6969
request.charset, # $ tainted

0 commit comments

Comments
 (0)