CVE-2013-0178: Improper Input Validation (CWE-20)
value: Insecure temporary file vulnerability in Redis before 2.6 related to /tmp/redis-%p.vm.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 5.5
Published: 2019-11-01T19:15Z
Repository: https://github.com/antirez/redis
Language: C
Files changed: src/diskstore.c, src/networking.c, src/redis.c, src/redis.h
Commit date: 2010-12-28 15:20:20 +0100
Commit message: initial changes needed to turn the current VM code into a cache system. Tons of work to do still.

Diff: diff --git a/src/diskstore.c b/src/diskstore.c
index de0e9c3..acc7c16 100644
--- a/src/diskstore.c
+++ b/src/diskstore.c
@@ -14,12 +14,21 @@
  * In the end there are 65536 final directories (256 directories inside
  * every 256 top level directories), so that with 1 billion of files every
  * directory will contain in the average 15258 entires, that is ok with
  * most filesystems implementation.
  *
- * The actaul implementation of this disk store is highly related to the
- * filesystem implementation. This implementation may be replaced by
+ * Note that since Redis supports multiple databases, the actual key name
+ * is:
+ *
+ *  /0b/ee/<dbid>_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
+ *
+ *  so for instance if the key is inside DB 0:
+ *
+ *  /0b/ee/0_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
+ *
+ * The actaul implementation of this disk store is highly dependant to the
+ * filesystem implementation itself. This implementation may be replaced by
  * a B+TREE implementation in future implementations.
  *
  * Data ok every key is serialized using the same format used for .rdb
  * serialization. Everything is serialized on every entry: key name,
  * ttl information in case of keys with an associated expire time, and the
@@ -66,11 +75,11 @@
 #include <sys/stat.h>
 
 int dsOpen(void) {
     struct stat sb;
     int retval;
-    char *path = server.diskstore_path;
+    char *path = server.ds_path;
 
     if ((retval = stat(path,&sb) == -1) && errno != ENOENT) {
         redisLog(REDIS_WARNING, "Error opening disk store at %s: %s",
                 path, strerror(errno));
         return REDIS_ERR;
diff --git a/src/networking.c b/src/networking.c
index 26ee46f..73a514e 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -165,11 +165,11 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) {
     }
 }
 
 void addReply(redisClient *c, robj *obj) {
     if (_installWriteEvent(c) != REDIS_OK) return;
-    redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
+    redisAssert(!server.ds_enabled || obj->storage == REDIS_VM_MEMORY);
 
     /* This is an important place where we can avoid copy-on-write
      * when there is a saving child running, avoiding touching the
      * refcount field of the object if it's not needed.
      *
@@ -458,11 +458,11 @@ void freeClient(redisClient *c) {
     redisAssert(ln != NULL);
     listDelNode(server.clients,ln);
     /* Remove from the list of clients waiting for swapped keys, or ready
      * to be restarted, but not yet woken up again. */
     if (c->flags & REDIS_IO_WAIT) {
-        redisAssert(server.vm_enabled);
+        redisAssert(server.ds_enabled);
         if (listLength(c->io_keys) == 0) {
             ln = listSearchKey(server.io_ready_clients,c);
 
             /* When this client is waiting to be woken up (REDIS_IO_WAIT),
              * it should be present in the list io_ready_clients */
@@ -472,11 +472,11 @@ void freeClient(redisClient *c) {
             while (listLength(c->io_keys)) {
                 ln = listFirst(c->io_keys);
                 dontWaitForSwappedKey(c,ln->value);
             }
         }
-        server.vm_blocked_clients--;
+        server.cache_blocked_clients--;
     }
     listRelease(c->io_keys);
     /* Master/slave cleanup.
      * Case 1: we lost the connection with a slave. */
     if (c->flags & REDIS_SLAVE) {
diff --git a/src/redis.c b/src/redis.c
index 6e667f2..8e82ee0 100644
--- a/src/redis.c
+++ b/src/redis.c
@@ -616,31 +616,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
     /* Expire a few keys per cycle, only if this is a master.
      * On slaves we wait for DEL operations synthesized by the master
      * in order to guarantee a strict consistency. */
     if (server.masterhost == NULL) activeExpireCycle();
 
-    /* Swap a few keys on disk if we are over the memory limit and VM
-     * is enbled. Try to free objects from the free list first. */
-    if (vmCanSwapOut()) {
-        while (server.vm_enabled && zmalloc_used_memory() >
-                server.vm_max_memory)
-        {
-            int retval = (server.vm_max_threads == 0) ?
-                        vmSwapOneObjectBlocking() :
-                        vmSwapOneObjectThreaded();
-            if (retval == REDIS_ERR && !(loops % 300) &&
-                zmalloc_used_memory() >
-                (server.vm_max_memory+server.vm_max_memory/10))
-            {
-                redisLog(REDIS_WARNING,"WARNING: vm-max-memory limit exceeded by more than 10%% but unable to swap more objects out!");
-            }
-            /* Note that when using threade I/O we free just one object,
-             * because anyway when the I/O thread in charge to swap this
-             * object out will finish, the handler of completed jobs
-             * will try to swap more objects if we are still out of memory. */
-            if (retval == REDIS_ERR || server.vm_max_threads > 0) break;
-        }
+    /* Remove a few cached objects from memory if we are over the
+     * configured memory limit */
+    while (server.ds_enabled && zmalloc_used_memory() >
+            server.cache_max_memory)
+    {
+        cacheFreeOneEntry();
     }
 
     /* Replication cron function -- used to reconnect to master and
      * to detect transfer failures. */
     if (!(loops % 10)) replicationCron();
@@ -654,23 +639,23 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
 void beforeSleep(struct aeEventLoop *eventLoop) {
     REDIS_NOTUSED(eventLoop);
     listNode *ln;
     redisClient *c;
 
-    /* Awake clients that got all the swapped keys they requested */
-    if (server.vm_enabled && listLength(server.io_ready_clients)) {
+    /* Awake clients that got all the on disk keys they requested */
+    if (server.ds_enabled && listLength(server.io_ready_clients)) {
         listIter li;
 
         listRewind(server.io_ready_clients,&li);
         while((ln = listNext(&li))) {
             c = ln->value;
             struct redisCommand *cmd;
 
             /* Resume the client. */
             listDelNode(server.io_ready_clients,ln);
             c->flags &= (~REDIS_IO_WAIT);
-            server.vm_blocked_clients--;
+            server.cache_blocked_clients--;
             aeCreateFileEvent(server.el, c->fd, AE_READABLE,
                 readQueryFromClient, c);
             cmd = lookupCommand(c->argv[0]->ptr);
             redisAssert(cmd != NULL);
             call(c,cmd);
@@ -785,17 +770,14 @@ void initServerConfig() {
     server.maxclients = 0;
     server.bpop_blocked_clients = 0;
     server.maxmemory = 0;
     server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
     server.maxmemory_samples = 3;
-    server.vm_enabled = 0;
-    server.vm_swap_file = zstrdup("/tmp/redis-%p.vm");
-    server.vm_page_size = 256;          /* 256 bytes per page */
-    server.vm_pages = 1024*1024*100;    /* 104 millions of pages */
-    server.vm_max_memory = 1024LL*1024*1024*1; /* 1 GB of RAM */
-    server.vm_max_threads = 4;
-    server.vm_blocked_clients = 0;
+    server.ds_enabled = 0;
+    server.ds_path = zstrdup("/tmp/redis.ds");
+    server.cache_max_memory = 64LL*1024*1024; /* 64 MB of RAM */
+    server.cache_blocked_clients = 0;
     server.hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES;
     server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE;
     server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
     server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
     server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
@@ -871,11 +853,11 @@ void initServer() {
     for (j = 0; j < server.dbnum; j++) {
         server.db[j].dict = dictCreate(&dbDictType,NULL);
         server.db[j].expires = dictCreate(&keyptrDictType,NULL);
         server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
         server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
-        if (server.vm_enabled)
+        if (server.ds_enabled)
             server.db[j].io_keys = dictCreate(&keylistDictType,NULL);
         server.db[j].id = j;
     }
     server.pubsub_channels = dictCreate(&keylistDictType,NULL);
     server.pubsub_patterns = listCreate();
@@ -909,11 +891,11 @@ void initServer() {
                 strerror(errno));
             exit(1);
         }
     }
 
-    if (server.vm_enabled) vmInit();
+    if (server.ds_enabled) dsInit();
 }
 
 /* Populates the Redis Command Table starting from the hard coded list
  * we have on top of redis.c file. */
 void populateCommandTable(void) {
@@ -1048,12 +1030,12 @@ int processCommand(redisClient *c) {
         cmd->proc != multiCommand && cmd->proc != watchCommand)
     {
         queueMultiCommand(c,cmd);
         addReply(c,shared.queued);
     } else {
-        if (server.vm_enabled && server.vm_max_threads > 0 &&
-            blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR;
+        if (server.ds_enabled && blockClientOnSwappedKeys(c,cmd))
+            return REDIS_ERR;
         call(c,cmd);
     }
     return REDIS_OK;
 }
 
@@ -1070,11 +1052,10 @@ int prepareForShutdown() {
         rdbRemoveTempFile(server.bgsavechildpid);
     }
     if (server.appendonly) {
         /* Append only file: fsync() the AOF and exit */
         aof_fsync(server.appendfd);
-        if (server.vm_enabled) unlink(server.vm_swap_file);
     } else if (server.saveparamslen > 0) {
         /* Snapshotting. Perform a SYNC SAVE and exit */
         if (rdbSave(server.dbfilename) != REDIS_OK) {
             /* Ooops.. error saving! The best we can do is to continue
              * operating. Note that if there was a background saving process,
@@ -1183,11 +1164,11 @@ sds genRedisInfoString(void) {
         "keyspace_misses:%lld\r\n"
         "hash_max_zipmap_entries:%zu\r\n"
         "hash_max_zipmap_value:%zu\r\n"
         "pubsub_channels:%ld\r\n"
         "pubsub_patterns:%u\r\n"
-        "vm_enabled:%d\r\n"
+        "ds_enabled:%d\r\n"
         "role:%s\r\n"
         ,REDIS_VERSION,
         redisGitSHA1(),
         strtol(redisGitDirty(),NULL,10) > 0,
         (sizeof(long) == 8) ? "64" : "32",
@@ -1226,11 +1207,11 @@ sds genRedisInfoString(void) {
         server.stat_keyspace_misses,
         server.hash_max_zipmap_entries,
         server.hash_max_zipmap_value,
         dictSize(server.pubsub_channels),
         listLength(server.pubsub_patterns),
-        server.vm_enabled != 0,
+        server.ds_enabled != 0,
         server.masterhost == NULL ? "master" : "slave"
     );
     if (server.masterhost) {
         info = sdscatprintf(info,
             "master_host:%s\r\n"
@@ -1253,37 +1234,17 @@ sds genRedisInfoString(void) {
                 ,(long)server.repl_transfer_left,
                 (int)(time(NULL)-server.repl_transfer_lastio)
             );
         }
     }
-    if (server.vm_enabled) {
+    if (server.ds_enabled) {
         lockThreadedIO();
         info = sdscatprintf(info,
-            "vm_conf_max_memory:%llu\r\n"
-            "vm_conf_page_size:%llu\r\n"
-            "vm_conf_pages:%llu\r\n"
-            "vm_stats_used_pages:%llu\r\n"
-            "vm_stats_swapped_objects:%llu\r\n"
-            "vm_stats_swappin_count:%llu\r\n"
-            "vm_stats_swappout_count:%llu\r\n"
-            "vm_stats_io_newjobs_len:%lu\r\n"
-            "vm_stats_io_processing_len:%lu\r\n"
-            "vm_stats_io_processed_len:%lu\r\n"
-            "vm_stats_io_active_threads:%lu\r\n"
-            "vm_stats_blocked_clients:%lu\r\n"
-            ,(unsigned long long) server.vm_max_memory,
-            (unsigned long long) server.vm_page_size,
-            (unsigned long long) server.vm_pages,
-            (unsigned long long) server.vm_stats_used_pages,
-            (unsigned long long) server.vm_stats_swapped_objects,
-            (unsigned long long) server.vm_stats_swapins,
-            (unsigned long long) server.vm_stats_swapouts,
-            (unsigned long) listLength(server.io_newjobs),
-            (unsigned long) listLength(server.io_processing),
-            (unsigned long) listLength(server.io_processed),
-            (unsigned long) server.io_active_threads,
-            (unsigned long) server.vm_blocked_clients
+            "cache_max_memory:%llu\r\n"
+            "cache_blocked_clients:%lu\r\n"
+            ,(unsigned long long) server.cache_max_memory,
+            (unsigned long) server.cache_blocked_clients
         );
         unlockThreadedIO();
     }
     if (server.loading) {
         double perc;
diff --git a/src/redis.h b/src/redis.h
index 6e8b40b..b756fae 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -438,23 +438,21 @@ struct redisServer {
     unsigned long long maxmemory;
     int maxmemory_policy;
     int maxmemory_samples;
     /* Blocked clients */
     unsigned int bpop_blocked_clients;
-    unsigned int vm_blocked_clients;
+    unsigned int cache_blocked_clients;
     list *unblocked_clients;
     /* Sort parameters - qsort_r() is only available under BSD so we
      * have to take this state global, in order to pass it to sortCompare() */
     int sort_desc;
     int sort_alpha;
     int sort_bypattern;
     /* Virtual memory configuration */
-    int vm_enabled;
-    char *vm_swap_file;
-    off_t vm_page_size;
-    off_t vm_pages;
-    unsigned long long vm_max_memory;
+    int ds_enabled; /* backend disk in redis.conf */
+    char *ds_path;  /* location of the disk store on disk */
+    unsigned long long cache_max_memory;
     /* Zip structure config */
     size_t hash_max_zipmap_entries;
     size_t hash_max_zipmap_value;
     size_t list_max_ziplist_entries;
     size_t list_max_ziplist_value;

Vulnerable code:  * The actaul implementation of this disk store is highly related to the
 * filesystem implementation. This implementation may be replaced by
    char *path = server.diskstore_path;
    redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
        redisAssert(server.vm_enabled);
        server.vm_blocked_clients--;
    /* Swap a few keys on disk if we are over the memory limit and VM
     * is enbled. Try to free objects from the free list first. */
    if (vmCanSwapOut()) {
        while (server.vm_enabled && zmalloc_used_memory() >
                server.vm_max_memory)
        {
            int retval = (server.vm_max_threads == 0) ?
                        vmSwapOneObjectBlocking() :
                        vmSwapOneObjectThreaded();
            if (retval == REDIS_ERR && !(loops % 300) &&
                zmalloc_used_memory() >
                (server.vm_max_memory+server.vm_max_memory/10))
            {
                redisLog(REDIS_WARNING,"WARNING: vm-max-memory limit exceeded by more than 10%% but unable to swap more objects out!");
            }
            /* Note that when using threade I/O we free just one object,
             * because anyway when the I/O thread in charge to swap this
             * object out will finish, the handler of completed jobs
             * will try to swap more objects if we are still out of memory. */
            if (retval == REDIS_ERR || server.vm_max_threads > 0) break;
        }
    /* Awake clients that got all the swapped keys they requested */
    if (server.vm_enabled && listLength(server.io_ready_clients)) {
            server.vm_blocked_clients--;
    server.vm_enabled = 0;
    server.vm_swap_file = zstrdup("/tmp/redis-%p.vm");
    server.vm_page_size = 256;          /* 256 bytes per page */
    server.vm_pages = 1024*1024*100;    /* 104 millions of pages */
    server.vm_max_memory = 1024LL*1024*1024*1; /* 1 GB of RAM */
    server.vm_max_threads = 4;
    server.vm_blocked_clients = 0;
        if (server.vm_enabled)
    if (server.vm_enabled) vmInit();
        if (server.vm_enabled && server.vm_max_threads > 0 &&
            blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR;
        if (server.vm_enabled) unlink(server.vm_swap_file);
        "vm_enabled:%d\r\n"
        server.vm_enabled != 0,
    if (server.vm_enabled) {
            "vm_conf_max_memory:%llu\r\n"
            "vm_conf_page_size:%llu\r\n"
            "vm_conf_pages:%llu\r\n"
            "vm_stats_used_pages:%llu\r\n"
            "vm_stats_swapped_objects:%llu\r\n"
            "vm_stats_swappin_count:%llu\r\n"
            "vm_stats_swappout_count:%llu\r\n"
            "vm_stats_io_newjobs_len:%lu\r\n"
            "vm_stats_io_processing_len:%lu\r\n"
            "vm_stats_io_processed_len:%lu\r\n"
            "vm_stats_io_active_threads:%lu\r\n"
            "vm_stats_blocked_clients:%lu\r\n"
            ,(unsigned long long) server.vm_max_memory,
            (unsigned long long) server.vm_page_size,
            (unsigned long long) server.vm_pages,
            (unsigned long long) server.vm_stats_used_pages,
            (unsigned long long) server.vm_stats_swapped_objects,
            (unsigned long long) server.vm_stats_swapins,
            (unsigned long long) server.vm_stats_swapouts,
            (unsigned long) listLength(server.io_newjobs),
            (unsigned long) listLength(server.io_processing),
            (unsigned long) listLength(server.io_processed),
            (unsigned long) server.io_active_threads,
            (unsigned long) server.vm_blocked_clients
    unsigned int vm_blocked_clients;
    int vm_enabled;
    char *vm_swap_file;
    off_t vm_page_size;
    off_t vm_pages;
    unsigned long long vm_max_memory;
Fixed code:  * Note that since Redis supports multiple databases, the actual key name
 * is:
 *
 *  /0b/ee/<dbid>_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
 *
 *  so for instance if the key is inside DB 0:
 *
 *  /0b/ee/0_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
 *
 * The actaul implementation of this disk store is highly dependant to the
 * filesystem implementation itself. This implementation may be replaced by
    char *path = server.ds_path;
    redisAssert(!server.ds_enabled || obj->storage == REDIS_VM_MEMORY);
        redisAssert(server.ds_enabled);
        server.cache_blocked_clients--;
    /* Remove a few cached objects from memory if we are over the
     * configured memory limit */
    while (server.ds_enabled && zmalloc_used_memory() >
            server.cache_max_memory)
    {
        cacheFreeOneEntry();
    /* Awake clients that got all the on disk keys they requested */
    if (server.ds_enabled && listLength(server.io_ready_clients)) {
            server.cache_blocked_clients--;
    server.ds_enabled = 0;
    server.ds_path = zstrdup("/tmp/redis.ds");
    server.cache_max_memory = 64LL*1024*1024; /* 64 MB of RAM */
    server.cache_blocked_clients = 0;
        if (server.ds_enabled)
    if (server.ds_enabled) dsInit();
        if (server.ds_enabled && blockClientOnSwappedKeys(c,cmd))
            return REDIS_ERR;
        "ds_enabled:%d\r\n"
        server.ds_enabled != 0,
    if (server.ds_enabled) {
            "cache_max_memory:%llu\r\n"
            "cache_blocked_clients:%lu\r\n"
            ,(unsigned long long) server.cache_max_memory,
            (unsigned long) server.cache_blocked_clients
    unsigned int cache_blocked_clients;
    int ds_enabled; /* backend disk in redis.conf */
    char *ds_path;  /* location of the disk store on disk */
    unsigned long long cache_max_memory;