https://github.com/joernio/joern/tree/master/querydb
call-to-exec
Dangerous function 'java.lang.Runtime.exec:java.lang.Process(java.lang.String)' used
A call to the function `java.lang.Runtime.exec:java.lang.Process(java.lang.String)` could result in a potential remote code execution.
CPGQL Query:({cpg.method("java.lang.Runtime.exec").callIn}).l
call-to-gets
Dangerous function gets() used
Avoid `gets` function as it can lead to reads beyond buffer boundary and cause buffer overflows. Some secure alternatives are `fgets` and `gets_s`.
CPGQL Query:({cpg.method("(?i)gets").callIn}).l
int insecure_gets() {
char str[DST_BUFFER_SIZE];
gets(str);
printf("%s", str);
return 0;
}
int secure_gets() {
FILE *fp;
fp = fopen("file.txt" , "r");
char str[DST_BUFFER_SIZE];
fgets(str, DST_BUFFER_SIZE, fp);
printf("%s", str);
return 0;
}
call-to-getwd
Dangerous function getwd() used
Avoid the `getwd` function, it does not check buffer lengths. Use `getcwd` instead, as it checks the buffer size.
CPGQL Query:({cpg.method("(?i)getwd").callIn}).l
int insecure_getwd() {
char dir[12];
getwd(dir);
printf("Working directory:%s\n",dir);
return 0;
}
void secure_getcwd(char *buf, size_t len) {
getcwd(buf, len);
}
call-to-scanf
Insecure function scanf() used
Avoid `scanf` function as it can lead to reads beyond buffer boundary and cause buffer overflows. A secure alternative is `fgets`.
CPGQL Query:({cpg.method("(?i)scanf").callIn}).l
int insecure_scanf() {
char name[12];
scanf("%s", name);
printf("Hello %s!\n", name);
return 0
}
void secure_fgets(char *buf, int size, FILE *stream) {
fgets(buf, size, stream);
}
call-to-strcat
Dangerous functions `strcat` or `strncat` used
Avoid `strcat` or `strncat` functions. These can be used insecurely causing non null-termianted strings leading to memory corruption. A secure alternative is `strcat_s`.
CPGQL Query:({cpg.method("(?i)(strcat|strncat)").callIn}).l
void insecure_strcat(char *dest, char *src) {
strcat(dest, src);
}
void secure_strcat_s(char *dest, rsize_t destsz, char *src) {
strcat_s(dest, destsz, src);
}
call-to-strcpy
Dangerous functions `strcpy` or `strncpy` used
Avoid `strcpy` or `strncpy` function. `strcpy` does not check buffer lengths. A possible mitigation could be `strncpy` which could prevent buffer overflows but does not null-terminate strings leading to memory corruption. A secure alternative (on BSD) is `strlcpy`.
CPGQL Query:({cpg.method("(?i)(strcpy|strncpy)").callIn}).l
void insecure_strcpy(char *dest, char *src) {
strcpy(dest, src);
}
void secure_strlcpy(char *dest, char *src, size_t size) {
strlcpy(dest, src, size);
}
call-to-strcpy-ghidra
Dangerous functions `strcpy` or `strncpy` used
Avoid `strcpy` or `strncpy` function. `strcpy` does not check buffer lengths. A possible mitigation could be `strncpy` which could prevent buffer overflows but does not null-terminate strings leading to memory corruption. A secure alternative (on BSD) is `strlcpy`.
CPGQL Query:({cpg.method("(?i)(strcpy|strncpy)").callIn}).l
call-to-strtok
Dangerous function strtok() used
Avoid `strtok` function as it modifies the original string in place and appends a null character after each token. This makes the original string unsafe. Suggested alternative is `strtok_r` with `saveptr`.
CPGQL Query:({cpg.method("(?i)strtok").callIn}).l
int insecure_strtok() {
char *token;
char *path = getenv("PATH");
token = strtok(path, ":");
puts(token);
printf("PATH: %s\n", path); // original path string now has '/usr/bin\0' now and is insecure to use
return 0;
}
void secure_strtok_r(char *src, char *delim, char **saveptr) {
strtok_r(src, delim, saveptr);
}
constant-array-access-no-check
Array access at fixed offset but sufficient length check not determined
CPGQL Query:
({cpg.method.arrayAccess
.filter { access =>
val arrName = access.simpleName
!arrName.isEmpty && !arrName.forall(x => access.method.local.nameExact(x).nonEmpty)
}
.usesConstantOffset
.flatMap { arrayAccess =>
val lenFields =
potentialLengthFields(arrayAccess, arrayAccess.method)
if (lenFields.nonEmpty) {
List((arrayAccess, lenFields))
} else {
List()
}
}
.collect {
case (arrayAccess, lenFields) if !checked(arrayAccess, lenFields) =>
arrayAccess
}}).l
copy-loop
Copy loop detected
For (buf, indices) pairs, determine those inside control structures (for, while, if ...) where any of the calls made outside of the body (block) are Inc operations. Determine the first argument of that Inc operation and check if they are used as indices for the write operation into the buffer.
CPGQL Query:({cpg.assignment.target.arrayAccess
.map { access =>
(access.array, access.subscript.code.toSet)
}
.filter { case (buf, subscripts) =>
val incIdentifiers = buf.inAst.isControlStructure.astChildren
.filterNot(_.isBlock)
.assignment
.target
.code
.toSet
(incIdentifiers & subscripts).nonEmpty
}
.map(_._1)}).l
int index_into_dst_array (char *dst, char *src, int offset) {
for(i = 0; i < strlen(src); i++) {
dst[i + + j*8 + offset] = src[i];
}
}
// We do not want to detect this one because the
// index only specifies where to read from
int index_into_src_array() {
for(i = 0; i < strlen(src); i++) {
dst[k] = src[i];
}
}
file-operation-race
Two file operations on the same path can act on different files
Two subsequent file operations are performed on the same path. Depending on the permissions on this path, an attacker can exploit a race condition and replace the file or directory the path refers to between these calls. Use file operations based on file descriptor/pointer/handles instead of paths to avoid this issue.
CPGQL Query:({val operations: Map[String, Seq[Int]] = Map(
"access" -> Seq(1),
"chdir" -> Seq(1),
"chmod" -> Seq(1),
"chown" -> Seq(1),
"creat" -> Seq(1),
"faccessat" -> Seq(2),
"fchmodat" -> Seq(2),
"fopen" -> Seq(1),
"fstatat" -> Seq(2),
"lchown" -> Seq(1),
"linkat" -> Seq(2, 4),
"link" -> Seq(1, 2),
"lstat" -> Seq(1),
"mkdirat" -> Seq(2),
"mkdir" -> Seq(1),
"mkfifoat" -> Seq(2),
"mkfifo" -> Seq(1),
"mknodat" -> Seq(2),
"mknod" -> Seq(1),
"openat" -> Seq(2),
"open" -> Seq(1),
"readlinkat" -> Seq(2),
"readlink" -> Seq(1),
"renameat" -> Seq(2, 4),
"rename" -> Seq(1, 2),
"rmdir" -> Seq(1),
"stat" -> Seq(1),
"unlinkat" -> Seq(2),
"unlink" -> Seq(1)
)
def fileCalls(calls: Traversal[Call]) =
calls.nameExact(operations.keys.toSeq: _*)
def fileArgs(c: Call) =
c.argument.whereNot(_.isLiteral).argumentIndex(operations(c.name): _*)
fileCalls(cpg.call)
.filter(call => {
val otherCalls = fileCalls(call.method.ast.isCall).filter(_ != call)
val argsForOtherCalls =
otherCalls.flatMap(c => fileArgs(c)).code.toSet
fileArgs(call).code.exists(arg => argsForOtherCalls.contains(arg))
})}).l
void insecure_race(char *path) {
chmod(path, 0);
rename(path, "/some/new/path");
}
void secure_handle(char *path) {
FILE *file = fopen(path, "r");
fchown(fileno(file), 0, 0);
}
format-controlled-printf
Non-constant format string passed to printf/sprintf/vsprintf
Avoid user controlled format strings like "argv" in printf, sprintf and vsprintf functions as they can cause memory corruption. Some secure alternatives are `snprintf` and `vsnprintf`.
CPGQL Query:({val printfFns = cpg
.method("(?i)printf")
.callIn
.whereNot(_.argument.order(1).isLiteral)
val sprintsFns = cpg
.method("(?i)(sprintf|vsprintf)")
.callIn
.whereNot(_.argument.order(2).isLiteral)
(printfFns ++ sprintsFns)}).l
int insecure_printf() {
printf(argv[1], 4242);
}
int secure_printf() {
printf("Num: %d", 4242);
}
free-field-no-reassign
A field of a parameter is free'd and not reassigned on all paths
The function is able to modify a field of a structure passed in by the caller. It frees this field and does not guarantee that on all paths to the exit, the field is reassigned. If any caller now accesses the field, then it accesses memory that is no longer allocated. We also check that the function does not free or clear the entire structure, as in that case, it is unlikely that the passed in structure will be used again.
CPGQL Query:({val freeOfStructField = cpg
.method("free")
.callIn
.where(
_.argument(1)
.isCallTo("<operator>.*[fF]ieldAccess.*")
.filter(x => x.method.parameter.name.toSet.contains(x.argument(1).code))
)
.whereNot(_.argument(1).isCall.argument(1).filter { struct =>
struct.method.ast.isCall
.name(".*free$", "memset", "bzero")
.argument(1)
.codeExact(struct.code)
.nonEmpty
})
.l
freeOfStructField.argument(1).filter { arg =>
arg.method.methodReturn.reachableBy(arg).nonEmpty
}}).l
void free_field_reassigned(a_struct_type *a_struct) {
free(a_struct->ptr);
if (something) {
return;
}
a_struct->ptr = foo;
}
void not_free_field_reassigned(a_struct_type *a_struct) {
free(a_struct->ptr);
if (something) {
a_struct->ptr = NULL;
return;
}
a_struct->ptr = foo;
}
free-follows-value-reuse
A value that is free'd is reused without reassignment.
A value is used after being free'd in a path that leads to it without reassignment. Modeled after CVE-2019-18903.
CPGQL Query:({cpg.method
.name("(.*_)?free")
.filter(_.parameter.size == 1)
.callIn
.where(_.argument(1).isIdentifier)
.flatMap(f => {
val freedIdentifierCode = f.argument(1).code
val postDom = f.postDominatedBy.toSetImmutable
val assignedPostDom = postDom.isIdentifier
.where(_.inAssignment)
.codeExact(freedIdentifierCode)
.flatMap(id => id ++ id.postDominatedBy)
postDom
.removedAll(assignedPostDom)
.isIdentifier
.codeExact(freedIdentifierCode)
})}).l
void *bad() {
void *x = NULL;
if (cond)
free(x);
return x;
}
void *false_positive() {
void *x = NULL;
free(x);
if (cond)
x = NULL;
else
x = NULL;
return x;
}
void *false_negative() {
void *x = NULL;
if (cond) {
free(x);
if (cond2)
return x; // not post-dominated by free call
x = NULL;
}
return x;
}
void *good() {
void *x = NULL;
if (cond)
free(x);
x = NULL;
return x;
}
free-returned-value
A value that is returned through a parameter is free'd in a path
The function sets a field of a function parameter to a value of a local variable. This variable is then freed in some paths. Unless the value set in the function parameter is overridden later on, the caller has access to the free'd memory, which is undefined behavior. Finds bugs like CVE-2019-18902.
CPGQL Query:({def outParams =
cpg.parameter
.code(".+\\*.+")
.whereNot(
_.referencingIdentifiers
.argumentIndex(1)
.inCall
.nameExact(Operators.assignment, Operators.addressOf)
)
def assignedValues =
outParams.referencingIdentifiers
.argumentIndex(1)
.inCall
.nameExact(Operators.indirectFieldAccess, Operators.indirection, Operators.indirectIndexAccess)
.argumentIndex(1)
.inCall
.nameExact(Operators.assignment)
.argument(2)
.isIdentifier
def freeAssigned =
assignedValues.map(id =>
(
id,
id.refsTo
.flatMap {
case p: MethodParameterIn => p.referencingIdentifiers
case v: Local => v.referencingIdentifiers
}
.inCall
.name("(.*_)?free")
)
)
freeAssigned
.filter { case (id, freeCall) =>
freeCall.dominatedBy.exists(_ == id)
}
.flatMap(_._1)}).l
void bad(a_struct_type *a_struct) {
void *x = NULL;
a_struct->foo = x;
free(x);
}
void good1(a_struct_type *a_struct) {
void *x = NULL, *y = NULL;
a_struct->foo = x;
free(y);
}
void good2(a_struct_type *a_struct) {
void *x = NULL;
free(a_struct->foo);
a_struct->foo = x;
}
void bad_not_covered(a_struct_type *a_struct) {
void *x = NULL;
a_struct->foo = x;
free(a_struct->foo);
}
getenv-to-strcpy
`getenv` fn arguments used in strcpy source buffer
User-input ends up in source buffer argument of strcpy, which might overflow the destination buffer.
CPGQL Query:({def source =
cpg.call.methodFullName("getenv").cfgNext.isCall.argument(2)
def sink = cpg.method.fullName("strcpy").parameter.index(2)
sink.reachableBy(source).l}).l
ineffective-certificate-check
Ineffective Certificate Validation: The validation result is always positive
A certificate validation function is implemented as a function that only consists of a prologue where local variables are initialized to arguments, followed by a (positive) return statement.
CPGQL Query:({val validators = Map(
// javax.net.ssl.HostnameVerifier
"verify" -> "boolean(java.lang.String,javax.net.ssl.SSLSession)",
// javax.net.ssl.X509ExtendedTrustManager
"checkClientTrusted" -> "void(java.security.cert.X509Certificate[],java.lang.String,java.net.Socket)",
"checkClientTrusted" -> "void(java.security.cert.X509Certificate[],java.lang.String,javax.net.ssl.SSLEngine)",
"checkServerTrusted" -> "void(java.security.cert.X509Certificate[],java.lang.String,java.net.Socket)",
"checkServerTrusted" -> "void(java.security.cert.X509Certificate[],java.lang.String,javax.net.ssl.SSLEngine)"
)
// skip over arguments getting copied to local variables
def isPrologue(node: nodes.CfgNode): Boolean = node match {
case id: nodes.Identifier =>
id.refsTo.forall(_.isInstanceOf[nodes.Local])
case c: nodes.Call =>
c.methodFullName == Operators.assignment && c.argument.forall(isPrologue)
case _ => false
}
def skipPrologue(node: nodes.CfgNode): Traversal[nodes.CfgNode] =
node.repeat(_.cfgNext)(_.until(_.filter(!isPrologue(_))))
cpg.method
.nameExact(validators.keys.toSeq: _*)
.signatureExact(validators.values.toSeq: _*)
.cfgFirst
.flatMap(skipPrologue)
.filter {
case lit: nodes.Literal =>
lit.code == "1" && lit.cfgNext
.forall(_.isInstanceOf[nodes.Return])
case _: nodes.Return => true
case _ => false
}}).l
malloc-memcpy-int-overflow
Dangerous copy-operation into heap-allocated buffer
-
CPGQL Query:({val src =
cpg.method(".*malloc$").callIn.where(_.argument(1).arithmetic).l
cpg.method("(?i)memcpy").callIn.l.filter { memcpyCall =>
memcpyCall
.argument(1)
.reachableBy(src)
.where(_.inAssignment.target.codeExact(memcpyCall.argument(1).code))
.whereNot(_.argument(1).codeExact(memcpyCall.argument(3).code))
.hasNext
}}).l
int vulnerable(size_t len, char *src) {
char *dst = malloc(len + 8);
memcpy(dst, src, len + 7);
}
int non_vulnerable(size_t len, char *src) {
char *dst = malloc(len + 8);
memcpy(dst, src,len + 8);
}
int non_vulnerable2(size_t len, char *src) {
char *dst = malloc( some_size );
assert(dst);
memcpy(dst, src, some_size );
}
manifest-debuggable-enabled
Backups enabled in Android Manifest File
Backup flag is set to true in AndroidManifest.xml which means that the application data can be retrieved via adb.
CPGQL Query:({import javax.xml.parsers.SAXParserFactory
import scala.xml.{Elem, XML}
object SecureXmlParsing {
def parseXml(content: String): Option[Elem] = {
try {
val spf = SAXParserFactory.newInstance()
spf.setValidating(false)
spf.setNamespaceAware(false)
spf.setXIncludeAware(false)
spf.setFeature("http://xml.org/sax/features/validation", false)
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false)
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false)
spf.setFeature("http://xml.org/sax/features/external-general-entities", false)
Some(XML.withSAXParser(spf.newSAXParser()).loadString(content))
} catch {
case _: Throwable =>
None
}
}
}
val androidUri = "http://schemas.android.com/apk/res/android"
cpg.configFile
.filter(_.name.endsWith("AndroidManifest.xml"))
.where { config =>
config.content
.flatMap(SecureXmlParsing.parseXml)
.filter(_.label == "manifest")
.flatMap(_.child)
.filter(_.label == "application")
.filter { node =>
val isAllowBackup = node.attribute(androidUri, "allowBackup")
isAllowBackup match {
case Some(n) => n.toString == "true"
case None => false
}
}
}}).l
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.slimandroid">
<application
android:allowBackup="true"
android:label="SlimAndroid"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.slimandroid">
<application
android:allowBackup="false"
android:label="SlimAndroid"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
multiple-returns
Multiple returns
This query identifies functions with more than one return
CPGQL Query:({cpg.method.internal.filter(_.ast.isReturn.l.size > 1).nameNot("<global>")}).l
int func_with_multiple_returns (int x) {
if (x > 10) {
return 0;
} else {
return 1;
}
}
int func_without_multiple_returns() {
return 0;
}
setgid-without-setgroups
Process group membership is changed without setting ancillary groups first
The set*gid system calls do not affect the ancillary groups a process belongs to. Changes to the group membership should therefore always be preceded by a call to setgroups. Otherwise the process may still be a secondary member of the group it tries to disavow.
CPGQL Query:({cpg
.method("(?i)set(res|re|e|)gid")
.callIn
.whereNot(_.dominatedBy.isCall.name("setgroups"))}).l
void bad2() {
setresgid();
setresuid();
}
void good() {
setgroups();
setresgid();
setresuid();
}
setuid-without-setgid
Process user ID is changed without changing groups first
The set*uid system calls do not affect the groups a process belongs to. However, often there exists a group that is equivalent to a user (e.g. wheel or shadow groups are often equivalent to the root user). Group membership can only be changed by the root user. Changes to the user should therefore always be preceded by calls to set*gid and setgroups,
CPGQL Query:({cpg
.method("(?i)set(res|re|e|)uid")
.callIn
.whereNot(_.dominatedBy.isCall.name("set(res|re|e|)?gid"))}).l
void bad1() {
setresuid();
}
void bad3() {
setgroups();
setresuid();
}
void good() {
setgroups();
setresgid();
setresuid();
}
signed-left-shift
Signed Shift May Cause Undefined Behavior
Signed integer overflow is undefined behavior. Shifts of signed values to the left are very prone to overflow.
CPGQL Query:({cpg.call
.nameExact(Operators.shiftLeft, Operators.assignmentShiftLeft)
.where(_.argument(1).typ.fullNameExact("int", "long"))
.filterNot(_.argument.isLiteral.size == 2)}).l
void bad1(int val) {
val <<= 24;
}
void bad2(int val) {
255 << val;
}
void bad3(int val) {
val << val;
}
void good(unsigned int val) {
255 << 24; // we ignore signed shift with two literals
val <<= 24;
val << val;
}
simple-constant-detection
Simple Constant Detection: Finds identifiers with primitives only assigned once
Detect variables holding simple constants. A term is a simple constant if it assigns a primitive constant, or if all its operands are simple constants. This should be optimized by the compiler during compile time.
CPGQL Query:({cpg.assignment
.groupBy(_.argument.order(1).code.l)
.flatMap {
case (_: List[String], as: Traversal[OpNodes.Assignment]) => Option(as.l)
case _ => Option.empty
}
.filter(_.size == 1)
.flatMap {
case as: List[OpNodes.Assignment] =>
Option(as.head.argument.head, as.head.argument.l.head.typ.l)
case _ => Option.empty
}
.filter {
case (_: Identifier, ts: List[Type]) =>
ts.nonEmpty &&
ts.head.namespace.l.exists { x =>
x.name.contains("<global>")
} &&
!ts.head.fullName.contains("[]")
case _ => false
}
.flatMap {
case (i: Identifier, _: List[Type]) => Option(i)
case _ => Option.empty
}}).l
socket-send
Unchecked call to send
When calling `send`, the return value must be checked to determine if the send operation was successful and how many bytes were transmitted.
CPGQL Query:({implicit val noResolve: NoResolve.type = NoResolve
cpg
.method("send")
.filter(_.parameter.size == 4)
.callIn
.returnValueNotChecked}).l
void return_not_checked(int sockfd, void *buf, size_t len, int flags) {
send(sockfd, buf, len, flags);
}
void return_checked(int sockfd, void *buf, size_t len, int flags) {
if (send(sockfd, buf, len, flags) <= 0) {
// Do something
}
}
sql-injection
SQL injection: A parameter is used in an insecure database API call.
An attacker controlled parameter is used in an insecure database API call. If the parameter is not validated and sanitized, this is a SQL injection.
CPGQL Query:({def source =
cpg.method
.where(_.methodReturn.evalType("org.springframework.web.servlet.ModelAndView"))
.parameter
def sink = cpg.method.name("query").parameter.order(1)
sink.reachableBy(source).l}).l
strlen-truncation
Truncation in assignment involving `strlen` call
The return value of `strlen` is stored in a variable that is known to be of type `int` as opposed to `size_t`. `int` is only 32 bit wide on many 64 bit platforms, and thus, this may result in a truncation.
CPGQL Query:({cpg.method
.name("(?i)strlen")
.callIn
.inAssignment
.target
.evalType("(g?)int")}).l
int vulnerable(char *str) {
int len;
len = strlen(str);
}
int non_vulnerable(char *str) {
size_t len;
len = strlen(str);
}
strncpy-no-null-term
strncpy is used and no null termination is nearby
Upon calling `strncpy` with a source string that is larger than the destination buffer, the destination buffer is not null-terminated by `strncpy` and there is no explicit null termination nearby. This is unproblematic if the buffer size is at least 1 larger than the size passed to `strncpy`.
CPGQL Query:({val allocations = cpg.method(".*malloc$").callIn.argument(1).l
cpg
.method("(?i)strncpy")
.callIn
.map { c =>
(c.method, c.argument(1), c.argument(3))
}
.filter { case (method, dst, size) =>
dst.reachableBy(allocations).codeExact(size.code).nonEmpty &&
method.assignment
.where(_.target.arrayAccess.code(s"${dst.code}.*\\[.*"))
.source
.isLiteral
.code(".*0.*")
.isEmpty
}
.map(_._2)}).l
// If src points to a string that is at least `asize` long,
// then `ptr` will not be null-terminated after the `strncpy`
// call.
int bad() {
char *ptr = malloc(asize);
strncpy(ptr, src, asize);
}
// Null-termination is ensured if we can only copy
// less than `asize + 1` into the buffer
int good() {
char *ptr = malloc(asize + 1);
strncpy(ptr, src, asize);
}
// Null-termination is also ensured if it is performed
// explicitly
int alsogood() {
char *ptr = malloc(asize);
strncpy(ptr, src, asize);
ptr[asize -1] = '\0';
}
too-high-complexity
Cyclomatic complexity higher than 4
This query identifies functions with a cyclomatic complexity higher than 4
CPGQL Query:({cpg.method.internal.filter(_.controlStructure.size > n).nameNot("<global>")}).l
int high_cyclomatic_complexity(int x) {
while(true) {
for(int i = 0; i < 10; i++) {
}
if(foo()) {}
}
if (x > 10) {
for(int i = 0; i < 10; i++) {
}
}
}
void good(int x, int y) {
if (x > 0) {/* Stuff */ } else { /* Stuff */ }
if (y > 0) {/* Stuff */ } else { /* Stuff */ }
}
too-long
More than 1000 lines
This query identifies functions that are more than 1000 lines long
CPGQL Query:({cpg.method.internal.filter(_.numberOfLines > n).nameNot("<global>")}).l
int func_with_many_lines(int x) {
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
}
int func_with_few_lines(int x) {
x++;
}
too-many-loops
More than 4 loops
This query identifies functions with more than 4 loops
CPGQL Query:({cpg.method.internal
.filter(
_.ast.isControlStructure
.controlStructureType("(FOR|DO|WHILE)")
.size > n
)
.nameNot("<global>")}).l
int high_number_of_loops () {
for(int i = 0; i < 10; i++){
}
int j = 0;
do {
j++
} while(j < 10);
while(foo()) {}
while(bar()){}
}
int not_many_loops() {
while (true) {
// Do something
}
}
too-many-params
Number of parameters larger than 4
This query identifies functions with more than 4 formal parameters
CPGQL Query:({cpg.method.internal.filter(_.parameter.size > n).nameNot("<global>")}).l
int too_many_params(int a, int b, int c, int d, int e) {
}
void good(int a, int b, int c, int d) {
}
too-nested
Nesting level higher than 3
This query identifies functions with a nesting level higher than 3
CPGQL Query:({cpg.method.internal.filter(_.depth(_.isControlStructure) > n).nameNot("<global>")}).l
int func_with_nesting_level_of_3(int foo, int bar) {
if (foo > 10) {
if (bar > foo) {
for(int i = 0; i < bar ;i++) {
}
}
}
}
int func_with_nesting_level_of_1(int foo) {
if (foo > 10) {
// Do something
}
}
unchecked-read-recv-malloc
Unchecked read/recv/malloc
The return value of a read/recv/malloc call is not checked directly and the variable it has been assigned to (if any) does not occur in any check within the caller.
CPGQL Query:({implicit val noResolve: NoResolve.type = NoResolve
cpg
.method("(?i)(read|recv|malloc)")
.callIn
.returnValueNotChecked}).l
void unchecked_read() {
read(fd, buf, sizeof(buf));
}
void checks_something_else() {
int nbytes = read(fd, buf, sizeof(buf));
if( foo != sizeof(buf)) {
}
}
void checked_after_assignment() {
int nbytes = read(fd, buf, sizeof(buf));
if( nbytes != sizeof(buf)) {
}
}
void immediately_checked() {
if ( (read(fd, buf, sizeof(buf))) != sizeof(buf)) {
}
}
int notCheckedButDirectlyReturned() {
return read(fd, buf, sizeof(buf));
}
usage-of-insecure-protocol
Insecure Protocol used
Using insecure network protocols allows attackers who control the network to replace, remove and inject data.
CPGQL Query:({cpg.method
.fullNameExact("java.net.URL.<init>:void(java.lang.String)")
.callIn
.where(_.argument.isLiteral.code("^[^h]*http:.*"))
.l}).l
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL;
fun fn1() {
val url = URL("http://phrack.org") // <---- relevant line
val connection = url.openConnection()
BufferedReader(InputStreamReader(connection.getInputStream())).use { inp ->
var line: String?
while (inp.readLine().also { line = it } != null) {
println(line)
}
}
}
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL;
fun fn2() {
val url = URL("https://phrack.org") // <---- relevant line
val connection = url.openConnection()
BufferedReader(InputStreamReader(connection.getInputStream())).use { inp ->
var line: String?
while (inp.readLine().also { line = it } != null) {
println(line)
}
}
}
xss-servlet
Reflected Cross-Site Scripting: Servlet Returns HTTP Input in Response
A servlet returns a URL parameter as part of the response. Unless the parameter is escaped or validated in-between, this is a reflected XSS vulnerability.
CPGQL Query:({def source =
cpg.call.methodFullNameExact(
"javax.servlet.http.HttpServletRequest.getParameter:java.lang.String(java.lang.String)"
)
def responseWriter =
cpg.call.methodFullNameExact("javax.servlet.http.HttpServletResponse.getWriter:java.io.PrintWriter()")
def sinks =
cpg.call
.methodFullNameExact("java.io.PrintWriter.println:void(java.lang.String)")
.where(_.argument(0).reachableBy(responseWriter))
sinks.where(_.argument(1).reachableBy(source))}).l