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 { typedef struct {
FILE *f; FILE *f;
BOOL close_in_finalizer;
BOOL is_popen; BOOL is_popen;
} JSSTDFile; } JSSTDFile;
static BOOL is_stdio(FILE *f)
{
return f == stdin || f == stdout || f == stderr;
}
static void js_std_file_finalizer(JSRuntime *rt, JSValue val) static void js_std_file_finalizer(JSRuntime *rt, JSValue val)
{ {
JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id); JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id);
if (s) { if (s) {
if (s->f && s->close_in_finalizer) { if (s->f && !is_stdio(s->f)) {
#if !defined(__wasi__) #if !defined(__wasi__)
if (s->is_popen) if (s->is_popen)
pclose(s->f); pclose(s->f);
@ -914,9 +918,7 @@ static JSValue js_std_strerror(JSContext *ctx, JSValue this_val,
return JS_NewString(ctx, strerror(err)); return JS_NewString(ctx, strerror(err));
} }
static JSValue js_new_std_file(JSContext *ctx, FILE *f, static JSValue js_new_std_file(JSContext *ctx, FILE *f, BOOL is_popen)
BOOL close_in_finalizer,
BOOL is_popen)
{ {
JSSTDFile *s; JSSTDFile *s;
JSValue obj; JSValue obj;
@ -928,7 +930,6 @@ static JSValue js_new_std_file(JSContext *ctx, FILE *f,
JS_FreeValue(ctx, obj); JS_FreeValue(ctx, obj);
return JS_EXCEPTION; return JS_EXCEPTION;
} }
s->close_in_finalizer = close_in_finalizer;
s->is_popen = is_popen; s->is_popen = is_popen;
s->f = f; s->f = f;
JS_SetOpaque(obj, s); JS_SetOpaque(obj, s);
@ -971,7 +972,7 @@ static JSValue js_std_open(JSContext *ctx, JSValue this_val,
JS_FreeCString(ctx, mode); JS_FreeCString(ctx, mode);
if (!f) if (!f)
return JS_NULL; return JS_NULL;
return js_new_std_file(ctx, f, TRUE, FALSE); return js_new_std_file(ctx, f, FALSE);
fail: fail:
JS_FreeCString(ctx, filename); JS_FreeCString(ctx, filename);
JS_FreeCString(ctx, mode); JS_FreeCString(ctx, mode);
@ -1008,7 +1009,7 @@ static JSValue js_std_popen(JSContext *ctx, JSValue this_val,
JS_FreeCString(ctx, mode); JS_FreeCString(ctx, mode);
if (!f) if (!f)
return JS_NULL; return JS_NULL;
return js_new_std_file(ctx, f, TRUE, TRUE); return js_new_std_file(ctx, f, TRUE);
fail: fail:
JS_FreeCString(ctx, filename); JS_FreeCString(ctx, filename);
JS_FreeCString(ctx, mode); JS_FreeCString(ctx, mode);
@ -1043,7 +1044,7 @@ static JSValue js_std_fdopen(JSContext *ctx, JSValue this_val,
JS_FreeCString(ctx, mode); JS_FreeCString(ctx, mode);
if (!f) if (!f)
return JS_NULL; return JS_NULL;
return js_new_std_file(ctx, f, TRUE, FALSE); return js_new_std_file(ctx, f, FALSE);
fail: fail:
JS_FreeCString(ctx, mode); JS_FreeCString(ctx, mode);
return JS_EXCEPTION; 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); js_set_error_object(ctx, argv[0], f ? 0 : errno);
if (!f) if (!f)
return JS_NULL; return JS_NULL;
return js_new_std_file(ctx, f, TRUE, FALSE); return js_new_std_file(ctx, f, FALSE);
} }
#endif #endif
@ -1122,6 +1123,8 @@ static JSValue js_std_file_close(JSContext *ctx, JSValue this_val,
return JS_EXCEPTION; return JS_EXCEPTION;
if (!s->f) if (!s->f)
return JS_ThrowTypeError(ctx, "invalid file handle"); return JS_ThrowTypeError(ctx, "invalid file handle");
if (is_stdio(s->f))
return JS_ThrowTypeError(ctx, "cannot close stdio");
#if !defined(__wasi__) #if !defined(__wasi__)
if (s->is_popen) if (s->is_popen)
err = js_get_errno(pclose(s->f)); 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, JS_SetModuleExportList(ctx, m, js_std_funcs,
countof(js_std_funcs)); countof(js_std_funcs));
JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, 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, 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, FALSE)); JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE));
return 0; return 0;
} }

View file

@ -289,6 +289,20 @@ function test_timeout_order()
function d() { assert(s === "abc"); } // not "acb" 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_printf();
test_file1(); test_file1();
test_file2(); test_file2();
@ -299,3 +313,4 @@ test_os();
test_interval(); test_interval();
test_timeout(); test_timeout();
test_timeout_order(); test_timeout_order();
test_stdio_close();