Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion graph/Graph.i
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,10 @@ VertexPathIterator *
path_iterator(const RiseFall *rf,
const MinMax *min_max)
{
return new VertexPathIterator(self, rf, min_max, Sta::sta());
VertexPathIterator *iter = new VertexPathIterator(self, rf, min_max, Sta::sta());
// The iterator caches a raw Path*; register it for the stale-handle guard.
Sta::sta()->search()->registerValidHandle(iter);
return iter;
}

} // Vertex methods
Expand Down
14 changes: 14 additions & 0 deletions include/sta/Search.hh
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ public:
void deleteFilter();
void deleteFilteredArrivals();

// Stale path-handle guard (OpenROAD #10210). PathEnds and the Paths they
// expose are freed/reallocated on a search update, so a handle held across
// one dangles. These track the handles currently handed to Tcl so the Tcl
// accessors report a clean error instead of crashing (best-effort: a reused
// address is not detected). The Tcl-only guard does not cover C++ callers
// holding a Path*/PathEnd* directly; the contract is that a path is valid
// only until the next search update, so copy stable fields out first (cf. the
// rsz Path::prevArc() fix).
void registerValidHandle(const void *handle);
bool handleValid(const void *handle) const;

VertexSet &endpoints();
void endpointsInvalid();

Expand Down Expand Up @@ -671,6 +682,9 @@ protected:
bool postpone_latch_outputs_{false};
std::vector<Path*> enum_paths_;

// PathEnd*/Path* handles currently handed to external callers (guard methods).
std::unordered_set<const void*> valid_handles_;

VisitPathEnds *visit_path_ends_;
GatedClk *gated_clk_;
CheckCrpr *check_crpr_;
Expand Down
16 changes: 16 additions & 0 deletions search/Search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,26 @@ Search::clear()
void
Search::deletePathGroups()
{
// Free site for the stale-handle guard set (see Search.hh).
valid_handles_.clear();
for (Mode *mode : modes_)
mode->deletePathGroups();
}

// Stale path-handle guard (see Search.hh).
void
Search::registerValidHandle(const void *handle)
{
if (handle)
valid_handles_.insert(handle);
}

bool
Search::handleValid(const void *handle) const
{
return valid_handles_.contains(handle);
}

bool
Search::crprPathPruningEnabled() const
{
Expand Down
61 changes: 60 additions & 1 deletion search/Search.i
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,53 @@

using namespace sta;

// Stale path-handle guard (OpenROAD #10210). Error on a dangling handle; throws.
// Use directly inside %extend bodies (the global %exception catches the throw).
static void
reportStaleHandle()
{
Sta::sta()->report()->error(2310,
"stale path handle: a path or path end is only"
" valid until the next timing search update.");
}

// Same error for check typemaps, which run outside the global %exception: catch
// and reformat to a Tcl error here. Follow with SWIG_fail at the call site.
static void
staleHandleError(Tcl_Interp *interp)
{
try {
reportStaleHandle();
}
catch (ExceptionMsg &excp) {
if (!excp.suppressed()) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "Error: ", excp.what(), nullptr);
}
}
}

%}

// Validate every PathEnd*/Path* entering Tcl (accessor self args + free
// functions). Declared before the classes so it covers their %extend methods.
// VertexPathIterator is guarded inline in has_next()/next() instead -- a typemap
// would also block finish(), leaking a stale iterator that can't be deleted.
%typemap(check) PathEnd *, Path * {
if (!Sta::sta()->search()->handleValid($1)) {
staleHandleError(interp);
SWIG_fail;
}
}

// Register every Path* returned to Tcl so the check above can detect staleness.
// (PathEnds are registered in find_path_ends.)
%typemap(out) Path *, const Path * {
Sta::sta()->search()->registerValidHandle($1);
Tcl_SetObjResult(interp, SWIG_NewInstanceObj(SWIG_as_voidptr($1),
SWIGTYPE_p_Path, 0));
}

////////////////////////////////////////////////////////////////
//
// Empty class definitions to make swig happy.
Expand Down Expand Up @@ -383,6 +428,10 @@ find_path_ends(ExceptionFrom *from,
setup, hold,
recovery, removal,
clk_gating_setup, clk_gating_hold);
// Register for the Tcl stale-handle guard. Tcl-only: internal C++
// Sta::findPathEnds callers do not register and pay no cost.
for (PathEnd *end : ends)
sta->search()->registerValidHandle(end);
return ends;
}

Expand Down Expand Up @@ -1286,10 +1335,20 @@ start_path()
}

%extend VertexPathIterator {
bool has_next() { return self->hasNext(); }
// has_next()/next() deref the iterator's cached Path*, which a search update
// frees, so validate the handle first (registered in Vertex::path_iterator).
// finish() only deletes the iterator, so it stays unguarded.
bool has_next()
{
if (!Sta::sta()->search()->handleValid(self))
reportStaleHandle();
return self->hasNext();
}
Path *
next()
{
if (!Sta::sta()->search()->handleValid(self))
reportStaleHandle();
return self->next();
}

Expand Down
4 changes: 4 additions & 0 deletions tcl/StaTclTypes.i
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,8 @@ using namespace sta;
while (path_iter.hasNext()) {
Path *path = &path_iter.next();
Path *copy = new Path(path);
// Register for the stale-handle guard (see Search guard methods).
Sta::sta()->search()->registerValidHandle(copy);
Tcl_Obj *obj = SWIG_NewInstanceObj(copy, SWIGTYPE_p_Path, false);
Tcl_ListObjAppendElement(interp, list, obj);
}
Expand Down Expand Up @@ -1399,6 +1401,8 @@ using namespace sta;
case PropertyValue::Type::paths: {
Tcl_Obj *list = Tcl_NewListObj(0, nullptr);
for (const Path *path : *value.paths()) {
// Register for the stale-handle guard (see Search guard methods).
Sta::sta()->search()->registerValidHandle(path);
Tcl_Obj *obj = SWIG_NewInstanceObj(const_cast<Path*>(path), SWIGTYPE_p_Path, false);
Tcl_ListObjAppendElement(interp, list, obj);
}
Expand Down
3 changes: 3 additions & 0 deletions test/stale_path_uaf.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Error: 2310 stale path handle: a path or path end is only valid until the next timing search update.
Error: 2310 stale path handle: a path or path end is only valid until the next timing search update.
Error: 2310 stale path handle: a path or path end is only valid until the next timing search update.
37 changes: 37 additions & 0 deletions test/stale_path_uaf.tcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Guard for stale path handles across a search update (OpenROAD #10210).
# Holding a PathEnd, or a Path saved separately, past the next query must error
# cleanly, not crash.
read_liberty ../examples/nangate45_typ.lib.gz
read_verilog ../examples/example1.v
link_design top
create_clock -name clk -period 10 {clk1 clk2 clk3}
set_input_delay -clock clk 0 {in1 in2}

report_checks

# Stale PathEnd, second query = find_timing_paths.
set pe [lindex [find_timing_paths -through u1/Z] 0]
find_timing_paths -through u2/ZN
catch { puts "stale PathEnd : slack=[$pe slack]" } msg
puts $msg

# Stale PathEnd, second query = report_checks.
set pe [lindex [find_timing_paths -through u1/Z] 0]
report_checks -path_delay max
catch { puts "stale PathEnd : slack=[$pe slack]" } msg
puts $msg

# Stale Path saved separately (set p [$pe path]) across the next query.
set pe [lindex [find_timing_paths -through u1/Z] 0]
set p [$pe path]
find_timing_paths -through u2/ZN
catch { puts "stale Path : arrival=[$p arrival]" } msg
puts $msg

# Stale VertexPathIterator held across the next query. Iterate the filtered
# endpoint vertex, whose paths_ array the next query frees/reallocates.
set pe [lindex [find_timing_paths -through u1/Z] 0]
set it [[$pe vertex] path_iterator rise max]
find_timing_paths -through u2/ZN
catch { while {[$it has_next]} { set p [$it next]; puts "stale PathIter : arrival=[$p arrival]" } } msg
puts $msg