Skip to main content

Unsafe map get

Description

The use of certain methods (get, get_unchecked, try_get_unchecked) on a Map object in the Soroban environment without appropriate error handling can lead to potential runtime panics. This vulnerability stems from accessing the map's values with keys that may not exist, without using safer alternatives that check the existence of the key. Such practices can compromise the robustness of the smart contract by causing it to terminate unexpectedly, which may lead to denial of service or inconsistent state within the contract.

Exploit Scenario

Consider the following Soroban contract:

    #[contractimpl]
impl UnsafeMapGet {
pub fn get_from_map(env: Env) -> Option<i32> {
let map: Map<Val, Val> = map![&env, (1i32.into_val(&env), 2i64.into_val(&env))];
let map: Val = map.into();
let map: Map<i32, i32> = map.try_into_val(&env).unwrap();
map.get(1)
}
}

This function retrieves values from a map using map.get() without checking if the key actually exists in the map. If the key doesn't exist after the conversion, get will panic, causing the entire contract to fail.

The vulnerable code example can be found here.

Remediation

Both remediated functions presented below ensure the contract doesn't panic due to missing keys. The remediated contract avoid the unsafe map get vulnerability by using try_get for safer access and ensuring the map keys and values have compatible types throughout the process.

    #[contractimpl]
impl UnsafeMapGet {
pub fn get_map_with_different_values(env: Env, key: i32) -> Result<Option<i32>, Error> {
let map: Map<Val, Val> = map![
&env,
(1i32.into_val(&env), 2i32.into_val(&env)),
(3i32.into_val(&env), 4i64.into_val(&env)),
];
let map: Val = map.into();
let map: Map<i32, i32> = map.try_into_val(&env).unwrap();
map.try_get(key).map_err(Error::from)
}

pub fn get_map_with_different_keys(env: Env, key: i32) -> Result<Option<i32>, Error> {
let map: Map<Val, Val> = map![
&env,
(1i32.into_val(&env), 2i32.into_val(&env)),
(3i64.into_val(&env), 4i32.into_val(&env)),
];
let map: Val = map.into();
let map: Map<i32, i32> = map.try_into_val(&env).unwrap();
map.try_get(key).map_err(Error::from)
}
}

The remediated code example can be found here.