Siyao Meng created HDDS-17245:
---------------------------------

             Summary: SetLifecycleConfiguration validated against one bucket 
can be stored on a different bucket created under the same name before it is 
applied
                 Key: HDDS-17245
                 URL: https://issues.apache.org/jira/browse/HDDS-17245
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
MC-7-bind-lifecycle-configuration-to-validated-bucket.patch

h3. Mechanism
{{OMLifecycleConfigurationSetRequest.preExecute}} runs on the leader before the 
request is replicated. It resolves the bucket and checks that the layout tag of 
the configuration matches the layout of the bucket. {{validateAndUpdateCache}} 
runs when the request is applied. It checks only that a bucket with that name 
exists and that the rules are valid for the layout tag in the request, and then 
binds the configuration to that bucket's object ID. If the bucket is deleted 
and another one is created with the same name between the two steps, the 
configuration is stored on a bucket it was never validated against, and 
{{KeyLifecycleService}} accepts it because the stored object ID matches.

With a layout change this stores rules that the new bucket would reject. A 
configuration tagged OBJECT_STORE with the prefix {{dir1}} (no trailing slash) 
is stored on a FILE_SYSTEM_OPTIMIZED bucket, while the same request sent 
directly to that bucket is rejected, either for the layout mismatch or because 
an FSO prefix must end with {{/}}.

h3. Trigger
# A client sends SetLifecycleConfiguration for bucket {{b}}, and preExecute 
passes on the leader.
# Before the request is applied, {{b}} (empty) is deleted and created again 
under the same name.
# The request is applied and returns OK.

The window is the time between preExecute on the leader and the apply of the 
request. I reached the state through the request methods directly (see 
Reproduction) and did not race it over RPC.

h3. Impact
* The request succeeds and the configuration is attached to a bucket it was not 
validated for. It stays until it is replaced or deleted.
* For the OBJECT_STORE prefix stored on an FSO bucket above, 
{{KeyLifecycleService.getRuleUnion}} skips an FSO rule whose prefix does not 
end with {{/}} and logs a warning, so the rule silently does nothing (run with 
a scan of the bucket). When the configuration also has a rule without a prefix, 
every rule is evaluated per key and the rule acts with OBJECT_STORE prefix 
matching, which is what the client asked for (argued, not run).

h3. Reproduction
No standalone reproduction is attached. The regression test in the patch shows 
the behaviour: {{testValidateAndUpdateCacheRejectsRecreatedBucket}} runs 
{{preExecute}} against a bucket, replaces the bucket in the bucket table with 
another one of the same name, and applies the request. On the unfixed code the 
apply returns OK and binds the configuration to the new bucket.

For the layout case I ran the real {{preExecute}} and 
{{validateAndUpdateCache}} against a real {{OmMetadataManagerImpl}}, with 
bucket link resolution stubbed to report the layout of the bucket currently in 
the table and the delete and create of the bucket written to the bucket table 
between the two steps. Output on the unfixed code:
{noformat}
preExecute passed against OBJECT_STORE bucket objectID=1000
apply on recreated FSO bucket: status=OK
stored configuration: layout=OBJECT_STORE bucketObjectID=2000 prefix=dir1 | 
bucket: layout=FILE_SYSTEM_OPTIMIZED objectID=2000
control, OBJECT_STORE tagged request on FSO bucket: INVALID_REQUEST Bucket 
layout mismatch: requested lifecycle configuration has bucket layout 
OBJECT_STORE but the actual bucket has layout FILE_SYSTEM_OPTIMIZED
control, FSO tagged request on FSO bucket: INVALID_REQUEST 
FILE_SYSTEM_OPTIMIZED bucket prefix must end with '/'.
{noformat}

h3. Patch
[^MC-7-bind-lifecycle-configuration-to-validated-bucket.patch], against 
20d95ed37fe35013b5320713665db03c06ee36fa. It also applies to master at 
64d4a791502.

{{preExecute}} records the object ID of the bucket it validated in the existing 
{{bucketObjectID}} field of the request's {{LifecycleConfiguration}}, and 
{{validateAndUpdateCache}} rejects the request with BUCKET_NOT_FOUND if the 
bucket now has a different object ID. This does not depend on what changed with 
the bucket, so I preferred it to re-checking the layout at apply. A request 
without the field is applied as before. SetLifecycleConfiguration is behind the 
{{S3_LIFECYCLE_SUPPORT}} layout feature, which is not in a release yet, so no 
released OM applies these requests differently (only OMs built from master 
before this change would accept such a request in the window).

Covered by {{testValidateAndUpdateCacheRejectsRecreatedBucket}} in the existing 
{{TestOMLifecycleConfigurationSetRequest}}, which also checks that a request 
validated against the new bucket is applied to it. Without the change it fails 
with {{expected: <BUCKET_NOT_FOUND> but was: <OK>}}. With it the lifecycle 
request and helper suites (32 tests) and {{TestKeyLifecycleService}} (722 
tests, 18 skipped) pass and checkstyle is clean. With the patch the layout case 
above is rejected at the apply with BUCKET_NOT_FOUND.

Found by TLA+ model checking and code review of the OM volume, bucket and 
snapshot lifecycle paths under HDDS-15926, on commit 
20d95ed37fe35013b5320713665db03c06ee36fa. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. The attached patch is 
a proposal for review. Generated with Specula (Claude Opus 5.5).



--
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