Skip to content

Commit aa8872a

Browse files
added storage_var collision and dangerous import (#119)
* added storage_var collision and dangerous import * corrected errors in dangerous_public_imports_in_libraries * corrected errors in dangerous_public_imports_in_libraries
1 parent fde8afb commit aa8872a

File tree

4 files changed

+112
-1
lines changed

4 files changed

+112
-1
lines changed

not-so-smart-contracts/cairo/L1_to_L2_address_conversion/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function badDepositToL2(uint256 to, uint256 amount) public returns (bool) {
1717
return true;
1818
}
1919
20-
function goodDepositToL2(uint256 to, uint256 amount) public returns (bool) {
20+
function betterDepositToL2(uint256 to, uint256 amount) public returns (bool) {
2121
require(to !=0 && to < STARKNET_FIELD_PRIME, "invalid address"); //verifies 0 < to < P
2222
token.transferFrom(to, address(this),amount);
2323
emit Deposited(to,amount); // this message gets processed on L2

not-so-smart-contracts/cairo/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Each _Not So Smart Contract_ includes a standard set of information:
2323
| [Signature replays](replay_protection) | Account abstraction requires robust reuse protections |
2424
| [L1 to L2 Address Conversion](l1_to_l2_address_conversion) | L1 to L2 messaging requires L2 address checks |
2525
| [Incorrect Felt Comparison](incorrect_felt_comparison) | Unexpected results can occur during felt comparison |
26+
| [Namespace Storage Var Collision](namespace_storage_var_collision) | Storage variables are not scoped by namespaces |
27+
| [Dangerous Public Imports in Libraries](dangerous_public_imports_in_libraries) | Nonimported external functions can still be called |
2628

2729
## Credits
2830

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Dangerous Public Imports in Libraries
2+
3+
When a library is imported in Cairo, all functions can be called even if some of them are not declared in the import statement. As a result, it is possible to call functions that a developer may think is unexposed, leading to unexpected behavior.
4+
5+
# Example
6+
7+
Consider the library `library.cairo`. Even though the `example.cairo` file imports only the `check_owner()` and the `_do_something()` function, the `bypass_owner_do_something()` function is still exposed and can thus be called, making it possible to circumvent the owner check.
8+
9+
```cairo
10+
11+
# library.cairo
12+
13+
%lang starknet
14+
from starkware.cairo.common.cairo_builtins import HashBuiltin
15+
@storage_var
16+
func owner() -> (res: felt):
17+
end
18+
19+
func check_owner{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr: felt*}():
20+
let caller = get_caller_address()
21+
let owner = owner.read()
22+
assert caller = owner
23+
return ()
24+
end
25+
26+
func do_something():
27+
# do something potentially dangerous that only the owner can do
28+
return ()
29+
end
30+
31+
# for testing purposes only
32+
@external
33+
func bypass_owner_do_something():
34+
do_something()
35+
return ()
36+
end
37+
38+
39+
40+
# example.cairo
41+
%lang starknet
42+
%builtins pedersen range_check
43+
from starkware.cairo.common.cairo_builtins import HashBuiltin
44+
from library import check_owner(), do_something()
45+
# Even though we just import check_owner() and do_something(), we can still call bypass_owner_do_something()!
46+
func check_owner_and_do_something{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr: felt*}():
47+
check_owner()
48+
do_something()
49+
return ()
50+
end
51+
```
52+
53+
# Mitigations
54+
55+
Make sure to exercise caution when declaring external functions in a library. Recognize the possible state changes that can be made through the function and verify it is acceptable for anyone to call it. In addition, [Amarna](https://github.com/crytic/amarna) has a detector to uncover this issue.
56+
57+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Namespace Storage Variable Collsion
2+
3+
In Cairo, it is possible to use namespaces to scope functions under an identifier. However, storage variables are not scoped by these namespaces. If a developer accidentally uses the same variable name in two different namespaces, it could lead to a storage collision.
4+
5+
# Example
6+
7+
The following example has been copied from [here](https://gist.github.com/koloz193/18cb491167e844e9a28ac69825f68975). Suppose we have two different namespaces `A` and `B`, both with the same `balance` storage variable. In addition, both namespaces have respective functions `increase_balance()` and `get_balance()` to increment the storage variable and retrieve it respectively. When either `increase_balance_a` or `increase_balance_b()` is called, the expected behavior would be to have two seperate storage variables have their balance increased respectively. However, because storage variables are not scoped by namespaces, there will be one `balance` variable updated twice:
8+
9+
```cairo
10+
%lang starknet
11+
12+
from starkware.cairo.common.cairo_builtins import HashBuiltin
13+
14+
from openzeppelin.a import A
15+
from openzeppelin.b import B
16+
17+
@external
18+
func increase_balance_a{
19+
syscall_ptr : felt*, pedersen_ptr : HashBuiltin*,
20+
range_check_ptr}(amount : felt):
21+
A.increase_balance(amount)
22+
return ()
23+
end
24+
25+
@external
26+
func increase_balance_b{
27+
syscall_ptr : felt*, pedersen_ptr : HashBuiltin*,
28+
range_check_ptr}(amount : felt):
29+
B.increase_balance(amount)
30+
return ()
31+
end
32+
33+
@view
34+
func get_balance_a{
35+
syscall_ptr : felt*, pedersen_ptr : HashBuiltin*,
36+
range_check_ptr}() -> (res : felt):
37+
let (res) = A.get_balance()
38+
return (res)
39+
end
40+
41+
@view
42+
func get_balance_b{
43+
syscall_ptr : felt*, pedersen_ptr : HashBuiltin*,
44+
range_check_ptr}() -> (res : felt):
45+
let (res) = B.get_balance()
46+
return (res)
47+
end
48+
```
49+
50+
# Mitigations
51+
52+
Make sure to not use the same storage variable name in the namespace (or change the return value's name, see [here](https://github.com/crytic/amarna/issues/10)). Also use [Amarna](https://github.com/crytic/amarna) to uncover this issue, since it has a detector for storage variable collisions.

0 commit comments

Comments
 (0)