fix(ecstore): enforce the NAME_MAX segment budget on the write path too (#5826)

#5804 added the on-disk segment budget to check_bucket_and_object_names, but PUT validates through check_put_object_args, which has its own checks and never calls it. An over-NAME_MAX key therefore still reached the disk layer and came back to the client as ENAMETOOLONG → InternalError 500, exactly the behavior #5785 reported.

Caught by re-running the acceptance suite against the locked build 4b2d79f5d, which contains #5804: S3-003 still failed with a 512-byte key.

Multipart is unaffected — check_new_multipart_args and check_multipart_object_args both route through check_object_args → check_bucket_and_object_names, which already carries the budget.

Verification: new test pins the same boundaries on check_put_object_args (255 ok / 256 rejected, byte-based via CJK, multi-segment long keys ok, __XLDIR__ budget for directory keys); cargo test -p rustfs-ecstore --lib -- bucket::utils 17 passed; cargo clippy -p rustfs-ecstore --all-targets clean; make pre-commit green.
This commit is contained in:
Zhengchao An
2026-08-08 05:30:15 +08:00
committed by GitHub
parent 58c49672ca
commit 027456032f
+33
View File
@@ -409,6 +409,14 @@ pub fn check_put_object_args(bucket: &str, object: &str) -> Result<()> {
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string()));
}
// The write path validates arguments here rather than through
// check_bucket_and_object_names, so the on-disk segment budget has to be
// enforced in both places or an over-NAME_MAX key still reaches the disk
// layer and escapes as ENAMETOOLONG → InternalError 500 (rustfs#5785).
if !object_key_segments_fit_on_disk(object) {
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string()));
}
Ok(())
}
@@ -448,6 +456,31 @@ mod tests {
));
}
/// rustfs#5785 follow-up: the write path validates through
/// check_put_object_args, not check_bucket_and_object_names, so the same
/// budget has to hold there — otherwise an over-NAME_MAX PUT still reached
/// the disk layer and came back as InternalError 500.
#[test]
fn put_object_args_enforce_the_same_segment_budget() {
assert!(check_put_object_args("bucket", &"a".repeat(255)).is_ok());
assert!(matches!(
check_put_object_args("bucket", &"a".repeat(256)),
Err(StorageError::ObjectNameInvalid(_, _))
));
assert!(matches!(
check_put_object_args("bucket", &"\u{4e2d}".repeat(100)),
Err(StorageError::ObjectNameInvalid(_, _))
));
let segmented = ["b".repeat(200), "c".repeat(200), "d".repeat(200)].join("/");
assert!(check_put_object_args("bucket", &segmented).is_ok());
let dir_budget = 255 - rustfs_utils::path::GLOBAL_DIR_SUFFIX.len();
assert!(check_put_object_args("bucket", &format!("{}/", "e".repeat(dir_budget))).is_ok());
assert!(matches!(
check_put_object_args("bucket", &format!("{}/", "e".repeat(dir_budget + 1))),
Err(StorageError::ObjectNameInvalid(_, _))
));
}
// Test validation functions
#[test]
fn test_is_valid_object_name() {