Bug Summary

File:src/redis-check-rdb.c
Warning:line 286, column 18
Although the value stored to 'when_opcode' is used in the enclosing expression, the value is never actually read from 'when_opcode'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name redis-check-rdb.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model static -mthread-model posix -mframe-pointer=none -fmath-errno -fno-rounding-math -masm-verbose -mconstructor-aliases -munwind-tables -target-cpu x86-64 -dwarf-column-info -fno-split-dwarf-inlining -debugger-tuning=gdb -resource-dir /usr/lib/llvm-10/lib/clang/10.0.0 -D REDIS_STATIC= -I ../deps/hiredis -I ../deps/linenoise -I ../deps/lua/src -I ../deps/hdr_histogram -D USE_JEMALLOC -I ../deps/jemalloc/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/llvm-10/lib/clang/10.0.0/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -Wno-c11-extensions -Wno-missing-field-initializers -std=c11 -fdebug-compilation-dir /home/netto/Desktop/redis-6.2.1/src -ferror-limit 19 -fmessage-length 0 -fgnuc-version=4.2.1 -fobjc-runtime=gcc -fdiagnostics-show-option -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -o /tmp/scan-build-2021-03-14-133648-8817-1 -x c redis-check-rdb.c
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 "mt19937-64.h"
31#include "server.h"
32#include "rdb.h"
33
34#include <stdarg.h>
35#include <sys/time.h>
36#include <unistd.h>
37
38void createSharedObjects(void);
39void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
40int rdbCheckMode = 0;
41
42struct {
43 rio *rio;
44 robj *key; /* Current key we are reading. */
45 int key_type; /* Current key type if != -1. */
46 unsigned long keys; /* Number of keys processed. */
47 unsigned long expires; /* Number of keys with an expire. */
48 unsigned long already_expired; /* Number of keys already expired. */
49 int doing; /* The state while reading the RDB. */
50 int error_set; /* True if error is populated. */
51 char error[1024];
52} rdbstate;
53
54/* At every loading step try to remember what we were about to do, so that
55 * we can log this information when an error is encountered. */
56#define RDB_CHECK_DOING_START0 0
57#define RDB_CHECK_DOING_READ_TYPE1 1
58#define RDB_CHECK_DOING_READ_EXPIRE2 2
59#define RDB_CHECK_DOING_READ_KEY3 3
60#define RDB_CHECK_DOING_READ_OBJECT_VALUE4 4
61#define RDB_CHECK_DOING_CHECK_SUM5 5
62#define RDB_CHECK_DOING_READ_LEN6 6
63#define RDB_CHECK_DOING_READ_AUX7 7
64#define RDB_CHECK_DOING_READ_MODULE_AUX8 8
65
66char *rdb_check_doing_string[] = {
67 "start",
68 "read-type",
69 "read-expire",
70 "read-key",
71 "read-object-value",
72 "check-sum",
73 "read-len",
74 "read-aux",
75 "read-module-aux"
76};
77
78char *rdb_type_string[] = {
79 "string",
80 "list-linked",
81 "set-hashtable",
82 "zset-v1",
83 "hash-hashtable",
84 "zset-v2",
85 "module-value",
86 "","",
87 "hash-zipmap",
88 "list-ziplist",
89 "set-intset",
90 "zset-ziplist",
91 "hash-ziplist",
92 "quicklist",
93 "stream"
94};
95
96/* Show a few stats collected into 'rdbstate' */
97void rdbShowGenericInfo(void) {
98 printf("[info] %lu keys read\n", rdbstate.keys);
99 printf("[info] %lu expires\n", rdbstate.expires);
100 printf("[info] %lu already expired\n", rdbstate.already_expired);
101}
102
103/* Called on RDB errors. Provides details about the RDB and the offset
104 * we were when the error was detected. */
105void rdbCheckError(const char *fmt, ...) {
106 char msg[1024];
107 va_list ap;
108
109 va_start(ap, fmt)__builtin_va_start(ap, fmt);
110 vsnprintf(msg, sizeof(msg), fmt, ap);
111 va_end(ap)__builtin_va_end(ap);
112
113 printf("--- RDB ERROR DETECTED ---\n");
114 printf("[offset %llu] %s\n",
115 (unsigned long long) (rdbstate.rio ?
116 rdbstate.rio->processed_bytes : 0), msg);
117 printf("[additional info] While doing: %s\n",
118 rdb_check_doing_string[rdbstate.doing]);
119 if (rdbstate.key)
120 printf("[additional info] Reading key '%s'\n",
121 (char*)rdbstate.key->ptr);
122 if (rdbstate.key_type != -1)
123 printf("[additional info] Reading type %d (%s)\n",
124 rdbstate.key_type,
125 ((unsigned)rdbstate.key_type <
126 sizeof(rdb_type_string)/sizeof(char*)) ?
127 rdb_type_string[rdbstate.key_type] : "unknown");
128 rdbShowGenericInfo();
129}
130
131/* Print informations during RDB checking. */
132void rdbCheckInfo(const char *fmt, ...) {
133 char msg[1024];
134 va_list ap;
135
136 va_start(ap, fmt)__builtin_va_start(ap, fmt);
137 vsnprintf(msg, sizeof(msg), fmt, ap);
138 va_end(ap)__builtin_va_end(ap);
139
140 printf("[offset %llu] %s\n",
141 (unsigned long long) (rdbstate.rio ?
142 rdbstate.rio->processed_bytes : 0), msg);
143}
144
145/* Used inside rdb.c in order to log specific errors happening inside
146 * the RDB loading internals. */
147void rdbCheckSetError(const char *fmt, ...) {
148 va_list ap;
149
150 va_start(ap, fmt)__builtin_va_start(ap, fmt);
151 vsnprintf(rdbstate.error, sizeof(rdbstate.error), fmt, ap);
152 va_end(ap)__builtin_va_end(ap);
153 rdbstate.error_set = 1;
154}
155
156/* During RDB check we setup a special signal handler for memory violations
157 * and similar conditions, so that we can log the offending part of the RDB
158 * if the crash is due to broken content. */
159void rdbCheckHandleCrash(int sig, siginfo_t *info, void *secret) {
160 UNUSED(sig)((void) sig);
161 UNUSED(info)((void) info);
162 UNUSED(secret)((void) secret);
163
164 rdbCheckError("Server crash checking the specified RDB file!");
165 exit(1);
166}
167
168void rdbCheckSetupSignals(void) {
169 struct sigaction act;
170
171 sigemptyset(&act.sa_mask);
172 act.sa_flags = SA_NODEFER0x40000000 | SA_RESETHAND0x80000000 | SA_SIGINFO4;
173 act.sa_sigaction__sigaction_handler.sa_sigaction = rdbCheckHandleCrash;
174 sigaction(SIGSEGV11, &act, NULL((void*)0));
175 sigaction(SIGBUS7, &act, NULL((void*)0));
176 sigaction(SIGFPE8, &act, NULL((void*)0));
177 sigaction(SIGILL4, &act, NULL((void*)0));
178 sigaction(SIGABRT6, &act, NULL((void*)0));
179}
180
181/* Check the specified RDB file. Return 0 if the RDB looks sane, otherwise
182 * 1 is returned.
183 * The file is specified as a filename in 'rdbfilename' if 'fp' is not NULL,
184 * otherwise the already open file 'fp' is checked. */
185int redis_check_rdb(char *rdbfilename, FILE *fp) {
186 uint64_t dbid;
187 int type, rdbver;
188 char buf[1024];
189 long long expiretime, now = mstime();
190 static rio rdb; /* Pointed by global struct riostate. */
191
192 int closefile = (fp == NULL((void*)0));
193 if (fp == NULL((void*)0) && (fp = fopen(rdbfilename,"r")) == NULL((void*)0)) return 1;
194
195 rioInitWithFile(&rdb,fp);
196 rdbstate.rio = &rdb;
197 rdb.update_cksum = rdbLoadProgressCallback;
198 if (rioRead(&rdb,buf,9) == 0) goto eoferr;
199 buf[9] = '\0';
200 if (memcmp(buf,"REDIS",5) != 0) {
201 rdbCheckError("Wrong signature trying to load DB from file");
202 goto err;
203 }
204 rdbver = atoi(buf+5);
205 if (rdbver < 1 || rdbver > RDB_VERSION9) {
206 rdbCheckError("Can't handle RDB format version %d",rdbver);
207 goto err;
208 }
209
210 expiretime = -1;
211 startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE0);
212 while(1) {
213 robj *key, *val;
214
215 /* Read type. */
216 rdbstate.doing = RDB_CHECK_DOING_READ_TYPE1;
217 if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
218
219 /* Handle special types. */
220 if (type == RDB_OPCODE_EXPIRETIME253) {
221 rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE2;
222 /* EXPIRETIME: load an expire associated with the next key
223 * to load. Note that after loading an expire we need to
224 * load the actual type, and continue. */
225 expiretime = rdbLoadTime(&rdb);
226 expiretime *= 1000;
227 if (rioGetReadError(&rdb)) goto eoferr;
228 continue; /* Read next opcode. */
229 } else if (type == RDB_OPCODE_EXPIRETIME_MS252) {
230 /* EXPIRETIME_MS: milliseconds precision expire times introduced
231 * with RDB v3. Like EXPIRETIME but no with more precision. */
232 rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE2;
233 expiretime = rdbLoadMillisecondTime(&rdb, rdbver);
234 if (rioGetReadError(&rdb)) goto eoferr;
235 continue; /* Read next opcode. */
236 } else if (type == RDB_OPCODE_FREQ249) {
237 /* FREQ: LFU frequency. */
238 uint8_t byte;
239 if (rioRead(&rdb,&byte,1) == 0) goto eoferr;
240 continue; /* Read next opcode. */
241 } else if (type == RDB_OPCODE_IDLE248) {
242 /* IDLE: LRU idle time. */
243 if (rdbLoadLen(&rdb,NULL((void*)0)) == RDB_LENERR(18446744073709551615UL)) goto eoferr;
244 continue; /* Read next opcode. */
245 } else if (type == RDB_OPCODE_EOF255) {
246 /* EOF: End of file, exit the main loop. */
247 break;
248 } else if (type == RDB_OPCODE_SELECTDB254) {
249 /* SELECTDB: Select the specified database. */
250 rdbstate.doing = RDB_CHECK_DOING_READ_LEN6;
251 if ((dbid = rdbLoadLen(&rdb,NULL((void*)0))) == RDB_LENERR(18446744073709551615UL))
252 goto eoferr;
253 rdbCheckInfo("Selecting DB ID %d", dbid);
254 continue; /* Read type again. */
255 } else if (type == RDB_OPCODE_RESIZEDB251) {
256 /* RESIZEDB: Hint about the size of the keys in the currently
257 * selected data base, in order to avoid useless rehashing. */
258 uint64_t db_size, expires_size;
259 rdbstate.doing = RDB_CHECK_DOING_READ_LEN6;
260 if ((db_size = rdbLoadLen(&rdb,NULL((void*)0))) == RDB_LENERR(18446744073709551615UL))
261 goto eoferr;
262 if ((expires_size = rdbLoadLen(&rdb,NULL((void*)0))) == RDB_LENERR(18446744073709551615UL))
263 goto eoferr;
264 continue; /* Read type again. */
265 } else if (type == RDB_OPCODE_AUX250) {
266 /* AUX: generic string-string fields. Use to add state to RDB
267 * which is backward compatible. Implementations of RDB loading
268 * are requierd to skip AUX fields they don't understand.
269 *
270 * An AUX field is composed of two strings: key and value. */
271 robj *auxkey, *auxval;
272 rdbstate.doing = RDB_CHECK_DOING_READ_AUX7;
273 if ((auxkey = rdbLoadStringObject(&rdb)) == NULL((void*)0)) goto eoferr;
274 if ((auxval = rdbLoadStringObject(&rdb)) == NULL((void*)0)) goto eoferr;
275
276 rdbCheckInfo("AUX FIELD %s = '%s'",
277 (char*)auxkey->ptr, (char*)auxval->ptr);
278 decrRefCount(auxkey);
279 decrRefCount(auxval);
280 continue; /* Read type again. */
281 } else if (type == RDB_OPCODE_MODULE_AUX247) {
282 /* AUX: Auxiliary data for modules. */
283 uint64_t moduleid, when_opcode, when;
284 rdbstate.doing = RDB_CHECK_DOING_READ_MODULE_AUX8;
285 if ((moduleid = rdbLoadLen(&rdb,NULL((void*)0))) == RDB_LENERR(18446744073709551615UL)) goto eoferr;
286 if ((when_opcode = rdbLoadLen(&rdb,NULL((void*)0))) == RDB_LENERR(18446744073709551615UL)) goto eoferr;
Although the value stored to 'when_opcode' is used in the enclosing expression, the value is never actually read from 'when_opcode'
287 if ((when = rdbLoadLen(&rdb,NULL((void*)0))) == RDB_LENERR(18446744073709551615UL)) goto eoferr;
288
289 char name[10];
290 moduleTypeNameByID(name,moduleid);
291 rdbCheckInfo("MODULE AUX for: %s", name);
292
293 robj *o = rdbLoadCheckModuleValue(&rdb,name);
294 decrRefCount(o);
295 continue; /* Read type again. */
296 } else {
297 if (!rdbIsObjectType(type)((type >= 0 && type <= 7) || (type >= 9 &&
type <= 15))
) {
298 rdbCheckError("Invalid object type: %d", type);
299 goto err;
300 }
301 rdbstate.key_type = type;
302 }
303
304 /* Read key */
305 rdbstate.doing = RDB_CHECK_DOING_READ_KEY3;
306 if ((key = rdbLoadStringObject(&rdb)) == NULL((void*)0)) goto eoferr;
307 rdbstate.key = key;
308 rdbstate.keys++;
309 /* Read value */
310 rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE4;
311 if ((val = rdbLoadObject(type,&rdb,key->ptr)) == NULL((void*)0)) goto eoferr;
312 /* Check if the key already expired. */
313 if (expiretime != -1 && expiretime < now)
314 rdbstate.already_expired++;
315 if (expiretime != -1) rdbstate.expires++;
316 rdbstate.key = NULL((void*)0);
317 decrRefCount(key);
318 decrRefCount(val);
319 rdbstate.key_type = -1;
320 expiretime = -1;
321 }
322 /* Verify the checksum if RDB version is >= 5 */
323 if (rdbver >= 5 && server.rdb_checksum) {
324 uint64_t cksum, expected = rdb.cksum;
325
326 rdbstate.doing = RDB_CHECK_DOING_CHECK_SUM5;
327 if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;
328 memrev64ifbe(&cksum)((void)(0));
329 if (cksum == 0) {
330 rdbCheckInfo("RDB file was saved with checksum disabled: no check performed.");
331 } else if (cksum != expected) {
332 rdbCheckError("RDB CRC error");
333 goto err;
334 } else {
335 rdbCheckInfo("Checksum OK");
336 }
337 }
338
339 if (closefile) fclose(fp);
340 stopLoading(1);
341 return 0;
342
343eoferr: /* unexpected end of file is handled here with a fatal exit */
344 if (rdbstate.error_set) {
345 rdbCheckError(rdbstate.error);
346 } else {
347 rdbCheckError("Unexpected EOF reading RDB file");
348 }
349err:
350 if (closefile) fclose(fp);
351 stopLoading(0);
352 return 1;
353}
354
355/* RDB check main: called form server.c when Redis is executed with the
356 * redis-check-rdb alias, on during RDB loading errors.
357 *
358 * The function works in two ways: can be called with argc/argv as a
359 * standalone executable, or called with a non NULL 'fp' argument if we
360 * already have an open file to check. This happens when the function
361 * is used to check an RDB preamble inside an AOF file.
362 *
363 * When called with fp = NULL, the function never returns, but exits with the
364 * status code according to success (RDB is sane) or error (RDB is corrupted).
365 * Otherwise if called with a non NULL fp, the function returns C_OK or
366 * C_ERR depending on the success or failure. */
367int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
368 struct timeval tv;
369
370 if (argc != 2 && fp == NULL((void*)0)) {
371 fprintf(stderrstderr, "Usage: %s <rdb-file-name>\n", argv[0]);
372 exit(1);
373 }
374
375 gettimeofday(&tv, NULL((void*)0));
376 init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
377
378 /* In order to call the loading functions we need to create the shared
379 * integer objects, however since this function may be called from
380 * an already initialized Redis instance, check if we really need to. */
381 if (shared.integers[0] == NULL((void*)0))
382 createSharedObjects();
383 server.loading_process_events_interval_bytes = 0;
384 server.sanitize_dump_payload = SANITIZE_DUMP_YES1;
385 rdbCheckMode = 1;
386 rdbCheckInfo("Checking RDB file %s", argv[1]);
387 rdbCheckSetupSignals();
388 int retval = redis_check_rdb(argv[1],fp);
389 if (retval == 0) {
390 rdbCheckInfo("\\o/ RDB looks OK! \\o/");
391 rdbShowGenericInfo();
392 }
393 if (fp) return (retval == 0) ? C_OK0 : C_ERR-1;
394 exit(retval);
395}