Skip to content

Commit fa88f22

Browse files
committed
Python: Model hashing operations in cryptography package
1 parent c5f8265 commit fa88f22

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,81 @@ private module CryptographyModel {
330330
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
331331
}
332332
}
333+
334+
/** Provides models for the `cryptography.hazmat.primitives.hashes` package */
335+
private module Hashes {
336+
/**
337+
* Gets a reference to a `cryptography.hazmat.primitives.hashes` class, representing
338+
* a hashing algorithm.
339+
*/
340+
API::Node algorithmClassRef(string algorithmName) {
341+
result =
342+
API::moduleImport("cryptography")
343+
.getMember("hazmat")
344+
.getMember("primitives")
345+
.getMember("hashes")
346+
.getMember(algorithmName)
347+
}
348+
349+
/** Gets a reference to a Hash instance using algorithm with `algorithmName`. */
350+
private DataFlow::LocalSourceNode hashInstance(DataFlow::TypeTracker t, string algorithmName) {
351+
t.start() and
352+
exists(DataFlow::CallCfgNode call | result = call |
353+
call =
354+
API::moduleImport("cryptography")
355+
.getMember("hazmat")
356+
.getMember("primitives")
357+
.getMember("hashes")
358+
.getMember("Hash")
359+
.getACall() and
360+
algorithmClassRef(algorithmName).getReturn().getAUse() in [
361+
call.getArg(0), call.getArgByName("algorithm")
362+
]
363+
)
364+
or
365+
// Due to bad performance when using normal setup with `hashInstance(t2, algorithmName).track(t2, t)`
366+
// we have inlined that code and forced a join
367+
exists(DataFlow::TypeTracker t2 |
368+
exists(DataFlow::StepSummary summary |
369+
hashInstance_first_join(t2, algorithmName, result, summary) and
370+
t = t2.append(summary)
371+
)
372+
)
373+
}
374+
375+
pragma[nomagic]
376+
private predicate hashInstance_first_join(
377+
DataFlow::TypeTracker t2, string algorithmName, DataFlow::Node res,
378+
DataFlow::StepSummary summary
379+
) {
380+
DataFlow::StepSummary::step(hashInstance(t2, algorithmName), res, summary)
381+
}
382+
383+
/** Gets a reference to a Hash instance using algorithm with `algorithmName`. */
384+
DataFlow::Node hashInstance(string algorithmName) {
385+
hashInstance(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
386+
}
387+
388+
/**
389+
* An hashing operation from `cryptography.hazmat.primitives.hashes`.
390+
*/
391+
class CryptographyGenericHashOperation extends Cryptography::CryptographicOperation::Range,
392+
DataFlow::CallCfgNode {
393+
string algorithmName;
394+
395+
CryptographyGenericHashOperation() {
396+
exists(DataFlow::AttrRead attr |
397+
this.getFunction() = attr and
398+
attr.getAttributeName() = "update" and
399+
attr.getObject() = hashInstance(algorithmName)
400+
)
401+
}
402+
403+
override Cryptography::CryptographicAlgorithm getAlgorithm() {
404+
result.matchesName(algorithmName)
405+
}
406+
407+
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
408+
}
409+
}
333410
}

python/ql/test/experimental/library-tests/frameworks/cryptography/test_md5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
hasher = hashes.Hash(hashes.MD5())
7-
hasher.update(b"secret message") # $ MISSING: CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
7+
hasher.update(b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
88

99
digest = hasher.finalize()
1010
print(hexlify(digest).decode('utf-8'))

0 commit comments

Comments
 (0)