Fix use-after-free of captured interface typedef reference during parameter cloning (#8076)
This commit is contained in:
parent
93401038c0
commit
d2f62fda93
|
|
@ -254,19 +254,10 @@ LiveNodes collectLiveNodes() {
|
||||||
return liveNodes;
|
return liveNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A live snapshot, when supplied, stops the walk at the first stale back link;
|
// Find the module that owns this node; a snapshot, if given, stops the walk at a stale link.
|
||||||
// callers without one fall back to the sentinel guard below.
|
|
||||||
AstNodeModule* findOwnerModuleImpl(AstNode* nodep, const LiveNodes* liveNodesp) {
|
AstNodeModule* findOwnerModuleImpl(AstNode* nodep, const LiveNodes* liveNodesp) {
|
||||||
for (AstNode* curp = nodep; curp; curp = curp->backp()) {
|
for (AstNode* curp = nodep; curp; curp = curp->backp()) {
|
||||||
if (liveNodesp) {
|
if (liveNodesp && !liveNodesp->count(curp)) return nullptr;
|
||||||
if (!liveNodesp->count(curp)) return nullptr;
|
|
||||||
} else if (reinterpret_cast<uintptr_t>(curp) < 0x1000) {
|
|
||||||
// Legacy callers lack a liveness snapshot; retain the existing guard
|
|
||||||
// against sentinel values encountered in corrupted backp() chains.
|
|
||||||
// It cannot prove an arbitrary freed pointer safe - invalidating
|
|
||||||
// ledger entries at deletion time would make it unnecessary.
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp;
|
if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -365,6 +356,34 @@ void V3LinkDotIfaceCapture::purgeStaleRefs() {
|
||||||
nullStaleLedgerRefs(liveNodes);
|
nullStaleLedgerRefs(liveNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void V3LinkDotIfaceCapture::purgeDeletedSubtree(AstNode* nodep) {
|
||||||
|
if (!s_enabled || s_map.empty() || !nodep) return;
|
||||||
|
// Only track nodes something could point to, within the subtree being deleted.
|
||||||
|
std::unordered_set<const AstNode*> deadps;
|
||||||
|
nodep->foreach([&](AstNode* np) {
|
||||||
|
if (np->maybePointedTo()) deadps.insert(np);
|
||||||
|
});
|
||||||
|
for (auto& kv : s_map) {
|
||||||
|
CapturedEntry& entry = kv.second;
|
||||||
|
// If the main reference is dying, promote a live one so consumers
|
||||||
|
// (which skip an entry with a null reference) still retarget the rest.
|
||||||
|
if (entry.refp && deadps.count(entry.refp)) {
|
||||||
|
entry.refp = nullptr;
|
||||||
|
for (AstRefDType*& xrefp : entry.extraRefps) {
|
||||||
|
if (xrefp && !deadps.count(xrefp)) {
|
||||||
|
entry.refp = xrefp;
|
||||||
|
xrefp = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Null every remaining link into the deleted subtree.
|
||||||
|
entry.foreachLink([&](AstNode*& np) {
|
||||||
|
if (np && deadps.count(np)) np = nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void V3LinkDotIfaceCapture::dumpEntries(const string& label) {
|
void V3LinkDotIfaceCapture::dumpEntries(const string& label) {
|
||||||
UINFO(9, "========== iface capture dumpEntries: " << label << " (entries=" << s_map.size()
|
UINFO(9, "========== iface capture dumpEntries: " << label << " (entries=" << s_map.size()
|
||||||
<< ") ==========");
|
<< ") ==========");
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,9 @@ public:
|
||||||
// Called at pass boundaries before code dereferences ledger pointers.
|
// Called at pass boundaries before code dereferences ledger pointers.
|
||||||
static void purgeStaleRefs();
|
static void purgeStaleRefs();
|
||||||
|
|
||||||
|
// Remove any saved references that point into a subtree, just before it is deleted.
|
||||||
|
static void purgeDeletedSubtree(AstNode* nodep);
|
||||||
|
|
||||||
// Debug: dump all captured entries
|
// Debug: dump all captured entries
|
||||||
static void dumpEntries(const string& label);
|
static void dumpEntries(const string& label);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2190,6 +2190,11 @@ class WidthVisitor final : public VNVisitor {
|
||||||
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack());
|
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack());
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||||
}
|
}
|
||||||
|
// Delete a subtree after removing any saved references that point into it.
|
||||||
|
static void deleteTreeCaptured(AstNode* nodep) {
|
||||||
|
V3LinkDotIfaceCapture::purgeDeletedSubtree(nodep);
|
||||||
|
nodep->deleteTree();
|
||||||
|
}
|
||||||
void visit(AstAttrOf* nodep) override {
|
void visit(AstAttrOf* nodep) override {
|
||||||
VL_RESTORER(m_attrp);
|
VL_RESTORER(m_attrp);
|
||||||
m_attrp = nodep;
|
m_attrp = nodep;
|
||||||
|
|
@ -2206,7 +2211,7 @@ class WidthVisitor final : public VNVisitor {
|
||||||
= (nodep->attrType() == VAttrType::DIM_UNPK_DIMENSIONS ? dim.second
|
= (nodep->attrType() == VAttrType::DIM_UNPK_DIMENSIONS ? dim.second
|
||||||
: (dim.first + dim.second));
|
: (dim.first + dim.second));
|
||||||
nodep->replaceWith(new AstConst(nodep->fileline(), AstConst::Signed32{}, val));
|
nodep->replaceWith(new AstConst(nodep->fileline(), AstConst::Signed32{}, val));
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
VL_DO_DANGLING(deleteTreeCaptured(nodep), nodep);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case VAttrType::DIM_BITS_OR_NUMBER: {
|
case VAttrType::DIM_BITS_OR_NUMBER: {
|
||||||
|
|
@ -2249,7 +2254,7 @@ class WidthVisitor final : public VNVisitor {
|
||||||
case VAttrType::DIM_LOW: {
|
case VAttrType::DIM_LOW: {
|
||||||
AstNode* const newp = new AstConst(nodep->fileline(), AstConst::Signed32{}, 0);
|
AstNode* const newp = new AstConst(nodep->fileline(), AstConst::Signed32{}, 0);
|
||||||
nodep->replaceWith(newp);
|
nodep->replaceWith(newp);
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
VL_DO_DANGLING(deleteTreeCaptured(nodep), nodep);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case VAttrType::DIM_RIGHT:
|
case VAttrType::DIM_RIGHT:
|
||||||
|
|
@ -2271,7 +2276,7 @@ class WidthVisitor final : public VNVisitor {
|
||||||
AstNodeExpr* const newp
|
AstNodeExpr* const newp
|
||||||
= new AstConst(nodep->fileline(), AstConst::Signed32{}, -1);
|
= new AstConst(nodep->fileline(), AstConst::Signed32{}, -1);
|
||||||
nodep->replaceWith(newp);
|
nodep->replaceWith(newp);
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
VL_DO_DANGLING(deleteTreeCaptured(nodep), nodep);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case VAttrType::DIM_BITS: {
|
case VAttrType::DIM_BITS: {
|
||||||
|
|
@ -2304,14 +2309,14 @@ class WidthVisitor final : public VNVisitor {
|
||||||
AstConst* const newp = dimensionValue(nodep->fileline(), baseDTypep,
|
AstConst* const newp = dimensionValue(nodep->fileline(), baseDTypep,
|
||||||
nodep->attrType(), dim);
|
nodep->attrType(), dim);
|
||||||
nodep->replaceWith(newp);
|
nodep->replaceWith(newp);
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
VL_DO_DANGLING(deleteTreeCaptured(nodep), nodep);
|
||||||
}
|
}
|
||||||
} else if (VN_IS(nodep->dimp(), Const)) {
|
} else if (VN_IS(nodep->dimp(), Const)) {
|
||||||
const int dim = VN_AS(nodep->dimp(), Const)->toSInt();
|
const int dim = VN_AS(nodep->dimp(), Const)->toSInt();
|
||||||
AstConst* const newp
|
AstConst* const newp
|
||||||
= dimensionValue(nodep->fileline(), dtypep, nodep->attrType(), dim);
|
= dimensionValue(nodep->fileline(), dtypep, nodep->attrType(), dim);
|
||||||
nodep->replaceWith(newp);
|
nodep->replaceWith(newp);
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
VL_DO_DANGLING(deleteTreeCaptured(nodep), nodep);
|
||||||
} else { // Need a runtime lookup table. Yuk.
|
} else { // Need a runtime lookup table. Yuk.
|
||||||
UASSERT_OBJ(nodep->fromp() && dtypep, nodep, "Unsized expression");
|
UASSERT_OBJ(nodep->fromp() && dtypep, nodep, "Unsized expression");
|
||||||
AstVar* const varp = dimensionVarp(dtypep, nodep->attrType(), msbdim);
|
AstVar* const varp = dimensionVarp(dtypep, nodep->attrType(), msbdim);
|
||||||
|
|
@ -2319,7 +2324,7 @@ class WidthVisitor final : public VNVisitor {
|
||||||
AstNodeExpr* const newp
|
AstNodeExpr* const newp
|
||||||
= new AstArraySel{nodep->fileline(), newVarRefDollarUnit(varp), dimp};
|
= new AstArraySel{nodep->fileline(), newVarRefDollarUnit(varp), dimp};
|
||||||
nodep->replaceWith(newp);
|
nodep->replaceWith(newp);
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
VL_DO_DANGLING(deleteTreeCaptured(nodep), nodep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of either the GNU Lesser General Public License Version 3
|
||||||
|
# or the Perl Artistic License Version 2.0.
|
||||||
|
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
import vltest_bootstrap
|
||||||
|
|
||||||
|
test.scenarios('vlt')
|
||||||
|
|
||||||
|
test.lint()
|
||||||
|
|
||||||
|
test.passes()
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// DESCRIPTION: Verilator: interface typedef $bits() capture use-after-free
|
||||||
|
//
|
||||||
|
// UAF: the $bits fold computes the correct constant
|
||||||
|
// before freeing, so no value is wrong. It therefore fails under
|
||||||
|
// --enable-dev-asan (heap-use-after-free), and -- once the findOwnerModule
|
||||||
|
// address guard is removed -- as a deterministic SIGSEGV in a --debug build.
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||||
|
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
typedef struct packed {int unsigned W;} cfg_t;
|
||||||
|
|
||||||
|
interface types_if #(parameter cfg_t cfg = '{default: 0});
|
||||||
|
typedef logic [cfg.W-1:0] rq_t;
|
||||||
|
endinterface
|
||||||
|
|
||||||
|
module body #(parameter cfg_t cfg = '{default: 0});
|
||||||
|
types_if #(cfg) types();
|
||||||
|
localparam int DW = $bits(types.rq_t); // AstAttrOf(DIM_BITS) over captured ref
|
||||||
|
logic [DW-1:0] v;
|
||||||
|
initial v = '0;
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module t;
|
||||||
|
body #('{W: 8}) b0();
|
||||||
|
body #('{W: 16}) b1();
|
||||||
|
endmodule
|
||||||
Loading…
Reference in New Issue