summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2026-01-21 12:24:52 +0100
committerGravatar Nikias Bassen2026-01-21 12:26:13 +0100
commitc0f9df912d2a4001e56321fb53615e6474b32232 (patch)
treece3d46fa9ac9e173d2f86451037d1456205c067f
parentc18d6b323e8121c041e8b88d2ea6b6e85ca41274 (diff)
downloadlibplist-c0f9df912d2a4001e56321fb53615e6474b32232.tar.gz
libplist-c0f9df912d2a4001e56321fb53615e6474b32232.tar.bz2
jsmn: use size_t for token offsets and harden against overflow
Use size_t for token start/end offsets instead of int, replace the -1 sentinel with SIZE_MAX, and add a defensive guard against offset wraparound. This prevents overflow when parsing very large JSON inputs. This addresses issue #282. Credit to @ylwango613 for repporting.
-rw-r--r--src/jplist.c23
-rw-r--r--src/jsmn.c43
-rw-r--r--src/jsmn.h18
3 files changed, 59 insertions, 25 deletions
diff --git a/src/jplist.c b/src/jplist.c
index 9a40844..2c88756 100644
--- a/src/jplist.c
+++ b/src/jplist.c
@@ -719,8 +719,8 @@ static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index, uint3
719 return NULL; 719 return NULL;
720 } 720 }
721 plist_t arr = plist_new_array(); 721 plist_t arr = plist_new_array();
722 int num_tokens = ti->tokens[*index].size; 722 size_t num_tokens = ti->tokens[*index].size;
723 int num; 723 size_t num;
724 int j = (*index)+1; 724 int j = (*index)+1;
725 for (num = 0; num < num_tokens; num++) { 725 for (num = 0; num < num_tokens; num++) {
726 if (j >= ti->count) { 726 if (j >= ti->count) {
@@ -770,8 +770,8 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint
770 ti->err = PLIST_ERR_MAX_NESTING; 770 ti->err = PLIST_ERR_MAX_NESTING;
771 return NULL; 771 return NULL;
772 } 772 }
773 int num_tokens = ti->tokens[*index].size; 773 size_t num_tokens = ti->tokens[*index].size;
774 int num; 774 size_t num;
775 int j = (*index)+1; 775 int j = (*index)+1;
776 if (num_tokens % 2 != 0) { 776 if (num_tokens % 2 != 0) {
777 PLIST_JSON_ERR("%s: number of children must be even\n", __func__); 777 PLIST_JSON_ERR("%s: number of children must be even\n", __func__);
@@ -844,14 +844,15 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist)
844 844
845 jsmn_parser parser; 845 jsmn_parser parser;
846 jsmn_init(&parser); 846 jsmn_init(&parser);
847 int maxtoks = 256; 847 unsigned int maxtoks = 256;
848 int curtoks = 0; 848 unsigned int curtoks = 0;
849 int r = 0; 849 int r = 0;
850 jsmntok_t *tokens = NULL; 850 jsmntok_t *tokens = NULL;
851 851
852 do { 852 do {
853 jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks); 853 jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks);
854 if (!newtokens) { 854 if (!newtokens) {
855 free(tokens);
855 PLIST_JSON_ERR("%s: Out of memory\n", __func__); 856 PLIST_JSON_ERR("%s: Out of memory\n", __func__);
856 return PLIST_ERR_NO_MEM; 857 return PLIST_ERR_NO_MEM;
857 } 858 }
@@ -861,8 +862,14 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist)
861 862
862 r = jsmn_parse(&parser, json, length, tokens, maxtoks); 863 r = jsmn_parse(&parser, json, length, tokens, maxtoks);
863 if (r == JSMN_ERROR_NOMEM) { 864 if (r == JSMN_ERROR_NOMEM) {
865 if (maxtoks > (unsigned int)INT_MAX - 16) {
866 free(tokens);
867 return PLIST_ERR_NO_MEM;
868 }
864 maxtoks+=16; 869 maxtoks+=16;
865 continue; 870 continue;
871 } else if (r < 0) {
872 break;
866 } 873 }
867 } while (r == JSMN_ERROR_NOMEM); 874 } while (r == JSMN_ERROR_NOMEM);
868 875
@@ -879,6 +886,10 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist)
879 PLIST_JSON_ERR("%s: Incomplete JSON, more bytes expected\n", __func__); 886 PLIST_JSON_ERR("%s: Incomplete JSON, more bytes expected\n", __func__);
880 free(tokens); 887 free(tokens);
881 return PLIST_ERR_PARSE; 888 return PLIST_ERR_PARSE;
889 case JSMN_ERROR_LIMIT:
890 PLIST_JSON_ERR("%s: Input data too large\n", __func__);
891 free(tokens);
892 return PLIST_ERR_PARSE;
882 default: 893 default:
883 break; 894 break;
884 } 895 }
diff --git a/src/jsmn.c b/src/jsmn.c
index f190312..889b8d7 100644
--- a/src/jsmn.c
+++ b/src/jsmn.c
@@ -3,6 +3,8 @@
3 * Simple JSON parser 3 * Simple JSON parser
4 * 4 *
5 * Copyright (c) 2010 Serge A. Zaitsev 5 * Copyright (c) 2010 Serge A. Zaitsev
6 * Updated to use size_t for token offsets and harden against overflows.
7 * (Nikias Bassen, January 2026)
6 * 8 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy 9 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal 10 * of this software and associated documentation files (the "Software"), to deal
@@ -24,20 +26,25 @@
24 */ 26 */
25 27
26#include <stdlib.h> 28#include <stdlib.h>
29#include <stdint.h>
30#include <limits.h>
31#include <assert.h>
27 32
28#include "jsmn.h" 33#include "jsmn.h"
29 34
35#define JSMN_POS_INVALID ((size_t)SIZE_MAX)
36
30/** 37/**
31 * Allocates a fresh unused token from the token pull. 38 * Allocates a fresh unused token from the token pull.
32 */ 39 */
33static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, 40static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
34 jsmntok_t *tokens, int num_tokens) { 41 jsmntok_t *tokens, unsigned int num_tokens) {
35 jsmntok_t *tok; 42 jsmntok_t *tok;
36 if (parser->toknext >= num_tokens) { 43 if ((unsigned int)parser->toknext >= num_tokens) {
37 return NULL; 44 return NULL;
38 } 45 }
39 tok = &tokens[parser->toknext++]; 46 tok = &tokens[parser->toknext++];
40 tok->start = tok->end = -1; 47 tok->start = tok->end = JSMN_POS_INVALID;
41 tok->size = 0; 48 tok->size = 0;
42#ifdef JSMN_PARENT_LINKS 49#ifdef JSMN_PARENT_LINKS
43 tok->parent = -1; 50 tok->parent = -1;
@@ -49,7 +56,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
49 * Fills token type and boundaries. 56 * Fills token type and boundaries.
50 */ 57 */
51static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, 58static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
52 int start, int end) { 59 size_t start, size_t end) {
53 token->type = type; 60 token->type = type;
54 token->start = start; 61 token->start = start;
55 token->end = end; 62 token->end = end;
@@ -60,9 +67,9 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
60 * Fills next available token with JSON primitive. 67 * Fills next available token with JSON primitive.
61 */ 68 */
62static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, 69static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
63 jsmntok_t *tokens, int num_tokens) { 70 jsmntok_t *tokens, unsigned int num_tokens) {
64 jsmntok_t *token; 71 jsmntok_t *token;
65 int start; 72 size_t start;
66 73
67 start = parser->pos; 74 start = parser->pos;
68 75
@@ -107,10 +114,10 @@ found:
107 * Fills next token with JSON string. 114 * Fills next token with JSON string.
108 */ 115 */
109static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, 116static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
110 jsmntok_t *tokens, int num_tokens) { 117 jsmntok_t *tokens, unsigned int num_tokens) {
111 jsmntok_t *token; 118 jsmntok_t *token;
112 119
113 int start = parser->pos; 120 size_t start = parser->pos;
114 121
115 parser->pos++; 122 parser->pos++;
116 123
@@ -162,7 +169,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
162/** 169/**
163 * Parse JSON string and fill tokens. 170 * Parse JSON string and fill tokens.
164 */ 171 */
165jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, jsmntok_t *tokens, 172jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length, jsmntok_t *tokens,
166 unsigned int num_tokens) { 173 unsigned int num_tokens) {
167 jsmnerr_t r; 174 jsmnerr_t r;
168 int i; 175 int i;
@@ -170,6 +177,13 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j
170 177
171 parser->end = length; 178 parser->end = length;
172 179
180 if (num_tokens >= INT_MAX) {
181 return JSMN_ERROR_LIMIT;
182 }
183 if (length > SIZE_MAX / 2) {
184 return JSMN_ERROR_LIMIT;
185 }
186
173 for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) { 187 for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
174 char c; 188 char c;
175 jsmntype_t type; 189 jsmntype_t type;
@@ -198,10 +212,13 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j
198 } 212 }
199 token = &tokens[parser->toknext - 1]; 213 token = &tokens[parser->toknext - 1];
200 for (;;) { 214 for (;;) {
201 if (token->start != -1 && token->end == -1) { 215 if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
202 if (token->type != type) { 216 if (token->type != type) {
203 return JSMN_ERROR_INVAL; 217 return JSMN_ERROR_INVAL;
204 } 218 }
219 if (parser->pos == SIZE_MAX) {
220 return JSMN_ERROR_INVAL;
221 }
205 token->end = parser->pos + 1; 222 token->end = parser->pos + 1;
206 parser->toksuper = token->parent; 223 parser->toksuper = token->parent;
207 break; 224 break;
@@ -214,7 +231,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j
214#else 231#else
215 for (i = parser->toknext - 1; i >= 0; i--) { 232 for (i = parser->toknext - 1; i >= 0; i--) {
216 token = &tokens[i]; 233 token = &tokens[i];
217 if (token->start != -1 && token->end == -1) { 234 if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
218 if (token->type != type) { 235 if (token->type != type) {
219 return JSMN_ERROR_INVAL; 236 return JSMN_ERROR_INVAL;
220 } 237 }
@@ -227,7 +244,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j
227 if (i == -1) return JSMN_ERROR_INVAL; 244 if (i == -1) return JSMN_ERROR_INVAL;
228 for (; i >= 0; i--) { 245 for (; i >= 0; i--) {
229 token = &tokens[i]; 246 token = &tokens[i];
230 if (token->start != -1 && token->end == -1) { 247 if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
231 parser->toksuper = i; 248 parser->toksuper = i;
232 break; 249 break;
233 } 250 }
@@ -268,7 +285,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j
268 285
269 for (i = parser->toknext - 1; i >= 0; i--) { 286 for (i = parser->toknext - 1; i >= 0; i--) {
270 /* Unmatched opened object or array */ 287 /* Unmatched opened object or array */
271 if (tokens[i].start != -1 && tokens[i].end == -1) { 288 if (tokens[i].start != JSMN_POS_INVALID && tokens[i].end == JSMN_POS_INVALID) {
272 return JSMN_ERROR_PART; 289 return JSMN_ERROR_PART;
273 } 290 }
274 } 291 }
diff --git a/src/jsmn.h b/src/jsmn.h
index 380744d..629a0dd 100644
--- a/src/jsmn.h
+++ b/src/jsmn.h
@@ -3,6 +3,8 @@
3 * Simple JSON parser (header file) 3 * Simple JSON parser (header file)
4 * 4 *
5 * Copyright (c) 2010 Serge A. Zaitsev 5 * Copyright (c) 2010 Serge A. Zaitsev
6 * Updated to use size_t for token offsets and harden against overflows.
7 * (Nikias Bassen, January 2026)
6 * 8 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy 9 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal 10 * of this software and associated documentation files (the "Software"), to deal
@@ -25,6 +27,8 @@
25#ifndef __JSMN_H_ 27#ifndef __JSMN_H_
26#define __JSMN_H_ 28#define __JSMN_H_
27 29
30#include <stddef.h>
31
28/** 32/**
29 * JSON type identifier. Basic types are: 33 * JSON type identifier. Basic types are:
30 * o Object 34 * o Object
@@ -46,6 +50,8 @@ typedef enum {
46 JSMN_ERROR_INVAL = -2, 50 JSMN_ERROR_INVAL = -2,
47 /* The string is not a full JSON packet, more bytes expected */ 51 /* The string is not a full JSON packet, more bytes expected */
48 JSMN_ERROR_PART = -3, 52 JSMN_ERROR_PART = -3,
53 /* Input exceeds implementation-defined limits */
54 JSMN_ERROR_LIMIT = -4,
49 /* Everything was fine */ 55 /* Everything was fine */
50 JSMN_SUCCESS = 0 56 JSMN_SUCCESS = 0
51} jsmnerr_t; 57} jsmnerr_t;
@@ -58,9 +64,9 @@ typedef enum {
58 */ 64 */
59typedef struct { 65typedef struct {
60 jsmntype_t type; 66 jsmntype_t type;
61 int start; 67 size_t start;
62 int end; 68 size_t end;
63 int size; 69 size_t size;
64#ifdef JSMN_PARENT_LINKS 70#ifdef JSMN_PARENT_LINKS
65 int parent; 71 int parent;
66#endif 72#endif
@@ -71,8 +77,8 @@ typedef struct {
71 * the string being parsed now and current position in that string 77 * the string being parsed now and current position in that string
72 */ 78 */
73typedef struct { 79typedef struct {
74 unsigned int pos; /* offset in the JSON string */ 80 size_t pos; /* offset in the JSON string */
75 unsigned int end; /* offset after last character of JSON string */ 81 size_t end; /* offset after last character of JSON string */
76 int toknext; /* next token to allocate */ 82 int toknext; /* next token to allocate */
77 int toksuper; /* superior token node, e.g parent object or array */ 83 int toksuper; /* superior token node, e.g parent object or array */
78} jsmn_parser; 84} jsmn_parser;
@@ -86,7 +92,7 @@ void jsmn_init(jsmn_parser *parser);
86 * Run JSON parser. It parses a JSON data string into and array of tokens, each describing 92 * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
87 * a single JSON object. 93 * a single JSON object.
88 */ 94 */
89jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, 95jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length,
90 jsmntok_t *tokens, unsigned int num_tokens); 96 jsmntok_t *tokens, unsigned int num_tokens);
91 97
92#endif /* __JSMN_H_ */ 98#endif /* __JSMN_H_ */