Infinite recursion possible with module dependencies
Description
Initia supports publishing Move modules from Move code. It is implemented as a publish request, which is posted from the Move code, and is executed at the end of the transaction. The newly published module will not be available until the next transaction. There can only be one pending request per transaction, but it is possible to publish an arbitrary number of modules simultaneously at the same owner address.
The to-be-published modules are taken as an array of binary modules, which are verified at some point before publication. However, before the verification, the following code is run to topologically sort the modules in the dependency graph.
pub fn sort_by_deps(
map: &BTreeMap<ModuleId, (&[u8], CompiledModule)>,
order: &mut Vec<ModuleId>,
id: ModuleId,
) {
if order.contains(&id) {
return;
}
let compiled = &map.get(&id).unwrap().1;
for dep in compiled.immediate_dependencies() {
// Only consider deps which are actually in this package. Deps for outside
// packages are considered fine because of package deployment order. Note
// that because of this detail, we can't use existing topsort from Move utils.
if map.contains_key(&dep) {
sort_by_deps(map, order, dep);
}
}
order.push(id)
}
Impact
If there is a cyclical dependency, this code recurses infinitely and leads to a chain halt.
Recommendations
Detect circular dependencies in this code before running this code.
Remediation
This issue has been acknowledged by Initia Labs, and a fix was implemented in commit 01d8125d↗.