Skip to content

Commit 16e6bfd

Browse files
authored
Fix memory leak in string_map.c in rcutils (ros2#411)
* Add in a string_map test for failure after certain allocations. Signed-off-by: Chris Lalancette <clalancette@gmail.com> Signed-off-by: Nikolai Morin <nikolai.morin@apex.ai>
1 parent 1db1f16 commit 16e6bfd

2 files changed

Lines changed: 113 additions & 58 deletions

File tree

src/string_map.c

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@ extern "C"
2929
#include "rcutils/format_string.h"
3030
#include "rcutils/types/rcutils_ret.h"
3131

32+
typedef struct key_value_pair
33+
{
34+
char * key;
35+
char * value;
36+
} key_value_pair_t;
37+
3238
typedef struct rcutils_string_map_impl_s
3339
{
34-
char ** keys;
35-
char ** values;
40+
key_value_pair_t * key_value_pairs;
3641
size_t capacity;
3742
size_t size;
3843
rcutils_allocator_t allocator;
@@ -64,8 +69,7 @@ rcutils_string_map_init(
6469
RCUTILS_SET_ERROR_MSG("failed to allocate memory for string map impl struct");
6570
return RCUTILS_RET_BAD_ALLOC;
6671
}
67-
string_map->impl->keys = NULL;
68-
string_map->impl->values = NULL;
72+
string_map->impl->key_value_pairs = NULL;
6973
string_map->impl->capacity = 0;
7074
string_map->impl->size = 0;
7175
string_map->impl->allocator = allocator;
@@ -130,6 +134,17 @@ rcutils_string_map_get_size(const rcutils_string_map_t * string_map, size_t * si
130134
return RCUTILS_RET_OK;
131135
}
132136

137+
static void
138+
__remove_key_and_value_at_index(rcutils_string_map_impl_t * string_map_impl, size_t index)
139+
{
140+
rcutils_allocator_t allocator = string_map_impl->allocator;
141+
allocator.deallocate(string_map_impl->key_value_pairs[index].key, allocator.state);
142+
string_map_impl->key_value_pairs[index].key = NULL;
143+
allocator.deallocate(string_map_impl->key_value_pairs[index].value, allocator.state);
144+
string_map_impl->key_value_pairs[index].value = NULL;
145+
string_map_impl->size--;
146+
}
147+
133148
rcutils_ret_t
134149
rcutils_string_map_reserve(rcutils_string_map_t * string_map, size_t capacity)
135150
{
@@ -147,46 +162,35 @@ rcutils_string_map_reserve(rcutils_string_map_t * string_map, size_t capacity)
147162
return RCUTILS_RET_OK;
148163
} else if (capacity == 0) {
149164
// if the requested capacity is zero, then make sure the existing keys and values are free'd
150-
allocator.deallocate(string_map->impl->keys, allocator.state);
151-
string_map->impl->keys = NULL;
152-
allocator.deallocate(string_map->impl->values, allocator.state);
153-
string_map->impl->values = NULL;
165+
// size is known to be 0 here because of the recursive call above.
166+
allocator.deallocate(string_map->impl->key_value_pairs, allocator.state);
167+
string_map->impl->key_value_pairs = NULL;
154168
// falls through to normal function end
155169
} else {
156170
// if the capacity non-zero and different, use realloc to increase/shrink the size
157171
// note that realloc when the pointer is NULL is the same as malloc
158172
// note also that realloc will shrink the space if needed
159173

160-
// ensure that reallocate won't overflow capacity
161-
if (capacity > (SIZE_MAX / sizeof(char *))) {
174+
// ensure that reallocate won't overflow SIZE_MAX
175+
if (capacity > (SIZE_MAX / sizeof(key_value_pair_t))) {
162176
RCUTILS_SET_ERROR_MSG("requested capacity for string_map too large");
163177
return RCUTILS_RET_BAD_ALLOC;
164178
}
165179

166-
// resize the keys, assigning the result only if it succeeds
167-
char ** new_keys =
168-
allocator.reallocate(string_map->impl->keys, capacity * sizeof(char *), allocator.state);
169-
if (NULL == new_keys) {
170-
RCUTILS_SET_ERROR_MSG("failed to allocate memory for string_map keys");
171-
return RCUTILS_RET_BAD_ALLOC;
172-
}
173-
string_map->impl->keys = new_keys;
174-
175-
// resize the values, assigning the result only if it succeeds
176-
char ** new_values =
177-
allocator.reallocate(string_map->impl->values, capacity * sizeof(char *), allocator.state);
178-
if (NULL == new_values) {
179-
RCUTILS_SET_ERROR_MSG("failed to allocate memory for string_map values");
180+
// resize the keys and values, assigning the result only if it succeeds
181+
key_value_pair_t * new_key_value_pairs = allocator.reallocate(
182+
string_map->impl->key_value_pairs, capacity * sizeof(key_value_pair_t), allocator.state);
183+
if (NULL == new_key_value_pairs) {
184+
RCUTILS_SET_ERROR_MSG("failed to allocate memory for string_map key-value pairs");
180185
return RCUTILS_RET_BAD_ALLOC;
181186
}
182-
string_map->impl->values = new_values;
187+
string_map->impl->key_value_pairs = new_key_value_pairs;
183188

184189
// zero out the new memory, if there is any (expanded instead of shrunk)
185190
if (capacity > string_map->impl->capacity) {
186-
size_t i = string_map->impl->capacity;
187-
for (; i < capacity; ++i) {
188-
string_map->impl->keys[i] = NULL;
189-
string_map->impl->values[i] = NULL;
191+
for (size_t i = string_map->impl->capacity; i < capacity; ++i) {
192+
string_map->impl->key_value_pairs[i].key = NULL;
193+
string_map->impl->key_value_pairs[i].value = NULL;
190194
}
191195
}
192196
// falls through to normal function end
@@ -195,29 +199,19 @@ rcutils_string_map_reserve(rcutils_string_map_t * string_map, size_t capacity)
195199
return RCUTILS_RET_OK;
196200
}
197201

198-
static void
199-
__remove_key_and_value_at_index(rcutils_string_map_impl_t * string_map_impl, size_t index)
200-
{
201-
rcutils_allocator_t allocator = string_map_impl->allocator;
202-
allocator.deallocate(string_map_impl->keys[index], allocator.state);
203-
string_map_impl->keys[index] = NULL;
204-
allocator.deallocate(string_map_impl->values[index], allocator.state);
205-
string_map_impl->values[index] = NULL;
206-
string_map_impl->size--;
207-
}
208-
209202
rcutils_ret_t
210203
rcutils_string_map_clear(rcutils_string_map_t * string_map)
211204
{
212205
RCUTILS_CHECK_ARGUMENT_FOR_NULL(string_map, RCUTILS_RET_INVALID_ARGUMENT);
213206
RCUTILS_CHECK_FOR_NULL_WITH_MSG(
214207
string_map->impl, "invalid string map", return RCUTILS_RET_STRING_MAP_INVALID);
215-
size_t i = 0;
216-
for (; i < string_map->impl->capacity; ++i) {
217-
if (string_map->impl->keys[i] != NULL) {
208+
209+
for (size_t i = 0; i < string_map->impl->capacity; ++i) {
210+
if (string_map->impl->key_value_pairs[i].key != NULL) {
218211
__remove_key_and_value_at_index(string_map->impl, i);
219212
}
220213
}
214+
221215
return RCUTILS_RET_OK;
222216
}
223217

@@ -255,14 +249,14 @@ __get_index_of_key_if_exists(
255249
{
256250
size_t i = 0;
257251
for (; i < string_map_impl->capacity; ++i) {
258-
if (NULL == string_map_impl->keys[i]) {
252+
if (NULL == string_map_impl->key_value_pairs[i].key) {
259253
continue;
260254
}
261-
size_t cmp_count = strlen(string_map_impl->keys[i]);
255+
size_t cmp_count = strlen(string_map_impl->key_value_pairs[i].key);
262256
if (key_length > cmp_count) {
263257
cmp_count = key_length;
264258
}
265-
if (strncmp(key, string_map_impl->keys[i], cmp_count) == 0) {
259+
if (strncmp(key, string_map_impl->key_value_pairs[i].key, cmp_count) == 0) {
266260
*index = i;
267261
return true;
268262
}
@@ -292,30 +286,30 @@ rcutils_string_map_set_no_resize(
292286
return RCUTILS_RET_NOT_ENOUGH_SPACE;
293287
}
294288
for (key_index = 0; key_index < string_map->impl->capacity; ++key_index) {
295-
if (NULL == string_map->impl->keys[key_index]) {
289+
if (NULL == string_map->impl->key_value_pairs[key_index].key) {
296290
break;
297291
}
298292
}
299293
assert(key_index < string_map->impl->capacity); // defensive, this should not happen
300-
string_map->impl->keys[key_index] = rcutils_strdup(key, allocator);
301-
if (NULL == string_map->impl->keys[key_index]) {
294+
string_map->impl->key_value_pairs[key_index].key = rcutils_strdup(key, allocator);
295+
if (NULL == string_map->impl->key_value_pairs[key_index].key) {
302296
RCUTILS_SET_ERROR_MSG("failed to allocate memory for key");
303297
return RCUTILS_RET_BAD_ALLOC;
304298
}
305299
should_free_key_on_error = true;
306300
}
307301
// at this point the key is in the map, waiting for the value to set/overwritten
308-
char * original_value = string_map->impl->values[key_index];
302+
char * original_value = string_map->impl->key_value_pairs[key_index].value;
309303
char * new_value = rcutils_strdup(value, allocator);
310304
if (NULL == new_value) {
311-
RCUTILS_SET_ERROR_MSG("failed to allocate memory for key");
305+
RCUTILS_SET_ERROR_MSG("failed to allocate memory for value");
312306
if (should_free_key_on_error) {
313-
allocator.deallocate(string_map->impl->keys[key_index], allocator.state);
314-
string_map->impl->keys[key_index] = NULL;
307+
allocator.deallocate(string_map->impl->key_value_pairs[key_index].key, allocator.state);
308+
string_map->impl->key_value_pairs[key_index].key = NULL;
315309
}
316310
return RCUTILS_RET_BAD_ALLOC;
317311
}
318-
string_map->impl->values[key_index] = new_value;
312+
string_map->impl->key_value_pairs[key_index].value = new_value;
319313
if (original_value != NULL) {
320314
// clean up the old value if not NULL
321315
allocator.deallocate(original_value, allocator.state);
@@ -386,7 +380,7 @@ rcutils_string_map_getn(
386380
}
387381
size_t key_index;
388382
if (__get_index_of_key_if_exists(string_map->impl, key, key_length, &key_index)) {
389-
return string_map->impl->values[key_index];
383+
return string_map->impl->key_value_pairs[key_index].value;
390384
}
391385
return NULL;
392386
}
@@ -408,7 +402,7 @@ rcutils_string_map_get_next_key(
408402
bool given_key_found = false;
409403
size_t i = 0;
410404
for (; i < string_map->impl->capacity; ++i) {
411-
if (string_map->impl->keys[i] == key) {
405+
if (string_map->impl->key_value_pairs[i].key == key) {
412406
given_key_found = true;
413407
// given key found at index i, start there + 1
414408
start_index = i + 1;
@@ -422,9 +416,9 @@ rcutils_string_map_get_next_key(
422416
// iterate through the storage and look for another non-NULL key to return
423417
size_t i = start_index;
424418
for (; i < string_map->impl->capacity; ++i) {
425-
if (string_map->impl->keys[i] != NULL) {
419+
if (string_map->impl->key_value_pairs[i].key != NULL) {
426420
// next key found, return it
427-
return string_map->impl->keys[i];
421+
return string_map->impl->key_value_pairs[i].key;
428422
}
429423
}
430424
// next key (or first key) not found

test/test_string_map.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,3 +1939,64 @@ TEST(test_string_map, strange_keys) {
19391939
EXPECT_STREQ("value1", rcutils_string_map_get(&string_map, "key with spaces"));
19401940
}
19411941
}
1942+
1943+
static int realloc_counter = 0;
1944+
static int realloc_fail_after = -1;
1945+
1946+
static void *
1947+
reallocate_fail_after(void * pointer, size_t size, void * state)
1948+
{
1949+
RCUTILS_UNUSED(state);
1950+
1951+
if (realloc_fail_after >= 0 && realloc_counter++ >= realloc_fail_after) {
1952+
return NULL;
1953+
}
1954+
1955+
return realloc(pointer, size);
1956+
}
1957+
1958+
TEST(test_string_map, partial_allocation_failures)
1959+
{
1960+
{
1961+
rcutils_string_map_t map = rcutils_get_zero_initialized_string_map();
1962+
1963+
rcutils_allocator_t allocator = rcutils_get_default_allocator();
1964+
allocator.reallocate = reallocate_fail_after;
1965+
1966+
realloc_counter = 0;
1967+
realloc_fail_after = 0;
1968+
1969+
rcutils_ret_t ret;
1970+
1971+
// Since we set realloc_fail_after to 0, that means we'll fail immediately
1972+
// and hence rcutils_string_map_init should return BAD_ALLOC
1973+
ret = rcutils_string_map_init(&map, 4, allocator);
1974+
ASSERT_EQ(ret, RCUTILS_RET_BAD_ALLOC);
1975+
}
1976+
1977+
{
1978+
rcutils_string_map_t map = rcutils_get_zero_initialized_string_map();
1979+
1980+
rcutils_allocator_t allocator = rcutils_get_default_allocator();
1981+
allocator.reallocate = reallocate_fail_after;
1982+
1983+
realloc_counter = 0;
1984+
realloc_fail_after = 1;
1985+
1986+
rcutils_ret_t ret;
1987+
1988+
// Since we set realloc_fail_after to 1, that means we'll succeed on the
1989+
// first allocation, so rcutils_string_map_init should return OK
1990+
ret = rcutils_string_map_init(&map, 4, allocator);
1991+
ASSERT_EQ(ret, RCUTILS_RET_OK);
1992+
1993+
// but reserve should fail
1994+
ret = rcutils_string_map_reserve(&map, 2);
1995+
ASSERT_EQ(ret, RCUTILS_RET_BAD_ALLOC);
1996+
1997+
// and we should still be able to fini things without accessing
1998+
// out-of-bounds memory or leaking.
1999+
ret = rcutils_string_map_fini(&map);
2000+
ASSERT_EQ(ret, RCUTILS_RET_OK);
2001+
}
2002+
}

0 commit comments

Comments
 (0)