diff options
| author | 2026-01-21 12:24:52 +0100 | |
|---|---|---|
| committer | 2026-01-21 12:26:13 +0100 | |
| commit | c0f9df912d2a4001e56321fb53615e6474b32232 (patch) | |
| tree | ce3d46fa9ac9e173d2f86451037d1456205c067f | |
| 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.
| -rw-r--r-- | src/jplist.c | 23 | ||||
| -rw-r--r-- | src/jsmn.c | 43 | ||||
| -rw-r--r-- | src/jsmn.h | 18 |
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 return NULL; } plist_t arr = plist_new_array(); - int num_tokens = ti->tokens[*index].size; - int num; + size_t num_tokens = ti->tokens[*index].size; + size_t num; int j = (*index)+1; for (num = 0; num < num_tokens; num++) { if (j >= ti->count) { @@ -770,8 +770,8 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint ti->err = PLIST_ERR_MAX_NESTING; return NULL; } - int num_tokens = ti->tokens[*index].size; - int num; + size_t num_tokens = ti->tokens[*index].size; + size_t num; int j = (*index)+1; if (num_tokens % 2 != 0) { 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) jsmn_parser parser; jsmn_init(&parser); - int maxtoks = 256; - int curtoks = 0; + unsigned int maxtoks = 256; + unsigned int curtoks = 0; int r = 0; jsmntok_t *tokens = NULL; do { jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks); if (!newtokens) { + free(tokens); PLIST_JSON_ERR("%s: Out of memory\n", __func__); return PLIST_ERR_NO_MEM; } @@ -861,8 +862,14 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist) r = jsmn_parse(&parser, json, length, tokens, maxtoks); if (r == JSMN_ERROR_NOMEM) { + if (maxtoks > (unsigned int)INT_MAX - 16) { + free(tokens); + return PLIST_ERR_NO_MEM; + } maxtoks+=16; continue; + } else if (r < 0) { + break; } } while (r == JSMN_ERROR_NOMEM); @@ -879,6 +886,10 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist) PLIST_JSON_ERR("%s: Incomplete JSON, more bytes expected\n", __func__); free(tokens); return PLIST_ERR_PARSE; + case JSMN_ERROR_LIMIT: + PLIST_JSON_ERR("%s: Input data too large\n", __func__); + free(tokens); + return PLIST_ERR_PARSE; default: break; } @@ -3,6 +3,8 @@ * Simple JSON parser * * Copyright (c) 2010 Serge A. Zaitsev + * Updated to use size_t for token offsets and harden against overflows. + * (Nikias Bassen, January 2026) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,20 +26,25 @@ */ #include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <assert.h> #include "jsmn.h" +#define JSMN_POS_INVALID ((size_t)SIZE_MAX) + /** * Allocates a fresh unused token from the token pull. */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, - jsmntok_t *tokens, int num_tokens) { + jsmntok_t *tokens, unsigned int num_tokens) { jsmntok_t *tok; - if (parser->toknext >= num_tokens) { + if ((unsigned int)parser->toknext >= num_tokens) { return NULL; } tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; + tok->start = tok->end = JSMN_POS_INVALID; tok->size = 0; #ifdef JSMN_PARENT_LINKS tok->parent = -1; @@ -49,7 +56,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, * Fills token type and boundaries. */ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, - int start, int end) { + size_t start, size_t end) { token->type = type; token->start = start; token->end = end; @@ -60,9 +67,9 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, * Fills next available token with JSON primitive. */ static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, int num_tokens) { + jsmntok_t *tokens, unsigned int num_tokens) { jsmntok_t *token; - int start; + size_t start; start = parser->pos; @@ -107,10 +114,10 @@ found: * Fills next token with JSON string. */ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, int num_tokens) { + jsmntok_t *tokens, unsigned int num_tokens) { jsmntok_t *token; - int start = parser->pos; + size_t start = parser->pos; parser->pos++; @@ -162,7 +169,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /** * Parse JSON string and fill tokens. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, jsmntok_t *tokens, +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length, jsmntok_t *tokens, unsigned int num_tokens) { jsmnerr_t r; int i; @@ -170,6 +177,13 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j parser->end = length; + if (num_tokens >= INT_MAX) { + return JSMN_ERROR_LIMIT; + } + if (length > SIZE_MAX / 2) { + return JSMN_ERROR_LIMIT; + } + for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; @@ -198,10 +212,13 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j } token = &tokens[parser->toknext - 1]; for (;;) { - if (token->start != -1 && token->end == -1) { + if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) { if (token->type != type) { return JSMN_ERROR_INVAL; } + if (parser->pos == SIZE_MAX) { + return JSMN_ERROR_INVAL; + } token->end = parser->pos + 1; parser->toksuper = token->parent; break; @@ -214,7 +231,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j #else for (i = parser->toknext - 1; i >= 0; i--) { token = &tokens[i]; - if (token->start != -1 && token->end == -1) { + if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) { if (token->type != type) { return JSMN_ERROR_INVAL; } @@ -227,7 +244,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j if (i == -1) return JSMN_ERROR_INVAL; for (; i >= 0; i--) { token = &tokens[i]; - if (token->start != -1 && token->end == -1) { + if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) { parser->toksuper = i; break; } @@ -268,7 +285,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, j for (i = parser->toknext - 1; i >= 0; i--) { /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { + if (tokens[i].start != JSMN_POS_INVALID && tokens[i].end == JSMN_POS_INVALID) { return JSMN_ERROR_PART; } } @@ -3,6 +3,8 @@ * Simple JSON parser (header file) * * Copyright (c) 2010 Serge A. Zaitsev + * Updated to use size_t for token offsets and harden against overflows. + * (Nikias Bassen, January 2026) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,6 +27,8 @@ #ifndef __JSMN_H_ #define __JSMN_H_ +#include <stddef.h> + /** * JSON type identifier. Basic types are: * o Object @@ -46,6 +50,8 @@ typedef enum { JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3, + /* Input exceeds implementation-defined limits */ + JSMN_ERROR_LIMIT = -4, /* Everything was fine */ JSMN_SUCCESS = 0 } jsmnerr_t; @@ -58,9 +64,9 @@ typedef enum { */ typedef struct { jsmntype_t type; - int start; - int end; - int size; + size_t start; + size_t end; + size_t size; #ifdef JSMN_PARENT_LINKS int parent; #endif @@ -71,8 +77,8 @@ typedef struct { * the string being parsed now and current position in that string */ typedef struct { - unsigned int pos; /* offset in the JSON string */ - unsigned int end; /* offset after last character of JSON string */ + size_t pos; /* offset in the JSON string */ + size_t end; /* offset after last character of JSON string */ int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g parent object or array */ } jsmn_parser; @@ -86,7 +92,7 @@ void jsmn_init(jsmn_parser *parser); * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, unsigned int length, +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length, jsmntok_t *tokens, unsigned int num_tokens); #endif /* __JSMN_H_ */ |
