MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix use-after-free in child_process

+73 -56
+14 -1
examples/spec/child_process.js
··· 67 67 resolve(); 68 68 })); 69 69 70 + let stdinRoundTrip = ''; 71 + const stdinChild = spawn('cat'); 72 + stdinChild.on('stdout', data => { 73 + stdinRoundTrip += data; 74 + }); 75 + stdinChild.stdin.write('ping'); 76 + stdinChild.stdin.end(); 77 + 78 + const stdinChildDoneP = new Promise(resolve => stdinChild.on('close', () => { 79 + test('spawn stdin round trip', stdinRoundTrip, 'ping'); 80 + resolve(); 81 + })); 82 + 70 83 const shellChildDoneP = childClosedP.then(() => new Promise(resolve => { 71 84 const shellChild = spawn('echo $HOME', [], { shell: true }); 72 85 shellChild.on('close', () => { ··· 89 102 const killResult = longChild.kill('SIGTERM'); 90 103 test('spawn kill returns true', killResult, true); 91 104 92 - Promise.all([execDoneP, execFailDoneP, shellChildDoneP]).then(() => { 105 + Promise.all([execDoneP, execFailDoneP, shellChildDoneP, stdinChildDoneP]).then(() => { 93 106 test('exec async completed', execDone, true); 94 107 test('exec fail async completed', execFailDone, true); 95 108 summary();
+59 -55
src/modules/child_process.c
··· 176 176 } 177 177 178 178 static const char *stream_kind_name(child_stream_kind_t kind) { 179 - switch (kind) { 180 - case CHILD_STREAM_STDIN: return "stdin"; 181 - case CHILD_STREAM_STDOUT: return "stdout"; 182 - case CHILD_STREAM_STDERR: return "stderr"; 183 - default: return "unknown"; 184 - } 185 - } 179 + switch (kind) { 180 + case CHILD_STREAM_STDIN: return "stdin"; 181 + case CHILD_STREAM_STDOUT: return "stdout"; 182 + case CHILD_STREAM_STDERR: return "stderr"; 183 + default: return "unknown"; 184 + }} 186 185 187 186 static void emit_stream_event( 188 187 child_process_t *cp, ··· 215 214 return cp->stdio_modes[kind] == STDIO_PIPE; 216 215 } 217 216 217 + static bool *child_closed_flag(child_process_t *cp, child_stream_kind_t kind) { 218 + switch (kind) { 219 + case CHILD_STREAM_STDIN: return &cp->stdin_closed; 220 + case CHILD_STREAM_STDOUT: return &cp->stdout_closed; 221 + case CHILD_STREAM_STDERR: return &cp->stderr_closed; 222 + default: return NULL; 223 + } 224 + } 225 + 218 226 static void add_pending_child(child_process_t *cp) { 219 227 cp->next = NULL; 220 228 cp->prev = pending_children_tail; ··· 235 243 236 244 static void free_child_process(child_process_t *cp) { 237 245 if (!cp) return; 246 + 247 + if (vtype(cp->child_obj) == T_OBJ) js_set_slot(cp->child_obj, SLOT_DATA, js_mkundef()); 248 + if (vtype(cp->stdin_obj) == T_OBJ) js_set_slot(cp->stdin_obj, SLOT_DATA, js_mkundef()); 249 + if (vtype(cp->stdout_obj) == T_OBJ) js_set_slot(cp->stdout_obj, SLOT_DATA, js_mkundef()); 250 + if (vtype(cp->stderr_obj) == T_OBJ) js_set_slot(cp->stderr_obj, SLOT_DATA, js_mkundef()); 238 251 239 252 if (cp->stdout_buf) free(cp->stdout_buf); 240 253 if (cp->stderr_buf) free(cp->stderr_buf); ··· 266 279 } 267 280 268 281 static void try_free_child(child_process_t *cp) { 282 + if (!cp) return; 269 283 if (cp->exited && cp->stdout_closed && cp->stderr_closed && cp->pending_closes == 0) { 270 284 remove_pending_child(cp); 271 285 free_child_process(cp); ··· 309 323 snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %lld", (long long)cp->exit_code); 310 324 ant_value_t err = js_mkstr(cp->js, err_msg, strlen(err_msg)); 311 325 js_reject_promise(cp->js, cp->promise, err); 312 - } else { 313 - js_resolve_promise(cp->js, cp->promise, result); 314 - } 326 + } else js_resolve_promise(cp->js, cp->promise, result); 315 327 } 316 328 317 329 try_free_child(cp); ··· 326 338 } 327 339 } 328 340 341 + static void close_child_handle(child_process_t *cp, uv_handle_t *handle) { 342 + if (!cp || !handle || uv_is_closing(handle)) return; 343 + cp->pending_closes++; 344 + uv_close(handle, on_handle_close); 345 + } 346 + 347 + static void close_child_pipe(child_process_t *cp, child_stream_kind_t kind, bool stop_read) { 348 + bool *closed = NULL; 349 + uv_pipe_t *pipe = NULL; 350 + 351 + if (!cp || !child_stdio_is_pipe(cp, kind)) return; 352 + 353 + closed = child_closed_flag(cp, kind); 354 + pipe = child_pipe(cp, kind); 355 + if (!closed || !pipe || *closed || uv_is_closing((uv_handle_t *)pipe)) return; 356 + 357 + if (stop_read && kind != CHILD_STREAM_STDIN) { 358 + uv_read_stop((uv_stream_t *)pipe); 359 + } 360 + 361 + *closed = true; 362 + close_child_handle(cp, (uv_handle_t *)pipe); 363 + } 364 + 329 365 static void on_process_exit(uv_process_t *proc, int64_t exit_status, int term_signal) { 330 366 child_process_t *cp = (child_process_t *)proc->data; 331 367 cp->exit_code = exit_status; ··· 341 377 ant_value_t exit_args[2] = { js_mknum((double)exit_status), term_signal ? js_mknum((double)term_signal) : js_mknull() }; 342 378 emit_event(cp, "exit", exit_args, 2); 343 379 344 - cp->pending_closes++; 345 - uv_close((uv_handle_t *)proc, on_handle_close); 346 - 347 - if (child_stdio_is_pipe(cp, CHILD_STREAM_STDIN) && !cp->stdin_closed && !uv_is_closing((uv_handle_t *)&cp->stdin_pipe)) { 348 - cp->pending_closes++; 349 - uv_close((uv_handle_t *)&cp->stdin_pipe, on_handle_close); 350 - cp->stdin_closed = true; 351 - } 352 - if (child_stdio_is_pipe(cp, CHILD_STREAM_STDOUT) && !cp->stdout_closed && !uv_is_closing((uv_handle_t *)&cp->stdout_pipe)) { 353 - uv_read_stop((uv_stream_t *)&cp->stdout_pipe); 354 - cp->pending_closes++; 355 - uv_close((uv_handle_t *)&cp->stdout_pipe, on_handle_close); 356 - cp->stdout_closed = true; 357 - } 358 - if (child_stdio_is_pipe(cp, CHILD_STREAM_STDERR) && !cp->stderr_closed && !uv_is_closing((uv_handle_t *)&cp->stderr_pipe)) { 359 - uv_read_stop((uv_stream_t *)&cp->stderr_pipe); 360 - cp->pending_closes++; 361 - uv_close((uv_handle_t *)&cp->stderr_pipe, on_handle_close); 362 - cp->stderr_closed = true; 363 - } 380 + close_child_handle(cp, (uv_handle_t *)proc); 381 + close_child_pipe(cp, CHILD_STREAM_STDIN, false); 382 + close_child_pipe(cp, CHILD_STREAM_STDOUT, true); 383 + close_child_pipe(cp, CHILD_STREAM_STDERR, true); 364 384 365 385 check_completion(cp); 366 386 } ··· 415 435 if (nread != UV_EOF) 416 436 emit_stream_event(cp, CHILD_STREAM_STDOUT, "end", NULL, 0); 417 437 418 - cp->pending_closes++; 419 - uv_close((uv_handle_t *)stream, on_handle_close); 420 - 421 - cp->stdout_closed = true; 438 + close_child_pipe(cp, CHILD_STREAM_STDOUT, true); 422 439 check_completion(cp); 423 440 } 424 441 } ··· 467 484 } 468 485 469 486 emit_stream_event(cp, CHILD_STREAM_STDERR, "end", NULL, 0); 470 - cp->pending_closes++; 471 - uv_close((uv_handle_t *)stream, on_handle_close); 472 - 473 - cp->stderr_closed = true; 487 + close_child_pipe(cp, CHILD_STREAM_STDERR, true); 474 488 check_completion(cp); 475 489 } 476 490 } ··· 599 613 } 600 614 601 615 static ant_value_t child_end_impl(child_process_t *cp) { 602 - if (!cp->stdin_closed) { 603 - uv_close((uv_handle_t *)&cp->stdin_pipe, NULL); 604 - cp->stdin_closed = true; 605 - } 606 - 616 + close_child_pipe(cp, CHILD_STREAM_STDIN, false); 607 617 return js_mkundef(); 608 618 } 609 619 ··· 738 748 739 749 uv_handle_t *h = child_stream_handle(cp, ctx->kind); 740 750 if (h && !uv_is_closing(h)) { 741 - if (ctx->kind == CHILD_STREAM_STDOUT) { 742 - uv_read_stop((uv_stream_t *)&cp->stdout_pipe); 743 - cp->stdout_closed = true; 744 - } else if (ctx->kind == CHILD_STREAM_STDERR) { 745 - uv_read_stop((uv_stream_t *)&cp->stderr_pipe); 746 - cp->stderr_closed = true; 747 - } 748 - cp->pending_closes++; 749 - uv_close(h, on_handle_close); 751 + if (ctx->kind == CHILD_STREAM_STDOUT) close_child_pipe(cp, CHILD_STREAM_STDOUT, true); 752 + else if (ctx->kind == CHILD_STREAM_STDERR) close_child_pipe(cp, CHILD_STREAM_STDERR, true); 750 753 check_completion(cp); 751 754 } 752 755 ··· 1134 1137 cp->promise = js_mkpromise(js); 1135 1138 cp->keep_alive = true; 1136 1139 1137 - uv_pipe_init(uv_default_loop(), &cp->stdin_pipe, 0); 1140 + cp->stdio_modes[CHILD_STREAM_STDIN] = STDIO_IGNORE; 1141 + cp->stdio_modes[CHILD_STREAM_STDOUT] = STDIO_PIPE; 1142 + cp->stdio_modes[CHILD_STREAM_STDERR] = STDIO_PIPE; 1143 + 1138 1144 uv_pipe_init(uv_default_loop(), &cp->stdout_pipe, 0); 1139 1145 uv_pipe_init(uv_default_loop(), &cp->stderr_pipe, 0); 1140 1146 1141 - cp->stdin_pipe.data = cp; 1142 1147 cp->stdout_pipe.data = cp; 1143 1148 cp->stderr_pipe.data = cp; 1144 1149 cp->process.data = cp; ··· 1619 1624 HASH_ITER(hh, cp->events, evt, tmp) 1620 1625 for (int i = 0; i < evt->count; i++) mark(js, evt->listeners[i].callback); 1621 1626 }} 1622 -