1 /*
2  * Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *   * Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *   * Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *   * Neither the name of Redis nor the names of its contributors may be used
14  *     to endorse or promote products derived from this software without
15  *     specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "server.h"
31 #include "rdb.h"
32 
33 #include <stdarg.h>
34 
35 void createSharedObjects(void);
36 void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
37 int rdbCheckMode = 0;
38 
39 struct {
40     rio *rio;
41     robj *key;                      /* Current key we are reading. */
42     int key_type;                   /* Current key type if != -1. */
43     unsigned long keys;             /* Number of keys processed. */
44     unsigned long expires;          /* Number of keys with an expire. */
45     unsigned long already_expired;  /* Number of keys already expired. */
46     int doing;                      /* The state while reading the RDB. */
47     int error_set;                  /* True if error is populated. */
48     char error[1024];
49 } rdbstate;
50 
51 /* At every loading step try to remember what we were about to do, so that
52  * we can log this information when an error is encountered. */
53 #define RDB_CHECK_DOING_START 0
54 #define RDB_CHECK_DOING_READ_TYPE 1
55 #define RDB_CHECK_DOING_READ_EXPIRE 2
56 #define RDB_CHECK_DOING_READ_KEY 3
57 #define RDB_CHECK_DOING_READ_OBJECT_VALUE 4
58 #define RDB_CHECK_DOING_CHECK_SUM 5
59 #define RDB_CHECK_DOING_READ_LEN 6
60 #define RDB_CHECK_DOING_READ_AUX 7
61 
62 char *rdb_check_doing_string[] = {
63     "start",
64     "read-type",
65     "read-expire",
66     "read-key",
67     "read-object-value",
68     "check-sum",
69     "read-len",
70     "read-aux"
71 };
72 
73 char *rdb_type_string[] = {
74     "string",
75     "list-linked",
76     "set-hashtable",
77     "zset-v1",
78     "hash-hashtable",
79     "zset-v2",
80     "module-value",
81     "","",
82     "hash-zipmap",
83     "list-ziplist",
84     "set-intset",
85     "zset-ziplist",
86     "hash-ziplist",
87     "quicklist",
88     "stream"
89 };
90 
91 /* Show a few stats collected into 'rdbstate' */
rdbShowGenericInfo(void)92 void rdbShowGenericInfo(void) {
93     printf("[info] %lu keys read\n", rdbstate.keys);
94     printf("[info] %lu expires\n", rdbstate.expires);
95     printf("[info] %lu already expired\n", rdbstate.already_expired);
96 }
97 
98 /* Called on RDB errors. Provides details about the RDB and the offset
99  * we were when the error was detected. */
rdbCheckError(const char * fmt,...)100 void rdbCheckError(const char *fmt, ...) {
101     char msg[1024];
102     va_list ap;
103 
104     va_start(ap, fmt);
105     vsnprintf(msg, sizeof(msg), fmt, ap);
106     va_end(ap);
107 
108     printf("--- RDB ERROR DETECTED ---\n");
109     printf("[offset %llu] %s\n",
110         (unsigned long long) (rdbstate.rio ?
111             rdbstate.rio->processed_bytes : 0), msg);
112     printf("[additional info] While doing: %s\n",
113         rdb_check_doing_string[rdbstate.doing]);
114     if (rdbstate.key)
115         printf("[additional info] Reading key '%s'\n",
116             (char*)rdbstate.key->ptr);
117     if (rdbstate.key_type != -1)
118         printf("[additional info] Reading type %d (%s)\n",
119             rdbstate.key_type,
120             ((unsigned)rdbstate.key_type <
121              sizeof(rdb_type_string)/sizeof(char*)) ?
122                 rdb_type_string[rdbstate.key_type] : "unknown");
123     rdbShowGenericInfo();
124 }
125 
126 /* Print informations during RDB checking. */
rdbCheckInfo(const char * fmt,...)127 void rdbCheckInfo(const char *fmt, ...) {
128     char msg[1024];
129     va_list ap;
130 
131     va_start(ap, fmt);
132     vsnprintf(msg, sizeof(msg), fmt, ap);
133     va_end(ap);
134 
135     printf("[offset %llu] %s\n",
136         (unsigned long long) (rdbstate.rio ?
137             rdbstate.rio->processed_bytes : 0), msg);
138 }
139 
140 /* Used inside rdb.c in order to log specific errors happening inside
141  * the RDB loading internals. */
rdbCheckSetError(const char * fmt,...)142 void rdbCheckSetError(const char *fmt, ...) {
143     va_list ap;
144 
145     va_start(ap, fmt);
146     vsnprintf(rdbstate.error, sizeof(rdbstate.error), fmt, ap);
147     va_end(ap);
148     rdbstate.error_set = 1;
149 }
150 
151 /* During RDB check we setup a special signal handler for memory violations
152  * and similar conditions, so that we can log the offending part of the RDB
153  * if the crash is due to broken content. */
rdbCheckHandleCrash(int sig,siginfo_t * info,void * secret)154 void rdbCheckHandleCrash(int sig, siginfo_t *info, void *secret) {
155     UNUSED(sig);
156     UNUSED(info);
157     UNUSED(secret);
158 
159     rdbCheckError("Server crash checking the specified RDB file!");
160     exit(1);
161 }
162 
rdbCheckSetupSignals(void)163 void rdbCheckSetupSignals(void) {
164     struct sigaction act;
165 
166     sigemptyset(&act.sa_mask);
167     act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
168     act.sa_sigaction = rdbCheckHandleCrash;
169     sigaction(SIGSEGV, &act, NULL);
170     sigaction(SIGBUS, &act, NULL);
171     sigaction(SIGFPE, &act, NULL);
172     sigaction(SIGILL, &act, NULL);
173 }
174 
175 /* Check the specified RDB file. Return 0 if the RDB looks sane, otherwise
176  * 1 is returned.
177  * The file is specified as a filename in 'rdbfilename' if 'fp' is not NULL,
178  * otherwise the already open file 'fp' is checked. */
redis_check_rdb(char * rdbfilename,FILE * fp)179 int redis_check_rdb(char *rdbfilename, FILE *fp) {
180     uint64_t dbid;
181     int type, rdbver;
182     char buf[1024];
183     long long expiretime, now = mstime();
184     static rio rdb; /* Pointed by global struct riostate. */
185 
186     int closefile = (fp == NULL);
187     if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
188 
189     rioInitWithFile(&rdb,fp);
190     rdbstate.rio = &rdb;
191     rdb.update_cksum = rdbLoadProgressCallback;
192     if (rioRead(&rdb,buf,9) == 0) goto eoferr;
193     buf[9] = '\0';
194     if (memcmp(buf,"REDIS",5) != 0) {
195         rdbCheckError("Wrong signature trying to load DB from file");
196         goto err;
197     }
198     rdbver = atoi(buf+5);
199     if (rdbver < 1 || rdbver > RDB_VERSION) {
200         rdbCheckError("Can't handle RDB format version %d",rdbver);
201         goto err;
202     }
203 
204     expiretime = -1;
205     startLoading(fp);
206     while(1) {
207         robj *key, *val;
208 
209         /* Read type. */
210         rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
211         if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
212 
213         /* Handle special types. */
214         if (type == RDB_OPCODE_EXPIRETIME) {
215             rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
216             /* EXPIRETIME: load an expire associated with the next key
217              * to load. Note that after loading an expire we need to
218              * load the actual type, and continue. */
219             if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
220             expiretime *= 1000;
221             continue; /* Read next opcode. */
222         } else if (type == RDB_OPCODE_EXPIRETIME_MS) {
223             /* EXPIRETIME_MS: milliseconds precision expire times introduced
224              * with RDB v3. Like EXPIRETIME but no with more precision. */
225             rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
226             if ((expiretime = rdbLoadMillisecondTime(&rdb, rdbver)) == -1) goto eoferr;
227             continue; /* Read next opcode. */
228         } else if (type == RDB_OPCODE_FREQ) {
229             /* FREQ: LFU frequency. */
230             uint8_t byte;
231             if (rioRead(&rdb,&byte,1) == 0) goto eoferr;
232             continue; /* Read next opcode. */
233         } else if (type == RDB_OPCODE_IDLE) {
234             /* IDLE: LRU idle time. */
235             if (rdbLoadLen(&rdb,NULL) == RDB_LENERR) goto eoferr;
236             continue; /* Read next opcode. */
237         } else if (type == RDB_OPCODE_EOF) {
238             /* EOF: End of file, exit the main loop. */
239             break;
240         } else if (type == RDB_OPCODE_SELECTDB) {
241             /* SELECTDB: Select the specified database. */
242             rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
243             if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
244                 goto eoferr;
245             rdbCheckInfo("Selecting DB ID %d", dbid);
246             continue; /* Read type again. */
247         } else if (type == RDB_OPCODE_RESIZEDB) {
248             /* RESIZEDB: Hint about the size of the keys in the currently
249              * selected data base, in order to avoid useless rehashing. */
250             uint64_t db_size, expires_size;
251             rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
252             if ((db_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
253                 goto eoferr;
254             if ((expires_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
255                 goto eoferr;
256             continue; /* Read type again. */
257         } else if (type == RDB_OPCODE_AUX) {
258             /* AUX: generic string-string fields. Use to add state to RDB
259              * which is backward compatible. Implementations of RDB loading
260              * are requierd to skip AUX fields they don't understand.
261              *
262              * An AUX field is composed of two strings: key and value. */
263             robj *auxkey, *auxval;
264             rdbstate.doing = RDB_CHECK_DOING_READ_AUX;
265             if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
266             if ((auxval = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
267 
268             rdbCheckInfo("AUX FIELD %s = '%s'",
269                 (char*)auxkey->ptr, (char*)auxval->ptr);
270             decrRefCount(auxkey);
271             decrRefCount(auxval);
272             continue; /* Read type again. */
273         } else {
274             if (!rdbIsObjectType(type)) {
275                 rdbCheckError("Invalid object type: %d", type);
276                 goto err;
277             }
278             rdbstate.key_type = type;
279         }
280 
281         /* Read key */
282         rdbstate.doing = RDB_CHECK_DOING_READ_KEY;
283         if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
284         rdbstate.key = key;
285         rdbstate.keys++;
286         /* Read value */
287         rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
288         if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
289         /* Check if the key already expired. */
290         if (expiretime != -1 && expiretime < now)
291             rdbstate.already_expired++;
292         if (expiretime != -1) rdbstate.expires++;
293         rdbstate.key = NULL;
294         decrRefCount(key);
295         decrRefCount(val);
296         rdbstate.key_type = -1;
297         expiretime = -1;
298     }
299     /* Verify the checksum if RDB version is >= 5 */
300     if (rdbver >= 5 && server.rdb_checksum) {
301         uint64_t cksum, expected = rdb.cksum;
302 
303         rdbstate.doing = RDB_CHECK_DOING_CHECK_SUM;
304         if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;
305         memrev64ifbe(&cksum);
306         if (cksum == 0) {
307             rdbCheckInfo("RDB file was saved with checksum disabled: no check performed.");
308         } else if (cksum != expected) {
309             rdbCheckError("RDB CRC error");
310             goto err;
311         } else {
312             rdbCheckInfo("Checksum OK");
313         }
314     }
315 
316     if (closefile) fclose(fp);
317     return 0;
318 
319 eoferr: /* unexpected end of file is handled here with a fatal exit */
320     if (rdbstate.error_set) {
321         rdbCheckError(rdbstate.error);
322     } else {
323         rdbCheckError("Unexpected EOF reading RDB file");
324     }
325 err:
326     if (closefile) fclose(fp);
327     return 1;
328 }
329 
330 /* RDB check main: called form redis.c when Redis is executed with the
331  * redis-check-rdb alias, on during RDB loading errors.
332  *
333  * The function works in two ways: can be called with argc/argv as a
334  * standalone executable, or called with a non NULL 'fp' argument if we
335  * already have an open file to check. This happens when the function
336  * is used to check an RDB preamble inside an AOF file.
337  *
338  * When called with fp = NULL, the function never returns, but exits with the
339  * status code according to success (RDB is sane) or error (RDB is corrupted).
340  * Otherwise if called with a non NULL fp, the function returns C_OK or
341  * C_ERR depending on the success or failure. */
redis_check_rdb_main(int argc,char ** argv,FILE * fp)342 int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
343     if (argc != 2 && fp == NULL) {
344         fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
345         exit(1);
346     }
347     /* In order to call the loading functions we need to create the shared
348      * integer objects, however since this function may be called from
349      * an already initialized Redis instance, check if we really need to. */
350     if (shared.integers[0] == NULL)
351         createSharedObjects();
352     server.loading_process_events_interval_bytes = 0;
353     rdbCheckMode = 1;
354     rdbCheckInfo("Checking RDB file %s", argv[1]);
355     rdbCheckSetupSignals();
356     int retval = redis_check_rdb(argv[1],fp);
357     if (retval == 0) {
358         rdbCheckInfo("\\o/ RDB looks OK! \\o/");
359         rdbShowGenericInfo();
360     }
361     if (fp) return (retval == 0) ? C_OK : C_ERR;
362     exit(retval);
363 }
364