Skip to content

Commit 0185ca5

Browse files
leoyvensFilippo Costa
authored andcommitted
runtime: Adjust gas cost and limit input to json.fromBytes (#4595)
1 parent aa65cf6 commit 0185ca5

File tree

3 files changed

+33
-8
lines changed

3 files changed

+33
-8
lines changed

graph/src/runtime/gas/costs.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,10 @@ pub const STORE_GET: GasOp = GasOp {
7474
};
7575

7676
pub const STORE_REMOVE: GasOp = STORE_SET;
77+
78+
// Deeply nested JSON can take over 100x the memory of the serialized format, so multiplying the
79+
// size cost by 100 makes sense.
80+
pub const JSON_FROM_BYTES: GasOp = GasOp {
81+
base_cost: DEFAULT_BASE_COST,
82+
size_mult: DEFAULT_GAS_PER_BYTE * 100,
83+
};

runtime/test/src/test.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -327,31 +327,39 @@ async fn test_json_parsing(api_version: Version, gas_used: u64) {
327327
)
328328
.await;
329329

330+
// Parse valid JSON and get it back
331+
let s = "\"foo\""; // Valid because there are quotes around `foo`
332+
let bytes: &[u8] = s.as_ref();
333+
let return_value: AscPtr<AscString> = module.invoke_export1("handleJsonError", bytes);
334+
335+
let output: String = module.asc_get(return_value).unwrap();
336+
assert_eq!(output, "OK: foo, ERROR: false");
337+
assert_eq!(module.gas_used(), gas_used);
338+
330339
// Parse invalid JSON and handle the error gracefully
331340
let s = "foo"; // Invalid because there are no quotes around `foo`
332341
let bytes: &[u8] = s.as_ref();
333342
let return_value: AscPtr<AscString> = module.invoke_export1("handleJsonError", bytes);
334343
let output: String = module.asc_get(return_value).unwrap();
335344
assert_eq!(output, "ERROR: true");
336345

337-
// Parse valid JSON and get it back
338-
let s = "\"foo\""; // Valid because there are quotes around `foo`
346+
// Parse JSON that's too long and handle the error gracefully
347+
let s = format!("\"f{}\"", "o".repeat(10_000_000));
339348
let bytes: &[u8] = s.as_ref();
340349
let return_value: AscPtr<AscString> = module.invoke_export1("handleJsonError", bytes);
341350

342351
let output: String = module.asc_get(return_value).unwrap();
343-
assert_eq!(output, "OK: foo, ERROR: false");
344-
assert_eq!(module.gas_used(), gas_used);
352+
assert_eq!(output, "ERROR: true");
345353
}
346354

347355
#[tokio::test]
348356
async fn json_parsing_v0_0_4() {
349-
test_json_parsing(API_VERSION_0_0_4, 2722284).await;
357+
test_json_parsing(API_VERSION_0_0_4, 4373087).await;
350358
}
351359

352360
#[tokio::test]
353361
async fn json_parsing_v0_0_5() {
354-
test_json_parsing(API_VERSION_0_0_5, 3862933).await;
362+
test_json_parsing(API_VERSION_0_0_5, 5153540).await;
355363
}
356364

357365
async fn test_ipfs_cat(api_version: Version) {

runtime/wasm/src/host_exports.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,18 @@ impl<C: Blockchain> HostExports<C> {
766766
bytes: &Vec<u8>,
767767
gas: &GasCounter,
768768
) -> Result<serde_json::Value, DeterministicHostError> {
769-
gas.consume_host_fn(gas::DEFAULT_GAS_OP.with_args(gas::complexity::Size, &bytes))?;
770-
serde_json::from_reader(bytes.as_slice())
769+
// Max JSON size is 10MB.
770+
const MAX_JSON_SIZE: usize = 10_000_000;
771+
772+
gas.consume_host_fn(gas::JSON_FROM_BYTES.with_args(gas::complexity::Size, &bytes))?;
773+
774+
if bytes.len() > MAX_JSON_SIZE {
775+
return Err(DeterministicHostError::Other(
776+
anyhow!("JSON size exceeds max size of {}", MAX_JSON_SIZE).into(),
777+
));
778+
}
779+
780+
serde_json::from_slice(bytes.as_slice())
771781
.map_err(|e| DeterministicHostError::from(Error::from(e)))
772782
}
773783

0 commit comments

Comments
 (0)