mirror of
https://git.openwrt.org/project/cgi-io.git
synced 2026-01-07 00:52:47 +08:00
Fixes following error found by fuzzer:
ERROR: AddressSanitizer: SEGV on unknown address 0x60c000120000 (pc 0x00000054f64f bp 0x000000000008 sp 0x7ffe4fc2c120 T0)
The signal is caused by a READ memory access.
#0 0x54f64f in postdecode_fields cgi-io/util.c:93:7
#1 0x54f382 in LLVMFuzzerTestOneInput cgi-io/tests/fuzz/test-fuzz.c:36:6
That is caused by reading 1 byte past the size of the buffer.
Signed-off-by: Petr Štetiar <ynezz@true.cz>
287 lines
4.2 KiB
C
287 lines
4.2 KiB
C
#include <ctype.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "util.h"
|
|
|
|
char **
|
|
parse_command(const char *cmdline)
|
|
{
|
|
const char *p = cmdline, *s;
|
|
char **argv = NULL, *out;
|
|
size_t arglen = 0;
|
|
int argnum = 0;
|
|
bool esc;
|
|
|
|
while (isspace(*cmdline))
|
|
cmdline++;
|
|
|
|
for (p = cmdline, s = p, esc = false; p; p++) {
|
|
if (esc) {
|
|
esc = false;
|
|
}
|
|
else if (*p == '\\' && p[1] != 0) {
|
|
esc = true;
|
|
}
|
|
else if (isspace(*p) || *p == 0) {
|
|
if (p > s) {
|
|
argnum += 1;
|
|
arglen += sizeof(char *) + (p - s) + 1;
|
|
}
|
|
|
|
s = p + 1;
|
|
}
|
|
|
|
if (*p == 0)
|
|
break;
|
|
}
|
|
|
|
if (arglen == 0)
|
|
return NULL;
|
|
|
|
argv = calloc(1, arglen + sizeof(char *));
|
|
|
|
if (!argv)
|
|
return NULL;
|
|
|
|
out = (char *)argv + sizeof(char *) * (argnum + 1);
|
|
argv[0] = out;
|
|
|
|
for (p = cmdline, s = p, esc = false, argnum = 0; p; p++) {
|
|
if (esc) {
|
|
esc = false;
|
|
*out++ = *p;
|
|
}
|
|
else if (*p == '\\' && p[1] != 0) {
|
|
esc = true;
|
|
}
|
|
else if (isspace(*p) || *p == 0) {
|
|
if (p > s) {
|
|
*out++ = ' ';
|
|
argv[++argnum] = out;
|
|
}
|
|
|
|
s = p + 1;
|
|
}
|
|
else {
|
|
*out++ = *p;
|
|
}
|
|
|
|
if (*p == 0)
|
|
break;
|
|
}
|
|
|
|
argv[argnum] = NULL;
|
|
out[-1] = 0;
|
|
|
|
return argv;
|
|
}
|
|
|
|
char *
|
|
postdecode_fields(char *postbuf, ssize_t len, char **fields, int n_fields)
|
|
{
|
|
char *p;
|
|
int i, field, found = 0;
|
|
|
|
for (p = postbuf, i = 0; i < len; i++)
|
|
{
|
|
if (postbuf[i] == '=')
|
|
{
|
|
postbuf[i] = 0;
|
|
|
|
for (field = 0; field < (n_fields * 2); field += 2)
|
|
{
|
|
if (!strcmp(p, fields[field]))
|
|
{
|
|
fields[field + 1] = postbuf + i + 1;
|
|
found++;
|
|
}
|
|
}
|
|
}
|
|
else if (postbuf[i] == '&' || postbuf[i] == '\0')
|
|
{
|
|
postbuf[i] = 0;
|
|
|
|
if (found >= n_fields)
|
|
break;
|
|
|
|
p = postbuf + i + 1;
|
|
}
|
|
}
|
|
|
|
for (field = 0; field < (n_fields * 2); field += 2)
|
|
{
|
|
if (!urldecode(fields[field + 1]))
|
|
{
|
|
free(postbuf);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return postbuf;
|
|
}
|
|
|
|
char *
|
|
postdecode(char **fields, int n_fields)
|
|
{
|
|
const char *var;
|
|
char *p, *postbuf;
|
|
ssize_t len = 0, rlen = 0, content_length = 0;
|
|
|
|
var = getenv("CONTENT_TYPE");
|
|
|
|
if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
|
|
return NULL;
|
|
|
|
var = getenv("CONTENT_LENGTH");
|
|
|
|
if (!var)
|
|
return NULL;
|
|
|
|
content_length = strtol(var, &p, 10);
|
|
|
|
if (p == var || content_length <= 0 || content_length >= POST_LIMIT)
|
|
return NULL;
|
|
|
|
postbuf = calloc(1, content_length + 1);
|
|
|
|
if (postbuf == NULL)
|
|
return NULL;
|
|
|
|
for (len = 0; len < content_length; )
|
|
{
|
|
rlen = read(0, postbuf + len, content_length - len);
|
|
|
|
if (rlen <= 0)
|
|
break;
|
|
|
|
len += rlen;
|
|
}
|
|
|
|
if (len < content_length)
|
|
{
|
|
free(postbuf);
|
|
return NULL;
|
|
}
|
|
|
|
return postdecode_fields(postbuf, len, fields, n_fields);
|
|
}
|
|
|
|
char *
|
|
datadup(const void *in, size_t len)
|
|
{
|
|
char *out = malloc(len + 1);
|
|
|
|
if (!out)
|
|
return NULL;
|
|
|
|
memcpy(out, in, len);
|
|
|
|
*(out + len) = 0;
|
|
|
|
return out;
|
|
}
|
|
|
|
char *
|
|
canonicalize_path(const char *path, size_t len)
|
|
{
|
|
char *canonpath, *cp;
|
|
const char *p, *e;
|
|
|
|
if (path == NULL || *path == '\0')
|
|
return NULL;
|
|
|
|
canonpath = datadup(path, len);
|
|
|
|
if (canonpath == NULL)
|
|
return NULL;
|
|
|
|
/* normalize */
|
|
for (cp = canonpath, p = path, e = path + len; p < e; ) {
|
|
if (*p != '/')
|
|
goto next;
|
|
|
|
/* skip repeating / */
|
|
if ((p + 1 < e) && (p[1] == '/')) {
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
/* /./ or /../ */
|
|
if ((p + 1 < e) && (p[1] == '.')) {
|
|
/* skip /./ */
|
|
if ((p + 2 >= e) || (p[2] == '/')) {
|
|
p += 2;
|
|
continue;
|
|
}
|
|
|
|
/* collapse /x/../ */
|
|
if ((p + 2 < e) && (p[2] == '.') && ((p + 3 >= e) || (p[3] == '/'))) {
|
|
while ((cp > canonpath) && (*--cp != '/'))
|
|
;
|
|
|
|
p += 3;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
next:
|
|
*cp++ = *p++;
|
|
}
|
|
|
|
/* remove trailing slash if not root / */
|
|
if ((cp > canonpath + 1) && (cp[-1] == '/'))
|
|
cp--;
|
|
else if (cp == canonpath)
|
|
*cp++ = '/';
|
|
|
|
*cp = '\0';
|
|
|
|
return canonpath;
|
|
}
|
|
|
|
bool
|
|
urldecode(char *buf)
|
|
{
|
|
char *c, *p;
|
|
|
|
if (!buf || !*buf)
|
|
return true;
|
|
|
|
#define hex(x) \
|
|
(((x) <= '9') ? ((x) - '0') : \
|
|
(((x) <= 'F') ? ((x) - 'A' + 10) : \
|
|
((x) - 'a' + 10)))
|
|
|
|
for (c = p = buf; *p; c++)
|
|
{
|
|
if (*p == '%')
|
|
{
|
|
if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
|
|
return false;
|
|
|
|
*c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
|
|
|
|
p += 3;
|
|
}
|
|
else if (*p == '+')
|
|
{
|
|
*c = ' ';
|
|
p++;
|
|
}
|
|
else
|
|
{
|
|
*c = *p++;
|
|
}
|
|
}
|
|
|
|
*c = 0;
|
|
|
|
return true;
|
|
}
|