Potential out-of-gas reversion when checking object permissions
Description
In Initia, an object can belong to another object, recursively. An object is allowed to be transferred without the TransferRef
special permission if the path to the owner (any ancestor) that is currently transferring the object has the allow_ungated_transfer
flag set. This means all objects on that ownership path must have that flag set and that the path must end in the "currently transferring owner", which must sign the operation.
In order to check this condition, the following loop is used.
let count = 0;
while (owner != current_address) {
let count = count + 1;
assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING));
// At this point, the first object exists and so the more likely case is that the
// object's owner is not an object. So we return a more sensible error.
assert!(
exists<ObjectCore>(current_address),
error::permission_denied(ENOT_OBJECT_OWNER),
);
let object = borrow_global<ObjectCore>(current_address);
assert!(
object.allow_ungated_transfer,
error::permission_denied(ENO_UNGATED_TRANSFERS),
);
current_address = object.owner;
};
Note that the buggy section of the code shown below appears in two places in both copies of object.move, for a total of four occurrences.
let count = 0;
while (owner != current_address) {
let count = count + 1;
assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING));
The variable count
inside the loop shadows the variable outside the loop, which makes it always have the value 1
and always pass the check.
Impact
If this loop continues for too long, either due to a dependency cycle or if the path is simply too long, there may be an out-of-gas condition. Since the correct functioning of the code would result in a revert anyway, this does not have a severe impact.
Recommendations
Make the count
variable no longer shadowed.