Merge branch 'main' into redsun82/cargo-upgrade-2

This commit is contained in:
Paolo Tranquilli
2025-08-18 15:55:15 +02:00
70 changed files with 851 additions and 46 deletions

View File

@@ -14,6 +14,9 @@ import semmle.code.cpp.ConfigurationTestFile
from GlobalVariable gv
where
gv.getName().length() <= 3 and
// We will give an alert for the TemplateVariable, so we don't
// need to also give one for each instantiation
not gv instanceof VariableTemplateInstantiation and
not gv.isStatic() and
not gv.getFile() instanceof ConfigurationTestFile // variables in files generated during configuration are likely false positives
select gv,

View File

@@ -82,6 +82,16 @@ module OverflowDestinationConfig implements DataFlow::ConfigSig {
nodeIsBarrierEqualityCandidate(node, access, checkedVar)
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(FunctionCall fc | result = fc.getLocation() |
sourceSized(fc, sink.asIndirectConvertedExpr())
)
}
}
module OverflowDestination = TaintTracking::Global<OverflowDestinationConfig>;

View File

@@ -168,6 +168,19 @@ module NonConstFlowConfig implements DataFlow::ConfigSig {
cannotContainString(t)
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
result = sink.getLocation()
or
exists(FormattingFunctionCall call, Expr formatString | result = call.getLocation() |
isSinkImpl(sink, formatString) and
call.getArgument(call.getFormatParameterIndex()) = formatString
)
}
}
module NonConstFlow = TaintTracking::Global<NonConstFlowConfig>;

View File

@@ -215,6 +215,10 @@ private module LeapYearCheckConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) {
exists(ChecksForLeapYearFunctionCall fc | sink.asExpr() = fc.getAnArgument())
}
predicate observeDiffInformedIncrementalMode() {
none() // only used negatively in UncheckedLeapYearAfterYearModification.ql
}
}
module LeapYearCheckFlow = DataFlow::Global<LeapYearCheckConfig>;
@@ -285,6 +289,14 @@ private module PossibleYearArithmeticOperationCheckConfig implements DataFlow::C
aexpr.getLValue() = fa
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) {
result = source.asExpr().getLocation()
}
Location getASelectedSinkLocation(DataFlow::Node sink) { result = sink.asExpr().getLocation() }
}
module PossibleYearArithmeticOperationCheckFlow =

View File

@@ -93,6 +93,12 @@ module TaintedPathConfig implements DataFlow::ConfigSig {
// make sinks barriers so that we only report the closest instance
isSink(node)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
result = sink.asIndirectArgument().getLocation()
}
}
module TaintedPath = TaintTracking::Global<TaintedPathConfig>;

View File

@@ -150,6 +150,17 @@ module ExecTaintConfig implements DataFlow::StateConfigSig {
predicate isBarrierOut(DataFlow::Node node) {
isSink(node, _) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(DataFlow::Node concatResult, Expr command, ExecState state |
result = [concatResult.getLocation(), command.getLocation()] and
isSink(sink, state) and
isSinkImpl(sink, command, _) and
concatResult = state.getOutgoingNode()
)
}
}
module ExecTaint = TaintTracking::GlobalWithState<ExecTaintConfig>;

View File

@@ -39,6 +39,12 @@ module Config implements DataFlow::ConfigSig {
or
node.asCertainDefinition().getUnspecifiedType() instanceof ArithmeticType
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) {
exists(QueryString query | result = query.getLocation() | query = source.asIndirectExpr())
}
}
module Flow = TaintTracking::Global<Config>;

View File

@@ -54,6 +54,12 @@ module SqlTaintedConfig implements DataFlow::ConfigSig {
sql.barrierSqlArgument(input, _)
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(Expr taintedArg | result = taintedArg.getLocation() | taintedArg = asSinkExpr(sink))
}
}
module SqlTainted = TaintTracking::Global<SqlTaintedConfig>;

View File

@@ -124,6 +124,12 @@ module Config implements DataFlow::ConfigSig {
// Block flow if the node is guarded by any <, <= or = operations.
node = DataFlow::BarrierGuard<lessThanOrEqual/3>::getABarrierNode()
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(BufferWrite bw | result = bw.getLocation() | isSink(sink, bw, _))
}
}
module Flow = TaintTracking::Global<Config>;

View File

@@ -43,6 +43,12 @@ private module Config implements DataFlow::ConfigSig {
}
predicate isSink(DataFlow::Node sink) { isSink(sink, _) }
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(VariableAccess va | result = va.getLocation() | isSink(sink, va))
}
}
module Flow = TaintTracking::Global<Config>;

View File

@@ -106,6 +106,12 @@ module Config implements DataFlow::ConfigSig {
not iTo instanceof PointerArithmeticInstruction
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(Expr e | result = e.getLocation() | isSink(sink, _, e))
}
}
module Flow = TaintTracking::Global<Config>;

View File

@@ -120,6 +120,12 @@ module UncontrolledArithConfig implements DataFlow::ConfigSig {
// block unintended flow to pointers
node.asExpr().getUnspecifiedType() instanceof PointerType
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) {
result = getExpr(source).getLocation()
}
}
module UncontrolledArith = TaintTracking::Global<UncontrolledArithConfig>;

View File

@@ -113,6 +113,12 @@ module Config implements DataFlow::ConfigSig {
not iTo instanceof PointerArithmeticInstruction
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(VariableAccess va | result = va.getLocation() | isSink(sink, va, _))
}
}
module Flow = TaintTracking::Global<Config>;

View File

@@ -91,6 +91,12 @@ module TaintedAllocationSizeConfig implements DataFlow::ConfigSig {
// to duplicate results)
any(HeuristicAllocationFunction f).getAParameter() = node.asParameter()
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(Expr alloc | result = alloc.getLocation() | allocSink(alloc, sink))
}
}
module TaintedAllocationSize = TaintTracking::Global<TaintedAllocationSizeConfig>;

View File

@@ -72,6 +72,12 @@ module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { isSource(source, _) }
predicate isSink(DataFlow::Node sink) { isSink(sink, _) }
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(Expr condition | result = condition.getLocation() | isSink(sink, condition))
}
}
module Flow = TaintTracking::Global<Config>;

View File

@@ -31,6 +31,14 @@ module VerifyResultConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) {
exists(GuardCondition guard | guard.getAChild*() = sink.asExpr())
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(GuardCondition guard | result = guard.getLocation() |
guard.comparesEq(sink.asExpr(), _, 0, false, _)
)
}
}
module VerifyResult = DataFlow::Global<VerifyResultConfig>;

View File

@@ -47,6 +47,12 @@ module ToBufferConfig implements DataFlow::ConfigSig {
}
predicate isSink(DataFlow::Node sink) { isSinkImpl(sink, _) }
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(SensitiveBufferWrite w | result = w.getLocation() | isSinkImpl(sink, w))
}
}
module ToBufferFlow = TaintTracking::Global<ToBufferConfig>;

View File

@@ -31,6 +31,16 @@ module FromSensitiveConfig implements DataFlow::ConfigSig {
predicate isBarrier(DataFlow::Node node) {
node.asExpr().getUnspecifiedType() instanceof IntegralType
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node sourceNode) {
exists(SensitiveExpr source | result = source.getLocation() | isSourceImpl(sourceNode, source))
}
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(FileWrite w | result = w.getLocation() | isSinkImpl(sink, w, _))
}
}
module FromSensitiveFlow = TaintTracking::Global<FromSensitiveConfig>;

View File

@@ -245,6 +245,14 @@ module FromSensitiveConfig implements DataFlow::ConfigSig {
// sources to not get path duplication.
isSource(node)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(NetworkSendRecv networkSendRecv | result = networkSendRecv.getLocation() |
isSinkSendRecv(sink, networkSendRecv)
)
}
}
module FromSensitiveFlow = TaintTracking::Global<FromSensitiveConfig>;
@@ -266,6 +274,10 @@ module ToEncryptionConfig implements DataFlow::ConfigSig {
// sources to not get path duplication.
isSource(node)
}
predicate observeDiffInformedIncrementalMode() {
none() // only used negatively
}
}
module ToEncryptionFlow = TaintTracking::Global<ToEncryptionConfig>;
@@ -281,6 +293,10 @@ module FromEncryptionConfig implements DataFlow::ConfigSig {
predicate isBarrier(DataFlow::Node node) {
node.asExpr().getUnspecifiedType() instanceof IntegralType
}
predicate observeDiffInformedIncrementalMode() {
none() // only used negatively
}
}
module FromEncryptionFlow = TaintTracking::Global<FromEncryptionConfig>;

View File

@@ -123,6 +123,20 @@ module FromSensitiveConfig implements DataFlow::ConfigSig {
content.(DataFlow::FieldContent).getField() = getRecField(t.stripType())
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) {
exists(SensitiveExpr sensitive | result = sensitive.getLocation() |
isSourceImpl(source, sensitive)
)
}
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(SqliteFunctionCall sqliteCall | result = sqliteCall.getLocation() |
isSinkImpl(sink, sqliteCall, _)
)
}
}
module FromSensitiveFlow = TaintTracking::Global<FromSensitiveConfig>;

View File

@@ -87,6 +87,14 @@ module HttpStringToUrlOpenConfig implements DataFlow::ConfigSig {
sink.asIndirectExpr() = fc.getArgument(3)
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) {
result = source.asIndirectExpr().getLocation()
}
Location getASelectedSinkLocation(DataFlow::Node sink) { none() }
}
module HttpStringToUrlOpen = TaintTracking::Global<HttpStringToUrlOpenConfig>;

View File

@@ -44,6 +44,12 @@ module KeyStrengthFlowConfig implements DataFlow::ConfigSig {
exists(getMinimumKeyStrength(name, param))
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(FunctionCall fc | result = fc.getLocation() | sink.asExpr() = fc.getArgument(_))
}
}
module KeyStrengthFlow = DataFlow::Global<KeyStrengthFlowConfig>;

View File

@@ -145,6 +145,18 @@ module Config implements DataFlow::StateConfigSig {
// ```
result instanceof DataFlow::FeatureHasSinkCallContext
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(DataFlow::Node mid, FlowState state | result = mid.getLocation() |
destroyedToBeginSink(sink) and
isSink(sink, state) and
state = Config::DestroyedToBegin(mid)
)
}
}
module Flow = DataFlow::GlobalWithState<Config>;

View File

@@ -62,6 +62,16 @@ module NullAppNameCreateProcessFunctionConfig implements DataFlow::ConfigSig {
val = call.getArgument(call.getApplicationNameArgumentId())
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(CreateProcessFunctionCall call | result = call.getLocation() |
sink.asExpr() = call.getArgument(call.getApplicationNameArgumentId())
)
}
}
module NullAppNameCreateProcessFunction = DataFlow::Global<NullAppNameCreateProcessFunctionConfig>;
@@ -82,6 +92,16 @@ module QuotedCommandInCreateProcessFunctionConfig implements DataFlow::ConfigSig
val = call.getArgument(call.getCommandLineArgumentId())
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(CreateProcessFunctionCall call | result = call.getLocation() |
sink.asExpr() = call.getArgument(call.getCommandLineArgumentId())
)
}
}
module QuotedCommandInCreateProcessFunction =

View File

@@ -37,6 +37,16 @@ module NullDaclConfig implements DataFlow::ConfigSig {
val = call.getArgument(2)
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(SetSecurityDescriptorDaclFunctionCall call | result = call.getLocation() |
sink.asExpr() = call.getArgument(2)
)
}
}
module NullDaclFlow = DataFlow::Global<NullDaclConfig>;
@@ -68,6 +78,10 @@ module NonNullDaclConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) {
exists(SetSecurityDescriptorDaclFunctionCall call | sink.asExpr() = call.getArgument(2))
}
predicate observeDiffInformedIncrementalMode() {
none() // only used negatively
}
}
module NonNullDaclFlow = DataFlow::Global<NonNullDaclConfig>;

View File

@@ -65,6 +65,16 @@ module Config implements DataFlow::ConfigSig {
iFrom1 != iFrom2
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
result = sink.getLocation()
or
exists(Expr raise | result = raise.getLocation() |
sensitiveCondition([sink.asExpr(), sink.asIndirectExpr()], raise)
)
}
}
module Flow = TaintTracking::Global<Config>;

View File

@@ -178,6 +178,10 @@ module Config implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(UnsafeCast cast).getUnconverted() }
int fieldFlowBranchLimit() { result = 0 }
predicate observeDiffInformedIncrementalMode() {
none() // used both positively and negatively
}
}
module Flow = DataFlow::Global<Config>;

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The `cpp/short-global-name` query will no longer give alerts for instantiations of template variables, only for the template itself.

View File

@@ -183,6 +183,20 @@ module ArrayAddressToDerefConfig implements DataFlow::StateConfigSig {
pointerArithOverflow(pai, _)
)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) {
exists(Variable v | result = v.getLocation() | isSourceImpl(source, v))
}
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(PointerArithmeticInstruction pai, Instruction deref |
result = [pai, deref].getLocation() and
isInvalidPointerDerefSink2(sink, deref, _) and
isSink(sink, ArrayAddressToDerefConfig::TOverflowArithmetic(pai))
)
}
}
module ArrayAddressToDerefFlow = DataFlow::GlobalWithState<ArrayAddressToDerefConfig>;

View File

@@ -28,6 +28,14 @@ module DecompressionTaintConfig implements DataFlow::ConfigSig {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
any(DecompressionFlowStep s).isAdditionalFlowStep(node1, node2)
}
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
exists(FunctionCall fc | result = [sink.getLocation(), fc.getLocation()] | isSink(fc, sink))
}
}
module DecompressionTaint = TaintTracking::Global<DecompressionTaintConfig>;

View File

@@ -1,2 +1,7 @@
| main.cpp:3:5:3:5 | x | Poor global variable name 'x'. Prefer longer, descriptive names for globals (eg. kMyGlobalConstant, not foo). |
| main.cpp:4:5:4:6 | ys | Poor global variable name 'ys'. Prefer longer, descriptive names for globals (eg. kMyGlobalConstant, not foo). |
| main.cpp:9:5:9:6 | v1 | Poor global variable name 'v1'. Prefer longer, descriptive names for globals (eg. kMyGlobalConstant, not foo). |
| main.cpp:10:5:10:6 | v2 | Poor global variable name 'v2'. Prefer longer, descriptive names for globals (eg. kMyGlobalConstant, not foo). |
| main.cpp:12:5:12:5 | v3 | Poor global variable name 'v3'. Prefer longer, descriptive names for globals (eg. kMyGlobalConstant, not foo). |
| main.cpp:14:5:14:5 | v4 | Poor global variable name 'v4'. Prefer longer, descriptive names for globals (eg. kMyGlobalConstant, not foo). |
| main.cpp:16:5:16:5 | v5 | Poor global variable name 'v5'. Prefer longer, descriptive names for globals (eg. kMyGlobalConstant, not foo). |

View File

@@ -5,3 +5,19 @@ int ys[1000000]; // BAD: too short
int descriptive_name; // GOOD: sufficient
static int z; // GOOD: not a global
int v1; // BAD: too short
int v2; // BAD: too short
template <typename T>
T v3; // BAD: too short
template <typename T>
T v4; // BAD: too short
template <typename T>
T v5; // BAD: too short
void use_some_fs() {
v2 = 100;
v4<int> = 200;
v5<int> = 300;
v5<const char *> = "string";
}

View File

@@ -0,0 +1,121 @@
typedef unsigned long size_t;
typedef struct sqlite3 sqlite3;
typedef struct sqlite3_stmt sqlite3_stmt;
typedef struct sqlite3_str sqlite3_str;
int snprintf(char *str, size_t size, const char *format, ...);
int sqlite3_open(const char *filename, sqlite3 **ppDb);
int sqlite3_close(sqlite3*);
int sqlite3_exec(sqlite3*, const char *sql, int (*callback)(void*,int,char**,char**), void *, char **errmsg);
int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail);
int sqlite3_step(sqlite3_stmt*);
int sqlite3_finalize(sqlite3_stmt*);
int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
sqlite3_str* sqlite3_str_new(sqlite3*);
void sqlite3_str_appendf(sqlite3_str*, const char *zFormat, ...);
char* sqlite3_str_finish(sqlite3_str*);
#define SQLITE_TRANSIENT ((void(*)(void*))-1)
// Simulate a sensitive value
const char* getSensitivePassword() {
return "super_secret_password";
}
void storePasswordCleartext(sqlite3* db, const char* password) {
// BAD: Storing sensitive data in cleartext
char sql[256];
// Unsafe: no escaping, for test purposes only
snprintf(sql, sizeof(sql), "INSERT INTO users(password) VALUES('%s');", password); // $ Source
char* errMsg = 0;
sqlite3_exec(db, sql, 0, 0, &errMsg); // $ Alert
}
void storePasswordWithPrepare(sqlite3* db, const char* password) {
// BAD: Storing sensitive data in cleartext using sqlite3_prepare
char sql[256];
snprintf(sql, sizeof(sql), "INSERT INTO users(password) VALUES('%s');", password); // $ Source
sqlite3_stmt* stmt = 0;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0); // $ Alert
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
void storePasswordWithBind(sqlite3* db, const char* password) {
// BAD: Storing sensitive data in cleartext using sqlite3_bind_text
const char* sql = "INSERT INTO users(password) VALUES(?);";
sqlite3_stmt* stmt = 0;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, password, -1, SQLITE_TRANSIENT); // $ Alert
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
void storePasswordWithAppendf(sqlite3_str* pStr, const char* password) {
// BAD: Storing sensitive data in cleartext using sqlite3_str_appendf
sqlite3_str_appendf(pStr, "INSERT INTO users(password) VALUES('%s');", password); // $ Alert
}
// Example sanitizer: hashes the sensitive value before storage
void hashSensitiveValue(const char* input, char* output, size_t outSize) {
// Dummy hash for illustration (not cryptographically secure)
unsigned int hash = 5381;
for (const char* p = input; *p; ++p)
hash = ((hash << 5) + hash) + (unsigned char)(*p);
snprintf(output, outSize, "%u", hash);
}
void storeSanitizedPasswordCleartext(sqlite3* db, const char* password) {
// GOOD: Sanitizing sensitive data before storage
char hashed[64];
hashSensitiveValue(password, hashed, sizeof(hashed));
char sql[256];
snprintf(sql, sizeof(sql), "INSERT INTO users(password) VALUES('%s');", hashed);
char* errMsg = 0;
sqlite3_exec(db, sql, 0, 0, &errMsg);
}
void storeSanitizedPasswordWithBind(sqlite3* db, const char* password) {
// GOOD: Sanitizing sensitive data before storage with bind
char hashed[64];
hashSensitiveValue(password, hashed, sizeof(hashed));
const char* sql = "INSERT INTO users(password) VALUES(?);";
sqlite3_stmt* stmt = 0;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, hashed, -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
void storeSanitizedPasswordWithAppendf(sqlite3_str* pStr, const char* password) {
// GOOD: Sanitizing sensitive data before storage with appendf
char hashed[64];
hashSensitiveValue(password, hashed, sizeof(hashed));
sqlite3_str_appendf(pStr, "INSERT INTO users(password) VALUES('%s');", hashed);
}
int main() {
sqlite3* db = 0;
sqlite3_open(":memory:", &db);
// Create table
const char* createTableSQL = "CREATE TABLE users(id INTEGER PRIMARY KEY, password TEXT);";
sqlite3_exec(db, createTableSQL, 0, 0, 0);
const char* sensitive = getSensitivePassword();
storePasswordCleartext(db, sensitive);
storePasswordWithPrepare(db, sensitive);
storePasswordWithBind(db, sensitive);
storeSanitizedPasswordCleartext(db, sensitive);
storeSanitizedPasswordWithBind(db, sensitive);
// If sqlite3_str is available
sqlite3_str* pStr = sqlite3_str_new(db);
storePasswordWithAppendf(pStr, sensitive);
storeSanitizedPasswordWithAppendf(pStr, sensitive);
sqlite3_str_finish(pStr);
sqlite3_close(db);
return 0;
}

View File

@@ -0,0 +1,16 @@
#select
| CleartextSqliteDatabase.cpp:31:5:31:16 | call to sqlite3_exec | CleartextSqliteDatabase.cpp:29:77:29:84 | password | CleartextSqliteDatabase.cpp:31:22:31:24 | *sql | This SQLite call may store $@ in a non-encrypted SQLite database. | CleartextSqliteDatabase.cpp:29:77:29:84 | password | sensitive information |
| CleartextSqliteDatabase.cpp:39:5:39:22 | call to sqlite3_prepare_v2 | CleartextSqliteDatabase.cpp:37:77:37:84 | password | CleartextSqliteDatabase.cpp:39:28:39:30 | *sql | This SQLite call may store $@ in a non-encrypted SQLite database. | CleartextSqliteDatabase.cpp:37:77:37:84 | password | sensitive information |
| CleartextSqliteDatabase.cpp:49:5:49:21 | call to sqlite3_bind_text | CleartextSqliteDatabase.cpp:49:32:49:39 | password | CleartextSqliteDatabase.cpp:49:32:49:39 | password | This SQLite call may store $@ in a non-encrypted SQLite database. | CleartextSqliteDatabase.cpp:49:32:49:39 | password | sensitive information |
| CleartextSqliteDatabase.cpp:56:5:56:23 | call to sqlite3_str_appendf | CleartextSqliteDatabase.cpp:56:76:56:83 | password | CleartextSqliteDatabase.cpp:56:76:56:83 | password | This SQLite call may store $@ in a non-encrypted SQLite database. | CleartextSqliteDatabase.cpp:56:76:56:83 | password | sensitive information |
edges
| CleartextSqliteDatabase.cpp:29:77:29:84 | password | CleartextSqliteDatabase.cpp:31:22:31:24 | *sql | provenance | TaintFunction |
| CleartextSqliteDatabase.cpp:37:77:37:84 | password | CleartextSqliteDatabase.cpp:39:28:39:30 | *sql | provenance | TaintFunction |
nodes
| CleartextSqliteDatabase.cpp:29:77:29:84 | password | semmle.label | password |
| CleartextSqliteDatabase.cpp:31:22:31:24 | *sql | semmle.label | *sql |
| CleartextSqliteDatabase.cpp:37:77:37:84 | password | semmle.label | password |
| CleartextSqliteDatabase.cpp:39:28:39:30 | *sql | semmle.label | *sql |
| CleartextSqliteDatabase.cpp:49:32:49:39 | password | semmle.label | password |
| CleartextSqliteDatabase.cpp:56:76:56:83 | password | semmle.label | password |
subpaths

View File

@@ -0,0 +1,4 @@
query: Security/CWE/CWE-313/CleartextSqliteDatabase.ql
postprocess:
- utils/test/PrettyPrintModels.ql
- utils/test/InlineExpectationsTestQuery.ql

View File

@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Added library models for the relevant method calls under `jakarta.servlet.ServletRequest` and `jakarta.servlet.http.HttpServletRequest` as remote flow sources.

View File

@@ -4,3 +4,15 @@ extensions:
extensible: sourceModel
data:
- ["jakarta.servlet.http", "HttpServletRequest", True, "getServletPath", "", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getHeader", "(String)", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getHeaderNames", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getHeaders", "(String)", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getParameter", "(String)", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getParameterMap", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getParameterNames", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getParameterValues", "(String)", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getPathInfo", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getQueryString", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getRemoteUser", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getRequestURI", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet.http", "HttpServletRequest", False, "getRequestURL", "()", "", "ReturnValue", "remote", "manual"]

View File

@@ -1,4 +1,14 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: sourceModel
data:
- ["jakarta.servlet", "ServletRequest", False, "getInputStream", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet", "ServletRequest", False, "getParameter", "(String)", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet", "ServletRequest", False, "getParameterMap", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet", "ServletRequest", False, "getParameterNames", "()", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet", "ServletRequest", False, "getParameterValues", "(String)", "", "ReturnValue", "remote", "manual"]
- ["jakarta.servlet", "ServletRequest", False, "getReader", "()", "", "ReturnValue", "remote", "manual"]
- addsTo:
pack: codeql/java-all
extensible: sinkModel

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved modeling of command-line argument parsing libraries [arg](https://www.npmjs.com/package/arg), [args](https://www.npmjs.com/package/args), [command-line-args](https://www.npmjs.com/package/command-line-args) and [commander](https://www.npmjs.com/package/commander)

View File

@@ -87,11 +87,43 @@ private class ArgsParseStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call |
call = DataFlow::moduleMember("args", "parse").getACall() or
call = DataFlow::moduleImport(["yargs-parser", "minimist", "subarg"]).getACall()
call =
DataFlow::moduleImport(["yargs-parser", "minimist", "subarg", "yargs/yargs", "yargs"])
.getACall()
|
succ = call and
pred = call.getArgument(0)
)
or
exists(API::Node commanderNode | commanderNode = commander() |
pred = commanderNode.getMember(["parse", "parseAsync"]).getACall().getAnArgument() and
succ =
[
commanderNode.getMember("opts").getACall(), commanderNode.getAMember().asSource(),
commander()
.getMember("action")
.getACall()
.getArgument(0)
.(DataFlow::FunctionNode)
.getAParameter()
]
)
or
exists(DataFlow::MethodCallNode methodCall | methodCall = yargs() |
pred = methodCall.getReceiver() and
succ = methodCall
)
or
exists(DataFlow::CallNode call, DataFlow::Node options |
call = DataFlow::moduleImport(["arg", "command-line-args"]).getACall() and
succ = call and
options = call.getArgument(1) and
exists(DataFlow::PropWrite write |
write.getBase() = options and
write.getPropertyName() = "argv" and
pred = write.getRhs()
)
)
}
}
@@ -115,7 +147,9 @@ private API::Node commander() {
* Either directly imported as a module, or through some chained method call.
*/
private DataFlow::SourceNode yargs() {
result = DataFlow::moduleImport("yargs")
result = DataFlow::moduleImport(["yargs", "yargs/yargs"])
or
result = DataFlow::moduleImport(["yargs", "yargs/yargs"]).getACall()
or
// script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba
exists(string method |

View File

@@ -5,6 +5,7 @@
*/
import javascript
private import codeql.threatmodels.ThreatModels
module RegExpInjection {
/**
@@ -32,19 +33,32 @@ module RegExpInjection {
/**
* An active threat-model source, considered as a flow source.
* Excludes environment variables by default - they require the "environment" threat model.
*/
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
ActiveThreatModelSourceAsSource() {
not this.isClientSideSource() and
not this.(ThreatModelSource).getThreatModel() = "environment"
}
}
private import IndirectCommandInjectionCustomizations
/**
* Environment variables as a source when the "environment" threat model is active.
*/
private class EnvironmentVariableAsSource extends Source instanceof ThreatModelSource {
EnvironmentVariableAsSource() {
this.getThreatModel() = "environment" and
currentThreatModel("environment")
}
override string describe() { result = "environment variable" }
}
/**
* A read of `process.env`, `process.argv`, and similar, considered as a flow source for regular
* expression injection.
* Command line arguments as a source for regular expression injection.
*/
class ArgvAsSource extends Source instanceof IndirectCommandInjection::Source {
override string describe() { result = IndirectCommandInjection::Source.super.describe() }
private class CommandLineArgumentAsSource extends Source instanceof CommandLineArguments {
override string describe() { result = "command-line argument" }
}
/**

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The `js/regex-injection` query no longer considers environment variables as sources by default. Environment variables can be re-enabled as sources by setting the threat model to include the "environment" category.

View File

@@ -21,6 +21,12 @@
| child_process-test.js:75:29:75:31 | cmd | child_process-test.js:73:25:73:31 | req.url | child_process-test.js:75:29:75:31 | cmd | This command line depends on a $@. | child_process-test.js:73:25:73:31 | req.url | user-provided value |
| child_process-test.js:83:19:83:36 | req.query.fileName | child_process-test.js:83:19:83:36 | req.query.fileName | child_process-test.js:83:19:83:36 | req.query.fileName | This command line depends on a $@. | child_process-test.js:83:19:83:36 | req.query.fileName | user-provided value |
| child_process-test.js:94:11:94:35 | "ping " ... ms.host | child_process-test.js:94:21:94:30 | ctx.params | child_process-test.js:94:11:94:35 | "ping " ... ms.host | This command line depends on a $@. | child_process-test.js:94:21:94:30 | ctx.params | user-provided value |
| command-line-libs.js:14:8:14:18 | options.cmd | command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:14:8:14:18 | options.cmd | This command line depends on a $@. | command-line-libs.js:9:16:9:23 | req.body | user-provided value |
| command-line-libs.js:15:8:15:18 | program.cmd | command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:15:8:15:18 | program.cmd | This command line depends on a $@. | command-line-libs.js:9:16:9:23 | req.body | user-provided value |
| command-line-libs.js:21:12:21:17 | script | command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:21:12:21:17 | script | This command line depends on a $@. | command-line-libs.js:9:16:9:23 | req.body | user-provided value |
| command-line-libs.js:29:10:29:24 | parsed['--cmd'] | command-line-libs.js:27:23:27:30 | req.body | command-line-libs.js:29:10:29:24 | parsed['--cmd'] | This command line depends on a $@. | command-line-libs.js:27:23:27:30 | req.body | user-provided value |
| command-line-libs.js:37:8:37:18 | options.cmd | command-line-libs.js:35:62:35:69 | req.body | command-line-libs.js:37:8:37:18 | options.cmd | This command line depends on a $@. | command-line-libs.js:35:62:35:69 | req.body | user-provided value |
| command-line-libs.js:49:8:49:17 | parsed.cmd | command-line-libs.js:42:16:42:23 | req.body | command-line-libs.js:49:8:49:17 | parsed.cmd | This command line depends on a $@. | command-line-libs.js:42:16:42:23 | req.body | user-provided value |
| exec-sh2.js:10:12:10:57 | cp.spaw ... ptions) | exec-sh2.js:14:25:14:31 | req.url | exec-sh2.js:10:40:10:46 | command | This command line depends on a $@. | exec-sh2.js:14:25:14:31 | req.url | user-provided value |
| exec-sh.js:15:12:15:61 | cp.spaw ... ptions) | exec-sh.js:19:25:19:31 | req.url | exec-sh.js:15:44:15:50 | command | This command line depends on a $@. | exec-sh.js:19:25:19:31 | req.url | user-provided value |
| execSeries.js:14:41:14:47 | command | execSeries.js:18:34:18:40 | req.url | execSeries.js:14:41:14:47 | command | This command line depends on a $@. | execSeries.js:18:34:18:40 | req.url | user-provided value |
@@ -116,6 +122,35 @@ edges
| child_process-test.js:73:15:73:38 | url.par ... , true) | child_process-test.js:73:9:73:49 | cmd | provenance | |
| child_process-test.js:73:25:73:31 | req.url | child_process-test.js:73:15:73:38 | url.par ... , true) | provenance | |
| child_process-test.js:94:21:94:30 | ctx.params | child_process-test.js:94:11:94:35 | "ping " ... ms.host | provenance | |
| command-line-libs.js:9:9:9:34 | args | command-line-libs.js:12:17:12:20 | args | provenance | |
| command-line-libs.js:9:9:9:34 | args | command-line-libs.js:23:29:23:32 | args | provenance | |
| command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:9:9:9:34 | args | provenance | |
| command-line-libs.js:12:17:12:20 | args | command-line-libs.js:13:19:13:32 | program.opts() | provenance | |
| command-line-libs.js:12:17:12:20 | args | command-line-libs.js:15:8:15:18 | program.cmd | provenance | |
| command-line-libs.js:12:17:12:20 | args | command-line-libs.js:20:14:20:19 | script | provenance | |
| command-line-libs.js:13:9:13:32 | options | command-line-libs.js:14:8:14:14 | options | provenance | |
| command-line-libs.js:13:19:13:32 | program.opts() | command-line-libs.js:13:9:13:32 | options | provenance | |
| command-line-libs.js:14:8:14:14 | options | command-line-libs.js:14:8:14:18 | options.cmd | provenance | |
| command-line-libs.js:20:14:20:19 | script | command-line-libs.js:21:12:21:17 | script | provenance | |
| command-line-libs.js:23:29:23:32 | args | command-line-libs.js:20:14:20:19 | script | provenance | |
| command-line-libs.js:27:11:27:41 | argsArray | command-line-libs.js:28:53:28:61 | argsArray | provenance | |
| command-line-libs.js:27:23:27:30 | req.body | command-line-libs.js:27:11:27:41 | argsArray | provenance | |
| command-line-libs.js:28:11:28:64 | parsed | command-line-libs.js:29:10:29:15 | parsed | provenance | |
| command-line-libs.js:28:20:28:64 | arg({ ' ... rray }) | command-line-libs.js:28:11:28:64 | parsed | provenance | |
| command-line-libs.js:28:53:28:61 | argsArray | command-line-libs.js:28:20:28:64 | arg({ ' ... rray }) | provenance | |
| command-line-libs.js:29:10:29:15 | parsed | command-line-libs.js:29:10:29:24 | parsed['--cmd'] | provenance | |
| command-line-libs.js:35:9:35:83 | options | command-line-libs.js:37:8:37:14 | options | provenance | |
| command-line-libs.js:35:19:35:83 | command ... \| [] }) | command-line-libs.js:35:9:35:83 | options | provenance | |
| command-line-libs.js:35:62:35:69 | req.body | command-line-libs.js:35:19:35:83 | command ... \| [] }) | provenance | |
| command-line-libs.js:37:8:37:14 | options | command-line-libs.js:37:8:37:18 | options.cmd | provenance | |
| command-line-libs.js:42:9:42:34 | args | command-line-libs.js:43:24:43:27 | args | provenance | |
| command-line-libs.js:42:16:42:23 | req.body | command-line-libs.js:42:9:42:34 | args | provenance | |
| command-line-libs.js:43:9:47:12 | parsed | command-line-libs.js:49:8:49:13 | parsed | provenance | |
| command-line-libs.js:43:18:43:28 | yargs(args) | command-line-libs.js:43:18:47:4 | yargs(a ... ue\\n }) | provenance | |
| command-line-libs.js:43:18:47:4 | yargs(a ... ue\\n }) | command-line-libs.js:43:18:47:12 | yargs(a ... parse() | provenance | |
| command-line-libs.js:43:18:47:12 | yargs(a ... parse() | command-line-libs.js:43:9:47:12 | parsed | provenance | |
| command-line-libs.js:43:24:43:27 | args | command-line-libs.js:43:18:43:28 | yargs(args) | provenance | |
| command-line-libs.js:49:8:49:13 | parsed | command-line-libs.js:49:8:49:17 | parsed.cmd | provenance | |
| exec-sh2.js:9:17:9:23 | command | exec-sh2.js:10:40:10:46 | command | provenance | |
| exec-sh2.js:14:9:14:49 | cmd | exec-sh2.js:15:12:15:14 | cmd | provenance | |
| exec-sh2.js:14:15:14:38 | url.par ... , true) | exec-sh2.js:14:9:14:49 | cmd | provenance | |
@@ -269,6 +304,38 @@ nodes
| child_process-test.js:83:19:83:36 | req.query.fileName | semmle.label | req.query.fileName |
| child_process-test.js:94:11:94:35 | "ping " ... ms.host | semmle.label | "ping " ... ms.host |
| child_process-test.js:94:21:94:30 | ctx.params | semmle.label | ctx.params |
| command-line-libs.js:9:9:9:34 | args | semmle.label | args |
| command-line-libs.js:9:16:9:23 | req.body | semmle.label | req.body |
| command-line-libs.js:12:17:12:20 | args | semmle.label | args |
| command-line-libs.js:13:9:13:32 | options | semmle.label | options |
| command-line-libs.js:13:19:13:32 | program.opts() | semmle.label | program.opts() |
| command-line-libs.js:14:8:14:14 | options | semmle.label | options |
| command-line-libs.js:14:8:14:18 | options.cmd | semmle.label | options.cmd |
| command-line-libs.js:15:8:15:18 | program.cmd | semmle.label | program.cmd |
| command-line-libs.js:20:14:20:19 | script | semmle.label | script |
| command-line-libs.js:21:12:21:17 | script | semmle.label | script |
| command-line-libs.js:23:29:23:32 | args | semmle.label | args |
| command-line-libs.js:27:11:27:41 | argsArray | semmle.label | argsArray |
| command-line-libs.js:27:23:27:30 | req.body | semmle.label | req.body |
| command-line-libs.js:28:11:28:64 | parsed | semmle.label | parsed |
| command-line-libs.js:28:20:28:64 | arg({ ' ... rray }) | semmle.label | arg({ ' ... rray }) |
| command-line-libs.js:28:53:28:61 | argsArray | semmle.label | argsArray |
| command-line-libs.js:29:10:29:15 | parsed | semmle.label | parsed |
| command-line-libs.js:29:10:29:24 | parsed['--cmd'] | semmle.label | parsed['--cmd'] |
| command-line-libs.js:35:9:35:83 | options | semmle.label | options |
| command-line-libs.js:35:19:35:83 | command ... \| [] }) | semmle.label | command ... \| [] }) |
| command-line-libs.js:35:62:35:69 | req.body | semmle.label | req.body |
| command-line-libs.js:37:8:37:14 | options | semmle.label | options |
| command-line-libs.js:37:8:37:18 | options.cmd | semmle.label | options.cmd |
| command-line-libs.js:42:9:42:34 | args | semmle.label | args |
| command-line-libs.js:42:16:42:23 | req.body | semmle.label | req.body |
| command-line-libs.js:43:9:47:12 | parsed | semmle.label | parsed |
| command-line-libs.js:43:18:43:28 | yargs(args) | semmle.label | yargs(args) |
| command-line-libs.js:43:18:47:4 | yargs(a ... ue\\n }) | semmle.label | yargs(a ... ue\\n }) |
| command-line-libs.js:43:18:47:12 | yargs(a ... parse() | semmle.label | yargs(a ... parse() |
| command-line-libs.js:43:24:43:27 | args | semmle.label | args |
| command-line-libs.js:49:8:49:13 | parsed | semmle.label | parsed |
| command-line-libs.js:49:8:49:17 | parsed.cmd | semmle.label | parsed.cmd |
| exec-sh2.js:9:17:9:23 | command | semmle.label | command |
| exec-sh2.js:10:40:10:46 | command | semmle.label | command |
| exec-sh2.js:14:9:14:49 | cmd | semmle.label | cmd |

View File

@@ -0,0 +1,50 @@
import express from 'express';
import { Command } from 'commander';
import { exec } from 'child_process';
import arg from 'arg';
const app = express();
app.use(express.json());
app.post('/Command', async (req, res) => {
const args = req.body.args || []; // $ Source
const program = new Command();
program.option('--cmd <value>', 'Command to execute');
program.parse(args, { from: 'user' });
const options = program.opts();
exec(options.cmd); // $ Alert
exec(program.cmd); // $ Alert
const program1 = new Command();
program1
.command('run <script>')
.action((script) => {
exec(script); // $ Alert
});
await program1.parseAsync(args);
});
app.post('/arg', (req, res) => {
const argsArray = req.body.args || []; // $ Source
const parsed = arg({ '--cmd': String }, { argv: argsArray });
exec(parsed['--cmd']); // $ Alert
});
app.post('/commandLineArgs', (req, res) => {
const commandLineArgs = require('command-line-args');
const optionDefinitions = [{ name: 'cmd', type: String }];
const options = commandLineArgs(optionDefinitions, { argv: req.body.args || [] }); // $ Source
if (!options.cmd) return res.status(400).send({ error: 'Missing --cmd' });
exec(options.cmd); // $ Alert
});
app.post('/yargs', (req, res) => {
const yargs = require('yargs/yargs');
const args = req.body.args || []; // $ Source
const parsed = yargs(args).option('cmd', {
type: 'string',
describe: 'Command to execute',
demandOption: true
}).parse();
exec(parsed.cmd); // $ Alert
});

View File

@@ -14,7 +14,6 @@
| RegExpInjection.js:49:14:49:52 | key.spl ... in("-") | RegExpInjection.js:5:13:5:28 | req.param("key") | RegExpInjection.js:49:14:49:52 | key.spl ... in("-") | This regular expression is constructed from a $@. | RegExpInjection.js:5:13:5:28 | req.param("key") | user-provided value |
| RegExpInjection.js:59:14:59:18 | input | RegExpInjection.js:55:39:55:56 | req.param("input") | RegExpInjection.js:59:14:59:18 | input | This regular expression is constructed from a $@. | RegExpInjection.js:55:39:55:56 | req.param("input") | user-provided value |
| RegExpInjection.js:82:14:82:55 | "^.*\\.( ... + ")$" | RegExpInjection.js:77:15:77:32 | req.param("input") | RegExpInjection.js:82:14:82:55 | "^.*\\.( ... + ")$" | This regular expression is constructed from a $@. | RegExpInjection.js:77:15:77:32 | req.param("input") | user-provided value |
| RegExpInjection.js:86:16:86:50 | `^${pro ... r.app$` | RegExpInjection.js:86:20:86:30 | process.env | RegExpInjection.js:86:16:86:50 | `^${pro ... r.app$` | This regular expression is constructed from a $@. | RegExpInjection.js:86:20:86:30 | process.env | environment variable |
| RegExpInjection.js:88:16:88:49 | `^${pro ... r.app$` | RegExpInjection.js:88:20:88:31 | process.argv | RegExpInjection.js:88:16:88:49 | `^${pro ... r.app$` | This regular expression is constructed from a $@. | RegExpInjection.js:88:20:88:31 | process.argv | command-line argument |
| RegExpInjection.js:95:14:95:22 | sanitized | RegExpInjection.js:92:15:92:32 | req.param("input") | RegExpInjection.js:95:14:95:22 | sanitized | This regular expression is constructed from a $@. | RegExpInjection.js:92:15:92:32 | req.param("input") | user-provided value |
| tst.js:6:16:6:35 | "^"+ data.name + "$" | tst.js:5:16:5:29 | req.query.data | tst.js:6:16:6:35 | "^"+ data.name + "$" | This regular expression is constructed from a $@. | tst.js:5:16:5:29 | req.query.data | user-provided value |
@@ -57,7 +56,6 @@ edges
| RegExpInjection.js:77:15:77:32 | req.param("input") | RegExpInjection.js:77:7:77:32 | input | provenance | |
| RegExpInjection.js:82:25:82:29 | input | RegExpInjection.js:82:25:82:48 | input.r ... g, "\|") | provenance | |
| RegExpInjection.js:82:25:82:48 | input.r ... g, "\|") | RegExpInjection.js:82:14:82:55 | "^.*\\.( ... + ")$" | provenance | |
| RegExpInjection.js:86:20:86:30 | process.env | RegExpInjection.js:86:16:86:50 | `^${pro ... r.app$` | provenance | |
| RegExpInjection.js:88:20:88:31 | process.argv | RegExpInjection.js:88:16:88:49 | `^${pro ... r.app$` | provenance | |
| RegExpInjection.js:92:7:92:32 | input | RegExpInjection.js:94:19:94:23 | input | provenance | |
| RegExpInjection.js:92:15:92:32 | req.param("input") | RegExpInjection.js:92:7:92:32 | input | provenance | |
@@ -109,8 +107,6 @@ nodes
| RegExpInjection.js:82:14:82:55 | "^.*\\.( ... + ")$" | semmle.label | "^.*\\.( ... + ")$" |
| RegExpInjection.js:82:25:82:29 | input | semmle.label | input |
| RegExpInjection.js:82:25:82:48 | input.r ... g, "\|") | semmle.label | input.r ... g, "\|") |
| RegExpInjection.js:86:16:86:50 | `^${pro ... r.app$` | semmle.label | `^${pro ... r.app$` |
| RegExpInjection.js:86:20:86:30 | process.env | semmle.label | process.env |
| RegExpInjection.js:88:16:88:49 | `^${pro ... r.app$` | semmle.label | `^${pro ... r.app$` |
| RegExpInjection.js:88:20:88:31 | process.argv | semmle.label | process.argv |
| RegExpInjection.js:92:7:92:32 | input | semmle.label | input |

View File

@@ -83,7 +83,7 @@ app.get('/has-sanitizer', function(req, res) {
});
app.get("argv", function(req, res) {
new RegExp(`^${process.env.HOME}/Foo/bar.app$`); // $ Alert[js/regex-injection]
new RegExp(`^${process.env.HOME}/Foo/bar.app$`); // environment variable, should be detected only with threat model enabled.
new RegExp(`^${process.argv[1]}/Foo/bar.app$`); // $ Alert[js/regex-injection]
});

View File

@@ -0,0 +1,34 @@
#select
| RegExpInjection.js:6:14:6:48 | `^${pro ... r.app$` | RegExpInjection.js:6:18:6:28 | process.env | RegExpInjection.js:6:14:6:48 | `^${pro ... r.app$` | This regular expression is constructed from a $@. | RegExpInjection.js:6:18:6:28 | process.env | environment variable |
| RegExpInjection.js:8:14:8:40 | `^${pro ... }/bin$` | RegExpInjection.js:8:18:8:28 | process.env | RegExpInjection.js:8:14:8:40 | `^${pro ... }/bin$` | This regular expression is constructed from a $@. | RegExpInjection.js:8:18:8:28 | process.env | environment variable |
| RegExpInjection.js:11:14:11:19 | envVar | RegExpInjection.js:10:16:10:26 | process.env | RegExpInjection.js:11:14:11:19 | envVar | This regular expression is constructed from a $@. | RegExpInjection.js:10:16:10:26 | process.env | environment variable |
| RegExpInjection.js:14:14:14:47 | `^${pro ... r.app$` | RegExpInjection.js:14:18:14:29 | process.argv | RegExpInjection.js:14:14:14:47 | `^${pro ... r.app$` | This regular expression is constructed from a $@. | RegExpInjection.js:14:18:14:29 | process.argv | command-line argument |
| RegExpInjection.js:17:14:17:17 | argv | RegExpInjection.js:16:14:16:25 | process.argv | RegExpInjection.js:17:14:17:17 | argv | This regular expression is constructed from a $@. | RegExpInjection.js:16:14:16:25 | process.argv | command-line argument |
| RegExpInjection.js:21:14:21:22 | userInput | RegExpInjection.js:20:19:20:36 | req.param("input") | RegExpInjection.js:21:14:21:22 | userInput | This regular expression is constructed from a $@. | RegExpInjection.js:20:19:20:36 | req.param("input") | user-provided value |
edges
| RegExpInjection.js:6:18:6:28 | process.env | RegExpInjection.js:6:14:6:48 | `^${pro ... r.app$` | provenance | |
| RegExpInjection.js:8:18:8:28 | process.env | RegExpInjection.js:8:14:8:40 | `^${pro ... }/bin$` | provenance | |
| RegExpInjection.js:10:7:10:35 | envVar | RegExpInjection.js:11:14:11:19 | envVar | provenance | |
| RegExpInjection.js:10:16:10:26 | process.env | RegExpInjection.js:10:7:10:35 | envVar | provenance | |
| RegExpInjection.js:14:18:14:29 | process.argv | RegExpInjection.js:14:14:14:47 | `^${pro ... r.app$` | provenance | |
| RegExpInjection.js:16:7:16:28 | argv | RegExpInjection.js:17:14:17:17 | argv | provenance | |
| RegExpInjection.js:16:14:16:25 | process.argv | RegExpInjection.js:16:7:16:28 | argv | provenance | |
| RegExpInjection.js:20:7:20:36 | userInput | RegExpInjection.js:21:14:21:22 | userInput | provenance | |
| RegExpInjection.js:20:19:20:36 | req.param("input") | RegExpInjection.js:20:7:20:36 | userInput | provenance | |
nodes
| RegExpInjection.js:6:14:6:48 | `^${pro ... r.app$` | semmle.label | `^${pro ... r.app$` |
| RegExpInjection.js:6:18:6:28 | process.env | semmle.label | process.env |
| RegExpInjection.js:8:14:8:40 | `^${pro ... }/bin$` | semmle.label | `^${pro ... }/bin$` |
| RegExpInjection.js:8:18:8:28 | process.env | semmle.label | process.env |
| RegExpInjection.js:10:7:10:35 | envVar | semmle.label | envVar |
| RegExpInjection.js:10:16:10:26 | process.env | semmle.label | process.env |
| RegExpInjection.js:11:14:11:19 | envVar | semmle.label | envVar |
| RegExpInjection.js:14:14:14:47 | `^${pro ... r.app$` | semmle.label | `^${pro ... r.app$` |
| RegExpInjection.js:14:18:14:29 | process.argv | semmle.label | process.argv |
| RegExpInjection.js:16:7:16:28 | argv | semmle.label | argv |
| RegExpInjection.js:16:14:16:25 | process.argv | semmle.label | process.argv |
| RegExpInjection.js:17:14:17:17 | argv | semmle.label | argv |
| RegExpInjection.js:20:7:20:36 | userInput | semmle.label | userInput |
| RegExpInjection.js:20:19:20:36 | req.param("input") | semmle.label | req.param("input") |
| RegExpInjection.js:21:14:21:22 | userInput | semmle.label | userInput |
subpaths

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["environment", true, 0]

View File

@@ -0,0 +1,22 @@
var express = require('express');
var app = express();
app.get('/test-environment', function(req, res) {
// Environment variables should be detected when "environment" threat model is enabled
new RegExp(`^${process.env.HOME}/Foo/bar.app$`); // $ Alert[js/regex-injection]
new RegExp(`^${process.env.PATH}/bin$`); // $ Alert[js/regex-injection]
var envVar = process.env.NODE_ENV; // $ Source[js/regex-injection]
new RegExp(envVar); // $ Alert[js/regex-injection]
// Command line arguments should still be detected
new RegExp(`^${process.argv[1]}/Foo/bar.app$`); // $ Alert[js/regex-injection]
var argv = process.argv[2]; // $ Source[js/regex-injection]
new RegExp(argv); // $ Alert[js/regex-injection]
// Regular user input should still be detected
var userInput = req.param("input"); // $ Source[js/regex-injection]
new RegExp(userInput); // $ Alert[js/regex-injection]
});

View File

@@ -0,0 +1,2 @@
query: Security/CWE-730/RegExpInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -26,6 +26,8 @@ private module PossibleTimingAttackAgainstHashConfig implements DataFlow::Config
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
predicate observeDiffInformedIncrementalMode() { any() }
}
module PossibleTimingAttackAgainstHashFlow =
@@ -38,4 +40,4 @@ from
PossibleTimingAttackAgainstHashFlow::PathNode sink
where PossibleTimingAttackAgainstHashFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Possible Timing attack against $@ validation.",
source.getNode().(ProduceCryptoCall).getResultType(), "message"
source.getNode(), source.getNode().(ProduceCryptoCall).getResultType() + " message"

View File

@@ -25,6 +25,8 @@ private module TimingAttackAgainstHashConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
predicate observeDiffInformedIncrementalMode() { any() }
}
module TimingAttackAgainstHashFlow = TaintTracking::Global<TimingAttackAgainstHashConfig>;
@@ -35,5 +37,5 @@ from TimingAttackAgainstHashFlow::PathNode source, TimingAttackAgainstHashFlow::
where
TimingAttackAgainstHashFlow::flowPath(source, sink) and
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
select sink.getNode(), source, sink, "Timing attack against $@ validation.",
source.getNode().(ProduceCryptoCall).getResultType(), "message"
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
source.getNode().(ProduceCryptoCall).getResultType() + " message"

View File

@@ -10,5 +10,5 @@ nodes
| TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | semmle.label | ControlFlowNode for sign() |
subpaths
#select
| TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | Possible Timing attack against $@ validation. | signature | message |
| TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | Possible Timing attack against $@ validation. | MAC | message |
| TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | Possible Timing attack against $@ validation. | TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | signature message |
| TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | Possible Timing attack against $@ validation. | TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | MAC message |

View File

@@ -0,0 +1,12 @@
edges
| TimingAttackAgainstHash.py:26:5:26:13 | ControlFlowNode for signature | TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | provenance | |
| TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:26:5:26:13 | ControlFlowNode for signature | provenance | |
| TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | provenance | |
nodes
| TimingAttackAgainstHash.py:26:5:26:13 | ControlFlowNode for signature | semmle.label | ControlFlowNode for signature |
| TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | semmle.label | ControlFlowNode for signature |
| TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | semmle.label | ControlFlowNode for sign() |
subpaths
#select

View File

@@ -0,0 +1 @@
experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql

View File

@@ -53,6 +53,7 @@ private predicate letElsePanic(BlockExpr be) {
*/
query predicate deadEnd(CfgImpl::Node node) {
Consistency::deadEnd(node) and
successfullyExtractedFile(node.getLocation().getFile()) and
not letElsePanic(node.getAstNode())
}

View File

@@ -619,7 +619,7 @@ module PatternTrees {
(
StandardPatTree.super.succ(pred, succ, c)
or
pred = this and first(this.getFirstChildNode(), succ) and completionIsValidFor(c, this)
pred = this and first(this.getFirstChildTree(), succ) and completionIsValidFor(c, this)
)
}

View File

@@ -18,10 +18,10 @@ final class CfgScope = CfgScopeImpl;
final class AsyncBlockScope extends CfgScopeImpl, AsyncBlockExpr instanceof ExprTrees::AsyncBlockExprTree
{
override predicate scopeFirst(AstNode first) { first(super.getFirstChildNode(), first) }
override predicate scopeFirst(AstNode first) { first(super.getFirstChildTree(), first) }
override predicate scopeLast(AstNode last, Completion c) {
last(super.getLastChildElement(), last, c)
last(super.getLastChildTree(), last, c)
or
last(super.getChildNode(_), last, c) and
not c instanceof NormalCompletion
@@ -48,7 +48,7 @@ final class CallableScope extends CfgScopeImpl, Callable {
}
override predicate scopeFirst(AstNode first) {
first(this.(CallableScopeTree).getFirstChildNode(), first)
first(this.(CallableScopeTree).getFirstChildTree(), first)
}
/** Holds if `scope` is exited when `last` finishes with completion `c`. */

View File

@@ -23,14 +23,18 @@ module Impl {
* ```
*/
class ImplTraitTypeRepr extends Generated::ImplTraitTypeRepr {
/** Gets the function for which this impl trait type occurs, if any. */
Function getFunction() {
this.getParentNode*() = [result.getRetType().getTypeRepr(), result.getAParam().getTypeRepr()]
pragma[nomagic]
private TypeRepr getFunctionTypeRepr(Function f) {
this.getParentNode*() = result and
result = [f.getRetType().getTypeRepr(), f.getAParam().getTypeRepr()]
}
/** Gets the function for which this impl trait type occurs, if any. */
Function getFunction() { exists(this.getFunctionTypeRepr(result)) }
/** Holds if this impl trait type occurs in the return type of a function. */
predicate isInReturnPos() {
this.getParentNode*() = this.getFunction().getRetType().getTypeRepr()
exists(Function f | f.getRetType().getTypeRepr() = this.getFunctionTypeRepr(f))
}
}
}

View File

@@ -2519,7 +2519,7 @@ pub mod pattern_matching_experimental {
}
pub mod exec {
// a *greatly* simplified model of `MySqlConnection.execute` in SQLX
// a highly simplified model of `MySqlConnection.execute` in SQLx
trait Connection {}
@@ -2555,6 +2555,54 @@ pub mod exec {
}
}
pub mod path_buf {
// a highly simplified model of `PathBuf::canonicalize`
pub struct Path {
}
impl Path {
pub const fn new() -> Path {
Path { }
}
pub fn canonicalize(&self) -> Result<PathBuf, ()> {
Ok(PathBuf::new()) // $ target=new
}
}
pub struct PathBuf {
}
impl PathBuf {
pub const fn new() -> PathBuf {
PathBuf { }
}
}
// `PathBuf` provides `canonicalize` via `Deref`:
impl std::ops::Deref for PathBuf {
type Target = Path;
#[inline]
fn deref(&self) -> &Path {
// (very much not a real implementation)
static path : Path = Path::new(); // $ target=new
&path
}
}
pub fn f() {
let path1 = Path::new(); // $ target=new type=path1:Path
let path2 = path1.canonicalize(); // $ target=canonicalize
let path3 = path2.unwrap(); // $ target=unwrap type=path3:PathBuf
let pathbuf1 = PathBuf::new(); // $ target=new type=pathbuf1:PathBuf
let pathbuf2 = pathbuf1.canonicalize(); // $ MISSING: target=canonicalize
let pathbuf3 = pathbuf2.unwrap(); // $ MISSING: target=unwrap type=pathbuf3:PathBuf
}
}
mod closure;
mod dereference;
mod dyn_type;
@@ -2587,6 +2635,7 @@ fn main() {
method_determined_by_argument_type::f(); // $ target=f
tuples::f(); // $ target=f
exec::f(); // $ target=f
path_buf::f(); // $ target=f
dereference::test(); // $ target=test
pattern_matching::test_all_patterns(); // $ target=test_all_patterns
pattern_matching_experimental::box_patterns(); // $ target=box_patterns

View File

@@ -4950,11 +4950,49 @@ inferType
| main.rs:2554:44:2554:44 | c | | main.rs:2541:5:2541:29 | MySqlConnection |
| main.rs:2554:47:2554:67 | "SELECT * FROM users" | | file://:0:0:0:0 | & |
| main.rs:2554:47:2554:67 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str |
| main.rs:2564:5:2564:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo |
| main.rs:2565:5:2565:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo |
| main.rs:2565:20:2565:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo |
| main.rs:2565:41:2565:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo |
| main.rs:2581:5:2581:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future |
| main.rs:2565:36:2567:9 | { ... } | | main.rs:2561:5:2562:5 | Path |
| main.rs:2566:13:2566:20 | Path {...} | | main.rs:2561:5:2562:5 | Path |
| main.rs:2569:29:2569:33 | SelfParam | | file://:0:0:0:0 | & |
| main.rs:2569:29:2569:33 | SelfParam | &T | main.rs:2561:5:2562:5 | Path |
| main.rs:2569:59:2571:9 | { ... } | | {EXTERNAL LOCATION} | Result |
| main.rs:2569:59:2571:9 | { ... } | E | file://:0:0:0:0 | () |
| main.rs:2569:59:2571:9 | { ... } | T | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2570:13:2570:30 | Ok(...) | | {EXTERNAL LOCATION} | Result |
| main.rs:2570:13:2570:30 | Ok(...) | E | file://:0:0:0:0 | () |
| main.rs:2570:13:2570:30 | Ok(...) | T | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2570:16:2570:29 | ...::new(...) | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2578:39:2580:9 | { ... } | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2579:13:2579:23 | PathBuf {...} | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2588:18:2588:22 | SelfParam | | file://:0:0:0:0 | & |
| main.rs:2588:18:2588:22 | SelfParam | &T | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2588:34:2592:9 | { ... } | | file://:0:0:0:0 | & |
| main.rs:2588:34:2592:9 | { ... } | &T | main.rs:2561:5:2562:5 | Path |
| main.rs:2590:34:2590:44 | ...::new(...) | | main.rs:2561:5:2562:5 | Path |
| main.rs:2591:13:2591:17 | &path | | file://:0:0:0:0 | & |
| main.rs:2591:13:2591:17 | &path | &T | main.rs:2561:5:2562:5 | Path |
| main.rs:2591:14:2591:17 | path | | main.rs:2561:5:2562:5 | Path |
| main.rs:2596:13:2596:17 | path1 | | main.rs:2561:5:2562:5 | Path |
| main.rs:2596:21:2596:31 | ...::new(...) | | main.rs:2561:5:2562:5 | Path |
| main.rs:2597:13:2597:17 | path2 | | {EXTERNAL LOCATION} | Result |
| main.rs:2597:13:2597:17 | path2 | E | file://:0:0:0:0 | () |
| main.rs:2597:13:2597:17 | path2 | T | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2597:21:2597:25 | path1 | | main.rs:2561:5:2562:5 | Path |
| main.rs:2597:21:2597:40 | path1.canonicalize() | | {EXTERNAL LOCATION} | Result |
| main.rs:2597:21:2597:40 | path1.canonicalize() | E | file://:0:0:0:0 | () |
| main.rs:2597:21:2597:40 | path1.canonicalize() | T | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2598:13:2598:17 | path3 | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2598:21:2598:25 | path2 | | {EXTERNAL LOCATION} | Result |
| main.rs:2598:21:2598:25 | path2 | E | file://:0:0:0:0 | () |
| main.rs:2598:21:2598:25 | path2 | T | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2598:21:2598:34 | path2.unwrap() | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2600:13:2600:20 | pathbuf1 | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2600:24:2600:37 | ...::new(...) | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2601:24:2601:31 | pathbuf1 | | main.rs:2574:5:2575:5 | PathBuf |
| main.rs:2612:5:2612:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo |
| main.rs:2613:5:2613:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo |
| main.rs:2613:20:2613:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo |
| main.rs:2613:41:2613:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo |
| main.rs:2629:5:2629:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future |
| pattern_matching.rs:13:26:133:1 | { ... } | | {EXTERNAL LOCATION} | Option |
| pattern_matching.rs:13:26:133:1 | { ... } | T | file://:0:0:0:0 | () |
| pattern_matching.rs:14:9:14:13 | value | | {EXTERNAL LOCATION} | Option |

View File

@@ -261,32 +261,44 @@ module MakeWithSplitting<
/** Gets the `i`th child element, in order of evaluation. */
abstract AstNode getChildNode(int i);
private AstNode getChildNodeRanked(int i) {
result = rank[i + 1](AstNode child, int j | child = this.getChildNode(j) | child order by j)
private ControlFlowTree getChildTreeRanked(int i) {
result =
rank[i + 1](ControlFlowTree child, int j | child = this.getChildNode(j) | child order by j)
}
/** Gets the first child node of this element. */
final AstNode getFirstChildNode() { result = this.getChildNodeRanked(0) }
deprecated final AstNode getFirstChildNode() { result = this.getChildTreeRanked(0) }
/** Gets the first child node of this element. */
final ControlFlowTree getFirstChildTree() { result = this.getChildTreeRanked(0) }
/** Gets the last child node of this node. */
final AstNode getLastChildElement() {
deprecated final AstNode getLastChildElement() {
exists(int last |
result = this.getChildNodeRanked(last) and
not exists(this.getChildNodeRanked(last + 1))
result = this.getChildTreeRanked(last) and
not exists(this.getChildTreeRanked(last + 1))
)
}
/** Gets the last child node of this node. */
final ControlFlowTree getLastChildTree() {
exists(int last |
result = this.getChildTreeRanked(last) and
not exists(this.getChildTreeRanked(last + 1))
)
}
/** Holds if this element has no children. */
predicate isLeafElement() { not exists(this.getFirstChildNode()) }
predicate isLeafElement() { not exists(this.getFirstChildTree()) }
override predicate propagatesAbnormal(AstNode child) { child = this.getChildNode(_) }
pragma[nomagic]
override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(int i |
last(this.getChildNodeRanked(i), pred, c) and
last(this.getChildTreeRanked(i), pred, c) and
completionIsNormal(c) and
first(this.getChildNodeRanked(i + 1), succ)
first(this.getChildTreeRanked(i + 1), succ)
)
}
}
@@ -294,7 +306,7 @@ module MakeWithSplitting<
/** A standard element that is executed in pre-order. */
abstract class StandardPreOrderTree extends StandardTree, PreOrderTree {
override predicate last(AstNode last, Completion c) {
last(this.getLastChildElement(), last, c)
last(this.getLastChildTree(), last, c)
or
this.isLeafElement() and
completionIsValidFor(c, this) and
@@ -305,7 +317,7 @@ module MakeWithSplitting<
StandardTree.super.succ(pred, succ, c)
or
pred = this and
first(this.getFirstChildNode(), succ) and
first(this.getFirstChildTree(), succ) and
completionIsSimple(c)
}
}
@@ -313,16 +325,16 @@ module MakeWithSplitting<
/** A standard element that is executed in post-order. */
abstract class StandardPostOrderTree extends StandardTree, PostOrderTree {
override predicate first(AstNode first) {
first(this.getFirstChildNode(), first)
first(this.getFirstChildTree(), first)
or
not exists(this.getFirstChildNode()) and
not exists(this.getFirstChildTree()) and
first = this
}
override predicate succ(AstNode pred, AstNode succ, Completion c) {
StandardTree.super.succ(pred, succ, c)
or
last(this.getLastChildElement(), pred, c) and
last(this.getLastChildTree(), pred, c) and
succ = this and
completionIsNormal(c)
}