diff options
| author | 2026-01-21 12:24:52 +0100 | |
|---|---|---|
| committer | 2026-01-21 12:26:13 +0100 | |
| commit | c0f9df912d2a4001e56321fb53615e6474b32232 (patch) | |
| tree | ce3d46fa9ac9e173d2f86451037d1456205c067f /src/jsmn.c | |
| parent | c18d6b323e8121c041e8b88d2ea6b6e85ca41274 (diff) | |
| download | libplist-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.
Diffstat (limited to 'src/jsmn.c')
| -rw-r--r-- | src/jsmn.c | 43 |
1 files changed, 30 insertions, 13 deletions
| @@ -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 | */ |
| 33 | static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, | 40 | static 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 | */ |
| 51 | static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, | 58 | static 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 | */ |
| 62 | static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, | 69 | static 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 | */ |
| 109 | static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, | 116 | static 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 | */ |
| 165 | jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, jsmntok_t *tokens, | 172 | jsmnerr_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 | } |
