Skip to content

Commit fc1047e

Browse files
committed
common: json_to_s64 accept only valid JSON numbers
bool str_to_s64 parses a decimal s64 from exactly buflen bytes; false on bad chars, overflow, leading +, hex like '0x...' . Similar behavior of json_to_u64 and str_to_u64. Tests added.
1 parent f80895b commit fc1047e

4 files changed

Lines changed: 276 additions & 23 deletions

File tree

common/json_parse_simple.c

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,31 +64,9 @@ bool json_to_u64(const char *buffer, const jsmntok_t *tok, u64 *num)
6464
return str_to_u64(buffer + tok->start, tok->end - tok->start, num);
6565
}
6666

67-
/* Uncommon, we don't optimize these */
6867
bool json_to_s64(const char *buffer, const jsmntok_t *tok, s64 *num)
6968
{
70-
const char *tmpbuf = json_strdup(tmpctx, buffer, tok);
71-
char *end;
72-
long long l;
73-
74-
errno = 0;
75-
l = strtoll(tmpbuf, &end, 0);
76-
if (tmpbuf[0] == '\0' || *end != '\0')
77-
return false;
78-
79-
BUILD_ASSERT(sizeof(l) >= sizeof(*num));
80-
*num = l;
81-
82-
/* Check for overflow/underflow */
83-
if ((l == LONG_MAX || l == LONG_MIN) && errno == ERANGE)
84-
return false;
85-
86-
/* Check if the number did not fit in `s64` (in case `long long`
87-
is a bigger type). */
88-
if (*num != l)
89-
return false;
90-
91-
return true;
69+
return str_to_s64(buffer + tok->start, tok->end - tok->start, num);
9270
}
9371

9472
bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num)

common/test/run-utils-str_to_s64.c

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
#include "config.h"
2+
#include <assert.h>
3+
#include <common/amount.h>
4+
#include <common/pseudorand.h>
5+
#include <common/setup.h>
6+
#include <common/utils.h>
7+
#include <stdio.h>
8+
#include <wire/wire.h>
9+
10+
/* AUTOGENERATED MOCKS START */
11+
/* Generated stub for amount_asset_is_main */
12+
bool amount_asset_is_main(struct amount_asset *asset UNNEEDED)
13+
{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); }
14+
/* Generated stub for amount_asset_to_sat */
15+
struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED)
16+
{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); }
17+
/* Generated stub for amount_feerate */
18+
bool amount_feerate(u32 *feerate UNNEEDED, struct amount_sat fee UNNEEDED, size_t weight UNNEEDED)
19+
{ fprintf(stderr, "amount_feerate called!\n"); abort(); }
20+
/* Generated stub for amount_sat */
21+
struct amount_sat amount_sat(u64 satoshis UNNEEDED)
22+
{ fprintf(stderr, "amount_sat called!\n"); abort(); }
23+
/* Generated stub for amount_sat_add */
24+
bool amount_sat_add(struct amount_sat *val UNNEEDED,
25+
struct amount_sat a UNNEEDED,
26+
struct amount_sat b UNNEEDED)
27+
{ fprintf(stderr, "amount_sat_add called!\n"); abort(); }
28+
/* Generated stub for amount_sat_eq */
29+
bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED)
30+
{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); }
31+
/* Generated stub for amount_sat_greater_eq */
32+
bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED)
33+
{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); }
34+
/* Generated stub for amount_sat_sub */
35+
bool amount_sat_sub(struct amount_sat *val UNNEEDED,
36+
struct amount_sat a UNNEEDED,
37+
struct amount_sat b UNNEEDED)
38+
{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); }
39+
/* Generated stub for amount_sat_to_asset */
40+
struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED)
41+
{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); }
42+
/* Generated stub for amount_tx_fee */
43+
struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED)
44+
{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); }
45+
/* Generated stub for fromwire */
46+
const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED)
47+
{ fprintf(stderr, "fromwire called!\n"); abort(); }
48+
/* Generated stub for fromwire_bool */
49+
bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
50+
{ fprintf(stderr, "fromwire_bool called!\n"); abort(); }
51+
/* Generated stub for fromwire_fail */
52+
void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
53+
{ fprintf(stderr, "fromwire_fail called!\n"); abort(); }
54+
/* Generated stub for fromwire_secp256k1_ecdsa_signature */
55+
void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
56+
secp256k1_ecdsa_signature *signature UNNEEDED)
57+
{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); }
58+
/* Generated stub for fromwire_sha256 */
59+
void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED)
60+
{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); }
61+
/* Generated stub for fromwire_tal_arrn */
62+
u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED,
63+
const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED)
64+
{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); }
65+
/* Generated stub for fromwire_u32 */
66+
u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
67+
{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); }
68+
/* Generated stub for fromwire_u64 */
69+
u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
70+
{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); }
71+
/* Generated stub for fromwire_u8 */
72+
u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
73+
{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); }
74+
/* Generated stub for fromwire_u8_array */
75+
void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED)
76+
{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); }
77+
/* Generated stub for siphash_seed */
78+
const struct siphash_seed *siphash_seed(void)
79+
{ fprintf(stderr, "siphash_seed called!\n"); abort(); }
80+
/* Generated stub for towire */
81+
void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED)
82+
{ fprintf(stderr, "towire called!\n"); abort(); }
83+
/* Generated stub for towire_bool */
84+
void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED)
85+
{ fprintf(stderr, "towire_bool called!\n"); abort(); }
86+
/* Generated stub for towire_secp256k1_ecdsa_signature */
87+
void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED,
88+
const secp256k1_ecdsa_signature *signature UNNEEDED)
89+
{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); }
90+
/* Generated stub for towire_sha256 */
91+
void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED)
92+
{ fprintf(stderr, "towire_sha256 called!\n"); abort(); }
93+
/* Generated stub for towire_u32 */
94+
void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED)
95+
{ fprintf(stderr, "towire_u32 called!\n"); abort(); }
96+
/* Generated stub for towire_u64 */
97+
void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED)
98+
{ fprintf(stderr, "towire_u64 called!\n"); abort(); }
99+
/* Generated stub for towire_u8 */
100+
void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED)
101+
{ fprintf(stderr, "towire_u8 called!\n"); abort(); }
102+
/* Generated stub for towire_u8_array */
103+
void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED)
104+
{ fprintf(stderr, "towire_u8_array called!\n"); abort(); }
105+
/* AUTOGENERATED MOCKS END */
106+
107+
#include <assert.h>
108+
#include <stdint.h>
109+
110+
static void test_str_to_s64(void)
111+
{
112+
s64 val;
113+
114+
/* Empty input */
115+
assert(!str_to_s64("", 0, &val));
116+
117+
/* Single digit */
118+
assert(str_to_s64("0", 1, &val) && val == 0);
119+
assert(str_to_s64("5", 1, &val) && val == 5);
120+
assert(str_to_s64("9", 1, &val) && val == 9);
121+
122+
/* Negative single digit */
123+
assert(str_to_s64("-0", 2, &val) && val == 0);
124+
assert(str_to_s64("-5", 2, &val) && val == -5);
125+
assert(str_to_s64("-9", 2, &val) && val == -9);
126+
127+
/* Multi-digit */
128+
assert(str_to_s64("123", 3, &val) && val == 123);
129+
assert(str_to_s64("-123", 4, &val) && val == -123);
130+
assert(str_to_s64("000", 3, &val) && val == 0);
131+
assert(str_to_s64("000123", 6, &val) && val == 123);
132+
assert(str_to_s64("-000123", 7, &val) && val == -123);
133+
134+
/* Exact length prefix */
135+
assert(str_to_s64("1234", 2, &val) && val == 12);
136+
assert(str_to_s64("-1234", 3, &val) && val == -12);
137+
138+
/* Max positive value (2^63-1) */
139+
assert(str_to_s64("9223372036854775807", 19, &val) &&
140+
val == 9223372036854775807LL);
141+
142+
/* Min negative value (-2^63) */
143+
assert(str_to_s64("-9223372036854775808", 20, &val) &&
144+
val == -9223372036854775807LL - 1);
145+
146+
/* Overflow (positive) */
147+
assert(!str_to_s64("9223372036854775808", 19, &val)); /* 2^63 */
148+
assert(!str_to_s64("92233720368547758080", 20, &val));
149+
150+
/* Overflow (negative) */
151+
assert(!str_to_s64("-9223372036854775809", 20, &val)); /* -2^63 - 1 */
152+
153+
/* Invalid characters */
154+
assert(!str_to_s64("12a3", 4, &val));
155+
assert(!str_to_s64("1.23", 4, &val));
156+
assert(!str_to_s64("123 ", 4, &val));
157+
assert(!str_to_s64(" 123", 4, &val));
158+
assert(!str_to_s64("--123", 5, &val)); /* Multiple signs */
159+
assert(!str_to_s64("+-123", 5, &val)); /* Mixed signs */
160+
161+
/* Non-digit at various positions */
162+
assert(!str_to_s64("x", 1, &val));
163+
assert(!str_to_s64("1x", 2, &val));
164+
assert(!str_to_s64("12x", 3, &val));
165+
assert(!str_to_s64("-x", 2, &val));
166+
assert(!str_to_s64("-1x", 3, &val));
167+
168+
/* Long string of zeros (21) must succeed as 0 */
169+
assert(str_to_s64("000000000000000000000", 21, &val) && val == 0);
170+
assert(str_to_s64("-000000000000000000000", 22, &val) && val == 0);
171+
172+
/* Max value with leading zeros */
173+
assert(str_to_s64("009223372036854775807", 21, &val) &&
174+
val == 9223372036854775807LL);
175+
assert(str_to_s64("-009223372036854775808", 22, &val) &&
176+
val == -9223372036854775807LL - 1);
177+
178+
/* 1 followed by 19 zeros (10^19), must overflow */
179+
assert(!str_to_s64("100000000000000000000", 21, &val));
180+
181+
/* 1 followed by 18 zeros (10^18), fits */
182+
assert(str_to_s64("1000000000000000000", 19, &val) &&
183+
val == 1000000000000000000LL);
184+
185+
/* 999...9 (19 times) overflows */
186+
assert(!str_to_s64("9999999999999999999", 19, &val));
187+
188+
/* Just below INT64_MAX */
189+
assert(str_to_s64("9223372036854775806", 19, &val) &&
190+
val == 9223372036854775806LL);
191+
192+
/* Just above INT64_MIN */
193+
assert(str_to_s64("-9223372036854775807", 20, &val) &&
194+
val == -9223372036854775807LL);
195+
196+
/* Prefix of the max (first 18 chars of the 19-digit max) */
197+
assert(str_to_s64("9223372036854775807", 18, &val) &&
198+
val == 922337203685477580LL);
199+
200+
/* Prefix of the min (first 19 chars of the 20-digit min) */
201+
assert(str_to_s64("-9223372036854775808", 19, &val) &&
202+
val == -922337203685477580LL);
203+
204+
/* Ensure we do not write *num on failure paths */
205+
{
206+
s64 before = 0xdeadbeefcafebabeULL;
207+
val = before;
208+
209+
assert(!str_to_s64("12a3", 4, &val));
210+
assert(val == before);
211+
212+
assert(!str_to_s64("", 0, &val));
213+
assert(val == before);
214+
215+
assert(!str_to_s64("9223372036854775808", 19, &val));
216+
assert(val == before);
217+
218+
assert(!str_to_s64("-9223372036854775809", 20, &val));
219+
assert(val == before);
220+
221+
assert(!str_to_s64("1234567890", 0, &val));
222+
assert(val == before);
223+
}
224+
}
225+
226+
227+
int main(int argc, char *argv[])
228+
{
229+
common_setup(argv[0]);
230+
test_str_to_s64();
231+
common_shutdown();
232+
233+
return 0;
234+
}

common/utils.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,41 @@ bool str_to_u64(const char *buf, size_t buflen, u64 *num)
284284
*num = val;
285285
return true;
286286
}
287+
288+
bool str_to_s64(const char *buf, size_t buflen, s64 *num)
289+
{
290+
s64 val = 0;
291+
bool negative = false;
292+
size_t start = 0;
293+
294+
if (buflen == 0)
295+
return false;
296+
297+
if (buf[0] == '-') {
298+
negative = true;
299+
start = 1;
300+
}
301+
302+
if (start == buflen)
303+
return false;
304+
305+
for (size_t i = start; i < buflen; i++) {
306+
s64 digit;
307+
if (buf[i] < '0' || buf[i] > '9')
308+
return false;
309+
digit = buf[i] - '0';
310+
311+
if (negative) {
312+
if (val < (INT64_MIN + digit) / 10)
313+
return false;
314+
val = val * 10 - digit;
315+
} else {
316+
if (val > (INT64_MAX - digit) / 10)
317+
return false;
318+
val = val * 10 + digit;
319+
}
320+
}
321+
322+
*num = val;
323+
return true;
324+
}

common/utils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ void tal_arr_appendn_(void *p, const void *append TAKES, size_t bytes);
133133
/* Parse a decimal u64 from exactly buflen bytes; false on bad chars or overflow */
134134
bool str_to_u64(const char *buf, size_t buflen, u64 *num);
135135

136+
/* Parse a decimal s64 from exactly buflen bytes; false on bad chars, overflow, or leading + */
137+
bool str_to_s64(const char *buf, size_t buflen, s64 *num);
138+
136139
/* Check for valid UTF-8 */
137140
bool utf8_check(const void *buf, size_t buflen);
138141

0 commit comments

Comments
 (0)