]> git-server-git.apps.pok.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>
Thu, 10 Jan 2019 18:21:59 +0000 (19:21 +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 c1a0e7b05ea00c9e66cee3d58342f0e44c31e9c1..5b62a3f7c3d663fc73fa2f4b188796f60e1ad07f 100644 (file)
@@ -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)"),
   });
 }
 
index 6e7daadcd22874f4b1cb7ccb8bf792b1d2ba68cb..c17d04988169e9cad96285d0c33c679cd1806012 100644 (file)
@@ -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()
index e4d8cd4a980bc9d952ef3b6b82635adcc2c75572..521a3d179d76c3079342174f8ae1a1cf4cb2473b 100644 (file)
@@ -2214,6 +2214,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 80a886ec5d118ba6cf2271a80a632d14f3000828..539cebeb6981dbf063988b77cf0a6e4c0ed3fcf3 100644 (file)
@@ -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");
index c9d96d9631bf2a6841c23b85e409523a2725971f..35e192c150ed350736a819609118c362bd5ac6dc 100644 (file)
@@ -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;