Skip to content

Commit b79bb72

Browse files
committed
Ruby: split up CipherInstantiation charpred
1 parent 2bd25da commit b79bb72

File tree

1 file changed

+117
-63
lines changed

1 file changed

+117
-63
lines changed

ruby/ql/lib/codeql/ruby/security/OpenSSL.qll

Lines changed: 117 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -363,80 +363,134 @@ private class CipherMode extends TCipherMode {
363363
predicate isWeak() { isWeakBlockMode(this.getBlockMode()) }
364364
}
365365

366-
// Convenience methods for getting constant value arguments in cipher instantiation
367-
private class CipherCallNode extends DataFlow::CallNode {
368-
string getStringArgument(int i) {
369-
result = super.getArgument(i).asExpr().getConstantValue().getStringOrSymbol()
370-
}
366+
private string getStringArgument(DataFlow::CallNode call, int i) {
367+
result = call.getArgument(i).asExpr().getConstantValue().getStringOrSymbol()
368+
}
371369

372-
int getIntArgument(int i) { result = super.getArgument(i).asExpr().getConstantValue().getInt() }
370+
private int getIntArgument(DataFlow::CallNode call, int i) {
371+
result = call.getArgument(i).asExpr().getConstantValue().getInt()
373372
}
374373

375-
/** A call to `OpenSSL::Cipher.new` or similar. */
376-
private class CipherInstantiation extends CipherCallNode {
377-
private OpenSSLCipher cipher;
378-
private CipherMode cipherMode;
374+
/**
375+
* Holds if `call` is a call to `OpenSSL::Cipher.new` that instantiates a
376+
* `cipher` instance with mode `cipherMode`.
377+
*/
378+
private predicate cipherInstantiationGeneric(
379+
DataFlow::CallNode call, OpenSSLCipher cipher, CipherMode cipherMode
380+
) {
381+
exists(string cipherName | cipher.matchesName(cipherName) |
382+
// `OpenSSL::Cipher.new('<cipherName>')`
383+
call = cipherApi().getAnInstantiation() and
384+
cipherName = getStringArgument(call, 0) and
385+
// CBC is used by default
386+
cipherMode.isBlockMode("CBC")
387+
)
388+
}
379389

380-
CipherInstantiation() {
381-
exists(string cipherName | cipher.matchesName(cipherName) |
382-
// `OpenSSL::Cipher.new('<cipherName>')`
383-
this = cipherApi().getAnInstantiation() and
384-
cipherName = this.getStringArgument(0) and
385-
// CBC is used by default
386-
cipherMode.isBlockMode("CBC")
390+
/**
391+
* Holds if `call` is a call to `OpenSSL::Cipher::AES.new` or
392+
* `OpenSSL::Cipher::AES{128,192,256}.new` that instantiates an AES `cipher` instance
393+
* with mode `cipherMode`.
394+
*/
395+
private predicate cipherInstantiationAES(
396+
DataFlow::CallNode call, OpenSSLCipher cipher, CipherMode cipherMode
397+
) {
398+
exists(string cipherName | cipher.matchesName(cipherName) |
399+
// `OpenSSL::Cipher::AES` instantiations
400+
call = cipherApi().getMember("AES").getAnInstantiation() and
401+
exists(string keyLength, string blockMode |
402+
// `OpenSSL::Cipher::AES.new('<keyLength-blockMode>')
403+
exists(string arg0 |
404+
arg0 = getStringArgument(call, 0) and
405+
keyLength = arg0.splitAt("-", 0) and
406+
blockMode = arg0.splitAt("-", 1).toUpperCase()
407+
)
387408
or
388-
// `OpenSSL::Cipher::AES` instantiations
389-
this = cipherApi().getMember("AES").getAnInstantiation() and
390-
exists(string keyLength, string blockMode |
391-
// `OpenSSL::Cipher::AES.new('<keyLength-blockMode>')
392-
exists(string arg0 |
393-
arg0 = this.getStringArgument(0) and
394-
keyLength = arg0.splitAt("-", 0) and
395-
blockMode = arg0.splitAt("-", 1).toUpperCase()
396-
)
397-
or
398-
// `OpenSSL::Cipher::AES.new(<keyLength>, '<blockMode>')`
399-
keyLength = this.getIntArgument(0).toString() and
400-
blockMode = this.getStringArgument(1).toUpperCase()
409+
// `OpenSSL::Cipher::AES.new(<keyLength>, '<blockMode>')`
410+
keyLength = getIntArgument(call, 0).toString() and
411+
blockMode = getStringArgument(call, 1).toUpperCase()
412+
|
413+
cipherName = "AES-" + keyLength + "-" + blockMode and
414+
cipherMode.isBlockMode(blockMode)
415+
)
416+
or
417+
// Modules for AES with specific key lengths
418+
exists(string mod, string blockAlgo | mod = ["AES128", "AES192", "AES256"] |
419+
call = cipherApi().getMember(mod).getAnInstantiation() and
420+
// Canonical representation is `AES-<keyLength>`
421+
blockAlgo = "AES-" + mod.suffix(3) and
422+
exists(string blockMode |
423+
if exists(getStringArgument(call, 0))
424+
then
425+
// `OpenSSL::Cipher::<blockAlgo>.new('<blockMode>')`
426+
blockMode = getStringArgument(call, 0).toUpperCase()
427+
else
428+
// `OpenSSL::Cipher::<blockAlgo>.new` uses CBC by default
429+
blockMode = "CBC"
401430
|
402-
cipherName = "AES-" + keyLength + "-" + blockMode and
431+
cipherName = blockAlgo + "-" + blockMode and
403432
cipherMode.isBlockMode(blockMode)
404433
)
405-
or
406-
// RC4 stream cipher
407-
this = cipherApi().getMember("RC4").getAnInstantiation() and
408-
cipherMode = TStreamCipher() and
409-
(
410-
if exists(this.getStringArgument(0))
411-
then cipherName = "RC4-" + this.getStringArgument(0).toUpperCase()
412-
else cipherName = "RC4"
413-
)
414-
or
415-
// Block ciphers with dedicated modules
416-
exists(string mod, string blockAlgo |
417-
mod = ["AES128", "AES192", "AES256", "BF", "CAST5", "DES", "IDEA", "RC2"]
434+
)
435+
)
436+
}
437+
438+
/**
439+
* Holds if `call` is a call that instantiates an OpenSSL cipher using a module
440+
* specific to a block encryption algorithm, e.g. Blowfish, DES, etc.
441+
*/
442+
private predicate cipherInstantiationSpecific(
443+
DataFlow::CallNode call, OpenSSLCipher cipher, CipherMode cipherMode
444+
) {
445+
exists(string cipherName | cipher.matchesName(cipherName) |
446+
// Block ciphers with dedicated modules
447+
exists(string blockAlgo | blockAlgo = ["BF", "CAST5", "DES", "IDEA", "RC2"] |
448+
call = cipherApi().getMember(blockAlgo).getAnInstantiation() and
449+
exists(string blockMode |
450+
if exists(getStringArgument(call, 0))
451+
then
452+
// `OpenSSL::Cipher::<blockAlgo>.new('<blockMode>')`
453+
blockMode = getStringArgument(call, 0).toUpperCase()
454+
else
455+
// `OpenSSL::Cipher::<blockAlgo>.new` uses CBC by default
456+
blockMode = "CBC"
418457
|
419-
this = cipherApi().getMember(mod).getAnInstantiation() and
420-
(
421-
// The `AES<keyLength>` modules are a special case in terms of naming
422-
if mod = ["AES128", "AES192", "AES256"]
423-
then blockAlgo = "AES-" + mod.suffix(3)
424-
else blockAlgo = mod
425-
) and
426-
exists(string blockMode |
427-
if exists(this.getStringArgument(0))
428-
then
429-
// `OpenSSL::Cipher::<blockAlgo>.new('<blockMode>')`
430-
blockMode = this.getStringArgument(0).toUpperCase()
431-
else
432-
// `OpenSSL::Cipher::<blockAlgo>.new` uses CBC by default
433-
blockMode = "CBC"
434-
|
435-
cipherName = blockAlgo + "-" + blockMode and
436-
cipherMode.isBlockMode(blockMode)
437-
)
458+
cipherName = blockAlgo + "-" + blockMode and
459+
cipherMode.isBlockMode(blockMode)
438460
)
439461
)
462+
)
463+
}
464+
465+
/**
466+
* Holds if `call` is a call to `OpenSSL::Cipher::RC4.new` or an RC4 `cipher`
467+
* instance with mode `cipherMode`.
468+
*/
469+
private predicate cipherInstantiationRC4(
470+
DataFlow::CallNode call, OpenSSLCipher cipher, CipherMode cipherMode
471+
) {
472+
exists(string cipherName | cipher.matchesName(cipherName) |
473+
// RC4 stream cipher
474+
call = cipherApi().getMember("RC4").getAnInstantiation() and
475+
cipherMode = TStreamCipher() and
476+
(
477+
if exists(getStringArgument(call, 0))
478+
then cipherName = "RC4-" + getStringArgument(call, 0).toUpperCase()
479+
else cipherName = "RC4"
480+
)
481+
)
482+
}
483+
484+
/** A call to `OpenSSL::Cipher.new` or similar. */
485+
private class CipherInstantiation extends DataFlow::CallNode {
486+
private OpenSSLCipher cipher;
487+
private CipherMode cipherMode;
488+
489+
CipherInstantiation() {
490+
cipherInstantiationGeneric(this, cipher, cipherMode) or
491+
cipherInstantiationAES(this, cipher, cipherMode) or
492+
cipherInstantiationSpecific(this, cipher, cipherMode) or
493+
cipherInstantiationRC4(this, cipher, cipherMode)
440494
}
441495

442496
/** Gets the `OpenSSLCipher` associated with this instance. */

0 commit comments

Comments
 (0)