Skip to content

Commit 30819e7

Browse files
authored
fix(ffi): prevent undefined behavior on empty slices (#894)
1 parent 1d75a7f commit 30819e7

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

ffi/firewood_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,3 +704,43 @@ func TestFakeRevision(t *testing.T) {
704704
_, err = db.Revision(validRoot)
705705
require.ErrorIs(t, err, errRevisionNotFound, "Revision(valid root)")
706706
}
707+
708+
// Tests that edge case `Get` calls are handled correctly.
709+
func TestGetNilCases(t *testing.T) {
710+
db := newTestDatabase(t)
711+
712+
// Commit 10 key-value pairs.
713+
keys := make([][]byte, 20)
714+
vals := make([][]byte, 20)
715+
for i := range keys {
716+
keys[i] = keyForTest(i)
717+
vals[i] = valForTest(i)
718+
}
719+
root, err := db.Update(keys[:10], vals[:10])
720+
require.NoError(t, err, "Update")
721+
722+
// Create the other views
723+
proposal, err := db.Propose(keys[10:], vals[10:])
724+
require.NoError(t, err, "Propose")
725+
revision, err := db.Revision(root)
726+
require.NoError(t, err, "Revision")
727+
728+
// Create edge case keys.
729+
specialKeys := [][]byte{
730+
nil,
731+
{}, // empty slice
732+
}
733+
for _, k := range specialKeys {
734+
got, err := db.Get(k)
735+
require.NoError(t, err, "db.Get(%q)", k)
736+
assert.Empty(t, got, "db.Get(%q)", k)
737+
738+
got, err = revision.Get(k)
739+
require.NoError(t, err, "Revision.Get(%q)", k)
740+
assert.Empty(t, got, "Revision.Get(%q)", k)
741+
742+
got, err = proposal.Get(k)
743+
require.NoError(t, err, "Proposal.Get(%q)", k)
744+
assert.Empty(t, got, "Proposal.Get(%q)", k)
745+
}
746+
}

ffi/src/lib.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,10 @@ fn batch(
296296
values: *const KeyValue,
297297
) -> Result<Value, String> {
298298
let start = coarsetime::Instant::now();
299-
// Check db is valid.
300299
let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?;
300+
if values.is_null() {
301+
return Err(String::from("key-value list is null"));
302+
}
301303

302304
// Create a batch of operations to perform.
303305
let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) };
@@ -366,6 +368,9 @@ fn propose_on_db(
366368
values: *const KeyValue,
367369
) -> Result<ProposalId, String> {
368370
let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?;
371+
if values.is_null() {
372+
return Err(String::from("key-value list is null"));
373+
}
369374

370375
// Create a batch of operations to perform.
371376
let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) };
@@ -424,6 +429,9 @@ fn propose_on_proposal(
424429
values: *const KeyValue,
425430
) -> Result<ProposalId, String> {
426431
let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?;
432+
if values.is_null() {
433+
return Err(String::from("key-value list is null"));
434+
}
427435

428436
// Create a batch of operations to perform.
429437
let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) };
@@ -594,7 +602,11 @@ impl Display for Value {
594602
impl Value {
595603
#[must_use]
596604
pub const fn as_slice(&self) -> &[u8] {
597-
unsafe { std::slice::from_raw_parts(self.data, self.len) }
605+
if self.data.is_null() {
606+
&[]
607+
} else {
608+
unsafe { std::slice::from_raw_parts(self.data, self.len) }
609+
}
598610
}
599611
}
600612

0 commit comments

Comments
 (0)