Skip to content
File

Blob: src/workerd/api/unsafe.c++

7.6 KB
1#include "unsafe.h"
2 
3#include <workerd/io/io-context.h>
4#include <workerd/jsg/jsg.h>
5#include <workerd/jsg/script.h>
6#include <workerd/util/autogate.h>
7 
8namespace workerd::api {
9 
10namespace {
11static constexpr auto EVAL_STR = "eval"_kjc;
12static constexpr auto ANON_STR = "anonymous"_kjc;
13static constexpr auto ASYNC_FN_PREFIX = "async function "_kjc;
14static constexpr auto ASYNC_FN_ARG_OPEN = "("_kjc;
15static constexpr auto ASYNC_FN_ARG_CLOSE = ") {"_kjc;
16static constexpr auto ASYNC_FN_SUFFIX = "}"_kjc;
17 
18inline kj::StringPtr getName(jsg::Optional<kj::String>& name, kj::StringPtr def) {
19 return name.map([](kj::String& str) { return str.asPtr(); }).orDefault(def);
20}
21} // namespace
22 
23#ifdef WORKERD_FUZZILLI
24void Stdin::reprl(jsg::Lock& js) {
25 js.setAllowEval(true);
26 /*
27 cov_init_builtins_edges(static_cast<uint32_t>(
28 v8::internal::BasicBlockProfiler::Get()
29 ->GetCoverageBitmap(reinterpret_cast<v8::Isolate*>(js.v8Isolate))
30 .size()));
31 */
32 
33 char helo[] = "HELO";
34 if (write(REPRL_CWFD, helo, 4) != 4 || read(REPRL_CRFD, helo, 4) != 4) {
35 printf("Invalid HELO response from parent\n");
36 }
37 
38 if (memcmp(helo, "HELO", 4) != 0) {
39 printf("Invalid response from parent\n");
40 }
41 
42 do {
43 v8::HandleScope handle_scope(js.v8Isolate);
44 v8::TryCatch try_catch(js.v8Isolate);
45 try_catch.SetVerbose(true);
46 
47 size_t script_size = 0;
48 unsigned action = 0;
49 ssize_t nread = read(REPRL_CRFD, &action, 4);
50 fflush(0);
51 fflush(stderr);
52 if (nread != 4 || action != 0x63657865) { // 'exec'
53 fprintf(stderr, "Unknown action: %x\n", action);
54 exit(-1);
55 }
56 
57 CHECK(read(REPRL_CRFD, &script_size, 8) == 8);
58 
59 char* script_ = (char*)malloc(script_size + 1);
60 CHECK(script_ != nullptr);
61 
62 char* source_buffer_tail = script_;
63 ssize_t remaining = (ssize_t)script_size;
64 
65 while (remaining > 0) {
66 ssize_t rv = read(REPRL_DRFD, source_buffer_tail, (size_t)remaining);
67 if (rv <= 0) {
68 fprintf(stderr, "Failed to load script\n");
69 exit(-1);
70 }
71 remaining -= rv;
72 source_buffer_tail += rv;
73 }
74 
75 script_[script_size] = '\0';
76 
77 int status = 0;
78 unsigned res_val = 0;
79 const kj::String script = kj::str(script_);
80 const kj::String wrapped = kj::str("{", script_, "}");
81 auto compiled = jsg::NonModuleScript::compile(js, wrapped, "reprl"_kj);
82 try {
83 auto result = compiled.runAndReturn(js);
84 res_val = jsg::check(v8::Local<v8::Value>(result)->Int32Value(js.v8Context()));
85 // if we reach that point execution was successful -> return 0
86 res_val = 0;
87 } catch (jsg::JsExceptionThrown&) {
88 res_val = 11;
89 if (try_catch.HasCaught()) {
90 auto str = workerd::jsg::check(try_catch.Message()->Get()->ToDetailString(js.v8Context()));
91 v8::String::Utf8Value utf8String(js.v8Isolate, str);
92 fflush(stdout);
93 }
94 }
95 
96 fflush(stdout);
97 fflush(stderr);
98 status = (res_val & 0xFF) << 8;
99 CHECK(write(REPRL_CWFD, &status, 4) == 4);
100 __sanitizer_cov_reset_edgeguards();
101 free(script_);
102 //cleanup context
103 
104 } while (true);
105}
106#endif
107 
108jsg::JsValue UnsafeEval::eval(jsg::Lock& js, kj::String script, jsg::Optional<kj::String> name) {
109 js.setAllowEval(true);
110 KJ_DEFER(js.setAllowEval(false));
111 auto compiled = jsg::NonModuleScript::compile(js, script, getName(name, EVAL_STR));
112 return compiled.runAndReturn(js);
113}
114 
115UnsafeEval::UnsafeEvalFunction UnsafeEval::newFunction(jsg::Lock& js,
116 jsg::JsString script,
117 jsg::Optional<kj::String> name,
118 jsg::Arguments<jsg::JsRef<jsg::JsString>> args,
119 const jsg::TypeHandler<UnsafeEvalFunction>& handler) {
120 js.setAllowEval(true);
121 KJ_DEFER(js.setAllowEval(false));
122 
123 auto nameStr = js.str(getName(name, ANON_STR));
124 v8::ScriptOrigin origin(nameStr);
125 v8::ScriptCompiler::Source source(script, origin);
126 
127 v8::LocalVector<v8::String> argNames(js.v8Isolate);
128 argNames.reserve(args.size());
129 for (auto& arg: args) {
130 argNames.push_back(arg.getHandle(js));
131 }
132 
133 auto fn = jsg::check(v8::ScriptCompiler::CompileFunction(
134 js.v8Context(), &source, argNames.size(), argNames.data(), 0, nullptr));
135 fn->SetName(nameStr);
136 
137 return KJ_ASSERT_NONNULL(handler.tryUnwrap(js, fn));
138}
139 
140UnsafeEval::UnsafeEvalFunction UnsafeEval::newAsyncFunction(jsg::Lock& js,
141 jsg::JsString script,
142 jsg::Optional<kj::String> name,
143 jsg::Arguments<jsg::JsRef<jsg::JsString>> args,
144 const jsg::TypeHandler<UnsafeEvalFunction>& handler) {
145 js.setAllowEval(true);
146 KJ_DEFER(js.setAllowEval(false));
147 
148 auto nameStr = js.str(getName(name, ANON_STR));
149 
150 // This case is sadly a bit more complicated than the newFunction variant
151 // because v8 currently (surprisingly) does not actually expose a way
152 // CompileAsyncFunction variant (silly v8). What we end up doing here is
153 // building a string that wraps the script provided by the caller:
154 //
155 // async function {name}({args}) { {script} }; {name}
156 //
157 // Where {name} is the name of the function as provided by the user or
158 // "anonymous" by default, {args} is the list of args provided by the
159 // caller, if any. We end the constructed string with the name of the
160 // function so that the result of running the compiled script is a reference
161 // to the compiled function.
162 
163 auto prepared = v8::String::Concat(js.v8Isolate, js.strIntern(ASYNC_FN_PREFIX), nameStr);
164 prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(ASYNC_FN_ARG_OPEN));
165 
166 for (auto& arg: args) {
167 prepared = v8::String::Concat(js.v8Isolate, prepared, arg.getHandle(js));
168 prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(","));
169 }
170 prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(ASYNC_FN_ARG_CLOSE));
171 prepared = v8::String::Concat(js.v8Isolate, prepared, script);
172 prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(ASYNC_FN_SUFFIX));
173 prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(";"));
174 prepared = v8::String::Concat(js.v8Isolate, prepared, nameStr);
175 
176 v8::ScriptOrigin origin(nameStr);
177 v8::ScriptCompiler::Source source(prepared, origin);
178 
179 auto compiled = jsg::check(v8::ScriptCompiler::Compile(js.v8Context(), &source));
180 auto result = jsg::check(compiled->Run(js.v8Context()));
181 
182 KJ_REQUIRE(result->IsAsyncFunction());
183 
184 return KJ_ASSERT_NONNULL(handler.tryUnwrap(js, result.As<v8::Function>()));
185}
186 
187jsg::JsValue UnsafeEval::newWasmModule(jsg::Lock& js, kj::Array<kj::byte> src) {
188 js.setAllowEval(true);
189 KJ_DEFER(js.setAllowEval(false));
190 
191 auto maybeWasmModule = v8::WasmModuleObject::Compile(
192 js.v8Isolate, v8::MemorySpan<const uint8_t>(src.begin(), src.size()));
193 return jsg::JsValue(jsg::check(maybeWasmModule));
194}
195 
196jsg::Promise<void> UnsafeModule::abortAllDurableObjects(jsg::Lock& js) {
197 auto& context = IoContext::current();
198 
199 auto exception = JSG_KJ_EXCEPTION(FAILED, Error, "Application called abortAllDurableObjects().");
200 context.abortAllActors(exception);
201 
202 // We used to perform the abort asynchronously, but that became no longer necessary when
203 // `Worker::Actor`'s destructor stopped requiring taking the isolate lock.
204 return js.resolvedPromise();
205}
206 
207jsg::Promise<void> UnsafeModule::deleteAllDurableObjects(jsg::Lock& js) {
208 auto& context = IoContext::current();
209 
210 auto exception = JSG_KJ_EXCEPTION(FAILED, Error, "Application called deleteAllDurableObjects().");
211 context.deleteAllActors(exception);
212 
213 return js.resolvedPromise();
214}
215 
216bool UnsafeModule::isTestAutogateEnabled() {
217 return util::Autogate::isEnabled(util::AutogateKey::TEST_WORKERD);
218}
219 
220#ifdef WORKERD_FUZZILLI
221void Fuzzilli::fuzzilli(jsg::Lock& js, jsg::Arguments<jsg::Value> args) {
222 // Delegate to the fuzzilli_handler in fuzzilli.c++
223 fuzzilli_handler(js, args);
224}
225#endif
226 
227} // namespace workerd::api