cpp: Add 'cpp/mmio-unsanitized-memcpy' query - #22438
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Hi @github/codeql-cpp just checking in on this query submission! The query adds MMIO/DMA-to-memcpy bounds modeling for embedded C/C++ drivers, complete with unit tests and QL documentation. Let me know whenever the team has a moment to review or trigger CI. |
There was a problem hiding this comment.
Hi, I'll try to find a reviewer for you. However, this query has very low quality (see below), not what we would consider "medium". Hence, at the very least it should be moved into the directory for experimental queries.
I ran the query on about 1000 databases, and most of the results seem unrelated to memory mapped I/O and look more cases where volatile is used for other (incorrect) reasons.
| # CWE-120: MMIO/DMA unsanitized memcpy (also selected by metadata; explicit for review) | ||
| - include: | ||
| id: cpp/mmio-unsanitized-memcpy |
| or | ||
| exists(FunctionCall call | | ||
| call = e and | ||
| call.getTarget().hasName(["READ_REG", "GET_MMIO", "REG_READ", "DMA_READ"]) |
There was a problem hiding this comment.
Going by your test some or all of these are macros, which means this will not work, as macros are not picked up as functions. You correct for this below, but if some of these are always macros or always functions, it would be better to avoid this duplication.
Relocate the query under experimental/, narrow sources to allowlisted MMIO register macros only, drop the security-extended suite include, and update tests and change notes for maintainer feedback on PR github#22438. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Hi @jketema — thank you again for the feedback and for running this across the DB corpus! You were spot on regarding the generic volatile False Positive trap. I have updated the PR with the following changes:
Let me know if this updated AST modeling looks ready for the experimental queue! |
|
|
||
| # Local CodeQL harness database cache (veraptos TP/TN validation) | ||
| codeql_harness_dbs/ |
| --- | ||
| category: minorAnalysis | ||
| --- | ||
| * Added a new experimental query, `cpp/experimental/mmio-unsanitized-memcpy`, to detect memory copy operations whose size argument is derived from allowlisted MMIO/DMA register-read macros without sufficient bounds validation. |
There was a problem hiding this comment.
We don't publish change notes for experimental queries.
| * macros without bounds validation may overflow destination buffers. | ||
| * @kind path-problem | ||
| * @problem.severity error | ||
| * @security-severity 8.6 |
There was a problem hiding this comment.
Likely wrong and not needed for experimental queries.
| * @security-severity 8.6 |
| * @problem.severity error | ||
| * @security-severity 8.6 | ||
| * @precision low | ||
| * @id cpp/experimental/mmio-unsanitized-memcpy |
There was a problem hiding this comment.
I'm pretty sure we can just leave this as:
| * @id cpp/experimental/mmio-unsanitized-memcpy | |
| * @id cpp/mmio-unsanitized-memcpy |
971e182 to
5c92f5e
Compare
|
QHelp previews: cpp/ql/src/experimental/Security/CWE/CWE-120/MmioUnsanitizedMemcpy.qhelpMMIO/DMA unsanitized memory copyFirmware and embedded drivers often copy data into buffers using lengths read from allowlisted MMIO register macros such as RecommendationAlways validate MMIO/DMA-derived lengths before passing them to ExampleBad: length from an MMIO register used directly as the copy size. #define READ_REG(addr) (*(volatile unsigned int *)(addr))
#define MAX_DMA_LEN 64
void *memcpy(void *dest, const void *src, unsigned long n);
void bad_mmio_memcpy(char *dst, char *src) {
unsigned int len = READ_REG(0x40001000);
memcpy(dst, src, len);
}Good: defensive bounds check before the copy. #define READ_REG(addr) (*(volatile unsigned int *)(addr))
#define MAX_DMA_LEN 64
void *memcpy(void *dest, const void *src, unsigned long n);
void good_mmio_memcpy(char *dst, char *src) {
unsigned int len = READ_REG(0x40001000);
if (len <= MAX_DMA_LEN)
memcpy(dst, src, len);
}References |
|
|
| where | ||
| MmioFlow::flowPath(source, sink) and | ||
| isMemcpySizeSink(sink.getNode(), memcpyCall) | ||
| select memcpyCall, source, sink, |
There was a problem hiding this comment.
Because you're not using the sink as alert location (you're using memcpyCall instead. CI is giving the errors below. To solve this you should either use the sink as alert location, or implement getASelectedSinkLocation (see here)
Cannot find (26,3)-(26,8).expected file.
--- expected
+++ actual
@@ -1,1 +1,3 @@
-
+Filtering alerts to these ranges:
+ test.c:(26,3)-(26,8)
+Wrongly omitted: | test.c:26:3:26:8 | call to memcpy | test.c:25:18:25:37 | * ... | test.c:26:20:26:22 | len | Memory copy size argument is derived from $@ without sufficient bounds validation. | test.c:25:18:25:37 | * ... | an MMIO/DMA hardware register read |
Error: [2/3] [eval 307ms] FAILED(RESULT) /home/runner/work/semmle-code/semmle-code/ql/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-120/MmioUnsanitizedMemcpy/DIFF-INFORMED/MmioUnsanitizedMemcpy/test.c/(26,3)-(26,8)
Cannot find (31,3)-(31,9).expected file.
--- expected
+++ actual
@@ -1,1 +1,3 @@
-
+Filtering alerts to these ranges:
+ test.c:(31,3)-(31,9)
+Wrongly omitted: | test.c:31:3:31:9 | call to memmove | test.c:30:18:30:37 | * ... | test.c:31:21:31:23 | len | Memory copy size argument is derived from $@ without sufficient bounds validation. | test.c:30:18:30:37 | * ... | an MMIO/DMA hardware register read |
Error: [2/3] [eval 292ms] FAILED(RESULT) /home/runner/work/semmle-code/semmle-code/ql/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-120/MmioUnsanitizedMemcpy/DIFF-INFORMED/MmioUnsanitizedMemcpy/test.c/(31,3)-(31,9)
Cannot find (36,3)-(36,9).expected file.
--- expected
+++ actual
@@ -1,1 +1,3 @@
-
+Filtering alerts to these ranges:
+ test.c:(36,3)-(36,9)
+Wrongly omitted: | test.c:36:3:36:9 | call to strncpy | test.c:35:18:35:37 | * ... | test.c:36:21:36:23 | len | Memory copy size argument is derived from $@ without sufficient bounds validation. | test.c:35:18:35:37 | * ... | an MMIO/DMA hardware register read |
Error: [2/3] [eval 254ms] FAILED(RESULT) /home/runner/work/semmle-code/semmle-code/ql/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-120/MmioUnsanitizedMemcpy/DIFF-INFORMED/MmioUnsanitizedMemcpy/test.c/(36,3)-(36,9)
Cannot find (41,3)-(41,8).expected file.
--- expected
+++ actual
@@ -1,1 +1,3 @@
-
+Filtering alerts to these ranges:
+ test.c:(41,3)-(41,8)
+Wrongly omitted: | test.c:41:3:41:8 | call to memcpy | test.c:40:18:40:37 | * ... | test.c:41:20:41:22 | len | Memory copy size argument is derived from $@ without sufficient bounds validation. | test.c:40:18:40:37 | * ... | an MMIO/DMA hardware register read |
Error: [2/3] [eval 234ms] FAILED(RESULT) /home/runner/work/semmle-code/semmle-code/ql/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-120/MmioUnsanitizedMemcpy/DIFF-INFORMED/MmioUnsanitizedMemcpy/test.c/(41,3)-(41,8)
Summary
Adds a new security query
cpp/mmio-unsanitized-memcpytargeting unsanitized memory copy operations (memcpy,memmove,strncpy) where size parameters derive directly from hardware registers (MMIO/DMA) without relational bounds checks.Motivation & Domain Context
Standard buffer overflow queries (
UnboundedWrite.ql,OverrunWrite.ql) model user-space strings and generic memory ops, but do not model volatile register macro reads (READ_REG,GET_MMIO) commonly found in microcontroller drivers, RTOS kernels, and embedded hardware stacks. This query fills a gap for embedded C/C++ static analysis.Query Design & Architecture
DataFlow::ConfigSigwithTaintTracking::Global.READ_REG,GET_MMIO,REG_READ,DMA_READ).memcpy,memmove,strncpy,wmemcpy,wmemmove.IRGuardsviaDataFlow::BarrierGuard<lessThanOrEqual/3>to recognizeif (len <= MAX)conditions and prevent false positives.lessThanOrEqualuses the publicOperand+getConvertedResultExpression()pattern.cpp,TaintTracking,IRGuards). Zerointernal./DataFlowImplCommondependencies.Verification & Test Results
codeql test run cpp/ql/test/query-tests/Security/CWE/CWE-120/MmioUnsanitizedMemcpy/All 1 tests passed(3 positive alerts, 3 false-positive barrier test cases clean).codeql generate query-helppassed DTD verification and rendered clean markdown.includeofcpp/mmio-unsanitized-memcpyincpp-security-extended.qls.Checklist
@kind path-problem,@precision medium,@security-severity 8.6)..qhelpfile provided with valid DTD structure and Bad/Good examples..qlref,test.c, and verified.expectedoutput..qlssuite.internal.module imports used.