mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
fix: reject invalid multipart part numbers (#3091)
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -26,7 +26,7 @@ use crate::storage::options::{
|
||||
};
|
||||
use crate::storage::s3_api::multipart::{
|
||||
ListMultipartUploadsParams, build_list_multipart_uploads_output, build_list_parts_output,
|
||||
parse_list_multipart_uploads_params, parse_list_parts_params,
|
||||
parse_list_multipart_uploads_params, parse_list_parts_params, parse_upload_part_number,
|
||||
};
|
||||
use crate::storage::sse::{build_ssec_read_headers, encryption_material_to_metadata, map_get_object_reader_error};
|
||||
use crate::storage::*;
|
||||
@@ -668,7 +668,7 @@ impl DefaultMultipartUsecase {
|
||||
..
|
||||
} = input;
|
||||
|
||||
let part_id = part_number as usize;
|
||||
let part_id = parse_upload_part_number(part_number)?;
|
||||
|
||||
let mut size = content_length;
|
||||
let mut body_stream = body.ok_or_else(|| s3_error!(IncompleteBody))?;
|
||||
@@ -1006,7 +1006,7 @@ impl DefaultMultipartUsecase {
|
||||
None
|
||||
};
|
||||
|
||||
let part_id = part_number as usize;
|
||||
let part_id = parse_upload_part_number(part_number)?;
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
@@ -1726,6 +1726,29 @@ mod tests {
|
||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_upload_part_copy_rejects_invalid_part_number_before_store_lookup() {
|
||||
for part_number in [-1, 0, 10001] {
|
||||
let input = UploadPartCopyInput::builder()
|
||||
.bucket("bucket".to_string())
|
||||
.key("object".to_string())
|
||||
.copy_source(CopySource::Bucket {
|
||||
bucket: "src-bucket".into(),
|
||||
key: "src-object".into(),
|
||||
version_id: None,
|
||||
})
|
||||
.part_number(part_number)
|
||||
.upload_id("upload-id".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
let req = build_request(input, Method::PUT);
|
||||
|
||||
let err = Box::pin(make_usecase().execute_upload_part_copy(req)).await.unwrap_err();
|
||||
assert_eq!(err.code(), &S3ErrorCode::InvalidArgument);
|
||||
assert_eq!(err.message(), Some("partNumber must be between 1 and 10000"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_copy_source_range_not_exceeds_returns_invalid_range_when_range_exceeds() {
|
||||
use super::validate_copy_source_range_not_exceeds;
|
||||
@@ -1778,4 +1801,22 @@ mod tests {
|
||||
let err = make_usecase().execute_upload_part(req).await.unwrap_err();
|
||||
assert_eq!(err.code(), &S3ErrorCode::IncompleteBody);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_upload_part_rejects_invalid_part_number_before_body_lookup() {
|
||||
for part_number in [-1, 0, 10001] {
|
||||
let input = UploadPartInput::builder()
|
||||
.bucket("bucket".to_string())
|
||||
.key("object".to_string())
|
||||
.upload_id("upload-id".to_string())
|
||||
.part_number(part_number)
|
||||
.build()
|
||||
.unwrap();
|
||||
let req = build_request(input, Method::PUT);
|
||||
|
||||
let err = make_usecase().execute_upload_part(req).await.unwrap_err();
|
||||
assert_eq!(err.code(), &S3ErrorCode::InvalidArgument);
|
||||
assert_eq!(err.message(), Some("partNumber must be between 1 and 10000"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use s3s::dto::{CommonPrefix, ListMultipartUploadsOutput, ListPartsOutput, Multip
|
||||
use s3s::{S3Error, S3ErrorCode};
|
||||
|
||||
const MAX_MULTIPART_UPLOADS_LIST: i32 = 1000;
|
||||
const MAX_MULTIPART_PART_NUMBER: i32 = 10000;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct ListPartsParams {
|
||||
@@ -103,6 +104,17 @@ pub(crate) fn parse_list_parts_params(
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parse_upload_part_number(part_number: i32) -> Result<usize, S3Error> {
|
||||
if !(1..=MAX_MULTIPART_PART_NUMBER).contains(&part_number) {
|
||||
return Err(S3Error::with_message(
|
||||
S3ErrorCode::InvalidArgument,
|
||||
format!("partNumber must be between 1 and {MAX_MULTIPART_PART_NUMBER}"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(part_number as usize)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_list_multipart_uploads_params(
|
||||
prefix: Option<String>,
|
||||
key_marker: Option<String>,
|
||||
@@ -176,7 +188,7 @@ pub(crate) fn build_list_multipart_uploads_output(
|
||||
mod tests {
|
||||
use super::{
|
||||
MAX_MULTIPART_UPLOADS_LIST, build_list_multipart_uploads_output, build_list_parts_output,
|
||||
parse_list_multipart_uploads_params, parse_list_parts_params,
|
||||
parse_list_multipart_uploads_params, parse_list_parts_params, parse_upload_part_number,
|
||||
};
|
||||
use crate::storage::s3_api::common::{rustfs_initiator, rustfs_owner};
|
||||
use rustfs_ecstore::client::object_api_utils::to_s3s_etag;
|
||||
@@ -319,6 +331,21 @@ mod tests {
|
||||
assert_eq!(err.message(), Some("part-number-marker must be non-negative"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_upload_part_number_accepts_s3_range() {
|
||||
assert_eq!(parse_upload_part_number(1).expect("part 1 should be valid"), 1);
|
||||
assert_eq!(parse_upload_part_number(10000).expect("part 10000 should be valid"), 10000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_upload_part_number_rejects_out_of_s3_range() {
|
||||
for part_number in [-1, 0, 10001] {
|
||||
let err = parse_upload_part_number(part_number).expect_err("expected invalid part number");
|
||||
assert_eq!(*err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert_eq!(err.message(), Some("partNumber must be between 1 and 10000"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_list_multipart_uploads_params_defaults_and_valid_values() {
|
||||
let parsed =
|
||||
|
||||
Reference in New Issue
Block a user