]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: enforce bounds on max-keys/max-uploads/max-parts
authorRobin H. Johnson <rjohnson@digitalocean.com>
Fri, 21 Sep 2018 21:49:34 +0000 (14:49 -0700)
committerAbhishek Lekshmanan <abhishek@suse.com>
Fri, 4 Jan 2019 14:52:33 +0000 (15:52 +0100)
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
src/rgw/rgw_op.cc
src/rgw/rgw_op.h
src/rgw/rgw_rest.cc
src/rgw/rgw_rest_swift.cc

index b2246579eb8c649b0d0314814d90c4241932218e..a543256d8ad33bafd12b66acd9b76c882eaa7193 100644 (file)
@@ -6233,6 +6233,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)"),
   });
 }
 
index 9e60510e95653dce27b3900c1fcc37207537a015..509592943c67bb955b08a170c66438f046329122 100644 (file)
@@ -2379,22 +2379,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()
index ba3db09821c40a6de5ba47634ca71cd598a096e9..57352ae8c1427167a239e8c5d2a577847a98de96 100644 (file)
@@ -2235,6 +2235,31 @@ public:
   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 */
index 302c834565c8bd23ef4024b9e7bb335fe8bad9ad..c87192d5674b82e4f9e3673fa9a9d582c59263a6 100644 (file)
@@ -1588,8 +1588,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;
 }
@@ -1599,10 +1598,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");
index ebf2803b6215d4fbfdac14905bcce5aba7123a34..27b2590e968ad5d800a2598150d75905feeead58 100644 (file)
@@ -304,6 +304,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;