hostapd/src/utils/json.c
Jouni Malinen 1e5506588d JSON: Fix string parsing when \\ escape is at the end of buffer
This would have resulted in reading one octet past the end of the buffer
before rejecting the string.

Signed-off-by: Jouni Malinen <j@w1.fi>
2019-02-11 02:35:29 +02:00

576 lines
12 KiB
C

/*
* JavaScript Object Notation (JSON) parser (RFC7159)
* Copyright (c) 2017, Qualcomm Atheros, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "common.h"
#include "base64.h"
#include "json.h"
#define JSON_MAX_DEPTH 10
#define JSON_MAX_TOKENS 500
void json_escape_string(char *txt, size_t maxlen, const char *data, size_t len)
{
char *end = txt + maxlen;
size_t i;
for (i = 0; i < len; i++) {
if (txt + 4 >= end)
break;
switch (data[i]) {
case '\"':
*txt++ = '\\';
*txt++ = '\"';
break;
case '\\':
*txt++ = '\\';
*txt++ = '\\';
break;
case '\n':
*txt++ = '\\';
*txt++ = 'n';
break;
case '\r':
*txt++ = '\\';
*txt++ = 'r';
break;
case '\t':
*txt++ = '\\';
*txt++ = 't';
break;
default:
if (data[i] >= 32 && data[i] <= 126) {
*txt++ = data[i];
} else {
txt += os_snprintf(txt, end - txt, "\\u%04x",
data[i]);
}
break;
}
}
*txt = '\0';
}
static char * json_parse_string(const char **json_pos, const char *end)
{
const char *pos = *json_pos;
char *str, *spos, *s_end;
size_t max_len, buf_len;
u8 bin[2];
pos++; /* skip starting quote */
max_len = end - pos + 1;
buf_len = max_len > 10 ? 10 : max_len;
str = os_malloc(buf_len);
if (!str)
return NULL;
spos = str;
s_end = str + buf_len;
for (; pos < end; pos++) {
if (buf_len < max_len && s_end - spos < 3) {
char *tmp;
int idx;
idx = spos - str;
buf_len *= 2;
if (buf_len > max_len)
buf_len = max_len;
tmp = os_realloc(str, buf_len);
if (!tmp)
goto fail;
str = tmp;
spos = str + idx;
s_end = str + buf_len;
}
switch (*pos) {
case '\"': /* end string */
*spos = '\0';
/* caller will move to the next position */
*json_pos = pos;
return str;
case '\\':
pos++;
if (pos >= end) {
wpa_printf(MSG_DEBUG,
"JSON: Truncated \\ escape");
goto fail;
}
switch (*pos) {
case '"':
case '\\':
case '/':
*spos++ = *pos;
break;
case 'n':
*spos++ = '\n';
break;
case 'r':
*spos++ = '\r';
break;
case 't':
*spos++ = '\t';
break;
case 'u':
if (end - pos < 5 ||
hexstr2bin(pos + 1, bin, 2) < 0 ||
bin[1] == 0x00) {
wpa_printf(MSG_DEBUG,
"JSON: Invalid \\u escape");
goto fail;
}
if (bin[0] == 0x00) {
*spos++ = bin[1];
} else {
*spos++ = bin[0];
*spos++ = bin[1];
}
pos += 4;
break;
default:
wpa_printf(MSG_DEBUG,
"JSON: Unknown escape '%c'", *pos);
goto fail;
}
break;
default:
*spos++ = *pos;
break;
}
}
fail:
os_free(str);
return NULL;
}
static int json_parse_number(const char **json_pos, const char *end,
int *ret_val)
{
const char *pos = *json_pos;
size_t len;
char *str;
for (; pos < end; pos++) {
if (*pos != '-' && (*pos < '0' || *pos > '9')) {
pos--;
break;
}
}
if (pos == end)
pos--;
if (pos < *json_pos)
return -1;
len = pos - *json_pos + 1;
str = os_malloc(len + 1);
if (!str)
return -1;
os_memcpy(str, *json_pos, len);
str[len] = '\0';
*ret_val = atoi(str);
os_free(str);
*json_pos = pos;
return 0;
}
static int json_check_tree_state(struct json_token *token)
{
if (!token)
return 0;
if (json_check_tree_state(token->child) < 0 ||
json_check_tree_state(token->sibling) < 0)
return -1;
if (token->state != JSON_COMPLETED) {
wpa_printf(MSG_DEBUG,
"JSON: Unexpected token state %d (name=%s type=%d)",
token->state, token->name ? token->name : "N/A",
token->type);
return -1;
}
return 0;
}
static struct json_token * json_alloc_token(unsigned int *tokens)
{
(*tokens)++;
if (*tokens > JSON_MAX_TOKENS) {
wpa_printf(MSG_DEBUG, "JSON: Maximum token limit exceeded");
return NULL;
}
return os_zalloc(sizeof(struct json_token));
}
struct json_token * json_parse(const char *data, size_t data_len)
{
struct json_token *root = NULL, *curr_token = NULL, *token = NULL;
const char *pos, *end;
char *str;
int num;
unsigned int depth = 0;
unsigned int tokens = 0;
pos = data;
end = data + data_len;
for (; pos < end; pos++) {
switch (*pos) {
case '[': /* start array */
case '{': /* start object */
if (!curr_token) {
token = json_alloc_token(&tokens);
if (!token)
goto fail;
if (!root)
root = token;
} else if (curr_token->state == JSON_WAITING_VALUE) {
token = curr_token;
} else if (curr_token->parent &&
curr_token->parent->type == JSON_ARRAY &&
curr_token->parent->state == JSON_STARTED &&
curr_token->state == JSON_EMPTY) {
token = curr_token;
} else {
wpa_printf(MSG_DEBUG,
"JSON: Invalid state for start array/object");
goto fail;
}
depth++;
if (depth > JSON_MAX_DEPTH) {
wpa_printf(MSG_DEBUG,
"JSON: Max depth exceeded");
goto fail;
}
token->type = *pos == '[' ? JSON_ARRAY : JSON_OBJECT;
token->state = JSON_STARTED;
token->child = json_alloc_token(&tokens);
if (!token->child)
goto fail;
curr_token = token->child;
curr_token->parent = token;
curr_token->state = JSON_EMPTY;
break;
case ']': /* end array */
case '}': /* end object */
if (!curr_token || !curr_token->parent ||
curr_token->parent->state != JSON_STARTED) {
wpa_printf(MSG_DEBUG,
"JSON: Invalid state for end array/object");
goto fail;
}
depth--;
curr_token = curr_token->parent;
if ((*pos == ']' &&
curr_token->type != JSON_ARRAY) ||
(*pos == '}' &&
curr_token->type != JSON_OBJECT)) {
wpa_printf(MSG_DEBUG,
"JSON: Array/Object mismatch");
goto fail;
}
if (curr_token->child->state == JSON_EMPTY &&
!curr_token->child->child &&
!curr_token->child->sibling) {
/* Remove pending child token since the
* array/object was empty. */
json_free(curr_token->child);
curr_token->child = NULL;
}
curr_token->state = JSON_COMPLETED;
break;
case '\"': /* string */
str = json_parse_string(&pos, end);
if (!str)
goto fail;
if (!curr_token) {
token = json_alloc_token(&tokens);
if (!token)
goto fail;
token->type = JSON_STRING;
token->string = str;
token->state = JSON_COMPLETED;
} else if (curr_token->parent &&
curr_token->parent->type == JSON_ARRAY &&
curr_token->parent->state == JSON_STARTED &&
curr_token->state == JSON_EMPTY) {
curr_token->string = str;
curr_token->state = JSON_COMPLETED;
curr_token->type = JSON_STRING;
wpa_printf(MSG_MSGDUMP,
"JSON: String value: '%s'",
curr_token->string);
} else if (curr_token->state == JSON_EMPTY) {
curr_token->type = JSON_VALUE;
curr_token->name = str;
curr_token->state = JSON_STARTED;
} else if (curr_token->state == JSON_WAITING_VALUE) {
curr_token->string = str;
curr_token->state = JSON_COMPLETED;
curr_token->type = JSON_STRING;
wpa_printf(MSG_MSGDUMP,
"JSON: String value: '%s' = '%s'",
curr_token->name,
curr_token->string);
} else {
wpa_printf(MSG_DEBUG,
"JSON: Invalid state for a string");
os_free(str);
goto fail;
}
break;
case ' ':
case '\t':
case '\r':
case '\n':
/* ignore whitespace */
break;
case ':': /* name/value separator */
if (!curr_token || curr_token->state != JSON_STARTED)
goto fail;
curr_token->state = JSON_WAITING_VALUE;
break;
case ',': /* member separator */
if (!curr_token)
goto fail;
curr_token->sibling = json_alloc_token(&tokens);
if (!curr_token->sibling)
goto fail;
curr_token->sibling->parent = curr_token->parent;
curr_token = curr_token->sibling;
curr_token->state = JSON_EMPTY;
break;
case 't': /* true */
case 'f': /* false */
case 'n': /* null */
if (!((end - pos >= 4 &&
os_strncmp(pos, "true", 4) == 0) ||
(end - pos >= 5 &&
os_strncmp(pos, "false", 5) == 0) ||
(end - pos >= 4 &&
os_strncmp(pos, "null", 4) == 0))) {
wpa_printf(MSG_DEBUG,
"JSON: Invalid literal name");
goto fail;
}
if (!curr_token) {
token = json_alloc_token(&tokens);
if (!token)
goto fail;
curr_token = token;
} else if (curr_token->state == JSON_WAITING_VALUE) {
wpa_printf(MSG_MSGDUMP,
"JSON: Literal name: '%s' = %c",
curr_token->name, *pos);
} else if (curr_token->parent &&
curr_token->parent->type == JSON_ARRAY &&
curr_token->parent->state == JSON_STARTED &&
curr_token->state == JSON_EMPTY) {
wpa_printf(MSG_MSGDUMP,
"JSON: Literal name: %c", *pos);
} else {
wpa_printf(MSG_DEBUG,
"JSON: Invalid state for a literal name");
goto fail;
}
switch (*pos) {
case 't':
curr_token->type = JSON_BOOLEAN;
curr_token->number = 1;
pos += 3;
break;
case 'f':
curr_token->type = JSON_BOOLEAN;
curr_token->number = 0;
pos += 4;
break;
case 'n':
curr_token->type = JSON_NULL;
pos += 3;
break;
}
curr_token->state = JSON_COMPLETED;
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/* number */
if (json_parse_number(&pos, end, &num) < 0)
goto fail;
if (!curr_token) {
token = json_alloc_token(&tokens);
if (!token)
goto fail;
token->type = JSON_NUMBER;
token->number = num;
token->state = JSON_COMPLETED;
} else if (curr_token->state == JSON_WAITING_VALUE) {
curr_token->number = num;
curr_token->state = JSON_COMPLETED;
curr_token->type = JSON_NUMBER;
wpa_printf(MSG_MSGDUMP,
"JSON: Number value: '%s' = '%d'",
curr_token->name,
curr_token->number);
} else if (curr_token->parent &&
curr_token->parent->type == JSON_ARRAY &&
curr_token->parent->state == JSON_STARTED &&
curr_token->state == JSON_EMPTY) {
curr_token->number = num;
curr_token->state = JSON_COMPLETED;
curr_token->type = JSON_NUMBER;
wpa_printf(MSG_MSGDUMP,
"JSON: Number value: %d",
curr_token->number);
} else {
wpa_printf(MSG_DEBUG,
"JSON: Invalid state for a number");
goto fail;
}
break;
default:
wpa_printf(MSG_DEBUG,
"JSON: Unexpected JSON character: %c", *pos);
goto fail;
}
if (!root)
root = token;
if (!curr_token)
curr_token = token;
}
if (json_check_tree_state(root) < 0) {
wpa_printf(MSG_DEBUG, "JSON: Incomplete token in the tree");
goto fail;
}
return root;
fail:
wpa_printf(MSG_DEBUG, "JSON: Parsing failed");
json_free(root);
return NULL;
}
void json_free(struct json_token *json)
{
if (!json)
return;
json_free(json->child);
json_free(json->sibling);
os_free(json->name);
os_free(json->string);
os_free(json);
}
struct json_token * json_get_member(struct json_token *json, const char *name)
{
struct json_token *token, *ret = NULL;
if (!json || json->type != JSON_OBJECT)
return NULL;
/* Return last matching entry */
for (token = json->child; token; token = token->sibling) {
if (token->name && os_strcmp(token->name, name) == 0)
ret = token;
}
return ret;
}
struct wpabuf * json_get_member_base64url(struct json_token *json,
const char *name)
{
struct json_token *token;
unsigned char *buf;
size_t buflen;
struct wpabuf *ret;
token = json_get_member(json, name);
if (!token || token->type != JSON_STRING)
return NULL;
buf = base64_url_decode((const unsigned char *) token->string,
os_strlen(token->string), &buflen);
if (!buf)
return NULL;
ret = wpabuf_alloc_ext_data(buf, buflen);
if (!ret)
os_free(buf);
return ret;
}
static const char * json_type_str(enum json_type type)
{
switch (type) {
case JSON_VALUE:
return "VALUE";
case JSON_OBJECT:
return "OBJECT";
case JSON_ARRAY:
return "ARRAY";
case JSON_STRING:
return "STRING";
case JSON_NUMBER:
return "NUMBER";
case JSON_BOOLEAN:
return "BOOLEAN";
case JSON_NULL:
return "NULL";
}
return "??";
}
static void json_print_token(struct json_token *token, int depth,
char *buf, size_t buflen)
{
size_t len;
int ret;
if (!token)
return;
len = os_strlen(buf);
ret = os_snprintf(buf + len, buflen - len, "[%d:%s:%s]",
depth, json_type_str(token->type),
token->name ? token->name : "");
if (os_snprintf_error(buflen - len, ret)) {
buf[len] = '\0';
return;
}
json_print_token(token->child, depth + 1, buf, buflen);
json_print_token(token->sibling, depth, buf, buflen);
}
void json_print_tree(struct json_token *root, char *buf, size_t buflen)
{
buf[0] = '\0';
json_print_token(root, 1, buf, buflen);
}