xref: /dragonfly/contrib/pam_passwdqc/passwdqc_filter.c (revision 44624ade5c7148822b0294730bae4f64e40124db)
1 /*
2  * Copyright (c) 2020,2021 by Solar Designer
3  * See LICENSE
4  */
5 
6 #define _FILE_OFFSET_BITS 64
7 #define _LARGEFILE_SOURCE 1
8 #define _LARGEFILE64_SOURCE 1
9 #define _LARGE_FILES 1
10 
11 #ifdef _MSC_VER
12 #define _CRT_NONSTDC_NO_WARNINGS /* we use POSIX function names */
13 #define _CRT_SECURE_NO_WARNINGS /* we use open() */
14 #include <stdio.h> /* for SEEK_SET and SEEK_END */
15 #include <io.h>
16 #define lseek _lseeki64
17 #define ssize_t int /* MSVC's read() returns int and we don't need more here */
18 #define SSIZE_MAX INT_MAX
19 #define OPEN_FLAGS (O_RDONLY | _O_BINARY | _O_RANDOM)
20 #else
21 #include <unistd.h>
22 #define OPEN_FLAGS O_RDONLY
23 #endif
24 
25 #include <stdint.h>
26 #include <limits.h>
27 #include <fcntl.h>
28 
29 #include "passwdqc.h"
30 #define PASSWDQC_FILTER_INTERNALS
31 #include "passwdqc_filter.h"
32 
read_loop(int fd,void * buffer,size_t count)33 static ssize_t read_loop(int fd, void *buffer, size_t count)
34 {
35           ssize_t offset, block;
36 
37           offset = 0;
38           while (count > 0 && count <= SSIZE_MAX) {
39                     block = read(fd, (char *)buffer + offset, count);
40 
41                     if (block < 0)
42                               return block;
43                     if (!block)
44                               return offset;
45 
46                     offset += block;
47                     count -= block;
48           }
49 
50           return offset;
51 }
52 
passwdqc_filter_open(passwdqc_filter_t * flt,const char * filename)53 int passwdqc_filter_open(passwdqc_filter_t *flt, const char *filename)
54 {
55           if ((flt->fd = open(filename, OPEN_FLAGS)) < 0)
56                     return -1;
57 
58           if (read_loop(flt->fd, &flt->header, sizeof(flt->header)) != sizeof(flt->header) ||
59               passwdqc_filter_verify_header(&flt->header) ||
60               flt->header.hash_id < PASSWDQC_FILTER_HASH_MIN || flt->header.hash_id > PASSWDQC_FILTER_HASH_MAX ||
61               (size_t)lseek(flt->fd, 0, SEEK_END) != sizeof(flt->header) + (flt->header.capacity << 2)) {
62                     passwdqc_filter_close(flt);
63                     return -1;
64           }
65 
66           return 0;
67 }
68 
passwdqc_filter_close(passwdqc_filter_t * flt)69 int passwdqc_filter_close(passwdqc_filter_t *flt)
70 {
71           int retval = close(flt->fd);
72           flt->fd = -1;
73           return retval;
74 }
75 
check(const passwdqc_filter_t * flt,passwdqc_filter_i_t i,passwdqc_filter_f_t f)76 static int check(const passwdqc_filter_t *flt, passwdqc_filter_i_t i, passwdqc_filter_f_t f)
77 {
78           int retval = -1;
79 
80           passwdqc_filter_packed_t p;
81           if (lseek(flt->fd, sizeof(flt->header) + (uint64_t)i * sizeof(p), SEEK_SET) < 0 ||
82               read_loop(flt->fd, &p, sizeof(p)) != sizeof(p))
83                     goto out;
84 
85           passwdqc_filter_unpacked_t u;
86           unsigned int n = (unsigned int)passwdqc_filter_unpack(&u, &p, NULL);
87           if (n > flt->header.bucket_size)
88                     goto out;
89 
90           unsigned int j;
91           for (j = 0; j < n; j++) {
92                     if (passwdqc_filter_f_eq(u.slots[j], f, flt->header.bucket_size)) {
93                               retval = 1;
94                               goto out;
95                     }
96           }
97 
98           retval = (n < flt->header.threshold) ? 0 : 2;
99 
100 out:
101           _passwdqc_memzero(&p, sizeof(p));
102           _passwdqc_memzero(&u, sizeof(u));
103           return retval;
104 }
105 
passwdqc_filter_lookup(const passwdqc_filter_t * flt,const char * plaintext)106 int passwdqc_filter_lookup(const passwdqc_filter_t *flt, const char *plaintext)
107 {
108           int retval = 3;
109           passwdqc_filter_hash_t h;
110           passwdqc_filter_f_t ftest;
111 
112 clean:
113           switch (flt->header.hash_id) {
114           case PASSWDQC_FILTER_HASH_MD4:
115                     passwdqc_filter_md4(&h, plaintext);
116                     ftest = 0x8c6420f439de2000ULL;
117                     break;
118           case PASSWDQC_FILTER_HASH_NTLM_CP1252:
119                     passwdqc_filter_ntlm_cp1252(&h, plaintext);
120                     ftest = 0x26bd9256ff7e052eULL;
121                     break;
122           default:
123                     return -1;
124           }
125 
126           uint32_t nbuckets = (uint32_t)(flt->header.capacity >> 2);
127           passwdqc_filter_i_t i = passwdqc_filter_h2i(&h, nbuckets);
128           passwdqc_filter_f_t f = passwdqc_filter_h2f(&h);
129 
130           _passwdqc_memzero(&h, sizeof(h));
131 
132 /*
133  * The tests of i and f here not only self-test the code, but also prevent the
134  * compiler from moving "return retval;" to before the computation of h, i, and
135  * f, which would leave sensitive data from the real hash computation around.
136  */
137           if (i >= nbuckets)
138                     return -1;
139 
140           if (retval <= 1) {
141 /* Waste two syscalls on overwriting lseek()'s stack and current file offset */
142                     i = passwdqc_filter_h2i(&h, nbuckets); /* 0 */
143                     if (check(flt, i, f) < 0)
144                               return -1;
145                     if (f != ftest)
146                               return -1;
147                     return retval;
148           }
149 
150 /* At least 1 character to overwrite passwdqc_filter_ntlm_cp1252()'s buffer */
151           plaintext = " 09AZaz\x7e\x7f\x80\x81\x9e\x9f\xa0\xff";
152 
153           retval = check(flt, i, f);
154           if (retval <= 1)
155                     goto clean;
156 
157           retval = check(flt, passwdqc_filter_alti(i, f, nbuckets), f);
158           if (retval == 2)
159                     retval = 0;
160           goto clean;
161 }
162