Skip to content

Commit

Permalink
Forbid closing stdio from quickjs-libc (#576)
Browse files Browse the repository at this point in the history
Intrinsically dangerous because it leaves the std{in,out,err} C globals
in an undefined state.
  • Loading branch information
bnoordhuis authored Oct 7, 2024
1 parent ddabcf5 commit 27715a4
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 13 deletions.
29 changes: 16 additions & 13 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -878,15 +878,19 @@ static JSClassID js_std_file_class_id;

typedef struct {
FILE *f;
BOOL close_in_finalizer;
BOOL is_popen;
} JSSTDFile;

static BOOL is_stdio(FILE *f)
{
return f == stdin || f == stdout || f == stderr;
}

static void js_std_file_finalizer(JSRuntime *rt, JSValue val)
{
JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id);
if (s) {
if (s->f && s->close_in_finalizer) {
if (s->f && !is_stdio(s->f)) {
#if !defined(__wasi__)
if (s->is_popen)
pclose(s->f);
Expand Down Expand Up @@ -914,9 +918,7 @@ static JSValue js_std_strerror(JSContext *ctx, JSValue this_val,
return JS_NewString(ctx, strerror(err));
}

static JSValue js_new_std_file(JSContext *ctx, FILE *f,
BOOL close_in_finalizer,
BOOL is_popen)
static JSValue js_new_std_file(JSContext *ctx, FILE *f, BOOL is_popen)
{
JSSTDFile *s;
JSValue obj;
Expand All @@ -928,7 +930,6 @@ static JSValue js_new_std_file(JSContext *ctx, FILE *f,
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
s->close_in_finalizer = close_in_finalizer;
s->is_popen = is_popen;
s->f = f;
JS_SetOpaque(obj, s);
Expand Down Expand Up @@ -971,7 +972,7 @@ static JSValue js_std_open(JSContext *ctx, JSValue this_val,
JS_FreeCString(ctx, mode);
if (!f)
return JS_NULL;
return js_new_std_file(ctx, f, TRUE, FALSE);
return js_new_std_file(ctx, f, FALSE);
fail:
JS_FreeCString(ctx, filename);
JS_FreeCString(ctx, mode);
Expand Down Expand Up @@ -1008,7 +1009,7 @@ static JSValue js_std_popen(JSContext *ctx, JSValue this_val,
JS_FreeCString(ctx, mode);
if (!f)
return JS_NULL;
return js_new_std_file(ctx, f, TRUE, TRUE);
return js_new_std_file(ctx, f, TRUE);
fail:
JS_FreeCString(ctx, filename);
JS_FreeCString(ctx, mode);
Expand Down Expand Up @@ -1043,7 +1044,7 @@ static JSValue js_std_fdopen(JSContext *ctx, JSValue this_val,
JS_FreeCString(ctx, mode);
if (!f)
return JS_NULL;
return js_new_std_file(ctx, f, TRUE, FALSE);
return js_new_std_file(ctx, f, FALSE);
fail:
JS_FreeCString(ctx, mode);
return JS_EXCEPTION;
Expand All @@ -1059,7 +1060,7 @@ static JSValue js_std_tmpfile(JSContext *ctx, JSValue this_val,
js_set_error_object(ctx, argv[0], f ? 0 : errno);
if (!f)
return JS_NULL;
return js_new_std_file(ctx, f, TRUE, FALSE);
return js_new_std_file(ctx, f, FALSE);
}
#endif

Expand Down Expand Up @@ -1122,6 +1123,8 @@ static JSValue js_std_file_close(JSContext *ctx, JSValue this_val,
return JS_EXCEPTION;
if (!s->f)
return JS_ThrowTypeError(ctx, "invalid file handle");
if (is_stdio(s->f))
return JS_ThrowTypeError(ctx, "cannot close stdio");
#if !defined(__wasi__)
if (s->is_popen)
err = js_get_errno(pclose(s->f));
Expand Down Expand Up @@ -1643,9 +1646,9 @@ static int js_std_init(JSContext *ctx, JSModuleDef *m)

JS_SetModuleExportList(ctx, m, js_std_funcs,
countof(js_std_funcs));
JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE, FALSE));
JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE, FALSE));
JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE, FALSE));
JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE));
JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE));
JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE));
return 0;
}

Expand Down
15 changes: 15 additions & 0 deletions tests/test_std.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,20 @@ function test_timeout_order()
function d() { assert(s === "abc"); } // not "acb"
}

function test_stdio_close()
{
for (const f of [std.in, std.out, std.err]) {
let caught = false;
try {
f.close();
} catch (e) {
assert(/cannot close stdio/.test(e.message));
caught = true;
}
assert(caught);
}
}

test_printf();
test_file1();
test_file2();
Expand All @@ -299,3 +313,4 @@ test_os();
test_interval();
test_timeout();
test_timeout_order();
test_stdio_close();

0 comments on commit 27715a4

Please sign in to comment.