Bug Summary

File:src/lib/libskey/skeylogin.c
Warning:line 446, column 3
Value stored to 'ptr' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name skeylogin.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 pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/lib/libskey/obj -resource-dir /usr/local/lib/clang/13.0.0 -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/lib/libskey/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/lib/libskey/skeylogin.c
1/* OpenBSD S/Key (skeylogin.c)
2 *
3 * Authors:
4 * Neil M. Haller <nmh@thumper.bellcore.com>
5 * Philip R. Karn <karn@chicago.qualcomm.com>
6 * John S. Walden <jsw@thumper.bellcore.com>
7 * Scott Chasin <chasin@crimelab.com>
8 * Todd C. Miller <millert@openbsd.org>
9 * Angelos D. Keromytis <adk@adk.gr>
10 *
11 * S/Key verification check, lookups, and authentication.
12 *
13 * $OpenBSD: skeylogin.c,v 1.62 2019/01/25 00:19:26 millert Exp $
14 */
15
16#ifdef QUOTA
17#include <sys/quota.h>
18#endif
19#include <sys/stat.h>
20#include <sys/time.h>
21#include <sys/resource.h>
22
23#include <ctype.h>
24#include <err.h>
25#include <errno(*__errno()).h>
26#include <fcntl.h>
27#include <paths.h>
28#include <poll.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33#include <unistd.h>
34#include <limits.h>
35#include <sha1.h>
36
37#include "skey.h"
38
39static void skey_fakeprompt(char *, char *);
40static char *tgetline(int, char *, size_t, int);
41static int skeygetent(int, struct skey *, const char *);
42
43/*
44 * Return an skey challenge string for user 'name'. If successful,
45 * fill in the caller's skey structure and return (0). If unsuccessful
46 * (e.g., if name is unknown) return (-1).
47 *
48 * The file read/write pointer is left at the start of the record.
49 */
50int
51skeychallenge2(int fd, struct skey *mp, char *name, char *ss)
52{
53 int rval;
54
55 memset(mp, 0, sizeof(*mp));
56 rval = skeygetent(fd, mp, name);
57
58 switch (rval) {
59 case 0: /* Lookup succeeded, return challenge */
60 (void)snprintf(ss, SKEY_MAX_CHALLENGE(11 + 6 + 16),
61 "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN6,
62 skey_get_algorithm(), mp->n - 1,
63 SKEY_MAX_SEED_LEN16, mp->seed);
64 return (0);
65
66 case 1: /* User not found */
67 if (mp->keyfile) {
68 (void)fclose(mp->keyfile);
69 mp->keyfile = NULL((void *)0);
70 }
71 /* FALLTHROUGH */
72
73 default: /* File error */
74 skey_fakeprompt(name, ss);
75 return (-1);
76 }
77}
78
79int
80skeychallenge(struct skey *mp, char *name, char *ss)
81{
82 return (skeychallenge2(-1, mp, name, ss));
83}
84
85/*
86 * Get an entry in the One-time Password database and lock it.
87 *
88 * Return codes:
89 * -1: error in opening database or unable to lock entry
90 * 0: entry found, file R/W pointer positioned at beginning of record
91 * 1: entry not found
92 */
93static int
94skeygetent(int fd, struct skey *mp, const char *name)
95{
96 char *cp, filename[PATH_MAX1024], *last;
97 struct stat statbuf;
98 const char *errstr;
99 size_t nread;
100 FILE *keyfile;
101
102 /* Check to see that /etc/skey has not been disabled. */
103 if (stat(_PATH_SKEYDIR"/etc/skey", &statbuf) != 0)
104 return (-1);
105 if ((statbuf.st_mode & ALLPERMS(0004000|0002000|0001000|0000700|0000070|0000007)) == 0) {
106 errno(*__errno()) = EPERM1;
107 return (-1);
108 }
109
110 if (fd == -1) {
111 /* Open the user's databse entry, creating it as needed. */
112 if (snprintf(filename, sizeof(filename), "%s/%s", _PATH_SKEYDIR"/etc/skey",
113 name) >= sizeof(filename)) {
114 errno(*__errno()) = ENAMETOOLONG63;
115 return (-1);
116 }
117 if ((fd = open(filename, O_RDWR0x0002 | O_NOFOLLOW0x0100 | O_NONBLOCK0x0004,
118 S_IRUSR0000400 | S_IWUSR0000200)) == -1) {
119 if (errno(*__errno()) == ENOENT2)
120 goto not_found;
121 return (-1);
122 }
123 }
124
125 /* Lock and stat the user's skey file. */
126 if (flock(fd, LOCK_EX0x02) != 0 || fstat(fd, &statbuf) != 0) {
127 close(fd);
128 return (-1);
129 }
130 if (statbuf.st_size == 0)
131 goto not_found;
132
133 /* Sanity checks. */
134 if ((statbuf.st_mode & ALLPERMS(0004000|0002000|0001000|0000700|0000070|0000007)) != (S_IRUSR0000400 | S_IWUSR0000200) ||
135 !S_ISREG(statbuf.st_mode)((statbuf.st_mode & 0170000) == 0100000) || statbuf.st_nlink != 1 ||
136 (keyfile = fdopen(fd, "r+")) == NULL((void *)0)) {
137 close(fd);
138 return (-1);
139 }
140
141 /* At this point, we are committed. */
142 mp->keyfile = keyfile;
143
144 if ((nread = fread(mp->buf, 1, sizeof(mp->buf), keyfile)) == 0 ||
145 !isspace((unsigned char)mp->buf[nread - 1]))
146 goto bad_keyfile;
147 mp->buf[nread - 1] = '\0';
148
149 if ((mp->logname = strtok_r(mp->buf, " \t\n\r", &last)) == NULL((void *)0) ||
150 strcmp(mp->logname, name) != 0)
151 goto bad_keyfile;
152 if ((cp = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0))
153 goto bad_keyfile;
154 if (skey_set_algorithm(cp) == NULL((void *)0))
155 goto bad_keyfile;
156 if ((cp = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0))
157 goto bad_keyfile;
158 mp->n = strtonum(cp, 0, UINT_MAX(2147483647 *2U +1U), &errstr);
159 if (errstr)
160 goto bad_keyfile;
161 if ((mp->seed = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0))
162 goto bad_keyfile;
163 if ((mp->val = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0))
164 goto bad_keyfile;
165
166 (void)fseek(keyfile, 0L, SEEK_SET0);
167 return (0);
168
169 bad_keyfile:
170 fclose(keyfile);
171 return (-1);
172
173 not_found:
174 /* No existing entry, fill in what we can and return */
175 memset(mp, 0, sizeof(*mp));
176 strlcpy(mp->buf, name, sizeof(mp->buf));
177 mp->logname = mp->buf;
178 if (fd != -1)
179 close(fd);
180 return (1);
181}
182
183/*
184 * Look up an entry in the One-time Password database and lock it.
185 * Zeroes out the passed in struct skey before using it.
186 *
187 * Return codes:
188 * -1: error in opening database or unable to lock entry
189 * 0: entry found, file R/W pointer positioned at beginning of record
190 * 1: entry not found
191 */
192int
193skeylookup(struct skey *mp, char *name)
194{
195 memset(mp, 0, sizeof(*mp));
196 return (skeygetent(-1, mp, name));
197}
198
199/*
200 * Get the next entry in the One-time Password database.
201 *
202 * Return codes:
203 * -1: error in opening database
204 * 0: next entry found and stored in mp
205 * 1: no more entries, keydir is closed.
206 */
207int
208skeygetnext(struct skey *mp)
209{
210 struct dirent entry, *dp;
211 int rval;
212
213 if (mp->keyfile != NULL((void *)0)) {
214 fclose(mp->keyfile);
215 mp->keyfile = NULL((void *)0);
216 }
217
218 /* Open _PATH_SKEYDIR if it exists, else return an error */
219 if (mp->keydir == NULL((void *)0) && (mp->keydir = opendir(_PATH_SKEYDIR"/etc/skey")) == NULL((void *)0))
220 return (-1);
221
222 rval = 1;
223 while ((readdir_r(mp->keydir, &entry, &dp)) == 0 && dp == &entry) {
224 /* Skip dot files and zero-length files. */
225 if (entry.d_name[0] != '.' &&
226 (rval = skeygetent(-1, mp, entry.d_name)) != 1)
227 break;
228 }
229
230 if (dp == NULL((void *)0)) {
231 closedir(mp->keydir);
232 mp->keydir = NULL((void *)0);
233 }
234
235 return (rval);
236}
237
238/*
239 * Verify response to a S/Key challenge.
240 *
241 * Return codes:
242 * -1: Error of some sort; database unchanged
243 * 0: Verify successful, database updated
244 * 1: Verify failed, database unchanged
245 *
246 * The database file is always closed by this call.
247 */
248int
249skeyverify(struct skey *mp, char *response)
250{
251 char key[SKEY_BINKEY_SIZE8], fkey[SKEY_BINKEY_SIZE8];
252 char filekey[SKEY_BINKEY_SIZE8], *cp, *last;
253 size_t nread;
254
255 if (response == NULL((void *)0))
256 goto verify_failure;
257
258 /*
259 * The record should already be locked but lock it again
260 * just to be safe. We don't wait for the lock to become
261 * available since we should already have it...
262 */
263 if (flock(fileno(mp->keyfile)(!__isthreaded ? ((mp->keyfile)->_file) : (fileno)(mp->
keyfile))
, LOCK_EX0x02 | LOCK_NB0x04) != 0)
264 goto verify_failure;
265
266 /* Convert response to binary */
267 rip(response);
268 if (etob(key, response) != 1 && atob8(key, response) != 0)
269 goto verify_failure; /* Neither english words nor ascii hex */
270
271 /* Compute fkey = f(key) */
272 (void)memcpy(fkey, key, sizeof(key));
273 f(fkey);
274
275 /*
276 * Reread the file record NOW in case it has been modified.
277 * The only field we really need to worry about is mp->val.
278 */
279 (void)fseek(mp->keyfile, 0L, SEEK_SET0);
280 if ((nread = fread(mp->buf, 1, sizeof(mp->buf), mp->keyfile)) == 0 ||
281 !isspace((unsigned char)mp->buf[nread - 1]))
282 goto verify_failure;
283 if ((mp->logname = strtok_r(mp->buf, " \t\r\n", &last)) == NULL((void *)0))
284 goto verify_failure;
285 if ((cp = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0))
286 goto verify_failure;
287 if ((cp = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0))
288 goto verify_failure;
289 if ((mp->seed = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0))
290 goto verify_failure;
291 if ((mp->val = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0))
292 goto verify_failure;
293
294 /* Convert file value to hex and compare. */
295 atob8(filekey, mp->val);
296 if (memcmp(filekey, fkey, SKEY_BINKEY_SIZE8) != 0)
297 goto verify_failure; /* Wrong response */
298
299 /*
300 * Update key in database.
301 * XXX - check return values of things that write to disk.
302 */
303 btoa8(mp->val,key);
304 mp->n--;
305 (void)fseek(mp->keyfile, 0L, SEEK_SET0);
306 (void)fprintf(mp->keyfile, "%s\n%s\n%d\n%s\n%s\n", mp->logname,
307 skey_get_algorithm(), mp->n, mp->seed, mp->val);
308 (void)fflush(mp->keyfile);
309 (void)ftruncate(fileno(mp->keyfile)(!__isthreaded ? ((mp->keyfile)->_file) : (fileno)(mp->
keyfile))
, ftello(mp->keyfile));
310 (void)fclose(mp->keyfile);
311 mp->keyfile = NULL((void *)0);
312 return (0);
313
314 verify_failure:
315 (void)fclose(mp->keyfile);
316 mp->keyfile = NULL((void *)0);
317 return (-1);
318}
319
320/*
321 * skey_haskey()
322 *
323 * Returns: 1 user doesn't exist, -1 file error, 0 user exists.
324 *
325 */
326int
327skey_haskey(char *username)
328{
329 struct skey skey;
330 int i;
331
332 i = skeylookup(&skey, username);
333 if (skey.keyfile != NULL((void *)0)) {
334 fclose(skey.keyfile);
335 skey.keyfile = NULL((void *)0);
336 }
337 return (i);
338}
339
340/*
341 * skey_keyinfo()
342 *
343 * Returns the current sequence number and
344 * seed for the passed user.
345 *
346 */
347char *
348skey_keyinfo(char *username)
349{
350 static char str[SKEY_MAX_CHALLENGE(11 + 6 + 16)];
351 struct skey skey;
352 int i;
353
354 i = skeychallenge(&skey, username, str);
355 if (i == -1)
356 return (0);
357
358 if (skey.keyfile != NULL((void *)0)) {
359 fclose(skey.keyfile);
360 skey.keyfile = NULL((void *)0);
361 }
362 return (str);
363}
364
365/*
366 * skey_passcheck()
367 *
368 * Check to see if answer is the correct one to the current
369 * challenge.
370 *
371 * Returns: 0 success, -1 failure
372 *
373 */
374int
375skey_passcheck(char *username, char *passwd)
376{
377 struct skey skey;
378 int i;
379
380 i = skeylookup(&skey, username);
381 if (i == -1 || i == 1)
382 return (-1);
383
384 if (skeyverify(&skey, passwd) == 0)
385 return (skey.n);
386
387 return (-1);
388}
389
390#define ROUND(x)(((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) <<
8) + ((x)[3]))
(((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \
391 ((x)[3]))
392
393/*
394 * hash_collapse()
395 */
396static u_int32_t
397hash_collapse(u_char *s)
398{
399 int len, target;
400 u_int32_t i;
401
402 if ((strlen(s) % sizeof(u_int32_t)) == 0)
403 target = strlen(s); /* Multiple of 4 */
404 else
405 target = strlen(s) - (strlen(s) % sizeof(u_int32_t));
406
407 for (i = 0, len = 0; len < target; len += 4)
408 i ^= ROUND(s + len)(((s + len)[0] << 24) + (((s + len)[1]) << 16) + (
((s + len)[2]) << 8) + ((s + len)[3]))
;
409
410 return i;
411}
412
413/*
414 * skey_fakeprompt()
415 *
416 * Generate a fake prompt for the specified user.
417 *
418 */
419static void
420skey_fakeprompt(char *username, char *skeyprompt)
421{
422 char secret[SKEY_MAX_SEED_LEN16], pbuf[SKEY_MAX_PW_LEN255+1], *p, *u;
423 u_char *up;
424 SHA1_CTX ctx;
425 u_int ptr;
426 int i;
427
428 /*
429 * Base first 4 chars of seed on hostname.
430 * Add some filler for short hostnames if necessary.
431 */
432 if (gethostname(pbuf, sizeof(pbuf)) == -1)
433 *(p = pbuf) = '.';
434 else
435 for (p = pbuf; isalnum((unsigned char)*p); p++)
436 if (isalpha((unsigned char)*p) &&
437 isupper((unsigned char)*p))
438 *p = (char)tolower((unsigned char)*p);
439 if (*p && pbuf - p < 4)
440 (void)strncpy(p, "asjd", 4 - (pbuf - p));
441 pbuf[4] = '\0';
442
443 /* Hash the username if possible */
444 if ((up = SHA1Data(username, strlen(username), NULL((void *)0))) != NULL((void *)0)) {
445 /* Collapse the hash */
446 ptr = hash_collapse(up);
Value stored to 'ptr' is never read
447 explicit_bzero(up, strlen(up));
448
449 /* Put that in your pipe and smoke it */
450 arc4random_buf(secret, sizeof(secret));
451
452 /* Hash secret value with username */
453 SHA1Init(&ctx);
454 SHA1Update(&ctx, secret, sizeof(secret));
455 SHA1Update(&ctx, username, strlen(username));
456 SHA1End(&ctx, up);
457
458 /* Zero out */
459 explicit_bzero(secret, sizeof(secret));
460
461 /* Now hash the hash */
462 SHA1Init(&ctx);
463 SHA1Update(&ctx, up, strlen(up));
464 SHA1End(&ctx, up);
465
466 ptr = hash_collapse(up + 4);
467
468 for (i = 4; i < 9; i++) {
469 pbuf[i] = (ptr % 10) + '0';
470 ptr /= 10;
471 }
472 pbuf[i] = '\0';
473
474 /* Sequence number */
475 ptr = ((up[2] + up[3]) % 99) + 1;
476
477 freezero(up, 20); /* SHA1 specific */
478
479 (void)snprintf(skeyprompt, SKEY_MAX_CHALLENGE(11 + 6 + 16),
480 "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN6,
481 skey_get_algorithm(), ptr, SKEY_MAX_SEED_LEN16, pbuf);
482 } else {
483 /* Base last 8 chars of seed on username */
484 u = username;
485 i = 8;
486 p = &pbuf[4];
487 do {
488 if (*u == 0) {
489 /* Pad remainder with zeros */
490 while (--i >= 0)
491 *p++ = '0';
492 break;
493 }
494
495 *p++ = (*u++ % 10) + '0';
496 } while (--i != 0);
497 pbuf[12] = '\0';
498
499 (void)snprintf(skeyprompt, SKEY_MAX_CHALLENGE(11 + 6 + 16),
500 "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN6,
501 skey_get_algorithm(), 99, SKEY_MAX_SEED_LEN16, pbuf);
502 }
503}
504
505/*
506 * skey_authenticate()
507 *
508 * Used when calling program will allow input of the user's
509 * response to the challenge.
510 *
511 * Returns: 0 success, -1 failure
512 *
513 */
514int
515skey_authenticate(char *username)
516{
517 char pbuf[SKEY_MAX_PW_LEN255+1], skeyprompt[SKEY_MAX_CHALLENGE(11 + 6 + 16)+1];
518 struct skey skey;
519 int i;
520
521 /* Get the S/Key challenge (may be fake) */
522 i = skeychallenge(&skey, username, skeyprompt);
523 (void)fprintf(stderr(&__sF[2]), "%s\nResponse: ", skeyprompt);
524 (void)fflush(stderr(&__sF[2]));
525
526 /* Time out on user input after 2 minutes */
527 tgetline(fileno(stdin)(!__isthreaded ? (((&__sF[0]))->_file) : (fileno)((&
__sF[0])))
, pbuf, sizeof(pbuf), 120);
528 sevenbit(pbuf);
529 (void)rewind(stdin(&__sF[0]));
530
531 /* Is it a valid response? */
532 if (i == 0 && skeyverify(&skey, pbuf) == 0) {
533 if (skey.n < 5) {
534 (void)fprintf(stderr(&__sF[2]),
535 "\nWarning! Key initialization needed soon. (%d logins left)\n",
536 skey.n);
537 }
538 return (0);
539 }
540 return (-1);
541}
542
543/*
544 * Unlock current entry in the One-time Password database.
545 *
546 * Return codes:
547 * -1: unable to lock the record
548 * 0: record was successfully unlocked
549 */
550int
551skey_unlock(struct skey *mp)
552{
553 if (mp->logname == NULL((void *)0) || mp->keyfile == NULL((void *)0))
554 return (-1);
555
556 return (flock(fileno(mp->keyfile)(!__isthreaded ? ((mp->keyfile)->_file) : (fileno)(mp->
keyfile))
, LOCK_UN0x08));
557}
558
559/*
560 * Get a line of input (optionally timing out) and place it in buf.
561 */
562static char *
563tgetline(int fd, char *buf, size_t bufsiz, int timeout)
564{
565 struct pollfd pfd[1];
566 size_t left;
567 char c, *cp;
568 ssize_t ss;
569 int n;
570
571 if (bufsiz == 0)
572 return (NULL((void *)0)); /* sanity */
573
574 cp = buf;
575 left = bufsiz;
576
577 /*
578 * Timeout of <= 0 means no timeout.
579 */
580 if (timeout > 0) {
581 timeout *= 1000; /* convert to milliseconds */
582
583 pfd[0].fd = fd;
584 pfd[0].events = POLLIN0x0001;
585 while (--left) {
586 /* Poll until we are ready or we time out */
587 while ((n = poll(pfd, 1, timeout)) == -1 &&
588 (errno(*__errno()) == EINTR4 || errno(*__errno()) == EAGAIN35))
589 ;
590 if (n <= 0 ||
591 (pfd[0].revents & (POLLERR0x0008|POLLHUP0x0010|POLLNVAL0x0020)))
592 break; /* timeout or error */
593
594 /* Read a character, exit loop on error, EOF or EOL */
595 ss = read(fd, &c, 1);
596 if (ss != 1 || c == '\n' || c == '\r')
597 break;
598 *cp++ = c;
599 }
600 } else {
601 /* Keep reading until out of space, EOF, error, or newline */
602 while (--left && read(fd, &c, 1) == 1 && c != '\n' && c != '\r')
603 *cp++ = c;
604 }
605 *cp = '\0';
606
607 return (cp == buf ? NULL((void *)0) : buf);
608}