173 lines
5.7 KiB
Diff
173 lines
5.7 KiB
Diff
|
|
From 4337e6a7d9f92c8549ebee20d0dd67a01e49857f Mon Sep 17 00:00:00 2001
|
||
|
|
From: "Robin H. Johnson" <rjohnson@digitalocean.com>
|
||
|
|
Date: Fri, 21 Sep 2018 14:49:34 -0700
|
||
|
|
Subject: [PATCH] rgw: enforce bounds on max-keys/max-uploads/max-parts
|
||
|
|
|
||
|
|
RGW S3 listing operations provided a way for authenticated users to
|
||
|
|
cause a denial of service against OMAPs holding bucket indices.
|
||
|
|
|
||
|
|
Bound the min & max values that a user could pass into the max-X
|
||
|
|
parameters, to keep the system safe. The default of 1000 is chosen to
|
||
|
|
match AWS S3 behavior.
|
||
|
|
|
||
|
|
Affected operations:
|
||
|
|
- ListBucket, via max-keys
|
||
|
|
- ListBucketVersions, via max-keys
|
||
|
|
- ListBucketMultiPartUploads, via max-uploads
|
||
|
|
- ListMultipartUploadParts, via max-parts
|
||
|
|
|
||
|
|
The Swift bucket listing codepath already enforced a limit, so is
|
||
|
|
unaffected by this issue.
|
||
|
|
|
||
|
|
Prior to this commit, the effective limit is the lower of
|
||
|
|
osd_max_omap_entries_per_request or osd_max_omap_bytes_per_request.
|
||
|
|
|
||
|
|
Backport: luminous, mimic
|
||
|
|
Fixes: http://tracker.ceph.com/issues/35994
|
||
|
|
Signed-off-by: Robin H. Johnson <rjohnson@digitalocean.com>
|
||
|
|
(cherry picked from commit d79f68a1e31f4bc917eec1b6bbc8e8446377dc6b)
|
||
|
|
|
||
|
|
Conflicts:
|
||
|
|
src/common/options.cc:
|
||
|
|
Conflicts due to options from master
|
||
|
|
---
|
||
|
|
src/common/options.cc | 11 +++++++++++
|
||
|
|
src/rgw/rgw_op.cc | 21 +++++----------------
|
||
|
|
src/rgw/rgw_op.h | 25 +++++++++++++++++++++++++
|
||
|
|
src/rgw/rgw_rest.cc | 11 +++++------
|
||
|
|
src/rgw/rgw_rest_swift.cc | 2 ++
|
||
|
|
5 files changed, 48 insertions(+), 22 deletions(-)
|
||
|
|
|
||
|
|
diff --git a/src/common/options.cc b/src/common/options.cc
|
||
|
|
index c1a0e7b05ea0..5b62a3f7c3d6 100644
|
||
|
|
--- a/src/common/options.cc
|
||
|
|
+++ b/src/common/options.cc
|
||
|
|
@@ -5705,6 +5705,17 @@ std::vector<Option> get_rgw_options() {
|
||
|
|
"of RGW instances under heavy use. If you would like "
|
||
|
|
"to turn off cache expiry, set this value to zero."),
|
||
|
|
|
||
|
|
+ Option("rgw_max_listing_results", Option::TYPE_UINT,
|
||
|
|
+ Option::LEVEL_ADVANCED)
|
||
|
|
+ .set_default(1000)
|
||
|
|
+ .set_min_max(1, 100000)
|
||
|
|
+ .add_service("rgw")
|
||
|
|
+ .set_description("Upper bound on results in listing operations, ListBucket max-keys"),
|
||
|
|
+ .set_long_description("This caps the maximum permitted value for listing-like operations in RGW S3. "
|
||
|
|
+ "Affects ListBucket(max-keys), "
|
||
|
|
+ "ListBucketVersions(max-keys), "
|
||
|
|
+ "ListBucketMultiPartUploads(max-uploads), "
|
||
|
|
+ "ListMultipartUploadParts(max-parts)"),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc
|
||
|
|
index 6e7daadcd228..c17d04988169 100644
|
||
|
|
--- a/src/rgw/rgw_op.cc
|
||
|
|
+++ b/src/rgw/rgw_op.cc
|
||
|
|
@@ -2279,22 +2279,11 @@ int RGWListBucket::verify_permission()
|
||
|
|
|
||
|
|
int RGWListBucket::parse_max_keys()
|
||
|
|
{
|
||
|
|
- if (!max_keys.empty()) {
|
||
|
|
- char *endptr;
|
||
|
|
- max = strtol(max_keys.c_str(), &endptr, 10);
|
||
|
|
- if (endptr) {
|
||
|
|
- if (endptr == max_keys.c_str()) return -EINVAL;
|
||
|
|
- while (*endptr && isspace(*endptr)) // ignore white space
|
||
|
|
- endptr++;
|
||
|
|
- if (*endptr) {
|
||
|
|
- return -EINVAL;
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
- } else {
|
||
|
|
- max = default_max;
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- return 0;
|
||
|
|
+ // Bound max value of max-keys to configured value for security
|
||
|
|
+ // Bound min value of max-keys to '0'
|
||
|
|
+ // Some S3 clients explicitly send max-keys=0 to detect if the bucket is
|
||
|
|
+ // empty without listing any items.
|
||
|
|
+ op_ret = parse_value_and_bound(max_keys, &max, 0, g_conf()->rgw_max_listing_results, default_max);
|
||
|
|
}
|
||
|
|
|
||
|
|
void RGWListBucket::pre_exec()
|
||
|
|
diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h
|
||
|
|
index e4d8cd4a980b..521a3d179d76 100644
|
||
|
|
--- a/src/rgw/rgw_op.h
|
||
|
|
+++ b/src/rgw/rgw_op.h
|
||
|
|
@@ -2214,6 +2214,31 @@ class RGWGetClusterStat : public RGWOp {
|
||
|
|
virtual const string name() { return "get_cluster_stat"; }
|
||
|
|
};
|
||
|
|
|
||
|
|
+static inline int parse_value_and_bound(const string &input, long *output, const long lower_bound, const long upper_bound, const long default_val)
|
||
|
|
+{
|
||
|
|
+ if (!input.empty()) {
|
||
|
|
+ char *endptr;
|
||
|
|
+ *output = strtol(input.c_str(), &endptr, 10);
|
||
|
|
+ if (endptr) {
|
||
|
|
+ if (endptr == input.c_str()) return -EINVAL;
|
||
|
|
+ while (*endptr && isspace(*endptr)) // ignore white space
|
||
|
|
+ endptr++;
|
||
|
|
+ if (*endptr) {
|
||
|
|
+ return -EINVAL;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ if(*output > upper_bound) {
|
||
|
|
+ *output = upper_bound;
|
||
|
|
+ }
|
||
|
|
+ if(*output < lower_bound) {
|
||
|
|
+ *output = lower_bound;
|
||
|
|
+ }
|
||
|
|
+ } else {
|
||
|
|
+ *output = default_val;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return 0;
|
||
|
|
+}
|
||
|
|
|
||
|
|
|
||
|
|
#endif /* CEPH_RGW_OP_H */
|
||
|
|
diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc
|
||
|
|
index 80a886ec5d11..539cebeb6981 100644
|
||
|
|
--- a/src/rgw/rgw_rest.cc
|
||
|
|
+++ b/src/rgw/rgw_rest.cc
|
||
|
|
@@ -1659,8 +1659,7 @@ int RGWListMultipart_ObjStore::get_params()
|
||
|
|
}
|
||
|
|
|
||
|
|
string str = s->info.args.get("max-parts");
|
||
|
|
- if (!str.empty())
|
||
|
|
- max_parts = atoi(str.c_str());
|
||
|
|
+ op_ret = parse_value_and_bound(str, &max_parts, 0, g_conf()->rgw_max_listing_results, max_parts);
|
||
|
|
|
||
|
|
return op_ret;
|
||
|
|
}
|
||
|
|
@@ -1670,10 +1669,10 @@ int RGWListBucketMultiparts_ObjStore::get_params()
|
||
|
|
delimiter = s->info.args.get("delimiter");
|
||
|
|
prefix = s->info.args.get("prefix");
|
||
|
|
string str = s->info.args.get("max-uploads");
|
||
|
|
- if (!str.empty())
|
||
|
|
- max_uploads = atoi(str.c_str());
|
||
|
|
- else
|
||
|
|
- max_uploads = default_max;
|
||
|
|
+ op_ret = parse_value_and_bound(str, &max_uploads, 0, g_conf()->rgw_max_listing_results, default_max);
|
||
|
|
+ if (op_ret < 0) {
|
||
|
|
+ return op_ret;
|
||
|
|
+ }
|
||
|
|
|
||
|
|
string key_marker = s->info.args.get("key-marker");
|
||
|
|
string upload_id_marker = s->info.args.get("upload-id-marker");
|
||
|
|
diff --git a/src/rgw/rgw_rest_swift.cc b/src/rgw/rgw_rest_swift.cc
|
||
|
|
index c9d96d9631bf..35e192c150ed 100644
|
||
|
|
--- a/src/rgw/rgw_rest_swift.cc
|
||
|
|
+++ b/src/rgw/rgw_rest_swift.cc
|
||
|
|
@@ -303,6 +303,8 @@ int RGWListBucket_ObjStore_SWIFT::get_params()
|
||
|
|
if (op_ret < 0) {
|
||
|
|
return op_ret;
|
||
|
|
}
|
||
|
|
+ // S3 behavior is to silently cap the max-keys.
|
||
|
|
+ // Swift behavior is to abort.
|
||
|
|
if (max > default_max)
|
||
|
|
return -ERR_PRECONDITION_FAILED;
|
||
|
|
|