Skip to content

Commit

Permalink
fix: panic when head-skipping to a single-byte key
Browse files Browse the repository at this point in the history
- This was detected by fuzzing! Searching for `{` or `[`
with descendant would cause a panic
if the input started with the sequence
`{"` or `["`, respectively.

Ref: #281
  • Loading branch information
V0ldek committed Sep 21, 2023
1 parent 5c1bf7d commit 581a9eb
Show file tree
Hide file tree
Showing 14 changed files with 1,949 additions and 779 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

All notable changes to this project will be documented in this file.

## [unreleased]

### Bug fixes

- Fixed a bug when head-skipping to a single-byte key would panic.
- This was detected by fuzzing!
- The queries `$..["{"]` and `$..["["]` would panic
on inputs starting with the bytes `{"` or `["`, respectively.
- Fixed a bug where disabling the `simd` feature would not actually
disable SIMD acceleration.

## [0.8.1] - 2023-09-20

### Features
Expand Down
14 changes: 5 additions & 9 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -233,19 +233,15 @@ commit msg:

# === HOOKS ===

tmpdiff := if os() == "windows" {
`New-TemporaryFile`
} else {
`mktemp -t pre-commit-hook-diff-XXXXXXXX.$$`
}

[private]
hook-pre-commit:
hook-pre-commit:
#!/bin/sh
tmpdiff=$(mktemp -t pre-commit-hook-diff-XXXXXXXX.$$)
just assert-benchmarks-committed
git diff --full-index --binary > {{tmpdiff}}
git diff --full-index --binary > $tmpdiff
git stash -q --keep-index
(just verify-fmt && just verify-check); \
git apply --whitespace=nowarn < {{tmpdiff}} && git stash drop -q; rm {{tmpdiff}}
git apply --whitespace=nowarn < $tmpdiff}} && git stash drop -q; rm $tmpdiff

[private]
@hook-post-checkout: checkout-benchmarks
Expand Down
2 changes: 1 addition & 1 deletion crates/rsonpath-benchmarks
2 changes: 1 addition & 1 deletion crates/rsonpath-lib/src/classification/memmem/nosimd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ where
while let Some(block) = self.iter.next()? {
let res = block.iter().copied().enumerate().find(|&(i, c)| {
let j = offset + i;
c == first_c && self.input.is_member_match(j - 1, j + label_size - 2, label)
c == first_c && j > 0 && self.input.is_member_match(j - 1, j + label_size - 2, label)
});

if let Some((res, _)) = res {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub(crate) fn find_in_mask<I: Input>(
let mut result = (previous_block | (first << 1)) & second;
while result != 0 {
let idx = result.trailing_zeros() as usize;
if input.is_member_match(offset + idx - 2, offset + idx + label_size - 3, label) {
if offset + idx > 1 && input.is_member_match(offset + idx - 2, offset + idx + label_size - 3, label) {
return Some(offset + idx - 2);
}
result &= !(1 << idx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub(crate) fn find_in_mask<I: Input>(
while result != 0 {
let idx = result.trailing_zeros() as usize;
debug!("{offset} + {idx} - 2 to {offset} + {idx} + {label_size} - 3");
if input.is_member_match(offset + idx - 2, offset + idx + label_size - 3, label) {
if offset + idx > 1 && input.is_member_match(offset + idx - 2, offset + idx + label_size - 3, label) {
return Some(offset + idx - 2);
}
result &= !(1 << idx);
Expand Down
8 changes: 7 additions & 1 deletion crates/rsonpath-lib/src/classification/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,13 @@ pub fn configure() -> SimdConfiguration {
}

cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if #[cfg(not(feature = "simd"))]
{
let highest_simd = SimdTag::Nosimd;
let fast_quotes = false;
let fast_popcnt = false;
}
else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let highest_simd = if is_x86_feature_detected!("avx2") {
SimdTag::Avx2
Expand Down
26 changes: 26 additions & 0 deletions crates/rsonpath-lib/tests/documents/toml/head_skip_for_curly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Define the JSON input for all query test cases.
[input]
# Short description of the input structure.
description = "Object with an empty key."
# Set to true only if your specific test input is fully compressed (no extraneous whitespace).
is_compressed = false

# Inline JSON document.
[input.source]
json_string = '{"":null}'

# Define queries to test on the input.
[[queries]]
# Valid JSONPath query string.
query = '$..["{"]'
# Short descritpion of the query semantics.
description = "descendant search for a key equal to the curly brace"

[queries.results]
# Number of expected matches.
count = 0
# Byte locations of spans of all matches, in order.
spans = []
# Stringified values of all matches, verbatim as in the input,
# in the same order as above.
nodes = []
26 changes: 26 additions & 0 deletions crates/rsonpath-lib/tests/documents/toml/head_skip_for_square.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Define the JSON input for all query test cases.
[input]
# Short description of the input structure.
description = "List with an empty string."
# Set to true only if your specific test input is fully compressed (no extraneous whitespace).
is_compressed = false

# Inline JSON document.
[input.source]
json_string = '[""]'

# Define queries to test on the input.
[[queries]]
# Valid JSONPath query string.
query = '$..["["]'
# Short descritpion of the query semantics.
description = "descendant search for a key equal to the square brace"

[queries.results]
# Number of expected matches.
count = 0
# Byte locations of spans of all matches, in order.
spans = []
# Stringified values of all matches, verbatim as in the input,
# in the same order as above.
nodes = []
Loading

0 comments on commit 581a9eb

Please sign in to comment.