Skip to content

Commit 2196df5

Browse files
YuCai18caiyu
andauthored
fix: Specia Redis directives can cause pika service to crash (#3100)
* Specia Redis directives can cause pika service to crash * Special Redis directives can cause pika service to crash * Special Redis directives can cause pika service to crash * Add support for reading proto-max-bulk-len from configuration file * optimize boundary checks and empty string handling in GetRange function of pika_cache * Complete the required items for pika_conf.cc and pika_conf.h files --------- Co-authored-by: caiyu <[email protected]>
1 parent bf7228b commit 2196df5

6 files changed

Lines changed: 90 additions & 11 deletions

File tree

conf/pika.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ db-path : ./db/
9090
# Supported Units [K|M|G], write-buffer-size default unit is in [bytes].
9191
write-buffer-size : 256M
9292

93+
# The maximum size of a single bulk string in Pika protocol.
94+
# This value is used to limit the size of a single bulk string in Pika protocol.
95+
# The default value is 512M.
96+
proto-max-bulk-len : 512M
97+
9398
# The size of one block in arena memory allocation.
9499
# If <= 0, a proper value is automatically calculated.
95100
# (usually 1/8 of writer-buffer-size, rounded up to a multiple of 4KB)

include/pika_conf.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ class PikaConf : public pstd::BaseConf {
167167
std::shared_lock l(rwlock_);
168168
return write_buffer_size_;
169169
}
170+
int64_t proto_max_bulk_len() {
171+
std::shared_lock l(rwlock_);
172+
return proto_max_bulk_len_;
173+
}
170174
int min_write_buffer_number_to_merge() {
171175
std::shared_lock l(rwlock_);
172176
return min_write_buffer_number_to_merge_;
@@ -858,6 +862,11 @@ class PikaConf : public pstd::BaseConf {
858862
TryPushDiffCommands("rsync-timeout-ms", std::to_string(value));
859863
rsync_timeout_ms_.store(value);
860864
}
865+
void SetProtoMaxBulkLen(const int64_t value) {
866+
std::lock_guard l(rwlock_);
867+
TryPushDiffCommands("proto-max-bulk-len", std::to_string(value));
868+
proto_max_bulk_len_ = value;
869+
}
861870

862871
int RocksDBPerfLevel() const {
863872
return rocksdb_perf_level_.load();
@@ -1035,6 +1044,7 @@ class PikaConf : public pstd::BaseConf {
10351044
int64_t least_free_disk_to_resume_ = 268435456; // 256 MB
10361045
double min_check_resume_ratio_ = 0.7;
10371046
int64_t write_buffer_size_ = 0;
1047+
int64_t proto_max_bulk_len_ = 0;
10381048
int64_t arena_block_size_ = 0;
10391049
int64_t slotmigrate_thread_num_ = 0;
10401050
int64_t thread_migrate_keys_num_ = 0;

src/pika_cache.cc

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,41 @@ Status PikaCache::Appendxx(std::string& key, std::string &value) {
364364
return Status::NotFound("key not exist");
365365
}
366366

367+
/*
368+
Added boundary checks for start and end parameters to the PikaCache::GetRange function,
369+
and used the full_value variable to store the actual length of string type,
370+
avoiding excessive memory allocation by sdsnewlen.
371+
*/
367372
Status PikaCache::GetRange(std::string& key, int64_t start, int64_t end, std::string *value) {
368373
int cache_index = CacheIndex(key);
369374
std::lock_guard lm(*cache_mutexs_[cache_index]);
370-
return caches_[cache_index]->GetRange(key, start, end, value);
371-
}
372375

376+
std::string full_value;
377+
auto s = caches_[cache_index]->Get(key, &full_value);
378+
if (!s.ok()) {
379+
return s;
380+
}
381+
int64_t strlen = full_value.size();
382+
383+
if (start < 0) {
384+
start = strlen + start;
385+
}
386+
if (end < 0) {
387+
end = strlen + end;
388+
}
389+
390+
if (start < 0) start = 0;
391+
if (end < 0) end = 0;
392+
if (end >= strlen) end = strlen - 1;
393+
394+
if (start > end || strlen == 0) {
395+
value->clear();
396+
return Status::OK();
397+
}
398+
399+
*value = full_value.substr(start, end - start + 1);
400+
return Status::OK();
401+
}
373402
Status PikaCache::SetRangeIfKeyExist(std::string& key, int64_t start, std::string &value) {
374403
int cache_index = CacheIndex(key);
375404
std::lock_guard lm(*cache_mutexs_[cache_index]);

src/pika_conf.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,10 @@ int PikaConf::Load() {
366366
if (write_buffer_size_ <= 0) {
367367
write_buffer_size_ = 268435456; // 256Mb
368368
}
369-
369+
GetConfInt64Human("proto-max-bulk-len", &proto_max_bulk_len_);
370+
if (proto_max_bulk_len_ <= 0) {
371+
proto_max_bulk_len_ = 512 * 1024 * 1024; // 512MB
372+
}
370373
GetConfInt("level0-stop-writes-trigger", &level0_stop_writes_trigger_);
371374
if (level0_stop_writes_trigger_ < 36) {
372375
level0_stop_writes_trigger_ = 36;

src/pika_kv.cc

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,7 @@ void GetrangeCmd::Do() {
11461146
std::string substr;
11471147
STAGE_TIMER_GUARD(storage_duration_ms, true);
11481148
s_= db_->storage()->Getrange(key_, start_, end_, &substr);
1149+
11491150
if (s_.ok() || s_.IsNotFound()) {
11501151
res_.AppendStringLenUint64(substr.size());
11511152
res_.AppendContent(substr);
@@ -1194,12 +1195,25 @@ void SetrangeCmd::DoInitial() {
11941195
res_.SetRes(CmdRes::kWrongNum, kCmdNameSetrange);
11951196
return;
11961197
}
1197-
key_ = argv_[1];
1198+
key_ = argv_[1];
11981199
if (pstd::string2int(argv_[2].data(), argv_[2].size(), &offset_) == 0) {
11991200
res_.SetRes(CmdRes::kInvalidInt);
12001201
return;
12011202
}
1203+
12021204
value_ = argv_[3];
1205+
1206+
// Read the proto-max-bulk-len parameter settings in the pika configuration file pika_conf
1207+
const int64_t PROTO_MAX_BULK_LEN = g_pika_conf->proto_max_bulk_len();
1208+
//Handle the overflow issue of offset_
1209+
if (offset_ < 0) {
1210+
res_.SetRes(CmdRes::kInvalidInt, "offset is out of range");
1211+
return;
1212+
}
1213+
if (offset_ > PROTO_MAX_BULK_LEN - static_cast<int64_t>(value_.size())) {
1214+
res_.SetRes(CmdRes::kErrOther, "string exceeds maximum allowed size (proto-max-bulk-len)");
1215+
return;
1216+
}
12031217
}
12041218

12051219
void SetrangeCmd::Do() {

tests/integration/string_test.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ var _ = Describe("String Commands", func() {
277277
Expect(getBit.Err()).NotTo(HaveOccurred())
278278
Expect(getBit.Val()).To(Equal(int64(0)))
279279
})
280-
280+
281281
It("should GetRange", func() {
282282
set := client.Set(ctx, "key", "This is a string", 0)
283283
Expect(set.Err()).NotTo(HaveOccurred())
@@ -300,6 +300,24 @@ var _ = Describe("String Commands", func() {
300300
Expect(getRange.Val()).To(Equal("string"))
301301
})
302302

303+
//Caiyu's test cases for GETRANGE and SETRANGE to fix bug #3092.
304+
It("should not crash on huge GETRANGE", func() {
305+
set := client.Set(ctx, "key1", "abc", 0)
306+
Expect(set.Err()).NotTo(HaveOccurred())
307+
Expect(set.Val()).To(Equal("OK"))
308+
getRange1 := client.GetRange(ctx, "key1", 1, 4294967296)
309+
Expect(getRange1.Val()).To(Equal("bc"))
310+
getRange2 := client.GetRange(ctx, "key1", 1, 4294967296)
311+
Expect(getRange2.Val()).To(Equal("bc"))
312+
})
313+
It("should not crash on huge SETRANGE", func() {
314+
set := client.Set(ctx, "key1", "abc", 0)
315+
Expect(set.Err()).NotTo(HaveOccurred())
316+
Expect(set.Val()).To(Equal("OK"))
317+
setRange := client.SetRange(ctx, "key1", 9223372036854775757, "value2")
318+
Expect(setRange.Err()).To(HaveOccurred())
319+
})
320+
303321
It("should GetSet", func() {
304322
incr := client.Incr(ctx, "key")
305323
Expect(incr.Err()).NotTo(HaveOccurred())
@@ -798,24 +816,24 @@ var _ = Describe("String Commands", func() {
798816
})
799817

800818
It("should SetEX ten seconds", func() {
801-
err := client.SetEx(ctx, "x", "y", 10 * time.Second).Err()
819+
err := client.SetEx(ctx, "x", "y", 10*time.Second).Err()
802820
Expect(err).NotTo(HaveOccurred())
803-
821+
804822
val, err := client.Get(ctx, "x").Result()
805823
Expect(err).NotTo(HaveOccurred())
806824
Expect(val).To(Equal("y"))
807-
825+
808826
time.Sleep(11 * time.Second)
809827
//sleep 10 second x still exists
810-
828+
811829
err = client.Do(ctx, "compact").Err()
812830
Expect(err).NotTo(HaveOccurred())
813-
831+
814832
keys, err := client.Keys(ctx, "x").Result()
815833
Expect(err).NotTo(HaveOccurred())
816834
Expect(keys).To(BeEmpty())
817835
})
818-
836+
819837
It("should SetNX", func() {
820838
_, err := client.Del(ctx, "key").Result()
821839
Expect(err).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)