Forbid closing stdio from quickjs-libc (#576)

Intrinsically dangerous because it leaves the std{in,out,err} C globals
in an undefined state.
This commit is contained in:
Ben Noordhuis 2024-10-07 09:35:09 +02:00 committed by GitHub
parent ddabcf5e93
commit 27715a46bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 13 deletions

View file

@ -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);
@ -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;
@ -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);
@ -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);
@ -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);
@ -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;
@ -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
@ -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));
@ -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;
}

View file

@ -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();
@ -299,3 +313,4 @@ test_os();
test_interval();
test_timeout();
test_timeout_order();
test_stdio_close();