@wasm-oj/server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/THIRD_PARTY_NOTICES.md +302 -0
  4. package/crates/runtime-core/Cargo.lock +5099 -0
  5. package/crates/runtime-core/Cargo.toml +66 -0
  6. package/crates/runtime-core/README.md +47 -0
  7. package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
  8. package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
  9. package/crates/runtime-core/src/capabilities.rs +118 -0
  10. package/crates/runtime-core/src/compiler.rs +658 -0
  11. package/crates/runtime-core/src/contract.rs +5 -0
  12. package/crates/runtime-core/src/deterministic.rs +1051 -0
  13. package/crates/runtime-core/src/error.rs +58 -0
  14. package/crates/runtime-core/src/filesystem.rs +547 -0
  15. package/crates/runtime-core/src/filesystem_quota.rs +167 -0
  16. package/crates/runtime-core/src/go_compiler_session.rs +297 -0
  17. package/crates/runtime-core/src/interactive.rs +1019 -0
  18. package/crates/runtime-core/src/judge_package.rs +1539 -0
  19. package/crates/runtime-core/src/lib.rs +98 -0
  20. package/crates/runtime-core/src/memory.rs +84 -0
  21. package/crates/runtime-core/src/meter.rs +549 -0
  22. package/crates/runtime-core/src/module_imports.rs +149 -0
  23. package/crates/runtime-core/src/module_policy.rs +714 -0
  24. package/crates/runtime-core/src/output.rs +204 -0
  25. package/crates/runtime-core/src/run/mod.rs +208 -0
  26. package/crates/runtime-core/src/run/native.rs +260 -0
  27. package/crates/runtime-core/src/run/web.rs +229 -0
  28. package/crates/runtime-core/src/run/web_runtime.rs +109 -0
  29. package/crates/runtime-core/src/types.rs +268 -0
  30. package/crates/runtime-core/src/web.rs +83 -0
  31. package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
  32. package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
  33. package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
  34. package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
  35. package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
  36. package/dist/go-stage.mjs +193 -0
  37. package/dist/index.d.ts +221 -0
  38. package/dist/index.js +4393 -0
  39. package/dist/java-stage.mjs +111 -0
  40. package/dist/python-stage.mjs +90 -0
  41. package/dist/rustc-stage.mjs +301 -0
  42. package/dist/server-build-stage.mjs +2564 -0
  43. package/dist/server-runner-stage.mjs +155 -0
  44. package/licenses/fflate-MIT.txt +21 -0
  45. package/licenses/runtime-core-dependencies.html +6253 -0
  46. package/licenses/runtime-core-dependencies.json +3041 -0
  47. package/licenses/wasmer-sdk-MIT.txt +21 -0
  48. package/licenses/wasmer-sdk-dependencies.html +6901 -0
  49. package/licenses/wasmer-sdk-dependencies.json +3013 -0
  50. package/package.json +70 -0
  51. package/rust-toolchain.toml +5 -0
  52. package/testdata/wojjdg02-v2-text.hex +1 -0
  53. package/vendor/shared-buffer/Cargo.toml +22 -0
  54. package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
  55. package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
  56. package/vendor/shared-buffer/README.md +34 -0
  57. package/vendor/shared-buffer/src/lib.rs +58 -0
  58. package/vendor/shared-buffer/src/mmap.rs +250 -0
  59. package/vendor/shared-buffer/src/owned.rs +389 -0
  60. package/vendor/virtual-fs/Cargo.toml +181 -0
  61. package/vendor/virtual-fs/LICENSE +25 -0
  62. package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
  63. package/vendor/virtual-fs/src/arc_file.rs +182 -0
  64. package/vendor/virtual-fs/src/arc_fs.rs +68 -0
  65. package/vendor/virtual-fs/src/buffer_file.rs +103 -0
  66. package/vendor/virtual-fs/src/builder.rs +232 -0
  67. package/vendor/virtual-fs/src/combine_file.rs +101 -0
  68. package/vendor/virtual-fs/src/cow_file.rs +345 -0
  69. package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
  70. package/vendor/virtual-fs/src/empty_fs.rs +81 -0
  71. package/vendor/virtual-fs/src/filesystems.rs +108 -0
  72. package/vendor/virtual-fs/src/host_fs.rs +1390 -0
  73. package/vendor/virtual-fs/src/lib.rs +782 -0
  74. package/vendor/virtual-fs/src/limiter.rs +252 -0
  75. package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
  76. package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
  77. package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
  78. package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
  79. package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
  80. package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
  81. package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
  82. package/vendor/virtual-fs/src/null_file.rs +87 -0
  83. package/vendor/virtual-fs/src/ops.rs +364 -0
  84. package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
  85. package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
  86. package/vendor/virtual-fs/src/pipe.rs +603 -0
  87. package/vendor/virtual-fs/src/random_file.rs +88 -0
  88. package/vendor/virtual-fs/src/special_file.rs +108 -0
  89. package/vendor/virtual-fs/src/static_file.rs +133 -0
  90. package/vendor/virtual-fs/src/static_fs.rs +460 -0
  91. package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
  92. package/vendor/virtual-fs/src/trace_fs.rs +258 -0
  93. package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
  94. package/vendor/virtual-fs/src/zero_file.rs +90 -0
@@ -0,0 +1,294 @@
1
+ use base64::{Engine as _, engine::general_purpose::STANDARD};
2
+ use serde::{Deserialize, Serialize};
3
+ use std::collections::BTreeMap;
4
+ use std::io::{self, Read};
5
+ use std::path::PathBuf;
6
+ use wasm_oj_runtime_core::{
7
+ DeterminismConfig, ExecutionMetrics, ExecutionTermination, InteractiveMetrics,
8
+ InteractiveProcessResult, InteractiveProgram, InteractiveRequest, InteractiveResponse,
9
+ InteractiveResult, ResourcePolicy, RunFailure, RunRequest, RunResponse, RunResult,
10
+ WASM_OJ_INTERACTIVE_REQUEST_SCHEMA, WASM_OJ_RUN_REQUEST_SCHEMA, interactive_response,
11
+ run_response,
12
+ };
13
+
14
+ #[derive(Deserialize)]
15
+ #[serde(deny_unknown_fields, rename_all = "camelCase")]
16
+ struct CliRunRequest {
17
+ #[serde(rename = "schema")]
18
+ _schema: String,
19
+ wasm_base64: String,
20
+ #[serde(default)]
21
+ args: Vec<String>,
22
+ #[serde(default)]
23
+ env: BTreeMap<String, String>,
24
+ #[serde(default)]
25
+ stdin_base64: String,
26
+ #[serde(default)]
27
+ files_base64: BTreeMap<String, String>,
28
+ #[serde(default)]
29
+ output_paths: Vec<String>,
30
+ cwd: Option<String>,
31
+ startup_entropy_bytes: u64,
32
+ determinism: DeterminismConfig,
33
+ resources: ResourcePolicy,
34
+ }
35
+
36
+ #[derive(Serialize)]
37
+ #[serde(rename_all = "camelCase")]
38
+ struct CliRunResult {
39
+ code: i32,
40
+ stdout_base64: String,
41
+ stderr_base64: String,
42
+ files_base64: BTreeMap<String, String>,
43
+ termination: ExecutionTermination,
44
+ trap_message: Option<String>,
45
+ metrics: ExecutionMetrics,
46
+ determinism: DeterminismConfig,
47
+ resources: ResourcePolicy,
48
+ }
49
+
50
+ #[derive(Serialize)]
51
+ #[serde(rename_all = "camelCase")]
52
+ struct CliRunResponse {
53
+ ok: bool,
54
+ result: Option<CliRunResult>,
55
+ error: Option<RunFailure>,
56
+ }
57
+
58
+ #[derive(Deserialize)]
59
+ #[serde(deny_unknown_fields, rename_all = "camelCase")]
60
+ struct CliInteractiveRequest {
61
+ #[serde(rename = "schema")]
62
+ _schema: String,
63
+ contestant: CliInteractiveProgram,
64
+ interactor: CliInteractiveProgram,
65
+ determinism: DeterminismConfig,
66
+ }
67
+
68
+ #[derive(Deserialize)]
69
+ #[serde(deny_unknown_fields, rename_all = "camelCase")]
70
+ struct CliInteractiveProgram {
71
+ wasm_base64: String,
72
+ #[serde(default)]
73
+ args: Vec<String>,
74
+ #[serde(default)]
75
+ env: BTreeMap<String, String>,
76
+ #[serde(default)]
77
+ files_base64: BTreeMap<String, String>,
78
+ cwd: Option<String>,
79
+ startup_entropy_bytes: u64,
80
+ resources: ResourcePolicy,
81
+ }
82
+
83
+ #[derive(Serialize)]
84
+ #[serde(rename_all = "camelCase")]
85
+ struct CliInteractiveProcessResult {
86
+ code: i32,
87
+ stderr_base64: String,
88
+ termination: ExecutionTermination,
89
+ metrics: InteractiveMetrics,
90
+ }
91
+
92
+ #[derive(Serialize)]
93
+ #[serde(rename_all = "camelCase")]
94
+ struct CliInteractiveResult {
95
+ contestant: CliInteractiveProcessResult,
96
+ interactor: CliInteractiveProcessResult,
97
+ contestant_to_interactor_base64: String,
98
+ interactor_to_contestant_base64: String,
99
+ determinism: DeterminismConfig,
100
+ }
101
+
102
+ #[derive(Serialize)]
103
+ #[serde(rename_all = "camelCase")]
104
+ struct CliInteractiveResponse {
105
+ ok: bool,
106
+ result: Option<CliInteractiveResult>,
107
+ error: Option<RunFailure>,
108
+ }
109
+
110
+ fn main() {
111
+ match execute() {
112
+ Ok(true) => {}
113
+ Ok(false) => std::process::exit(1),
114
+ Err(error) => {
115
+ eprintln!("wasm-oj-runner: {error}");
116
+ std::process::exit(2);
117
+ }
118
+ }
119
+ }
120
+
121
+ fn execute() -> Result<bool, String> {
122
+ let input = read_input()?;
123
+ let value: serde_json::Value =
124
+ serde_json::from_slice(&input).map_err(|error| format!("invalid request JSON: {error}"))?;
125
+ let schema = value
126
+ .get("schema")
127
+ .and_then(serde_json::Value::as_str)
128
+ .ok_or_else(|| "request schema must be a string".to_string())?;
129
+ match schema {
130
+ WASM_OJ_RUN_REQUEST_SCHEMA => execute_run(value),
131
+ WASM_OJ_INTERACTIVE_REQUEST_SCHEMA => execute_interactive(value),
132
+ value => Err(format!("unsupported request schema '{value}'")),
133
+ }
134
+ }
135
+
136
+ fn execute_run(value: serde_json::Value) -> Result<bool, String> {
137
+ let encoded: CliRunRequest = serde_json::from_value(value)
138
+ .map_err(|error| format!("invalid run request JSON: {error}"))?;
139
+ let response = encode_response(run_response(decode_request(encoded)?));
140
+ serde_json::to_writer(io::stdout().lock(), &response)
141
+ .map_err(|error| format!("failed to serialize response: {error}"))?;
142
+ Ok(response.ok)
143
+ }
144
+
145
+ fn execute_interactive(value: serde_json::Value) -> Result<bool, String> {
146
+ let encoded: CliInteractiveRequest = serde_json::from_value(value)
147
+ .map_err(|error| format!("invalid interactive request JSON: {error}"))?;
148
+ let request = InteractiveRequest {
149
+ contestant: decode_interactive_program(encoded.contestant, "contestant")?,
150
+ interactor: decode_interactive_program(encoded.interactor, "interactor")?,
151
+ determinism: encoded.determinism,
152
+ };
153
+ let runtime = tokio::runtime::Builder::new_current_thread()
154
+ .enable_all()
155
+ .build()
156
+ .map_err(|error| format!("failed to initialize interactive runtime: {error}"))?;
157
+ let response = encode_interactive_response(runtime.block_on(interactive_response(request)));
158
+ serde_json::to_writer(io::stdout().lock(), &response)
159
+ .map_err(|error| format!("failed to serialize response: {error}"))?;
160
+ Ok(response.ok)
161
+ }
162
+
163
+ fn decode_request(encoded: CliRunRequest) -> Result<RunRequest, String> {
164
+ let mut files = BTreeMap::new();
165
+ for (path, contents) in encoded.files_base64 {
166
+ files.insert(
167
+ path.clone(),
168
+ serde_bytes::ByteBuf::from(
169
+ STANDARD
170
+ .decode(contents)
171
+ .map_err(|error| format!("invalid base64 for guest file '{path}': {error}"))?,
172
+ ),
173
+ );
174
+ }
175
+ Ok(RunRequest {
176
+ wasm: STANDARD
177
+ .decode(encoded.wasm_base64)
178
+ .map_err(|error| format!("invalid wasmBase64: {error}"))?,
179
+ args: encoded.args,
180
+ env: encoded.env,
181
+ stdin: STANDARD
182
+ .decode(encoded.stdin_base64)
183
+ .map_err(|error| format!("invalid stdinBase64: {error}"))?,
184
+ files,
185
+ output_paths: encoded.output_paths,
186
+ cwd: encoded.cwd,
187
+ startup_entropy_bytes: encoded.startup_entropy_bytes,
188
+ determinism: encoded.determinism,
189
+ resources: encoded.resources,
190
+ })
191
+ }
192
+
193
+ fn decode_interactive_program(
194
+ encoded: CliInteractiveProgram,
195
+ label: &str,
196
+ ) -> Result<InteractiveProgram, String> {
197
+ let mut files = BTreeMap::new();
198
+ for (path, contents) in encoded.files_base64 {
199
+ files.insert(
200
+ path.clone(),
201
+ serde_bytes::ByteBuf::from(STANDARD.decode(contents).map_err(|error| {
202
+ format!("invalid base64 for {label} guest file '{path}': {error}")
203
+ })?),
204
+ );
205
+ }
206
+ Ok(InteractiveProgram {
207
+ wasm: STANDARD
208
+ .decode(encoded.wasm_base64)
209
+ .map_err(|error| format!("invalid {label} wasmBase64: {error}"))?,
210
+ args: encoded.args,
211
+ env: encoded.env,
212
+ files,
213
+ cwd: encoded.cwd,
214
+ startup_entropy_bytes: encoded.startup_entropy_bytes,
215
+ resources: encoded.resources,
216
+ })
217
+ }
218
+
219
+ fn encode_response(response: RunResponse) -> CliRunResponse {
220
+ CliRunResponse {
221
+ ok: response.ok,
222
+ result: response.result.map(encode_result),
223
+ error: response.error,
224
+ }
225
+ }
226
+
227
+ fn encode_result(result: RunResult) -> CliRunResult {
228
+ CliRunResult {
229
+ code: result.code,
230
+ stdout_base64: STANDARD.encode(result.stdout),
231
+ stderr_base64: STANDARD.encode(result.stderr),
232
+ files_base64: result
233
+ .files
234
+ .into_iter()
235
+ .map(|(path, contents)| (path, STANDARD.encode(contents)))
236
+ .collect(),
237
+ termination: result.termination,
238
+ trap_message: result.trap_message,
239
+ metrics: result.metrics,
240
+ determinism: result.determinism,
241
+ resources: result.resources,
242
+ }
243
+ }
244
+
245
+ fn encode_interactive_response(response: InteractiveResponse) -> CliInteractiveResponse {
246
+ CliInteractiveResponse {
247
+ ok: response.ok,
248
+ result: response.result.map(encode_interactive_result),
249
+ error: response.error,
250
+ }
251
+ }
252
+
253
+ fn encode_interactive_result(result: InteractiveResult) -> CliInteractiveResult {
254
+ CliInteractiveResult {
255
+ contestant: encode_interactive_process(result.contestant),
256
+ interactor: encode_interactive_process(result.interactor),
257
+ contestant_to_interactor_base64: STANDARD.encode(result.contestant_to_interactor),
258
+ interactor_to_contestant_base64: STANDARD.encode(result.interactor_to_contestant),
259
+ determinism: result.determinism,
260
+ }
261
+ }
262
+
263
+ fn encode_interactive_process(result: InteractiveProcessResult) -> CliInteractiveProcessResult {
264
+ CliInteractiveProcessResult {
265
+ code: result.code,
266
+ stderr_base64: STANDARD.encode(result.stderr),
267
+ termination: result.termination,
268
+ metrics: result.metrics,
269
+ }
270
+ }
271
+
272
+ fn read_input() -> Result<Vec<u8>, String> {
273
+ let mut arguments = std::env::args_os();
274
+ let _program = arguments.next();
275
+ let first = arguments.next();
276
+ let second = arguments.next();
277
+ let trailing = arguments.next();
278
+ match (first, second, trailing) {
279
+ (None, None, None) => {
280
+ let mut input = Vec::new();
281
+ io::stdin()
282
+ .lock()
283
+ .read_to_end(&mut input)
284
+ .map_err(|error| format!("failed to read stdin: {error}"))?;
285
+ Ok(input)
286
+ }
287
+ (Some(flag), Some(path), None) if flag == "--request" => {
288
+ let path = PathBuf::from(path);
289
+ std::fs::read(&path)
290
+ .map_err(|error| format!("failed to read {}: {error}", path.display()))
291
+ }
292
+ _ => Err("usage: wasm-oj-runner [--request REQUEST.json]".to_string()),
293
+ }
294
+ }
@@ -0,0 +1,118 @@
1
+ use wasmer::{AsStoreMut, ExternType, Function, Imports, Module, RuntimeError};
2
+
3
+ /// Returns whether an import exposes host state that WASM-OJ deliberately keeps
4
+ /// outside the deterministic judge contract.
5
+ ///
6
+ /// Denied imports that are part of an admitted ABI remain valid at module
7
+ /// validation time. WASM-OJ resolves each declared function with its exact
8
+ /// signature and replaces the Wasmer implementation with a fail-closed trap,
9
+ /// so no forbidden host implementation is reachable.
10
+ pub(crate) fn is_denied_capability(namespace: &str, name: &str) -> bool {
11
+ match namespace {
12
+ "wasi" => name == "thread-spawn",
13
+ "wasi_snapshot_preview1" => name == "thread-spawn" || name.starts_with("sock_"),
14
+ "wasix_32v1" | "wasix_64v1" => {
15
+ matches!(
16
+ name,
17
+ "thread_spawn"
18
+ | "thread_spawn_v2"
19
+ | "thread_signal"
20
+ | "thread_join"
21
+ | "thread_exit"
22
+ | "futex_wait"
23
+ ) || name.starts_with("sock_")
24
+ || name.starts_with("port_")
25
+ || name.starts_with("bus_")
26
+ || name.starts_with("http_")
27
+ || name.starts_with("net_")
28
+ || matches!(name, "resolve" | "clock_time_set")
29
+ || (name.starts_with("proc_")
30
+ && !matches!(name, "proc_exit" | "proc_id" | "proc_parent"))
31
+ }
32
+ _ => false,
33
+ }
34
+ }
35
+
36
+ /// Replaces every denied WASI/WASIX function with a signature-preserving host
37
+ /// trap. This must run after Wasmer builds the normal WASI import object and
38
+ /// before instantiation so no forbidden implementation is ever reachable.
39
+ pub(crate) fn attach_capability_denials(
40
+ store: &mut impl AsStoreMut,
41
+ module: &Module,
42
+ imports: &mut Imports,
43
+ ) -> Result<(), String> {
44
+ let denied = module
45
+ .imports()
46
+ .filter(|import| is_denied_capability(import.module(), import.name()))
47
+ .map(|import| {
48
+ let ExternType::Function(function_type) = import.ty() else {
49
+ return Err(format!(
50
+ "denied capability import {}.{} must be a function",
51
+ import.module(),
52
+ import.name()
53
+ ));
54
+ };
55
+ Ok((
56
+ import.module().to_string(),
57
+ import.name().to_string(),
58
+ function_type.clone(),
59
+ ))
60
+ })
61
+ .collect::<Result<Vec<_>, String>>()?;
62
+
63
+ for (namespace, name, function_type) in denied {
64
+ let capability = format!("{namespace}.{name}");
65
+ let error_message = format!("WASM-OJ denied nondeterministic capability {capability}");
66
+ let denial = Function::new(store, function_type, move |_| {
67
+ Err(RuntimeError::new(error_message.clone()))
68
+ });
69
+ imports.define(&namespace, &name, denial);
70
+ }
71
+ Ok(())
72
+ }
73
+
74
+ #[cfg(test)]
75
+ mod tests {
76
+ use super::is_denied_capability;
77
+
78
+ #[test]
79
+ fn denies_declared_thread_process_and_network_capabilities() {
80
+ for (namespace, name) in [
81
+ ("wasi", "thread-spawn"),
82
+ ("wasi_snapshot_preview1", "sock_recv"),
83
+ ("wasix_32v1", "thread_spawn"),
84
+ ("wasix_32v1", "futex_wait"),
85
+ ("wasix_32v1", "proc_fork"),
86
+ ("wasix_32v1", "proc_exec"),
87
+ ("wasix_32v1", "sock_open"),
88
+ ("wasix_32v1", "resolve"),
89
+ ("wasix_64v1", "port_bridge"),
90
+ ] {
91
+ assert!(
92
+ is_denied_capability(namespace, name),
93
+ "{namespace}.{name} must be denied"
94
+ );
95
+ }
96
+ }
97
+
98
+ #[test]
99
+ fn preserves_deterministic_language_runtime_capabilities() {
100
+ for (namespace, name) in [
101
+ ("wasi_snapshot_preview1", "fd_write"),
102
+ ("wasi_snapshot_preview1", "clock_time_get"),
103
+ ("wasi_snapshot_preview1", "random_get"),
104
+ ("wasix_32v1", "getcwd"),
105
+ ("wasix_32v1", "callback_signal"),
106
+ ("wasix_32v1", "futex_wake"),
107
+ ("wasix_32v1", "thread_id"),
108
+ ("wasix_32v1", "thread_parallelism"),
109
+ ("wasix_32v1", "proc_id"),
110
+ ("wasix_32v1", "proc_parent"),
111
+ ] {
112
+ assert!(
113
+ !is_denied_capability(namespace, name),
114
+ "{namespace}.{name} must remain available"
115
+ );
116
+ }
117
+ }
118
+ }