[ 
https://issues.apache.org/jira/browse/HDDS-16436?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-16436:
------------------------------
    Description: 
h3. Finding
The LEGACY (non-FSO) rename handler lacks the per-key updateID guard that the 
delete and FSO-rename paths enforce, so a lifecycle move-to-trash carrying a 
scan-captured stale updateID destructively moves the client's live, overwritten 
(or delete+recreated) key into trash where reads return not-found and retention 
eventually purges it; the move is durably committed with no automatic recovery. 
Reachable in the default configuration (move-to-trash enabled by default) on a 
LEGACY bucket with a lifecycle rule and a post-scan overwrite. This is silent 
client-visible data loss.

h3. Classification
* Verdict: REPRODUCED
* Severity: Critical
* Source: Specula TLA+ model checking and confirmation debate, finding CR-4

h3. Reproduce
{noformat}
Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
Specula:      v1.1.0 (commit c6aa3dfa)
Target:       om-key-lifecycle
Guidance:     
campaigns/ozone-9fbf9ee/targets/006-om-key-lifecycle/.prompt-extra.md
{noformat}
{code:none}
specula run --agent=claude-code --effort=high --keep-original --max-parallel=2 \
  --enable-reviews --confirm-debate --tlc-memory-limit=28G --tlc-worker-limit=8 
\
  "om-key-lifecycle|apache/ozone|Java|Use the target-specific .prompt-extra.md"
{code}
Discovered under HDDS-16429 (Specula TLA+ verification effort). The TLA+ 
specification, counterexample, and confirmation debate live in the Specula run 
artifacts.

Generated with Specula (Claude Opus 4.8).

h3. Steps to reproduce

# Create a LEGACY bucket with move-to-trash enabled (fs.trash.interval > 0) and 
a lifecycle rule that matches its keys.
# Write a key K. The lifecycle scan records K and captures its updateID at scan 
time.
# Before the scan's rename-to-trash commits, overwrite K (a normal concurrent 
write bumps K's updateID), or delete and recreate the bucket so the same-named 
key now has a higher updateID.
# The lifecycle service issues a RenameKey to move K to .Trash/ with the stale 
scan-captured updateID (KeyLifecycleService.moveToTrash sets 
RenameKeyRequest.updateID to the value captured during the scan).
# The LEGACY rename handler (OMKeyRenameRequest) resolves the key by name and 
renames it unconditionally, moving the live (overwritten) key into .Trash. A 
client read then returns KEY_NOT_FOUND and trash retention eventually purges it.

The identical stale request is correctly rejected on the delete path 
(OMKeysDeleteRequest re-checks updateID) and on the FSO rename path 
(OMKeyRenameRequestWithFSO re-checks updateID). Only the LEGACY rename path is 
unguarded, so the fencing composition is not inductive.

h3. Root cause

Lifecycle stale-write fencing relies on a per-key updateID re-check at commit 
time. That guard is present on the delete path and on the FSO rename handler 
(OMKeyRenameRequestWithFSO.validateAndUpdateCache checks 
renameKeyRequest.hasUpdateID() and rejects with UPDATE_ID_NOT_MATCH when 
fromKeyInfo.getUpdateID() differs), but is absent on the LEGACY (non-FSO) 
OMKeyRenameRequest.validateAndUpdateCache. The LEGACY handler loads fromKey by 
name and renames it without ever reading renameKeyRequest.getUpdateID(), so a 
stale lifecycle rename-to-trash commits against whatever key currently holds 
that name. The FSO guard and the trash-move rename path were added together, 
but the LEGACY variant of the handler was never given the matching guard.

h3. Suggested fix

Add the same updateID re-check to OMKeyRenameRequest.validateAndUpdateCache: 
after the source key is loaded, when the request carries an updateID, reject 
with UPDATE_ID_NOT_MATCH if the live key's updateID does not match. This 
mirrors the existing FSO guard and makes all three destructive lifecycle paths 
(delete, FSO rename, LEGACY rename) fence stale writes consistently. See 
[^HDDS-16436.001.patch]. The patch adds a targeted stale-updateID rejection 
test and a matching-updateID success test to TestOMKeyRenameRequest (LEGACY) 
and TestOMKeyRenameRequestWithFSO (FSO regression guard).

Patch is an AI-drafted proposal (Specula + Claude); pending human build, full 
test, and review before merge.


  was:
h3. Finding
The LEGACY (non-FSO) rename handler lacks the per-key updateID guard that the 
delete and FSO-rename paths enforce, so a lifecycle move-to-trash carrying a 
scan-captured stale updateID destructively moves the client's live, overwritten 
(or delete+recreated) key into trash where reads return not-found and retention 
eventually purges it; the move is durably committed with no automatic recovery. 
Reachable in the default configuration (move-to-trash enabled by default) on a 
LEGACY bucket with a lifecycle rule and a post-scan overwrite. This is silent 
client-visible data loss.

h3. Classification
* Verdict: REPRODUCED
* Severity: Critical
* Source: Specula TLA+ model checking and confirmation debate, finding CR-4

h3. Reproduce
{noformat}
Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
Specula:      v1.1.0 (commit c6aa3dfa)
Target:       om-key-lifecycle
Guidance:     
campaigns/ozone-9fbf9ee/targets/006-om-key-lifecycle/.prompt-extra.md
{noformat}
{code:none}
specula run --agent=claude-code --effort=high --keep-original --max-parallel=2 \
  --enable-reviews --confirm-debate --tlc-memory-limit=28G --tlc-worker-limit=8 
\
  "om-key-lifecycle|apache/ozone|Java|Use the target-specific .prompt-extra.md"
{code}
Discovered under HDDS-16429 (Specula TLA+ verification effort). The TLA+ 
specification, counterexample, and confirmation debate live in the Specula run 
artifacts.

Generated with Specula (Claude Opus 4.8).


> LEGACY bucket lifecycle move to trash with a stale updateID can destructively 
> trash a live key
> ----------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16436
>                 URL: https://issues.apache.org/jira/browse/HDDS-16436
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Critical
>         Attachments: HDDS-16436.001.patch
>
>
> h3. Finding
> The LEGACY (non-FSO) rename handler lacks the per-key updateID guard that the 
> delete and FSO-rename paths enforce, so a lifecycle move-to-trash carrying a 
> scan-captured stale updateID destructively moves the client's live, 
> overwritten (or delete+recreated) key into trash where reads return not-found 
> and retention eventually purges it; the move is durably committed with no 
> automatic recovery. Reachable in the default configuration (move-to-trash 
> enabled by default) on a LEGACY bucket with a lifecycle rule and a post-scan 
> overwrite. This is silent client-visible data loss.
> h3. Classification
> * Verdict: REPRODUCED
> * Severity: Critical
> * Source: Specula TLA+ model checking and confirmation debate, finding CR-4
> h3. Reproduce
> {noformat}
> Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
> Specula:      v1.1.0 (commit c6aa3dfa)
> Target:       om-key-lifecycle
> Guidance:     
> campaigns/ozone-9fbf9ee/targets/006-om-key-lifecycle/.prompt-extra.md
> {noformat}
> {code:none}
> specula run --agent=claude-code --effort=high --keep-original 
> --max-parallel=2 \
>   --enable-reviews --confirm-debate --tlc-memory-limit=28G 
> --tlc-worker-limit=8 \
>   "om-key-lifecycle|apache/ozone|Java|Use the target-specific 
> .prompt-extra.md"
> {code}
> Discovered under HDDS-16429 (Specula TLA+ verification effort). The TLA+ 
> specification, counterexample, and confirmation debate live in the Specula 
> run artifacts.
> Generated with Specula (Claude Opus 4.8).
> h3. Steps to reproduce
> # Create a LEGACY bucket with move-to-trash enabled (fs.trash.interval > 0) 
> and a lifecycle rule that matches its keys.
> # Write a key K. The lifecycle scan records K and captures its updateID at 
> scan time.
> # Before the scan's rename-to-trash commits, overwrite K (a normal concurrent 
> write bumps K's updateID), or delete and recreate the bucket so the 
> same-named key now has a higher updateID.
> # The lifecycle service issues a RenameKey to move K to .Trash/ with the 
> stale scan-captured updateID (KeyLifecycleService.moveToTrash sets 
> RenameKeyRequest.updateID to the value captured during the scan).
> # The LEGACY rename handler (OMKeyRenameRequest) resolves the key by name and 
> renames it unconditionally, moving the live (overwritten) key into .Trash. A 
> client read then returns KEY_NOT_FOUND and trash retention eventually purges 
> it.
> The identical stale request is correctly rejected on the delete path 
> (OMKeysDeleteRequest re-checks updateID) and on the FSO rename path 
> (OMKeyRenameRequestWithFSO re-checks updateID). Only the LEGACY rename path 
> is unguarded, so the fencing composition is not inductive.
> h3. Root cause
> Lifecycle stale-write fencing relies on a per-key updateID re-check at commit 
> time. That guard is present on the delete path and on the FSO rename handler 
> (OMKeyRenameRequestWithFSO.validateAndUpdateCache checks 
> renameKeyRequest.hasUpdateID() and rejects with UPDATE_ID_NOT_MATCH when 
> fromKeyInfo.getUpdateID() differs), but is absent on the LEGACY (non-FSO) 
> OMKeyRenameRequest.validateAndUpdateCache. The LEGACY handler loads fromKey 
> by name and renames it without ever reading renameKeyRequest.getUpdateID(), 
> so a stale lifecycle rename-to-trash commits against whatever key currently 
> holds that name. The FSO guard and the trash-move rename path were added 
> together, but the LEGACY variant of the handler was never given the matching 
> guard.
> h3. Suggested fix
> Add the same updateID re-check to OMKeyRenameRequest.validateAndUpdateCache: 
> after the source key is loaded, when the request carries an updateID, reject 
> with UPDATE_ID_NOT_MATCH if the live key's updateID does not match. This 
> mirrors the existing FSO guard and makes all three destructive lifecycle 
> paths (delete, FSO rename, LEGACY rename) fence stale writes consistently. 
> See [^HDDS-16436.001.patch]. The patch adds a targeted stale-updateID 
> rejection test and a matching-updateID success test to TestOMKeyRenameRequest 
> (LEGACY) and TestOMKeyRenameRequestWithFSO (FSO regression guard).
> Patch is an AI-drafted proposal (Specula + Claude); pending human build, full 
> test, and review before merge.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to