Skip to content

DAOS-13563 vos: fix engine crash on iod_nr==0 fetch of flat-dkey objects#18710

Open
hgichon wants to merge 1 commit into
daos-stack:masterfrom
hgichon:pr/vos-flat-dkey-iod0-crash
Open

DAOS-13563 vos: fix engine crash on iod_nr==0 fetch of flat-dkey objects#18710
hgichon wants to merge 1 commit into
daos-stack:masterfrom
hgichon:pr/vos-flat-dkey-iod0-crash

Conversation

@hgichon

@hgichon hgichon commented Jul 23, 2026

Copy link
Copy Markdown

Summary

dkey_fetch() crashes the engine on a CHECK_EXISTENCE / SET_TS_ONLY fetch (iod_nr == 0) of a flat-dkey object (array / flat KV on a pool with VOS_POOL_FEAT_FLAT_DKEY). vos_ioc_create() permits iod_nr == 0 for those flags, leaving ioc->ic_iods == NULL, but the flat path in dkey_fetch() dereferences ic_iods[0] without an iod_nr guard:

  • SUBTR_EVT computation — ic_iods[0].iod_typeSIGSEGV (NULL + 0x18)
  • KREC_BF_NO_AKEY branch — iod_set_cursor(ioc, 0) trips D_ASSERT(sgl_at < ic_iod_nr)SIGABRT, and ic_iods[0] is then dereferenced NULL in fetch_value()

Regression introduced by ae105cb ("DAOS-13563 vos: Implement flat dkey for array and flat KV object types", #13175). Present on master and any branch carrying that commit.

Fix

Guard both flat-path ic_iods[0] accesses with ic_iod_nr > 0. The non-flat path is already guarded by its for (i = 0; i < ic_iod_nr; i++) loop; key existence is established earlier by key_tree_prepare() + key_ilog_check(), so the value fetch is correctly skipped when there are no iods.

How it was found

versitygw's native DAOS backend issues an S3 HEAD / conditional-PUT existence probe as a CHECK_EXISTENCE fetch (iod_nr == 0) against a flat DFS object. This crashed the target engine (SIGSEGV/SIGABRT), surfacing to the S3 client as dc_rw_cb ... DER_TIMEDOUT. Observed backtrace (release build):

vos_fetch_begin+0xa7e   -> SIGSEGV, "Failing for address: 0x18"   (SUBTR_EVT path)
vos_io.c:1540 iod_set_cursor() Assertion 'sgl_at < ioc->ic_iod_nr' failed -> SIGABRT (after guarding the first)
  <- obj_prep_fetch_sgls <- ds_obj_rw_handler

Test

Adds VOS283 to src/vos/tests/vts_io.c (io test group), reproducing the crash:

  • Creates a flat-dkey array object (DAOS_OT_ARRAY_BYTE, uint64 dkey) and updates it, so the object/dkey exists. The earlier draft used a string dkey, which an array object rejects with -DER_INVAL in io_test_obj_update() before the fetch under test was reached — that was the "key format" issue, not a fault in the fix.
  • Issues the iod_nr == 0 VOS_OF_FETCH_CHECK_EXISTENCE fetch — the crash trigger.
  • Asserts the no-crash contract: rc == 0 || rc == -DER_NONEXIST. With no iods the flat-array dkey's subtree type is unknown to the probe, so a clean DER_NONEXIST is legitimate; reaching a clean rc rather than a SIGSEGV/SIGABRT is what the test verifies.
  • A teardown restores the shared arg->oid so a failure can't perturb the rest of the io suite.

Verified on hardware (md-on-SSD build, PMEMOBJ_CONF=sds.at_create=0), same vos_tests binary with only libvos.so swapped:

  • unfixed libvos.soVOS283 ... Segmentation fault(11) (exit 1)
  • fixed libvos.soVOS283 OK, io group 21/21 PASS

Also reproduced the SIGSEGV on pure upstream master (the merge-base commit, no local changes) with the same test, confirming this is the upstream regression rather than a local artifact.


Signed-off-by: hgichon hgichon@gmail.com

@github-actions

Copy link
Copy Markdown

Ticket title is 'Flat-KV object '
Status is 'Resolved'
Labels: 'md_on_ssd2'
https://daosio.atlassian.net/browse/DAOS-13563

@hgichon
hgichon marked this pull request as ready for review July 24, 2026 08:16
@hgichon
hgichon requested review from a team as code owners July 24, 2026 08:16
@hgichon

hgichon commented Jul 24, 2026

Copy link
Copy Markdown
Author

@jolivier23 would you be able to review this? It's a small regression fix in the flat-dkey fetch path you introduced in ae105cb ("DAOS-13563 vos: Implement flat dkey for array and flat KV object types", #13175).

Context: dkey_fetch()'s ic_skip_akey_support branch dereferences ioc->ic_iods[0] (and the KREC_BF_NO_AKEY path calls iod_set_cursor(ioc, 0)) without an ic_iod_nr guard. A CHECK_EXISTENCE / SET_TS_ONLY fetch is allowed to pass iod_nr == 0 (see vos_ioc_create()), so on a flat-dkey object (array / flat KV) this NULL-derefs → SIGSEGV, and after guarding the first deref, iod_set_cursor()'s D_ASSERT(sgl_at < ic_iod_nr) → SIGABRT. The non-flat path is naturally guarded by its for (i = 0; i < ic_iod_nr; i++) loop, and existence is already established by key_tree_prepare() + key_ilog_check(), so the fix just guards both flat-path accesses with ic_iod_nr > 0.

Originally hit via versitygw's native DAOS backend (an S3 HEAD / conditional-PUT issues a CHECK_EXISTENCE fetch on a flat DFS object), which crashed the target engine.

One thing I'd appreciate your eye on: the VOS283 reproduction test is a draft — I based it on the existing vts_io.c helpers and the default VTS pool (which enables VOS_POOL_FEAT_FLAT_DKEY), but the flat-object update setup should be validated against however you prefer to exercise flat-dkey objects in the unit tests. Happy to rework it to match. Thanks!

@hgichon
hgichon force-pushed the pr/vos-flat-dkey-iod0-crash branch from 88a5be4 to 45e7fe7 Compare July 24, 2026 08:48
@hgichon

hgichon commented Jul 24, 2026

Copy link
Copy Markdown
Author

Update: I dropped the draft VOS283 unit test from this PR — it's now fix-only (src/vos/vos_io.c).

On actually running it in vos_tests, the draft didn't hold up: it created an DAOS_OT_ARRAY_BYTE object but generated string dkeys, so the setup io_test_obj_update() failed at btr_verify_key() ("invalid integer key, expected: 8, got: 7") long before reaching the iod_nr == 0 fetch — so it never exercised the crash, and (on the assert longjmp) it also left arg->oid unrestored, cascading into the following io_tests. Getting a correct flat-dkey reproduction in VTS needs the right object/key setup and a pool with VOS_POOL_FEAT_FLAT_DKEY, which is really your area.

The fix itself is verified independently: the two guarded derefs are confirmed in the compiled libvos_srv.so (disassembly), and the crash + the fix's effect were reproduced on a live cluster via versitygw's DAOS backend (S3 HEAD/existence probe on a flat DFS object → target-engine SIGSEGV/SIGABRT before the fix; clean after, with our projid-quota suite passing and no engine abort).

If you'd like a unit test in this PR, I'm happy to add one — could you point me at the preferred way to stand up a flat-dkey (array / flat-KV) object in vts_io.c (and confirm the VTS pool enables FLAT_DKEY)? Otherwise happy to leave the regression coverage to your test infra. Thanks!

A CHECK_EXISTENCE / SET_TS_ONLY fetch carries no iods (iod_nr == 0 is
permitted for those flags by vos_ioc_create()). For a flat-dkey object
(array / flat KV on a pool with VOS_POOL_FEAT_FLAT_DKEY) dkey_fetch()
dereferenced ioc->ic_iods[0] without an iod_nr guard, crashing the engine:

  - the SUBTR_EVT computation: ic_iods[0].iod_type -> SIGSEGV (NULL deref)
  - the KREC_BF_NO_AKEY branch: iod_set_cursor(ioc, 0) trips
    D_ASSERT(sgl_at < ic_iod_nr) -> SIGABRT, and ic_iods[0] is then
    dereferenced NULL in fetch_value()

The non-flat path is naturally guarded by the "for (i = 0; i < ic_iod_nr;
i++)" loop, and object/key existence is already established by
key_tree_prepare() + key_ilog_check(), so the value fetch is simply skipped
when there are no iods. Guard both flat-path ic_iods[0] accesses with
ic_iod_nr > 0.

Regression introduced by commit ae105cb ("DAOS-13563 vos: Implement flat
dkey for array and flat KV object types", daos-stack#13175). Originally observed via
versitygw's native DAOS backend: an S3 HEAD/PUT existence probe issues a
CHECK_EXISTENCE fetch (iod_nr == 0) against a flat DFS object, crashing the
target engine (surfaced to the client as dc_rw_cb DER_TIMEDOUT).

Add VOS283 reproducing the crash under the io test group: it updates a
flat-dkey array object (DAOS_OT_ARRAY_BYTE, uint64 dkey) and then issues the
iod_nr == 0 CHECK_EXISTENCE fetch. Without the fix vos_tests SIGSEGVs in
dkey_fetch(); with it the fetch returns cleanly (DER_NONEXIST is acceptable
here - with no iods the flat-array subtree type is unknown to the probe, so
reaching a clean rc rather than a crash is what the test asserts). A
teardown restores the shared arg->oid so a failure cannot perturb the rest
of the io suite. Verified on real hardware: the same vos_tests binary
crashes against an unfixed libvos and passes against the fixed one (io group
21/21), and the crash reproduces on pure upstream master as well.

Signed-off-by: hgichon <hgichon@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hgichon
hgichon force-pushed the pr/vos-flat-dkey-iod0-crash branch from 45e7fe7 to a3cf556 Compare July 24, 2026 11:09
@hgichon

hgichon commented Jul 24, 2026

Copy link
Copy Markdown
Author

Re-added the VOS283 regression test (it was dropped earlier as a draft). The draft was failing in setup, not because of the fix: an array object (DAOS_OT_ARRAY_BYTE) requires a uint64 dkey, but the draft used a string dkey, so io_test_obj_update() returned -DER_INVAL before the fetch under test was ever reached — hence the "key format" trouble I mentioned.

Corrected test (vos_tests -i, io group):

  • Create a flat-dkey array object (DAOS_OT_ARRAY_BYTE, uint64 dkey) and update it, so the object/dkey exists.
  • Issue the iod_nr == 0 VOS_OF_FETCH_CHECK_EXISTENCE fetch — the crash trigger.
  • Assert the no-crash contract: rc == 0 || rc == -DER_NONEXIST. With no iods the flat-array dkey's subtree type is unknown to the probe, so a clean DER_NONEXIST is legitimate here; the point is reaching a clean rc instead of a SIGSEGV/SIGABRT.
  • A teardown restores the shared arg->oid so a failure can't perturb the rest of the io suite.

Verified on hardware (md-on-SSD build, PMEMOBJ_CONF=sds.at_create=0), same vos_tests binary with only libvos.so swapped:

  • unfixed libvos.soVOS283 ... Segmentation fault(11), process exit 1
  • fixed libvos.soVOS283 OK, io group 21/21 PASS

I also physically reproduced the SIGSEGV on pure upstream master (the merge-base commit, none of my local changes) with the same test, confirming this is an upstream regression from ae105cb rather than a local artifact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant