Skip to content

Commit 73ffef7

Browse files
authored
Merge pull request #407 from MattCatz/master
2 parents 70d6c85 + b057472 commit 73ffef7

10 files changed

Lines changed: 252 additions & 49 deletions

‎lib.c‎

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,7 @@ uc_filter(uc_vm_t *vm, size_t nargs)
10081008
return NULL;
10091009

10101010
arr = ucv_array_new(vm);
1011+
uc_vm_stack_push(vm, ucv_get(arr));
10111012

10121013
for (arrlen = ucv_array_length(obj), arridx = 0; arridx < arrlen; arridx++) {
10131014
uc_vm_ctx_push(vm);
@@ -1030,6 +1031,8 @@ uc_filter(uc_vm_t *vm, size_t nargs)
10301031
ucv_put(rv);
10311032
}
10321033

1034+
ucv_put(uc_vm_stack_pop(vm));
1035+
10331036
return arr;
10341037
}
10351038

@@ -1301,6 +1304,7 @@ uc_map(uc_vm_t *vm, size_t nargs)
13011304
return NULL;
13021305

13031306
arr = ucv_array_new(vm);
1307+
uc_vm_stack_push(vm, ucv_get(arr));
13041308

13051309
for (arrlen = ucv_array_length(obj), arridx = 0; arridx < arrlen; arridx++) {
13061310
uc_vm_ctx_push(vm);
@@ -1320,6 +1324,8 @@ uc_map(uc_vm_t *vm, size_t nargs)
13201324
ucv_array_push(arr, rv);
13211325
}
13221326

1327+
ucv_put(uc_vm_stack_pop(vm));
1328+
13231329
return arr;
13241330
}
13251331

@@ -3543,17 +3549,17 @@ uc_json_from_object(uc_vm_t *vm, uc_value_t *obj, json_object **jso)
35433549
{
35443550
bool trail = false, eof = false;
35453551
enum json_tokener_error err;
3546-
struct json_tokener *tok;
3547-
uc_value_t *rfn, *rbuf;
3552+
struct json_tokener *tok = NULL;
3553+
uc_value_t *rfn, *rbuf = NULL;
35483554
uc_stringbuf_t *buf;
35493555

3550-
rfn = ucv_property_get(obj, "read");
3556+
rfn = ucv_get(ucv_property_get(obj, "read"));
35513557

35523558
if (!ucv_is_callable(rfn)) {
35533559
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
35543560
"Input object does not implement read() method");
35553561

3556-
return NULL;
3562+
goto out;
35573563
}
35583564

35593565
tok = xjs_new_tokener();
@@ -3563,11 +3569,8 @@ uc_json_from_object(uc_vm_t *vm, uc_value_t *obj, json_object **jso)
35633569
uc_vm_stack_push(vm, ucv_get(rfn));
35643570
uc_vm_stack_push(vm, ucv_int64_new(1024));
35653571

3566-
if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE) {
3567-
json_tokener_free(tok);
3568-
3569-
return NULL;
3570-
}
3572+
if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
3573+
goto fail;
35713574

35723575
rbuf = uc_vm_stack_pop(vm);
35733576

@@ -3576,8 +3579,6 @@ uc_json_from_object(uc_vm_t *vm, uc_value_t *obj, json_object **jso)
35763579

35773580
/* on EOF, stop parsing unless trailing garbage was detected which handled below */
35783581
if (eof && !trail) {
3579-
ucv_put(rbuf);
3580-
35813582
/* Didn't parse a complete object yet, possibly a non-delimited atomic value
35823583
such as `null`, `true` etc. - nudge parser by sending final zero byte.
35833584
See json-c issue #681 <https://github.com/json-c/json-c/issues/681> */
@@ -3591,10 +3592,7 @@ uc_json_from_object(uc_vm_t *vm, uc_value_t *obj, json_object **jso)
35913592
uc_vm_raise_exception(vm, EXCEPTION_SYNTAX,
35923593
"Trailing garbage after JSON data");
35933594

3594-
json_tokener_free(tok);
3595-
ucv_put(rbuf);
3596-
3597-
return NULL;
3595+
goto fail;
35983596
}
35993597

36003598
if (ucv_type(rbuf) != UC_STRING) {
@@ -3616,13 +3614,24 @@ uc_json_from_object(uc_vm_t *vm, uc_value_t *obj, json_object **jso)
36163614
}
36173615

36183616
ucv_put(rbuf);
3617+
rbuf = NULL;
36193618

36203619
err = json_tokener_get_error(tok);
36213620

36223621
if (err != json_tokener_success && err != json_tokener_continue)
36233622
break;
36243623
}
36253624

3625+
goto out;
3626+
3627+
fail:
3628+
json_tokener_free(tok);
3629+
tok = NULL;
3630+
3631+
out:
3632+
ucv_put(rfn);
3633+
ucv_put(rbuf);
3634+
36263635
return tok;
36273636
}
36283637

@@ -5833,7 +5842,7 @@ static uc_value_t *
58335842
uc_callfunc(uc_vm_t *vm, size_t nargs)
58345843
{
58355844
size_t argoff = vm->stack.count - nargs, i;
5836-
uc_value_t *fn_scope, *prev_scope, *res;
5845+
uc_value_t *fn_scope, *prev_scope = NULL, *res;
58375846
uc_value_t *fn = uc_fn_arg(0);
58385847
uc_value_t *this = uc_fn_arg(1);
58395848
uc_value_t *scope = uc_fn_arg(2);
@@ -5859,24 +5868,27 @@ uc_callfunc(uc_vm_t *vm, size_t nargs)
58595868
fn_scope = NULL;
58605869
}
58615870

5871+
if (fn_scope) {
5872+
prev_scope = ucv_get(uc_vm_scope_get(vm));
5873+
uc_vm_stack_push(vm, ucv_get(prev_scope));
5874+
uc_vm_scope_set(vm, fn_scope);
5875+
}
5876+
58625877
uc_vm_stack_push(vm, ucv_get(this));
58635878
uc_vm_stack_push(vm, ucv_get(fn));
58645879

58655880
for (i = 3; i < nargs; i++)
58665881
uc_vm_stack_push(vm, ucv_get(vm->stack.entries[3 + argoff++]));
58675882

5868-
if (fn_scope) {
5869-
prev_scope = ucv_get(uc_vm_scope_get(vm));
5870-
uc_vm_scope_set(vm, fn_scope);
5871-
}
5872-
58735883
if (uc_vm_call(vm, true, i - 3) == EXCEPTION_NONE)
58745884
res = uc_vm_stack_pop(vm);
58755885
else
58765886
res = NULL;
58775887

5878-
if (fn_scope)
5888+
if (fn_scope) {
58795889
uc_vm_scope_set(vm, prev_scope);
5890+
ucv_put(uc_vm_stack_pop(vm));
5891+
}
58805892

58815893
return res;
58825894
}

‎lib/socket.c‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2701,11 +2701,14 @@ uc_socket_poll(uc_vm_t *vm, size_t nargs)
27012701
if (errno != 0 || timeout < (int64_t)INT_MIN || timeout > (int64_t)INT_MAX)
27022702
err_return(ERANGE, "Invalid timeout value");
27032703

2704+
size_t argoff = vm->stack.count - nargs;
2705+
27042706
rv = ucv_array_new(vm);
2707+
uc_vm_stack_push(vm, ucv_get(rv));
27052708

27062709
for (size_t i = 1; i < nargs; i++) {
27072710
uc_vector_grow(&pfds);
2708-
item = uv_to_pollfd(vm, uc_fn_arg(i), &pfds.entries[pfds.count]);
2711+
item = uv_to_pollfd(vm, vm->stack.entries[argoff + i], &pfds.entries[pfds.count]);
27092712

27102713
if (item)
27112714
ucv_array_set(rv, pfds.count++, item);
@@ -2714,6 +2717,7 @@ uc_socket_poll(uc_vm_t *vm, size_t nargs)
27142717
ret = poll(pfds.entries, pfds.count, timeout);
27152718

27162719
if (ret == -1) {
2720+
ucv_put(uc_vm_stack_pop(vm));
27172721
ucv_put(rv);
27182722
uc_vector_clear(&pfds);
27192723
err_return(errno, "poll()");
@@ -2723,6 +2727,7 @@ uc_socket_poll(uc_vm_t *vm, size_t nargs)
27232727
ucv_array_set(ucv_array_get(rv, i), 1,
27242728
ucv_int64_new(pfds.entries[i].revents));
27252729

2730+
ucv_put(uc_vm_stack_pop(vm));
27262731
uc_vector_clear(&pfds);
27272732
ok_return(rv);
27282733
}

‎lib/uloop.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1770,7 +1770,7 @@ uc_uloop_task(uc_vm_t *vm, size_t nargs)
17701770
cbs = ucv_array_new(NULL);
17711771
ucv_array_set(cbs, 0, ucv_get(output_cb));
17721772
ucv_array_set(cbs, 1, ucv_get(input_cb));
1773-
ucv_resource_value_set(task->cb.obj, 1, ucv_get(cbs));
1773+
ucv_resource_value_set(task->cb.obj, 1, cbs);
17741774

17751775
ok_return(task->cb.obj);
17761776
}

‎lib/zlib.c‎

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def_chunks(zstrm_t * const zstrm)
9494

9595
/* run deflate() on input until output buffer not full */
9696
do {
97-
printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
97+
printbuf_memset(zstrm->outbuf, -1, 0, CHUNK);
9898
zstrm->outbuf->bpos -= CHUNK;
9999

100100
zstrm->strm.avail_out = CHUNK;
@@ -114,15 +114,15 @@ static bool
114114
uc_zlib_def_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
115115
{
116116
int ret;
117-
bool eof = false;
118-
uc_value_t *rfn, *rbuf;
117+
bool eof = false, rv = false;
118+
uc_value_t *rfn, *rbuf = NULL;
119119

120-
rfn = ucv_property_get(obj, "read");
120+
rfn = ucv_get(ucv_property_get(obj, "read"));
121121

122122
if (!ucv_is_callable(rfn)) {
123123
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
124124
"Input object does not implement read() method");
125-
return false;
125+
goto out;
126126
}
127127

128128
do {
@@ -132,15 +132,15 @@ uc_zlib_def_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zs
132132
uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
133133

134134
if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
135-
goto fail;
135+
goto out;
136136

137137
rbuf = uc_vm_stack_pop(vm); // read output chunk
138138

139139
/* we only accept strings */
140140
if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
141141
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
142142
"Input object read() method returned non-string value");
143-
goto fail;
143+
goto out;
144144
}
145145

146146
/* check EOF */
@@ -154,14 +154,16 @@ uc_zlib_def_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zs
154154
(void)ret; // XXX make annoying compiler that ignores assert() happy
155155

156156
ucv_put(rbuf); // release rbuf
157+
rbuf = NULL;
157158
} while (!eof); // finish compression if all of source has been read in
158159
assert(ret == Z_STREAM_END); // stream will be complete
159160

160-
return true;
161+
rv = true;
161162

162-
fail:
163+
out:
163164
ucv_put(rbuf);
164-
return false;
165+
ucv_put(rfn);
166+
return rv;
165167
}
166168

167169
static bool
@@ -297,7 +299,7 @@ inf_chunks(zstrm_t * const zstrm)
297299

298300
/* run inflate() on input until output buffer not full */
299301
do {
300-
printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
302+
printbuf_memset(zstrm->outbuf, -1, 0, CHUNK);
301303
zstrm->outbuf->bpos -= CHUNK;
302304

303305
zstrm->strm.avail_out = CHUNK;
@@ -322,15 +324,15 @@ static bool
322324
uc_zlib_inf_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
323325
{
324326
int ret = Z_STREAM_ERROR; // error out if EOF on first loop
325-
bool eof = false;
326-
uc_value_t *rfn, *rbuf;
327+
bool eof = false, rv = false;
328+
uc_value_t *rfn, *rbuf = NULL;
327329

328-
rfn = ucv_property_get(obj, "read");
330+
rfn = ucv_get(ucv_property_get(obj, "read"));
329331

330332
if (!ucv_is_callable(rfn)) {
331333
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
332334
"Input object does not implement read() method");
333-
return false;
335+
goto out;
334336
}
335337

336338
do {
@@ -340,15 +342,15 @@ uc_zlib_inf_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zs
340342
uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
341343

342344
if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
343-
goto fail;
345+
goto out;
344346

345347
rbuf = uc_vm_stack_pop(vm); // read output chunk
346348

347349
/* we only accept strings */
348350
if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
349351
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
350352
"Input object read() method returned non-string value");
351-
goto fail;
353+
goto out;
352354
}
353355

354356
/* check EOF */
@@ -364,20 +366,19 @@ uc_zlib_inf_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zs
364366
case Z_NEED_DICT:
365367
case Z_DATA_ERROR:
366368
case Z_MEM_ERROR:
367-
goto fail;
369+
goto out;
368370
}
369371

370372
ucv_put(rbuf); // release rbuf
373+
rbuf = NULL;
371374
} while (ret != Z_STREAM_END); // done when inflate() says it's done
372375

373-
if (ret != Z_STREAM_END) // data error
374-
return false;
376+
rv = (ret == Z_STREAM_END); // data error otherwise
375377

376-
return true;
377-
378-
fail:
378+
out:
379379
ucv_put(rbuf);
380-
return false;
380+
ucv_put(rfn);
381+
return rv;
381382
}
382383

383384
static bool
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
Test that `filter()`, `map()`, and `call()` survive a `gc("collect")`
2+
triggered from inside the user callback. The builtins hold their working
3+
values in C locals while user code runs; under the bug, a GC sweep frees
4+
them and the builtins return corrupted or dangling data.
5+
6+
-- Testcase --
7+
{%
8+
let input = [];
9+
for (let i = 0; i < 10; i++)
10+
push(input, i);
11+
12+
function clobber() {
13+
gc("collect");
14+
15+
let trash = [];
16+
for (let i = 0; i < 256; i++)
17+
push(trash, sprintf("clobber_%04d", i));
18+
}
19+
20+
let f = filter(input, function(x) { clobber(); return true; });
21+
printf("filter: type=%s len=%d match=%s\n",
22+
type(f), length(f), (f == null) ? "n/a" : (`${f}` == `${input}` ? "yes" : "no"));
23+
24+
let m = map(input, function(x) { clobber(); return x; });
25+
printf("map: type=%s len=%d match=%s\n",
26+
type(m), length(m), (m == null) ? "n/a" : (`${m}` == `${input}` ? "yes" : "no"));
27+
28+
// uc_callfunc prev_scope UAF: a scope with its own prototype takes the
29+
// branch where fn_scope = ucv_get(scope), with no prototype linkage to
30+
// the prior globals. The prior globals (`prev_scope`) is then held only
31+
// in a C local while user code runs.
32+
global.tag = "original";
33+
34+
// capture builtins as closures so the user code can run them after
35+
// vm->globals has been swapped to the prototype-only sandbox scope
36+
let mygc = gc, mypush = push, mysprintf = sprintf;
37+
38+
call(function() {
39+
mygc("collect");
40+
let trash = [];
41+
for (let i = 0; i < 256; i++)
42+
mypush(trash, mysprintf("clobber_%04d", i));
43+
}, null, proto({}, {}));
44+
45+
// after call() returns, globals must have been restored to a live
46+
// object. accessing global.tag goes through vm->globals; if it is
47+
// dangling the lookup fails or returns garbage.
48+
printf("call: globals.tag=%s\n", global.tag);
49+
%}
50+
-- End --
51+
52+
-- Expect stdout --
53+
filter: type=array len=10 match=yes
54+
map: type=array len=10 match=yes
55+
call: globals.tag=original
56+
-- End --

0 commit comments

Comments
 (0)