Skip to content

Commit 4dbecf3

Browse files
authored
Support sanitizer test and fix uaf (#55)
Add sanitizer CI and fix UAF bug caused when key and feild expire at the same time.
1 parent d213140 commit 4dbecf3

4 files changed

Lines changed: 161 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,84 @@ jobs:
169169
run: |
170170
cd build
171171
lcov -c -d ./ -o cover.info
172+
- uses: codecov/codecov-action@v1
173+
with:
174+
file: build/cover.info
175+
token: ${{ secrets.CODECOV_TOKEN }}
176+
verbose: true
177+
178+
test-sanitizer-address-scan-mode:
179+
runs-on: ubuntu-latest
180+
steps:
181+
- uses: actions/checkout@v2
182+
- name: clone and make redis
183+
run: |
184+
sudo apt-get install git
185+
git clone https://github.com/redis/redis
186+
cd redis
187+
git checkout 7.0
188+
make SANITIZER=address REDIS_CFLAGS='-Werror' BUILD_TLS=yes MALLOC=libc
189+
- name: Install LCOV
190+
run: |
191+
sudo apt-get --assume-yes install lcov > /dev/null
192+
- name: make tairhash
193+
run: |
194+
mkdir build
195+
cd build
196+
cmake ../ -DSANITIZER_MODE=address
197+
make
198+
- name: test
199+
run: |
200+
sudo apt-get install tcl8.6 tclx
201+
work_path=$(pwd)
202+
module_path=$work_path/lib
203+
sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl
204+
sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl
205+
cd redis
206+
./runtest --stack-logging --single unit/type/tairhash
207+
- name: lcov collection
208+
run: |
209+
cd build
210+
lcov -c -d ./ -o cover.info
211+
- uses: codecov/codecov-action@v1
212+
with:
213+
file: build/cover.info
214+
token: ${{ secrets.CODECOV_TOKEN }}
215+
verbose: true
216+
217+
test-sanitizer-address-sort-mode:
218+
runs-on: ubuntu-latest
219+
steps:
220+
- uses: actions/checkout@v2
221+
- name: clone and make redis
222+
run: |
223+
sudo apt-get install git
224+
git clone https://github.com/redis/redis
225+
cd redis
226+
git checkout 7.0
227+
make SANITIZER=address REDIS_CFLAGS='-Werror' BUILD_TLS=yes MALLOC=libc
228+
- name: Install LCOV
229+
run: |
230+
sudo apt-get --assume-yes install lcov > /dev/null
231+
- name: make tairhash
232+
run: |
233+
mkdir build
234+
cd build
235+
cmake ../ -DSANITIZER_MODE=address -DSORT_MODE=yes
236+
make
237+
- name: test
238+
run: |
239+
sudo apt-get install tcl8.6 tclx
240+
work_path=$(pwd)
241+
module_path=$work_path/lib
242+
sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl
243+
sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl
244+
cd redis
245+
./runtest --stack-logging --single unit/type/tairhash
246+
- name: lcov collection
247+
run: |
248+
cd build
249+
lcov -c -d ./ -o cover.info
172250
- uses: codecov/codecov-action@v1
173251
with:
174252
file: build/cover.info

CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ if (GCOV_MODE)
1212
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
1313
endif()
1414

15+
if(SANITIZER_MODE MATCHES "address")
16+
set(CMAKE_BUILD_TYPE "DEBUG")
17+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -fsanitize=address -fno-omit-frame-pointer")
18+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=address -fno-omit-frame-pointer")
19+
elseif(SANITIZER_MODE MATCHES "undefined")
20+
set(CMAKE_BUILD_TYPE "DEBUG")
21+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -fsanitize=undefined -fno-omit-frame-pointer")
22+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=undefined -fno-omit-frame-pointer")
23+
elseif(SANITIZER_MODE MATCHES "thread")
24+
set(CMAKE_BUILD_TYPE "DEBUG")
25+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -fsanitize=thread -fno-omit-frame-pointer")
26+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=thread -fno-omit-frame-pointer")
27+
endif(SANITIZER_MODE MATCHES "address")
28+
1529
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
1630
set(CMAKE_C_VISIBILITY_PRESET hidden)
1731

src/scan_algorithm.c

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) {
5555

5656
RedisModuleString *key, *field;
5757
RedisModuleKey *real_key;
58+
int may_delkey = 0;
5859

5960
long long when, now;
6061
unsigned long zsl_len;
@@ -79,8 +80,7 @@ void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) {
7980
Module_Assert(RedisModule_CallReplyType(keys_reply) == REDISMODULE_REPLY_ARRAY);
8081
size_t keynum = RedisModule_CallReplyLength(keys_reply);
8182

82-
int j;
83-
for (j = 0; j < keynum; j++) {
83+
for (int j = 0; j < keynum; j++) {
8484
RedisModuleCallReply *key_reply = RedisModule_CallReplyArrayElement(keys_reply, j);
8585
Module_Assert(RedisModule_CallReplyType(key_reply) == REDISMODULE_REPLY_STRING);
8686
key = RedisModule_CreateStringFromCallReply(key_reply);
@@ -89,8 +89,10 @@ void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) {
8989
return REDISMODULE_KEYTYPE_EMPTY here, so we must deal with it until after this
9090
bugfix: https://github.com/redis/redis/commit/1833d008b3af8628835b5f082c5b4b1359557893 */
9191
if (RedisModule_KeyType(real_key) == REDISMODULE_KEYTYPE_EMPTY) {
92+
RedisModule_CloseKey(real_key);
9293
continue;
9394
}
95+
9496
if (RedisModule_ModuleTypeGetType(real_key) == TairHashType) {
9597
tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key);
9698
if (tair_hash_obj->expire_index->length > 0) {
@@ -117,17 +119,28 @@ void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) {
117119
m_listNode *node;
118120
while ((node = listFirst(keys)) != NULL) {
119121
key = listNodeValue(node);
122+
may_delkey = 0;
120123
real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH);
121124
int type = RedisModule_KeyType(real_key);
122125
if (type != REDISMODULE_KEYTYPE_EMPTY) {
123126
Module_Assert(RedisModule_ModuleTypeGetType(real_key) == TairHashType);
124127
} else {
125128
/* Note: redis scan may return dup key. */
126129
m_listDelNode(keys, node);
130+
RedisModule_CloseKey(real_key);
127131
continue;
128132
}
129133

134+
mstime_t key_ttl = RedisModule_GetExpire(real_key);
135+
if (key_ttl != REDISMODULE_NO_EXPIRE && key_ttl < 1000) { //
136+
may_delkey = 1;
137+
}
138+
130139
tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key);
140+
if (dictSize(tair_hash_obj->hash) == 1) {
141+
may_delkey = 1;
142+
}
143+
RedisModule_CloseKey(real_key);
131144

132145
zsl_len = tair_hash_obj->expire_index->length;
133146
Module_Assert(zsl_len > 0);
@@ -140,15 +153,30 @@ void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) {
140153
g_expire_algorithm.stat_active_expired_field[dbid]++;
141154
start_index++;
142155
expire_keys_per_loop--;
156+
if (may_delkey) {
157+
break;
158+
}
143159
} else {
144160
break;
145161
}
146162
ln2 = ln2->level[0].forward;
147163
}
148164

165+
// If the key happens to expire, it will be released in fieldExpireIfNeeded.
166+
if (may_delkey) {
167+
real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_OPEN_KEY_NOTOUCH);
168+
int type = RedisModule_KeyType(real_key);
169+
if (type == REDISMODULE_KEYTYPE_EMPTY) {
170+
m_listDelNode(keys, node);
171+
RedisModule_CloseKey(real_key);
172+
continue;
173+
}
174+
RedisModule_CloseKey(real_key);
175+
}
176+
149177
if (start_index) {
150178
m_zslDeleteRangeByRank(tair_hash_obj->expire_index, 1, start_index);
151-
delEmptyTairHashIfNeeded(ctx, real_key, key, tair_hash_obj);
179+
delEmptyTairHashIfNeeded(ctx, NULL, key, tair_hash_obj);
152180
}
153181
m_listDelNode(keys, node);
154182
}
@@ -164,6 +192,7 @@ void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) {
164192
RedisModuleString *field;
165193
RedisModuleKey *real_key;
166194
unsigned long zsl_len;
195+
int may_delkey = 0;
167196
/* 1. The current db does not have a key that needs to expire. */
168197
list *keys = m_listCreate();
169198

@@ -186,6 +215,7 @@ void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) {
186215
m_listNode *node;
187216
while ((node = listFirst(keys)) != NULL) {
188217
key = listNodeValue(node);
218+
may_delkey = 0;
189219
real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE);
190220
int type = RedisModule_KeyType(real_key);
191221

@@ -195,6 +225,17 @@ void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) {
195225
zsl_len = tair_hash_obj->expire_index->length;
196226
Module_Assert(zsl_len > 0);
197227

228+
mstime_t key_ttl = RedisModule_GetExpire(real_key);
229+
if (key_ttl != REDISMODULE_NO_EXPIRE && key_ttl < 100) { // 100ms
230+
may_delkey = 1;
231+
}
232+
233+
tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key);
234+
if (dictSize(tair_hash_obj->hash) == 1) {
235+
may_delkey = 1;
236+
}
237+
RedisModule_CloseKey(real_key);
238+
198239
start_index = 0;
199240
ln = tair_hash_obj->expire_index->header->level[0].forward;
200241
while (ln && keys_per_loop) {
@@ -203,17 +244,29 @@ void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) {
203244
g_expire_algorithm.stat_passive_expired_field[dbid]++;
204245
start_index++;
205246
keys_per_loop--;
247+
if (may_delkey) {
248+
break;
249+
}
206250
} else {
207251
break;
208252
}
209253
ln = ln->level[0].forward;
210254
}
211255

256+
if (may_delkey) {
257+
real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_OPEN_KEY_NOTOUCH);
258+
int type = RedisModule_KeyType(real_key);
259+
if (type == REDISMODULE_KEYTYPE_EMPTY) {
260+
m_listDelNode(keys, node);
261+
RedisModule_CloseKey(real_key);
262+
continue;
263+
}
264+
RedisModule_CloseKey(real_key);
265+
}
266+
212267
if (start_index) {
213268
m_zslDeleteRangeByRank(tair_hash_obj->expire_index, 1, start_index);
214-
if (!delEmptyTairHashIfNeeded(ctx, real_key, key, tair_hash_obj)) {
215-
RedisModule_CloseKey(real_key);
216-
}
269+
delEmptyTairHashIfNeeded(ctx, NULL, key, tair_hash_obj);
217270
}
218271
m_listDelNode(keys, node);
219272
}

src/tairhash.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ void _moduleAssert(const char *estr, const char *file, int line) {
5555
*((char *)-1) = 'x';
5656
}
5757

58-
inline struct TairHashVal *createTairHashVal(void) {
58+
struct TairHashVal *createTairHashVal(void) {
5959
struct TairHashVal *o;
6060
o = RedisModule_Calloc(1, sizeof(*o));
6161
return o;
6262
}
6363

64-
inline void tairHashValRelease(struct TairHashVal *o) {
64+
void tairHashValRelease(struct TairHashVal *o) {
6565
if (o) {
6666
if (o->value) {
6767
RedisModule_FreeString(NULL, o->value);
@@ -85,6 +85,14 @@ int delEmptyTairHashIfNeeded(RedisModuleCtx *ctx, RedisModuleKey *key, RedisModu
8585
return 0;
8686
}
8787

88+
if (key == NULL) {
89+
key = RedisModule_OpenKey(ctx, raw_key, REDISMODULE_WRITE);
90+
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
91+
RedisModule_CloseKey(key);
92+
return 0;
93+
}
94+
}
95+
8896
if (redis_major_ver < 6 || (redis_major_ver == 6 && redis_minor_ver < 2)) {
8997
/* See bugfix: https://github.com/redis/redis/pull/8617
9098
https://github.com/redis/redis/pull/8097

0 commit comments

Comments
 (0)