Skip to content

Commit

Permalink
hashtable: fix environment variable corruption
Browse files Browse the repository at this point in the history
Only first previously deleted entry was recognized, leading hsearch_r
to think that there was no previously deleted entry. It then conluded
that a free entry was found, even if there were no free entries and it
overwrote a random entry.

This patch makes sure all deleted or free entries are always found and
also introduces constants for the 0 and -1 numbers. Unit tests to excersise a
simple hash table usage and catch the corruption were added.

To trash your environment, simply run this loop:

setenv i 0
while true; do
    setenv v_$i $i
    setenv v_$i
    setexpr i $i + 1
done

Signed-off-by: Roman Kapl <[email protected]>
  • Loading branch information
Roman Kapl authored and trini committed Feb 9, 2019
1 parent 4d9dbb1 commit 9dfdbd9
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 3 deletions.
13 changes: 10 additions & 3 deletions lib/hashtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
#define CONFIG_ENV_MAX_ENTRIES 512
#endif

#define USED_FREE 0
#define USED_DELETED -1

#include <env_callback.h>
#include <env_flags.h>
#include <search.h>
Expand Down Expand Up @@ -303,7 +306,7 @@ int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,
*/
unsigned hval2;

if (htab->table[idx].used == -1
if (htab->table[idx].used == USED_DELETED
&& !first_deleted)
first_deleted = idx;

Expand Down Expand Up @@ -335,13 +338,17 @@ int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,
if (idx == hval)
break;

if (htab->table[idx].used == USED_DELETED
&& !first_deleted)
first_deleted = idx;

/* If entry is found use it. */
ret = _compare_and_overwrite_entry(item, action, retval,
htab, flag, hval, idx);
if (ret != -1)
return ret;
}
while (htab->table[idx].used);
while (htab->table[idx].used != USED_FREE);
}

/* An empty bucket has been found. */
Expand Down Expand Up @@ -433,7 +440,7 @@ static void _hdelete(const char *key, struct hsearch_data *htab, ENTRY *ep,
free(ep->data);
ep->callback = NULL;
ep->flags = 0;
htab->table[idx].used = -1;
htab->table[idx].used = USED_DELETED;

--htab->filled;
}
Expand Down
1 change: 1 addition & 0 deletions test/env/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

obj-y += cmd_ut_env.o
obj-y += attr.o
obj-y += hashtable.o
125 changes: 125 additions & 0 deletions test/env/hashtable.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: GPL-2.0
/*
* (C) Copyright 2019
* Roman Kapl, SYSGO, [email protected]
*/

#include <common.h>
#include <command.h>
#include <search.h>
#include <stdio.h>
#include <test/env.h>
#include <test/ut.h>

#define SIZE 32
#define ITERATIONS 10000

static int htab_fill(struct unit_test_state *uts,
struct hsearch_data *htab, size_t size)
{
size_t i;
ENTRY item;
ENTRY *ritem;
char key[20];

for (i = 0; i < size; i++) {
sprintf(key, "%d", (int)i);
item.callback = NULL;
item.data = key;
item.flags = 0;
item.key = key;
ut_asserteq(1, hsearch_r(item, ENTER, &ritem, htab, 0));
}

return 0;
}

static int htab_check_fill(struct unit_test_state *uts,
struct hsearch_data *htab, size_t size)
{
size_t i;
ENTRY item;
ENTRY *ritem;
char key[20];

for (i = 0; i < size; i++) {
sprintf(key, "%d", (int)i);
item.callback = NULL;
item.flags = 0;
item.data = key;
item.key = key;
hsearch_r(item, FIND, &ritem, htab, 0);
ut_assert(ritem);
ut_asserteq_str(key, ritem->key);
ut_asserteq_str(key, ritem->data);
}

return 0;
}

static int htab_create_delete(struct unit_test_state *uts,
struct hsearch_data *htab, size_t iterations)
{
size_t i;
ENTRY item;
ENTRY *ritem;
char key[20];

for (i = 0; i < iterations; i++) {
sprintf(key, "cd-%d", (int)i);
item.callback = NULL;
item.flags = 0;
item.data = key;
item.key = key;
hsearch_r(item, ENTER, &ritem, htab, 0);
ritem = NULL;

hsearch_r(item, FIND, &ritem, htab, 0);
ut_assert(ritem);
ut_asserteq_str(key, ritem->key);
ut_asserteq_str(key, ritem->data);

ut_asserteq(1, hdelete_r(key, htab, 0));
}

return 0;
}

/* Completely fill up the hash table */
static int env_test_htab_fill(struct unit_test_state *uts)
{
struct hsearch_data htab;

memset(&htab, 0, sizeof(htab));
ut_asserteq(1, hcreate_r(SIZE, &htab));

ut_assertok(htab_fill(uts, &htab, SIZE));
ut_assertok(htab_check_fill(uts, &htab, SIZE));
ut_asserteq(SIZE, htab.filled);

hdestroy_r(&htab);
return 0;
}

ENV_TEST(env_test_htab_fill, 0);

/* Fill the hashtable up halfway an repeateadly delete/create elements
* and check for corruption
*/
static int env_test_htab_deletes(struct unit_test_state *uts)
{
struct hsearch_data htab;

memset(&htab, 0, sizeof(htab));
ut_asserteq(1, hcreate_r(SIZE, &htab));

ut_assertok(htab_fill(uts, &htab, SIZE / 2));
ut_assertok(htab_create_delete(uts, &htab, ITERATIONS));
ut_assertok(htab_check_fill(uts, &htab, SIZE / 2));
ut_asserteq(SIZE / 2, htab.filled);

hdestroy_r(&htab);
return 0;
}

ENV_TEST(env_test_htab_deletes, 0);

0 comments on commit 9dfdbd9

Please sign in to comment.