Skip to content

Commit eac3c60

Browse files
committed
Fix binary safety of cluster key lookup functions
valkeyClusterGetSlotByKey and valkeyClusterGetNodeByKey used strlen() to determine the key length, which truncated keys containing embedded null bytes, resulting in incorrect slot calculations and node lookups. Add an explicit keylen parameter so callers provide the actual key length, consistent with how the rest of the library handles binary-safe keys. This is a breaking API change. Reference: https://valkey.io/topics/keyspace/ Signed-off-by: Björn Svensson <bjorn.a.svensson@est.tech>
1 parent b9285b5 commit eac3c60

File tree

5 files changed

+25
-17
lines changed

5 files changed

+25
-17
lines changed

docs/migration-guide.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The general actions needed are:
1111
All `libvalkey` headers are now found under `include/valkey/`.
1212
* Update used build options, e.g. `USE_TLS` replaces `USE_SSL`.
1313

14-
## Migrating from `hiredis` v1.2.0
14+
## Migrating from `hiredis` v1.2.0 or v1.3.0
1515

1616
The type `sds` is removed from the public API.
1717

@@ -92,6 +92,14 @@ The type `sds` is removed from the public API.
9292
* `HIRCLUSTER_FLAG_ADD_SLAVE` removed, flag can be replaced with an option, see `VALKEY_OPT_USE_REPLICAS`.
9393
* `HIRCLUSTER_FLAG_ROUTE_USE_SLOTS` removed, the use of `CLUSTER SLOTS` is enabled by default.
9494

95+
### Changed API function signatures
96+
97+
* `valkeyClusterGetSlotByKey` now requires a `keylen` parameter of type `size_t`.
98+
Previously the key length was determined using `strlen()`, which gave incorrect
99+
results for keys containing embedded null characters.
100+
* `valkeyClusterGetNodeByKey` now requires a `keylen` parameter of type `size_t`,
101+
for the same reason as above.
102+
95103
### Removed support for splitting multi-key commands per slot
96104

97105
Since old days (from `hiredis-vip`) there has been support for sending some commands with multiple keys that covers multiple slots.

include/valkey/cluster.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,9 @@ LIBVALKEY_API void valkeyClusterInitNodeIterator(valkeyClusterNodeIterator *iter
336336
LIBVALKEY_API valkeyClusterNode *valkeyClusterNodeNext(valkeyClusterNodeIterator *iter);
337337

338338
/* Helper functions */
339-
LIBVALKEY_API unsigned int valkeyClusterGetSlotByKey(char *key);
339+
LIBVALKEY_API unsigned int valkeyClusterGetSlotByKey(char *key, size_t keylen);
340340
LIBVALKEY_API valkeyClusterNode *valkeyClusterGetNodeByKey(valkeyClusterContext *cc,
341-
char *key);
341+
char *key, size_t keylen);
342342

343343
#ifdef __cplusplus
344344
}

src/cluster.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3426,12 +3426,12 @@ valkeyClusterNode *valkeyClusterNodeNext(valkeyClusterNodeIterator *iter) {
34263426
}
34273427

34283428
/* Get hash slot for given key string, which can include hash tags */
3429-
unsigned int valkeyClusterGetSlotByKey(char *key) {
3430-
return keyHashSlot(key, strlen(key));
3429+
unsigned int valkeyClusterGetSlotByKey(char *key, size_t keylen) {
3430+
return keyHashSlot(key, (int)keylen);
34313431
}
34323432

34333433
/* Get node that handles given key string, which can include hash tags */
34343434
valkeyClusterNode *valkeyClusterGetNodeByKey(valkeyClusterContext *cc,
3435-
char *key) {
3436-
return node_get_by_table(cc, keyHashSlot(key, strlen(key)));
3435+
char *key, size_t keylen) {
3436+
return node_get_by_table(cc, keyHashSlot(key, (int)keylen));
34373437
}

tests/ct_out_of_memory_handling.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ void test_alloc_failure_handling(void) {
158158
valkeyReply *reply;
159159
const char *cmd = "SET key value";
160160

161-
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"key");
161+
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"key", 3);
162162
assert(node);
163163

164164
int n;
@@ -217,7 +217,7 @@ void test_alloc_failure_handling(void) {
217217
valkeyReply *reply;
218218
const char *cmd = "SET foo one";
219219

220-
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"foo");
220+
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"foo", 3);
221221
assert(node);
222222

223223
/* Discover allocations needed for a successful append to node. */
@@ -259,8 +259,8 @@ void test_alloc_failure_handling(void) {
259259
prepare_allocation_test(cc, 1000);
260260

261261
/* Get the source information for the migration. */
262-
unsigned int slot = valkeyClusterGetSlotByKey((char *)"foo");
263-
valkeyClusterNode *srcNode = valkeyClusterGetNodeByKey(cc, (char *)"foo");
262+
unsigned int slot = valkeyClusterGetSlotByKey((char *)"foo", 3);
263+
valkeyClusterNode *srcNode = valkeyClusterGetNodeByKey(cc, (char *)"foo", 3);
264264
int srcPort = srcNode->port;
265265

266266
/* Get a destination node to migrate the slot to. */
@@ -318,7 +318,7 @@ void test_alloc_failure_handling(void) {
318318
* allowing a high number of allocations. */
319319
prepare_allocation_test(cc, 1000);
320320
/* Fetch the nodes again, in case the slotmap has been reloaded. */
321-
srcNode = valkeyClusterGetNodeByKey(cc, (char *)"foo");
321+
srcNode = valkeyClusterGetNodeByKey(cc, (char *)"foo", 3);
322322
dstNode = getNodeByPort(cc, dstPort);
323323
reply = valkeyClusterCommandToNode(
324324
cc, srcNode, "CLUSTER SETSLOT %d NODE %s", slot, replyDstId->str);

tests/ct_specific_nodes.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void test_command_to_all_nodes(valkeyClusterContext *cc) {
4343

4444
void test_transaction(valkeyClusterContext *cc) {
4545

46-
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"foo");
46+
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"foo", 3);
4747
assert(node);
4848

4949
valkeyReply *reply;
@@ -71,7 +71,7 @@ void test_streams(valkeyClusterContext *cc) {
7171
char *id;
7272

7373
/* Get the node that handles given stream */
74-
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"mystream");
74+
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"mystream", 8);
7575
assert(node);
7676

7777
/* Preparation: remove old stream/key */
@@ -80,7 +80,7 @@ void test_streams(valkeyClusterContext *cc) {
8080
freeReplyObject(reply);
8181

8282
/* Query wrong node */
83-
valkeyClusterNode *wrongNode = valkeyClusterGetNodeByKey(cc, (char *)"otherstream");
83+
valkeyClusterNode *wrongNode = valkeyClusterGetNodeByKey(cc, (char *)"otherstream", 11);
8484
assert(node != wrongNode);
8585
reply = valkeyClusterCommandToNode(cc, wrongNode, "XLEN mystream");
8686
CHECK_REPLY_ERROR(cc, reply, "MOVED");
@@ -230,7 +230,7 @@ void test_pipeline_transaction(valkeyClusterContext *cc) {
230230
int status;
231231
valkeyReply *reply;
232232

233-
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"foo");
233+
valkeyClusterNode *node = valkeyClusterGetNodeByKey(cc, (char *)"foo", 3);
234234
assert(node);
235235

236236
status = valkeyClusterAppendCommandToNode(cc, node, "MULTI");
@@ -466,7 +466,7 @@ void test_async_transaction(void) {
466466
valkeyClusterAsyncContext *acc = valkeyClusterAsyncConnectWithOptions(&options);
467467
ASSERT_MSG(acc && acc->err == 0, acc ? acc->errstr : "OOM");
468468

469-
valkeyClusterNode *node = valkeyClusterGetNodeByKey(&acc->cc, (char *)"foo");
469+
valkeyClusterNode *node = valkeyClusterGetNodeByKey(&acc->cc, (char *)"foo", 3);
470470
assert(node);
471471

472472
int status;

0 commit comments

Comments
 (0)