ceph/6001-CVE-2018-16846-1.patch

173 lines
5.7 KiB
Diff
Raw Normal View History

2019-09-30 10:34:07 -04:00
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;