+
+
+Firmware and embedded drivers often copy data into buffers using lengths read from
+allowlisted MMIO register macros such as READ_REG or GET_MMIO.
+When those lengths are not validated against the destination buffer size, an attacker who
+can influence hardware registers or DMA metadata can trigger buffer overflows.
+
+
+
+
+Always validate MMIO/DMA-derived lengths before passing them to memcpy,
+memmove, or strncpy. Compare against a compile-time maximum
+and reject or clamp out-of-range values before copying.
+
+
+
+Bad: length from an MMIO register used directly as the copy size.
+
+Good: defensive bounds check before the copy.
+
+
+
+
+CWE-120: Buffer Copy without Checking Size of Input
+
+
+CWE-787: Out-of-bounds Write
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-120/MmioUnsanitizedMemcpy.ql b/cpp/ql/src/experimental/Security/CWE/CWE-120/MmioUnsanitizedMemcpy.ql
new file mode 100644
index 000000000000..d48796c62338
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-120/MmioUnsanitizedMemcpy.ql
@@ -0,0 +1,64 @@
+/**
+ * @name MMIO/DMA unsanitized memory copy
+ * @description Memory copy sizes derived from allowlisted MMIO/DMA register-read
+ * macros without bounds validation may overflow destination buffers.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision low
+ * @id cpp/mmio-unsanitized-memcpy
+ * @tags security
+ * experimental
+ * external/cwe/cwe-120
+ * external/cwe/cwe-787
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.TaintTracking
+import semmle.code.cpp.controlflow.IRGuards
+import MmioFlow::PathGraph
+
+/** Holds if `source` reads MMIO/DMA state through an allowlisted register macro. */
+predicate isMmioSource(DataFlow::Node source) {
+ exists(MacroInvocation mi |
+ mi.getMacro().hasName(["READ_REG", "GET_MMIO", "REG_READ", "DMA_READ"]) and
+ source.asExpr() = mi.getExpr()
+ )
+}
+
+predicate isMemcpySizeSink(DataFlow::Node sink, FunctionCall fc) {
+ fc.getTarget().hasName(["memcpy", "memmove", "strncpy", "wmemcpy", "wmemmove"]) and
+ sink.asExpr() = fc.getArgument(2)
+}
+
+/** Recognizes relational comparison bounds checks using public IRGuards API. */
+predicate lessThanOrEqual(IRGuardCondition g, Expr e, boolean branch) {
+ exists(Operand left |
+ g.comparesLt(left, _, _, true, branch) or
+ g.comparesEq(left, _, _, true, branch)
+ |
+ left.getDef().getConvertedResultExpression() = e
+ )
+}
+
+module MmioConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { isMmioSource(source) }
+
+ predicate isSink(DataFlow::Node sink) { isMemcpySizeSink(sink, _) }
+
+ predicate isBarrier(DataFlow::Node node) {
+ node = DataFlow::BarrierGuard