/src/sudo/lib/util/strtonum.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * SPDX-License-Identifier: ISC |
3 | | * |
4 | | * Copyright (c) 2013-2015, 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | /* |
20 | | * This is an open source non-commercial project. Dear PVS-Studio, please check it. |
21 | | * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
22 | | */ |
23 | | |
24 | | #include <config.h> |
25 | | |
26 | | #include <ctype.h> |
27 | | #include <errno.h> |
28 | | |
29 | | #include <sudo_compat.h> |
30 | | #include <sudo_gettext.h> |
31 | | #include <sudo_util.h> |
32 | | |
33 | | enum strtonum_err { |
34 | | STN_INITIAL, |
35 | | STN_VALID, |
36 | | STN_INVALID, |
37 | | STN_TOOSMALL, |
38 | | STN_TOOBIG |
39 | | }; |
40 | | |
41 | | /* |
42 | | * Convert a string to a number in the range [minval, maxval] |
43 | | * Unlike strtonum(), this returns the first non-digit in endp (if not NULL). |
44 | | */ |
45 | | long long |
46 | | sudo_strtonumx(const char *str, long long minval, long long maxval, char **endp, |
47 | | const char **errstrp) |
48 | 1.00M | { |
49 | 1.00M | enum strtonum_err errval = STN_INITIAL; |
50 | 1.00M | long long lastval, result = 0; |
51 | 1.00M | const char *cp = str; |
52 | 1.00M | int remainder; |
53 | 1.00M | char ch, sign; |
54 | | |
55 | 1.00M | if (minval > maxval) { |
56 | 0 | errval = STN_INVALID; |
57 | 0 | goto done; |
58 | 0 | } |
59 | | |
60 | | /* Trim leading space and check sign, if any. */ |
61 | 1.00M | do { |
62 | 1.00M | ch = *cp++; |
63 | 1.00M | } while (isspace((unsigned char)ch)); |
64 | 1.00M | switch (ch) { |
65 | 4.71k | case '-': |
66 | 4.71k | sign = '-'; |
67 | 4.71k | ch = *cp++; |
68 | 4.71k | break; |
69 | 894 | case '+': |
70 | 894 | ch = *cp++; |
71 | 894 | FALLTHROUGH; |
72 | 1.00M | default: |
73 | 1.00M | sign = '+'; |
74 | 1.00M | break; |
75 | 1.00M | } |
76 | | |
77 | | /* |
78 | | * To prevent overflow we determine the highest (or lowest in |
79 | | * the case of negative numbers) value result can have *before* |
80 | | * if its multiplied (divided) by 10 as well as the remainder. |
81 | | * If result matches this value and the next digit is larger than |
82 | | * the remainder, we know the result is out of range. |
83 | | * The remainder is always positive since it is compared against |
84 | | * an unsigned digit. |
85 | | */ |
86 | 1.00M | if (sign == '-') { |
87 | 4.71k | lastval = minval / 10; |
88 | 4.71k | remainder = -(int)(minval % 10); |
89 | 4.71k | if (remainder < 0) { |
90 | 428 | lastval += 1; |
91 | 428 | remainder += 10; |
92 | 428 | } |
93 | 36.6k | for (;; ch = *cp++) { |
94 | 36.6k | if (!isdigit((unsigned char)ch)) |
95 | 4.06k | break; |
96 | 32.5k | ch -= '0'; |
97 | 32.5k | if (result < lastval || (result == lastval && ch > remainder)) { |
98 | | /* Skip remaining digits. */ |
99 | 1.90M | do { |
100 | 1.90M | ch = *cp++; |
101 | 1.90M | } while (isdigit((unsigned char)ch)); |
102 | 648 | errval = STN_TOOSMALL; |
103 | 648 | break; |
104 | 31.9k | } else { |
105 | 31.9k | result *= 10; |
106 | 31.9k | result -= ch; |
107 | 31.9k | errval = STN_VALID; |
108 | 31.9k | } |
109 | 32.5k | } |
110 | 4.71k | if (result > maxval) |
111 | 0 | errval = STN_TOOBIG; |
112 | 1.00M | } else { |
113 | 1.00M | lastval = maxval / 10; |
114 | 1.00M | remainder = (int)(maxval % 10); |
115 | 2.68M | for (;; ch = *cp++) { |
116 | 2.68M | if (!isdigit((unsigned char)ch)) |
117 | 1.00M | break; |
118 | 1.68M | ch -= '0'; |
119 | 1.68M | if (result > lastval || (result == lastval && ch > remainder)) { |
120 | | /* Skip remaining digits. */ |
121 | 2.11M | do { |
122 | 2.11M | ch = *cp++; |
123 | 2.11M | } while (isdigit((unsigned char)ch)); |
124 | 1.27k | errval = STN_TOOBIG; |
125 | 1.27k | break; |
126 | 1.68M | } else { |
127 | 1.68M | result *= 10; |
128 | 1.68M | result += ch; |
129 | 1.68M | errval = STN_VALID; |
130 | 1.68M | } |
131 | 1.68M | } |
132 | 1.00M | if (result < minval) |
133 | 368 | errval = STN_TOOSMALL; |
134 | 1.00M | } |
135 | | |
136 | 1.00M | done: |
137 | 1.00M | switch (errval) { |
138 | 1.47k | case STN_INITIAL: |
139 | 1.00M | case STN_VALID: |
140 | 1.00M | if (errstrp != NULL) |
141 | 1.00M | *errstrp = NULL; |
142 | 1.00M | break; |
143 | 0 | case STN_INVALID: |
144 | 0 | result = 0; |
145 | 0 | errno = EINVAL; |
146 | 0 | if (errstrp != NULL) |
147 | 0 | *errstrp = N_("invalid value"); |
148 | 0 | break; |
149 | 1.01k | case STN_TOOSMALL: |
150 | 1.01k | result = 0; |
151 | 1.01k | errno = ERANGE; |
152 | 1.01k | if (errstrp != NULL) |
153 | 1.01k | *errstrp = N_("value too small"); |
154 | 1.01k | break; |
155 | 1.27k | case STN_TOOBIG: |
156 | 1.27k | result = 0; |
157 | 1.27k | errno = ERANGE; |
158 | 1.27k | if (errstrp != NULL) |
159 | 1.27k | *errstrp = N_("value too large"); |
160 | 1.27k | break; |
161 | 1.00M | } |
162 | 1.00M | if (endp != NULL) { |
163 | 1.00M | if (errval == STN_INITIAL || errval == STN_INVALID) |
164 | 1.47k | *endp = (char *)str; |
165 | 1.00M | else |
166 | 1.00M | *endp = (char *)(cp - 1); |
167 | 1.00M | } |
168 | 1.00M | return result; |
169 | 1.00M | } |
170 | | |
171 | | /* |
172 | | * Convert a string to a number in the range [minval, maxval] |
173 | | */ |
174 | | long long |
175 | | sudo_strtonum(const char *str, long long minval, long long maxval, |
176 | | const char **errstrp) |
177 | 78.7k | { |
178 | 78.7k | const char *errstr; |
179 | 78.7k | char *ep; |
180 | 78.7k | long long ret; |
181 | | |
182 | 78.7k | ret = sudo_strtonumx(str, minval, maxval, &ep, &errstr); |
183 | | /* Check for empty string and terminating NUL. */ |
184 | 78.7k | if (str == ep || *ep != '\0') { |
185 | 1.70k | errno = EINVAL; |
186 | 1.70k | errstr = N_("invalid value"); |
187 | 1.70k | ret = 0; |
188 | 1.70k | } |
189 | 78.7k | if (errstrp != NULL) |
190 | 77.0k | *errstrp = errstr; |
191 | 78.7k | return ret; |
192 | 78.7k | } |