-
Hi, I was exploring the capabilities of CodeQL, and I come to a situation when neither dataflow nor taint tracking propagates through aliased pointers. I tried to detect use after free; however, I could not detect the following use. The simplified example of the code I analyzed: void test()
{
char* p = (char*)malloc(1);
if (!p)
return;
*p = 'x';
char* cpy = p;
free(p);
char use_after_free = *cpy;
putchar(use_after_free);
} The query I used (same with TaintTracking). import cpp
import semmle.code.cpp.dataflow.DataFlow
import DataFlow::PathGraph
class UseAfterFreeConfig extends DataFlow::Configuration {
UseAfterFreeConfig() { this = "Use after free config" }
override predicate isSource(DataFlow::Node arg) {
exists( FunctionCall call |
call.getArgument(0) = arg.asDefiningArgument() and
call.getTarget().hasGlobalOrStdName("free")
)
}
override predicate isSink(DataFlow::Node sink) {
dereferenced(sink.asExpr())
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, UseAfterFreeConfig useAfterFree
where useAfterFree.hasFlowPath(source, sink)
select sink, source, sink, "use after free" |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Hi @xlauko, This is indeed a known limitation of our alias analysis which we perform before computing dataflow. If I understand this sentence correctly:
you're saying that your query does detect this case: void test()
{
char* p = (char*)malloc(1);
if (!p)
return;
*p = 'x';
free(p);
char* cpy = p;
char use_after_free = *cpy;
putchar(use_after_free);
} which is expected. For various reasons we sometimes treat a read or write to a pointer (i.e., But in your original code example: void test()
{
char* p = (char*)malloc(1);
if (!p)
return;
*p = 'x';
char* cpy = p;
free(p);
char use_after_free = *cpy;
putchar(use_after_free);
} the aliasing relationship between If you really need to detect this case you can implement the import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import DataFlow::PathGraph
class UseAfterFreeConfig extends DataFlow::Configuration {
UseAfterFreeConfig() { this = "Use after free config" }
override predicate isSource(DataFlow::Node arg) {
exists(FunctionCall call |
call.getArgument(0) = arg.asDefiningArgument() and
call.getTarget().hasGlobalOrStdName("free")
)
}
override predicate isSink(DataFlow::Node sink) { dereferenced(sink.asExpr()) }
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
globalValueNumber(node1.asDefiningArgument()) = globalValueNumber(node2.asExpr())
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, UseAfterFreeConfig useAfterFree
where useAfterFree.hasFlowPath(source, sink)
select sink, source, sink, "use after free" This is likely not the final version of |
Beta Was this translation helpful? Give feedback.
Hi @xlauko,
This is indeed a known limitation of our alias analysis which we perform before computing dataflow. If I understand this sentence correctly:
you're saying that your query does detect this case:
which is expected. For various reasons we sometimes treat a read or write to a pointer (i.e.,
p
) as a read or write to the dereferenced value (i.e.,*p
). So in the case where we do dete…