Prevent UB in session weighted selection

```c
int pos, i;
memcpy(&pos, key, sizeof(pos));
pos %= pool->totalWeight;
```

As unlikely as it is, this may cause UB if user's are generating their
own session IDs shorter than three bytes. This commit checks the session
id length and only copies in the bytes that we have.

Technically this could change behavior for session ID's of length 2 or
less, but this seems acceptable because currently such an ID would end
up selecing a server at random, depending on whatever bytes were past
the end of the `zend_string`.

In a future release we will fix this logic to use all of the entropy of
the session ID as well as ensure `pool->totalWeight` is > 0.
This commit is contained in:
michael-grunder
2025-10-30 15:50:04 -07:00
committed by Michael Grunder
parent bf2047e067
commit 025b0e9772
+29 -12
View File
@@ -232,14 +232,31 @@ static int redis_simple_cmd(RedisSock *redis_sock, char *cmd, int cmdlen,
return len_written;
}
static inline int weighted_seed(zend_string *key) {
/* In GCC the fast-path is one 32-bit load with a union */
union { int pos; } u;
if (EXPECTED(ZSTR_LEN(key) >= sizeof(u.pos))) {
memcpy(&u.pos, ZSTR_VAL(key), sizeof(u.pos));
return u.pos;
}
u.pos = 0;
memcpy(&u.pos, ZSTR_VAL(key), ZSTR_LEN(key));
return u.pos;
}
PHP_REDIS_API redis_pool_member *
redis_pool_get_sock(redis_pool *pool, const char *key) {
redis_pool_get_sock(redis_pool *pool, zend_string *key) {
redis_pool_member *rpm;
int pos, i;
unsigned int pos, i;
memcpy(&pos, key, sizeof(pos));
pos %= pool->totalWeight;
/* In the next release, ensure pool->totalWeight > 0 */
pos = weighted_seed(key) % (pool->totalWeight ? pool->totalWeight : 1);
redis_pool_member *rpm = pool->head;
rpm = pool->head;
for(i = 0; i < pool->totalWeight;) {
if (pos >= i && pos < i + rpm->weight) {
@@ -590,7 +607,7 @@ PS_CLOSE_FUNC(redis)
if (pool) {
if (pool->lock_status.session_key) {
redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(pool->lock_status.session_key));
redis_pool_member *rpm = redis_pool_get_sock(pool, pool->lock_status.session_key);
RedisSock *redis_sock = rpm ? rpm->redis_sock : NULL;
if (redis_sock) {
@@ -640,7 +657,7 @@ PS_CREATE_SID_FUNC(redis)
while (retries-- > 0) {
zend_string* sid = php_session_create_id((void **) &pool);
redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(sid));
redis_pool_member *rpm = redis_pool_get_sock(pool, sid);
RedisSock *redis_sock = rpm ? rpm->redis_sock : NULL;
@@ -683,7 +700,7 @@ PS_VALIDATE_SID_FUNC(redis)
if (!skeylen) return FAILURE;
redis_pool *pool = PS_GET_MOD_DATA();
redis_pool_member *rpm = redis_pool_get_sock(pool, skey);
redis_pool_member *rpm = redis_pool_get_sock(pool, key);
RedisSock *redis_sock = rpm ? rpm->redis_sock : NULL;
if (!redis_sock) {
php_error_docref(NULL, E_WARNING, "Redis connection not available");
@@ -730,7 +747,7 @@ PS_UPDATE_TIMESTAMP_FUNC(redis)
}
redis_pool *pool = PS_GET_MOD_DATA();
redis_pool_member *rpm = redis_pool_get_sock(pool, skey);
redis_pool_member *rpm = redis_pool_get_sock(pool, key);
RedisSock *redis_sock = rpm ? rpm->redis_sock : NULL;
if (!redis_sock) {
php_error_docref(NULL, E_WARNING, "Redis connection not available");
@@ -772,7 +789,7 @@ PS_READ_FUNC(redis)
if (!skeylen) return FAILURE;
redis_pool *pool = PS_GET_MOD_DATA();
redis_pool_member *rpm = redis_pool_get_sock(pool, skey);
redis_pool_member *rpm = redis_pool_get_sock(pool, key);
RedisSock *redis_sock = rpm ? rpm->redis_sock : NULL;
if (!redis_sock) {
php_error_docref(NULL, E_WARNING, "Redis connection not available");
@@ -847,7 +864,7 @@ PS_WRITE_FUNC(redis)
if (!skeylen) return FAILURE;
redis_pool *pool = PS_GET_MOD_DATA();
redis_pool_member *rpm = redis_pool_get_sock(pool, skey);
redis_pool_member *rpm = redis_pool_get_sock(pool, key);
RedisSock *redis_sock = rpm ? rpm->redis_sock : NULL;
if (!redis_sock) {
php_error_docref(NULL, E_WARNING, "Redis connection not available");
@@ -901,7 +918,7 @@ PS_DESTROY_FUNC(redis)
size_t skeylen = ZSTR_LEN(key);
redis_pool *pool = PS_GET_MOD_DATA();
redis_pool_member *rpm = redis_pool_get_sock(pool, skey);
redis_pool_member *rpm = redis_pool_get_sock(pool, key);
RedisSock *redis_sock = rpm ? rpm->redis_sock : NULL;
if (!redis_sock) {
php_error_docref(NULL, E_WARNING, "Redis connection not available");