-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Quantum: Add OpenSSL signature models #19705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Quantum: Add OpenSSL signature models #19705
Conversation
…calls that return a known algorithm and calls that operate on a known algorithm. update KnownAlgorithmConstants to correct algType for signature algorithms. Update all instances and prior uses of the old mechanic to KnownAlgorithmConstants.
… that is an argument or a return.
…dd initial signature tests (no expected files yet). Add new openssl .h stubs. Clean up of OperationBase and associated uses. Update test case stubs to be closer to the actual stubs. Fix unncessary instanceof check in signatures.
…tep to handle paramgen. Remove redundant test. Overhaul of EVP update/initializer/final mechanics. Misc. updates for new API and refactoring EVPKeyGenOperation. Clean up of keygen_operaitons.ql.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
… initializer subclasses.
…del update. Remove setting RSA bits as an RSA algorithm. Fix bug in hash algorithm. Add missing PKey encryption to cipher ops. Consolidate ctx initializers. Add unit tests, and alter unit test directory structure to allow for application to other APIs. Update expected files for unit tests (not all updated yet, a work in progress).
…properties that configure downstream operations. Add key size tests
…to disallow null key and IV on initializers (typically do not represent an actual key or IV).
…. Update model to account for MAC algorithms.
…n and correct handling of MACs.
… for now and noting the issue for a future PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a brief review -- nothing semantic other than verifying that results look reasonable. There are other instances of the same code pattern issues I left some example comments for and open QL-for-QL alerts.
@@ -29,7 +29,7 @@ predicate knownOpenSSLConstantToHashFamilyType( | |||
or | |||
name.matches(["SHA", "SHA1"]) and type instanceof Crypto::SHA1 | |||
or | |||
name.matches("SHA+%") and not name.matches(["SHA1", "SHA3-"]) and type instanceof Crypto::SHA2 | |||
name.matches("SHA_%") and not name.matches(["SHA1", "SHA3-"]) and type instanceof Crypto::SHA2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is redundant. SHA_ cannot match SHA1 or SHA3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _
in matches means match one character, not any character (+ vs *), so it isn't redundant. The issue was I accidentally used the '+' syntax previously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! Four years of writing CodeQL, and I just learned that now...
Holds when the receiver matches the pattern. Patterns are matched by case sensitive string matching, and there are two wildcards: _ matches a single character, and % matches any sequence of characters.
Anyway, isn't _%
redundant then? Just %
should do the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I basically didn't want to match just "sha" and call it sha2. I wrote this awhile back so there is probably a better way, but basically it would not be correct to just use % because of that.
this instanceof DirectAlgorithmValueConsumer and getterCall = this | ||
this instanceof OpenSSLAlgorithmCall and | ||
this instanceof DirectAlgorithmValueConsumer and | ||
getterCall = this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this ever hold? This class does not extend OpenSSLAlgorithmValueConsumer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy Pasta? I'll fix this.
//ASSUMPTION: these cases will have operands for the call | ||
not exists(this.(Call).getAnArgument()) and | ||
exists(string name, string parsedTargetName | | ||
parsedTargetName = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing name normalization done outside of the model raises red flags for me. Does OpenSSL have the same algorithm specified in various places with either underscores or dashes? If so, that makes sense I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, why not change the matching logic for the OpenSSL-modelling-to-model-type mapping to have a wildcard on underscores or dashes rather than normalize it at the instance-modelling stage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From prior investigations, it appeared openssl direct algorithm fetches use underscores, whereas the constants OpenSSL uses for algorithms uses dashes. The constants (the strings) are mapped to NID with dashes, so this normalization attempts to take function names and normalize them to the other kinds of constant strings. I'm open to other suggestions here but it is probably best suited for a separate PR.
//TODO: this set will have to be exhaustive, and for each operation | ||
//further modeling will be necessary for each case to map the APIs operands | ||
//ASSUMPTION: these cases must have operands for the call | ||
exists(this.(Call).getAnArgument()) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it need to have an argument? What would those arguments be? Quick evaluating doesn't really answer my question at first glance either, so could you please clarify the documentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me preface this by saying how we handle direct operation calls is something that will need some work.
But, what I've done so far is to distinguish direct fetching calls (e.g., something like AES_256(), which would return an algorithm container of some kind), from operation calls (e.g., AES_256_encrypt(....)). In the former, we never see arguments passed in because you are just getting an algorithm directly. For the latter, it is an assumed operation, meaning you have to pass a parameter somehow to operate on data.
Does that answer your question?
exists(this.(Call).getAnArgument()) and | ||
//TODO: Each case would be enumerated here. Will likely need an exhaustive mapping much like | ||
// for known constants. | ||
knownOpenSSLAlgorithmOperationCall(this, normalizedName, algType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is no normalized name or algorithm type identified for this
, the instance will simply not exist. We want to report known unknowns as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per my comment above. How we handle direct operations needs a general revision. What we currently have is a POC for some of the work done with signatures, and even then it was partial. In the prior work (the PR we started from) example signatures were occurring using some direct operations. I want to open an issue to entirely refactor this, but for now it is a breadcrumb.
private class CtxPointerReturn extends CtxPointerExpr { | ||
CtxPointerReturn() { exists(Call c | c = this) } | ||
|
||
Call getCall() { result = this.(Call) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call getCall() { result = this.(Call) } | |
Call getCall() { result = this } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check
} | ||
|
||
predicate isSink(DataFlow::Node sink) { | ||
exists(Call call | call.(Call).getAnArgument() = sink.asExpr()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exists(Call call | call.(Call).getAnArgument() = sink.asExpr()) | |
exists(Call call | call.getAnArgument() = sink.asExpr()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check
} | ||
} | ||
|
||
class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer { | |
class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer instanceof Call { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you follow the inheritance back up, it is already an instanceof call.
} | ||
|
||
class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer { | ||
EVP_CipherInit_SKEY_Call() { this.(Call).getTarget().getName() in ["EVP_CipherInit_SKEY"] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EVP_CipherInit_SKEY_Call() { this.(Call).getTarget().getName() in ["EVP_CipherInit_SKEY"] } | |
EVP_CipherInit_SKEY_Call() { super.getTarget().hasName("EVP_CipherInit_SKEY") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could do this with the extends call you mentioned above I suppose but it already does extend a call so it's all very odd I have to restate it. If you prefer that though I can do it, but I just wanted to bring to your attention it is already explicitly instanceof call.
class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer { | ||
EVP_CipherInit_SKEY_Call() { this.(Call).getTarget().getName() in ["EVP_CipherInit_SKEY"] } | ||
|
||
override Expr getKeyOperationSubtypeArg() { result = this.(Call).getArgument(5) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
override Expr getKeyOperationSubtypeArg() { result = this.(Call).getArgument(5) } | |
override Expr getKeyOperationSubtypeArg() { result = super.getArgument(5) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments above.
…des/codeql into pawel_signatures_conversion
No description provided.