Compare commits

...

23 Commits

Author SHA1 Message Date
1b9d89cab3 Use kqueue on Darwin as well
Since poll() is implemented in terms of kqueue() there,
it doesn't seem like this could have improved anything.

Besides man 3 ev, libevent code, and [1],
I haven't managed to find much relevant information.

[1] https://daniel.haxx.se/blog/2016/10/11/poll-on-mac-10-12-is-broken/
2021-09-29 12:07:25 +02:00
a3ad5e7751 Ignore empty XDG_*_DIRS env. variables
As the specification says we should.  GLib does this as well.

It is still possible to achieve an empty set by using ":",
which are two non-absolute paths that should be ignored.
GLib doesn't implement this.  Thus, we're now better than GLib.
2021-09-26 08:49:51 +02:00
960420df3e Escape DEL character in config_item_write_string() 2020-10-31 21:28:29 +01:00
d71c47f8ce CMakeLists.txt: omit end{if,foreach} expressions
Their usefulness was almost negative.
2020-10-29 15:32:26 +01:00
425ea57b17 CMakeLists.txt: clean up OpenBSD support
A few things might have changed.
2020-10-29 15:31:05 +01:00
8822d06091 Don't suppress -Wimplicit-fallthrough
Might have already been resolved by: 9494e8e da75b6f
2020-10-26 18:25:32 +01:00
9639777814 Fix validation of overlong UTF-8
It was too strict and Egyptian dicks didn't want to pass,
so we'll do it half-arsedly for a subset.
2020-10-24 19:09:09 +02:00
929229a1d7 Fix config PEG grammar to match strtoll() 2020-10-24 08:05:17 +02:00
53bcebc2f0 Split out utf8_validate_cp(), adhere to RFC 3629 2020-10-21 05:20:20 +02:00
b08cf6c29f Reject overlong UTF-8 sequences 2020-10-21 05:08:59 +02:00
69101eb155 Fix optional arguments in --help output
An equals sign is necessary.
2020-10-13 21:27:46 +02:00
9d14562f7e Improve the UTF-8 API
We need to be able to detect partial sequences.
2020-10-12 22:56:22 +02:00
9b72304963 Fix a memory leak in mpd_client_parse_line() 2020-10-12 02:07:15 +02:00
1cd9ba8d97 Import configuration test from degesch 2020-10-12 02:07:15 +02:00
7e5b6c5343 Fix crashes in the config parser
It had a duality between not requiring null-terminated input
and relying on it, depending on where you looked.
2020-10-12 02:07:14 +02:00
c2c5031538 Add remaining fuzzing entry points
Closes #1
2020-10-12 02:07:07 +02:00
df3f53bd5c Add a basic fuzzing framework using libFuzzer
Updates #1
2020-10-11 20:04:34 +02:00
e029aae1d3 Import xwrite(), cstr_set(), resolve_..._template()
From degesch and json-rpc-shell.
2020-10-10 04:31:52 +02:00
b9457c321f Rename cstr_transform() argument
It does not always have to be tolower().
2020-10-10 04:30:19 +02:00
2201becca4 Mark some issues 2020-10-10 04:29:41 +02:00
7023c51347 Get rid of CMake dev warnings 2020-10-02 06:47:34 +02:00
d21f8466b5 Bump copyright years 2020-10-02 06:43:16 +02:00
7f919025ee Add iscntrl_ascii()
It's too easy to miss the DEL character.
2020-10-02 06:31:46 +02:00
10 changed files with 556 additions and 95 deletions

View File

@@ -4,9 +4,9 @@ cmake_minimum_required (VERSION 2.8.5)
# Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# -Wunused-function is pretty annoying here, as everything is static
set (wdisabled "-Wno-unused-function -Wno-implicit-fallthrough")
set (wdisabled "-Wno-unused-function")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
endif ()
# Dependencies
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
@@ -16,11 +16,9 @@ find_package (PkgConfig REQUIRED)
pkg_check_modules (libssl REQUIRED libssl libcrypto)
if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
include_directories (/usr/local/include)
link_directories (/usr/local/lib)
# Our POSIX version macros make these undefined
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
endif ()
set (common_libraries ${libssl_LIBRARIES})
include_directories (${libssl_INCLUDE_DIRS})
@@ -32,8 +30,8 @@ foreach (extra iconv rt)
find_library (extra_lib_${extra} ${extra})
if (extra_lib_${extra})
list (APPEND common_libraries ${extra})
endif (extra_lib_${extra})
endforeach (extra)
endif ()
endforeach ()
# Build some unit tests
include_directories (${PROJECT_SOURCE_DIR})
@@ -43,4 +41,4 @@ foreach (name liberty proto)
add_threads (test-${name})
target_link_libraries (test-${name} ${common_libraries})
add_test (NAME test-${name} COMMAND test-${name})
endforeach (name)
endforeach ()

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014 - 2018, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

View File

@@ -1,17 +1,17 @@
# Public Domain
find_package (PkgConfig REQUIRED)
pkg_check_modules (NCURSESW QUIET ncursesw)
pkg_check_modules (Ncursesw QUIET ncursesw)
# OpenBSD doesn't provide a pkg-config file
set (required_vars NCURSESW_LIBRARIES)
if (NOT NCURSESW_FOUND)
find_library (NCURSESW_LIBRARIES NAMES ncursesw)
find_path (NCURSESW_INCLUDE_DIRS ncurses.h)
list (APPEND required_vars NCURSESW_INCLUDE_DIRS)
endif (NOT NCURSESW_FOUND)
set (required_vars Ncursesw_LIBRARIES)
if (NOT Ncursesw_FOUND)
find_library (Ncursesw_LIBRARIES NAMES ncursesw)
find_path (Ncursesw_INCLUDE_DIRS ncurses.h)
list (APPEND required_vars Ncursesw_INCLUDE_DIRS)
endif (NOT Ncursesw_FOUND)
include (FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS (NCURSESW DEFAULT_MSG ${required_vars})
FIND_PACKAGE_HANDLE_STANDARD_ARGS (Ncursesw DEFAULT_MSG ${required_vars})
mark_as_advanced (NCURSESW_LIBRARIES NCURSESW_INCLUDE_DIRS)
mark_as_advanced (Ncursesw_LIBRARIES Ncursesw_INCLUDE_DIRS)

View File

@@ -1,10 +1,10 @@
# Public Domain
find_path (UNISTRING_INCLUDE_DIRS unistr.h)
find_library (UNISTRING_LIBRARIES NAMES unistring libunistring)
find_path (Unistring_INCLUDE_DIRS unistr.h)
find_library (Unistring_LIBRARIES NAMES unistring libunistring)
include (FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS (UNISTRING DEFAULT_MSG
UNISTRING_INCLUDE_DIRS UNISTRING_LIBRARIES)
FIND_PACKAGE_HANDLE_STANDARD_ARGS (Unistring DEFAULT_MSG
Unistring_INCLUDE_DIRS Unistring_LIBRARIES)
mark_as_advanced (UNISTRING_LIBRARIES UNISTRING_INCLUDE_DIRS)
mark_as_advanced (Unistring_LIBRARIES Unistring_INCLUDE_DIRS)

18
fuzz Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
# I'm not sure how to make maximum use of this invention
# Make sure to have llvm-symbolizer installed
clang -g -fsanitize=address,undefined,fuzzer -fno-sanitize-recover=all \
tests/fuzz.c -o fuzz-executor
fuzz () {
echo "`tput bold`-- Fuzzing $1`tput sgr0`"
mkdir -p /tmp/corpus-$1
./fuzz-executor -test=$1 -artifact_prefix=$1- \
-max_total_time=600 -timeout=1 /tmp/corpus-$1
}
if [ $# -gt 0 ]; then
for test in "$@"; do fuzz $test; done
else
for test in $(./fuzz-executor); do fuzz $test; done
fi

View File

@@ -1567,13 +1567,12 @@ mpd_client_parse_line (struct mpd_client *self, const char *line)
if (!strcmp (line, "list_OK"))
strv_append_owned (&self->data, NULL);
else if (mpd_client_parse_response (line, &response))
{
mpd_client_dispatch (self, &response);
free (response.current_command);
free (response.message_text);
}
else
strv_append (&self->data, line);
free (response.current_command);
free (response.message_text);
return true;
}

189
liberty.c
View File

@@ -1,7 +1,7 @@
/*
* liberty.c: the ultimate C unlibrary
*
* Copyright (c) 2014 - 2018, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -731,6 +731,21 @@ set_blocking (int fd, bool blocking)
return prev;
}
static bool
xwrite (int fd, const char *data, size_t len, struct error **e)
{
size_t written = 0;
while (written < len)
{
ssize_t res = write (fd, data + written, len - written);
if (res >= 0)
written += res;
else if (errno != EINTR)
return error_set (e, "%s", strerror (errno));
}
return true;
}
static void
xclose (int fd)
{
@@ -1357,6 +1372,7 @@ struct poller_idle
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// The heap could definitely be made faster but we'll prefer simplicity
struct poller_timers
{
struct poller_timer **heap; ///< Min-heap of timers
@@ -1733,10 +1749,9 @@ poller_run (struct poller *self)
self->revents_len = 0;
}
#elif defined (BSD)
// Mac OS X's kqueue is fatally broken, or so I've been told; leaving it out.
// Otherwise this is sort of similar to the epoll version.
// Sort of similar to the epoll version. Let's hope Darwin isn't broken,
// that'd mean reimplementing this in terms of select() just because of Crapple.
#elif defined (BSD) || defined (__APPLE__)
#include <sys/types.h>
#include <sys/event.h>
@@ -2672,6 +2687,12 @@ strncasecmp_ascii (const char *a, const char *b, size_t n)
return 0;
}
static bool
iscntrl_ascii (int c)
{
return (c >= 0 && c < 32) || c == 0x7f;
}
static bool
isalpha_ascii (int c)
{
@@ -2700,63 +2721,67 @@ isspace_ascii (int c)
// --- UTF-8 -------------------------------------------------------------------
/// Return a pointer to the next UTF-8 character, or NULL on error
static const char *
utf8_next (const char *s, size_t len, int32_t *codepoint)
/// Return the value of the UTF-8 character at `*s` and advance the pointer
/// to the next one. Returns -2 if there is only a partial but possibly valid
/// character sequence, or -1 on other errors. Either way, `*s` is untouched.
static int32_t
utf8_decode (const char **s, size_t len)
{
// End of string, we go no further
if (!len)
return NULL;
return -1;
// Find out how long the sequence is (0 for ASCII)
unsigned mask = 0x80;
unsigned sequence_len = 0;
const uint8_t *p = (const uint8_t *) s;
const uint8_t *p = (const uint8_t *) *s, *end = p + len;
while ((*p & mask) == mask)
{
// Invalid start of sequence
if (mask == 0xFE)
return NULL;
return -1;
mask |= mask >> 1;
sequence_len++;
}
// In the middle of a character or the input is too short
if (sequence_len == 1 || sequence_len > len)
return NULL;
// In the middle of a character
// or an overlong sequence (subset, possibly MUTF-8, not supported)
if (sequence_len == 1 || *p == 0xC0 || *p == 0xC1)
return -1;
// Check the rest of the sequence
uint32_t cp = *p++ & ~mask;
while (sequence_len && --sequence_len)
{
if (p == end)
return -2;
if ((*p & 0xC0) != 0x80)
return NULL;
return -1;
cp = cp << 6 | (*p++ & 0x3F);
}
if (codepoint)
*codepoint = cp;
return (const char *) p;
*s = (const char *) p;
return cp;
}
static inline bool
utf8_validate_cp (int32_t cp)
{
// RFC 3629, CESU-8 not allowed
return cp >= 0 && cp <= 0x10FFFF && (cp < 0xD800 || cp > 0xDFFF);
}
/// Very rough UTF-8 validation, just makes sure codepoints can be iterated
static bool
utf8_validate (const char *s, size_t len)
{
const char *next;
while (len)
{
int32_t codepoint;
// TODO: better validations
if (!(next = utf8_next (s, len, &codepoint))
|| codepoint > 0x10FFFF)
return false;
len -= next - s;
s = next;
}
return true;
const char *end = s + len;
int32_t codepoint;
while ((codepoint = utf8_decode (&s, end - s)) >= 0
&& utf8_validate_cp (codepoint))
;
return s == end;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -2780,12 +2805,12 @@ utf8_iter_next (struct utf8_iter *self, size_t *len)
return -1;
const char *old = self->s;
int32_t codepoint;
if (!soft_assert ((self->s = utf8_next (old, self->len, &codepoint))))
int32_t codepoint = utf8_decode (&self->s, self->len);
if (!soft_assert (codepoint >= 0))
{
// Invalid UTF-8
self->len = 0;
return -1;
return codepoint;
}
size_t advance = self->s - old;
@@ -2917,6 +2942,13 @@ base64_encode (const void *data, size_t len, struct str *output)
// --- Utilities ---------------------------------------------------------------
static void
cstr_set (char **s, char *new)
{
free (*s);
*s = new;
}
static void
cstr_split (const char *s, const char *delimiters, bool ignore_empty,
struct strv *out)
@@ -2946,10 +2978,10 @@ cstr_strip_in_place (char *s, const char *stripped_chars)
}
static void
cstr_transform (char *s, int (*tolower) (int c))
cstr_transform (char *s, int (*xform) (int c))
{
for (; *s; s++)
*s = tolower (*s);
*s = xform (*s);
}
static char *
@@ -2996,6 +3028,7 @@ iconv_xstrdup (iconv_t conv, char *in, size_t in_len, size_t *out_len)
char *in_ptr = in;
if (in_len == (size_t) -1)
// XXX: out_len will be one character longer than the string!
in_len = strlen (in) + 1;
while (iconv (conv, (char **) &in_ptr, &in_len,
@@ -3213,7 +3246,7 @@ get_xdg_config_dirs (struct strv *out)
str_free (&config_home);
const char *xdg_config_dirs;
if (!(xdg_config_dirs = getenv ("XDG_CONFIG_DIRS")))
if (!(xdg_config_dirs = getenv ("XDG_CONFIG_DIRS")) || !*xdg_config_dirs)
xdg_config_dirs = "/etc/xdg";
cstr_split (xdg_config_dirs, ":", true, out);
}
@@ -3238,7 +3271,7 @@ get_xdg_data_dirs (struct strv *out)
str_free (&data_home);
const char *xdg_data_dirs;
if (!(xdg_data_dirs = getenv ("XDG_DATA_DIRS")))
if (!(xdg_data_dirs = getenv ("XDG_DATA_DIRS")) || !*xdg_data_dirs)
xdg_data_dirs = "/usr/local/share/:/usr/share/";
cstr_split (xdg_data_dirs, ":", true, out);
}
@@ -3255,16 +3288,8 @@ resolve_relative_data_filename (const char *filename)
}
static char *
resolve_relative_runtime_filename (const char *filename)
resolve_relative_runtime_filename_finish (struct str path)
{
struct str path = str_make ();
const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
if (runtime_dir && *runtime_dir == '/')
str_append (&path, runtime_dir);
else
get_xdg_home_dir (&path, "XDG_DATA_HOME", ".local/share");
str_append_printf (&path, "/%s/%s", PROGRAM_NAME, filename);
// Try to create the file's ancestors;
// typically the user will want to immediately create a file in there
const char *last_slash = strrchr (path.str, '/');
@@ -3277,6 +3302,41 @@ resolve_relative_runtime_filename (const char *filename)
return str_steal (&path);
}
static char *
resolve_relative_runtime_filename (const char *filename)
{
struct str path = str_make ();
const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
if (runtime_dir && *runtime_dir == '/')
str_append (&path, runtime_dir);
else
get_xdg_home_dir (&path, "XDG_DATA_HOME", ".local/share");
str_append_printf (&path, "/%s/%s", PROGRAM_NAME, filename);
return resolve_relative_runtime_filename_finish (path);
}
/// This differs from resolve_relative_runtime_filename() in that we expect
/// the filename to be something like a pattern for mkstemp(), so the resulting
/// path can reside in a system-wide directory with no risk of a conflict.
/// However, we have to take care about permissions. Do we even need this?
static char *
resolve_relative_runtime_template (const char *template)
{
struct str path = str_make ();
const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
const char *tmpdir = getenv ("TMPDIR");
if (runtime_dir && *runtime_dir == '/')
str_append_printf (&path, "%s/%s", runtime_dir, PROGRAM_NAME);
else if (tmpdir && *tmpdir == '/')
str_append_printf (&path, "%s/%s.%d", tmpdir, PROGRAM_NAME, geteuid ());
else
str_append_printf (&path, "/tmp/%s.%d", PROGRAM_NAME, geteuid ());
str_append_printf (&path, "/%s", template);
return resolve_relative_runtime_filename_finish (path);
}
static char *
try_expand_tilde (const char *filename)
{
@@ -3729,7 +3789,7 @@ opt_handler_usage (const struct opt_handler *self, FILE *stream)
str_append_printf (&row, "--%s", opt->long_name);
if (opt->arg_hint)
str_append_printf (&row, (opt->flags & OPT_OPTIONAL_ARG)
? " [%s]" : " %s", opt->arg_hint);
? "[=%s]" : " %s", opt->arg_hint);
// TODO: keep the indent if there are multiple lines
if (row.len + 2 <= OPT_USAGE_ALIGNMENT_COLUMN)
@@ -4356,7 +4416,7 @@ socket_io_try_write (int socket_fd, struct str *wb)
// char = [\0-\177] # or any Unicode codepoint in the UTF-8 encoding
// escape = [\\"abfnrtv] / [xX][0-9A-Fa-f][0-9A-Fa-f]? / [0-7][0-7]?[0-7]?
//
// integer = lws '-'? [0-9]+ # whatever strtoll() accepts on your system
// integer = lws [-+]? [0-9]+ # whatever strtoll() accepts on your system
// null = lws 'null'
// boolean = lws 'yes' / lws 'YES' / lws 'no' / lws 'NO'
// / lws 'on' / lws 'ON' / lws 'off' / lws 'OFF'
@@ -4647,13 +4707,15 @@ config_item_write_string (struct str *output, const struct str *s)
for (size_t i = 0; i < s->len; i++)
{
unsigned char c = s->str[i];
if (c == '\n') str_append (output, "\\n");
else if (c == '\r') str_append (output, "\\r");
else if (c == '\t') str_append (output, "\\t");
else if (c == '\\') str_append (output, "\\\\");
else if (c == '"') str_append (output, "\\\"");
else if (c < 32) str_append_printf (output, "\\x%02x", c);
else str_append_c (output, c);
if (c == '\n') str_append (output, "\\n");
else if (c == '\r') str_append (output, "\\r");
else if (c == '\t') str_append (output, "\\t");
else if (c == '\\') str_append (output, "\\\\");
else if (c == '"') str_append (output, "\\\"");
else if (iscntrl_ascii (c))
str_append_printf (output, "\\x%02x", c);
else
str_append_c (output, c);
}
str_append_c (output, '"');
}
@@ -5030,18 +5092,21 @@ config_tokenizer_next (struct config_tokenizer *self, struct error **e)
return CONFIG_T_STRING;
}
char *end;
// Our input doesn't need to be NUL-terminated but we want to use strtoll()
char buf[48] = "", *end = buf;
size_t buf_len = MIN (sizeof buf - 1, self->len);
errno = 0;
self->integer = strtoll (self->p, &end, 10);
self->integer = strtoll (strncpy (buf, self->p, buf_len), &end, 10);
if (errno == ERANGE)
{
config_tokenizer_error (self, e, "integer out of range");
return CONFIG_T_ABORT;
}
if (end != self->p)
if (end != buf)
{
self->len -= end - self->p;
self->p = end;
self->len -= end - buf;
self->p += end - buf;
return CONFIG_T_INTEGER;
}
@@ -5054,7 +5119,7 @@ config_tokenizer_next (struct config_tokenizer *self, struct error **e)
str_reset (&self->string);
do
str_append_c (&self->string, config_tokenizer_advance (self));
while (config_tokenizer_is_word_char (*self->p));
while (self->len && config_tokenizer_is_word_char (*self->p));
if (!strcmp (self->string.str, "null"))
return CONFIG_T_NULL;

297
tests/fuzz.c Normal file
View File

@@ -0,0 +1,297 @@
/*
* tests/fuzz.c
*
* Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#define PROGRAM_NAME "fuzz"
#define PROGRAM_VERSION "0"
#define LIBERTY_WANT_SSL
// The MPD client is a full wrapper and needs the network
#define LIBERTY_WANT_POLLER
#define LIBERTY_WANT_ASYNC
#define LIBERTY_WANT_PROTO_IRC
#define LIBERTY_WANT_PROTO_HTTP
#define LIBERTY_WANT_PROTO_SCGI
#define LIBERTY_WANT_PROTO_FASTCGI
#define LIBERTY_WANT_PROTO_WS
#define LIBERTY_WANT_PROTO_MPD
#include "../liberty.c"
#include "../liberty-tui.c"
static bool
app_is_character_in_locale (ucs4_t ch)
{
return ch < 128;
}
// --- UTF-8 -------------------------------------------------------------------
static void
test_utf8_validate (const uint8_t *data, size_t size)
{
utf8_validate ((const char *) data, size);
}
// --- Base 64 -----------------------------------------------------------------
static void
test_base64_decode (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
struct str out = str_make ();
base64_decode (wrap.str, true /* ignore_ws */, &out);
str_free (&out);
str_free (&wrap);
}
// --- IRC ---------------------------------------------------------------------
static void
test_irc_parse_message (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
struct irc_message msg;
irc_parse_message (&msg, wrap.str);
irc_free_message (&msg);
str_free (&wrap);
}
// --- HTTP --------------------------------------------------------------------
static void
test_http_parse_media_type (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
char *type = NULL;
char *subtype = NULL;
struct str_map parameters = str_map_make (free);
http_parse_media_type (wrap.str, &type, &subtype, &parameters);
free (type);
free (subtype);
str_map_free (&parameters);
str_free (&wrap);
}
static void
test_http_parse_upgrade (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
struct http_protocol *protocols = NULL;
http_parse_upgrade (wrap.str, &protocols);
LIST_FOR_EACH (struct http_protocol, iter, protocols)
http_protocol_destroy (iter);
str_free (&wrap);
}
// --- SCGI --------------------------------------------------------------------
static bool
test_scgi_parser_on_headers_read (void *user_data)
{
(void) user_data;
return true;
}
static bool
test_scgi_parser_on_content (void *user_data, const void *data, size_t len)
{
(void) user_data;
(void) data;
(void) len;
return true;
}
static void
test_scgi_parser_push (const uint8_t *data, size_t size)
{
struct scgi_parser parser = scgi_parser_make ();
parser.on_headers_read = test_scgi_parser_on_headers_read;
parser.on_content = test_scgi_parser_on_content;
scgi_parser_push (&parser, data, size, NULL);
scgi_parser_free (&parser);
}
// --- WebSockets --------------------------------------------------------------
static bool
test_ws_parser_on_frame_header (void *user_data, const struct ws_parser *self)
{
(void) user_data;
(void) self;
return true;
}
static bool
test_ws_parser_on_frame (void *user_data, const struct ws_parser *self)
{
(void) user_data;
(void) self;
return true;
}
static void
test_ws_parser_push (const uint8_t *data, size_t size)
{
struct ws_parser parser = ws_parser_make ();
parser.on_frame_header = test_ws_parser_on_frame_header;
parser.on_frame = test_ws_parser_on_frame;
ws_parser_push (&parser, data, size);
ws_parser_free (&parser);
}
// --- FastCGI -----------------------------------------------------------------
static bool
test_fcgi_parser_on_message (const struct fcgi_parser *parser, void *user_data)
{
(void) parser;
(void) user_data;
return true;
}
static void
test_fcgi_parser_push (const uint8_t *data, size_t size)
{
struct fcgi_parser parser = fcgi_parser_make ();
parser.on_message = test_fcgi_parser_on_message;
fcgi_parser_push (&parser, data, size);
fcgi_parser_free (&parser);
}
static void
test_fcgi_nv_parser_push (const uint8_t *data, size_t size)
{
struct str_map values = str_map_make (free);
struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
nv_parser.output = &values;
fcgi_nv_parser_push (&nv_parser, data, size);
fcgi_nv_parser_free (&nv_parser);
str_map_free (&values);
}
// --- Config ------------------------------------------------------------------
static void
test_config_item_parse (const uint8_t *data, size_t size)
{
struct config_item *item =
config_item_parse ((const char *) data, size, false, NULL);
if (item)
config_item_destroy (item);
}
// --- TUI ---------------------------------------------------------------------
static void
test_attrs_decode (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
attrs_decode (wrap.str);
str_free (&wrap);
}
// --- MPD ---------------------------------------------------------------------
static void
test_mpd_client_process_input (const uint8_t *data, size_t size)
{
struct poller poller;
poller_init (&poller);
struct mpd_client mpd = mpd_client_make (&poller);
str_append_data (&mpd.read_buffer, data, size);
mpd_client_process_input (&mpd);
mpd_client_free (&mpd);
poller_free (&poller);
}
// --- Main --------------------------------------------------------------------
typedef void (*fuzz_test_fn) (const uint8_t *data, size_t size);
static fuzz_test_fn generator = NULL;
void
LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
generator (data, size);
}
int
LLVMFuzzerInitialize (int *argcp, char ***argvp)
{
struct str_map targets = str_map_make (NULL);
#define REGISTER(name) str_map_set (&targets, #name, test_ ## name);
REGISTER (utf8_validate)
REGISTER (base64_decode)
REGISTER (irc_parse_message)
REGISTER (http_parse_media_type)
REGISTER (http_parse_upgrade)
REGISTER (scgi_parser_push)
REGISTER (ws_parser_push)
REGISTER (fcgi_parser_push)
REGISTER (fcgi_nv_parser_push)
REGISTER (config_item_parse)
REGISTER (attrs_decode)
REGISTER (mpd_client_process_input)
char **argv = *argvp, *option = "-test=", *name = NULL;
for (int i = 1; i < *argcp; i++)
if (!strncmp (argv[i], option, strlen (option)))
{
name = argv[i] + strlen (option);
memmove (argv + i, argv + i + 1, (*argcp - i) * sizeof *argv);
(*argcp)--;
}
if (!name)
{
struct str_map_iter iter = str_map_iter_make (&targets);
while (str_map_iter_next (&iter))
printf ("%s\n", iter.link->key);
exit (EXIT_FAILURE);
}
if (!(generator = str_map_find (&targets, name)))
{
fprintf (stderr, "Unknown test: %s\n", name);
exit (EXIT_FAILURE);
}
str_map_free (&targets);
return 0;
}

View File

@@ -326,10 +326,19 @@ test_str_map (void)
static void
test_utf8 (void)
{
const char valid [] = "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm";
const char invalid[] = "\xf0\x90\x28\xbc";
soft_assert ( utf8_validate (valid, sizeof valid));
soft_assert (!utf8_validate (invalid, sizeof invalid));
const char *full = "\xc5\x99", *partial = full, *empty = full;
soft_assert (utf8_decode (&full, 2) == 0x0159);
soft_assert (utf8_decode (&partial, 1) == -2);
soft_assert (utf8_decode (&empty, 0) == -1);
const char valid_1[] = "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm";
const char valid_2[] = "\xf0\x93\x82\xb9";
const char invalid_1[] = "\xf0\x90\x28\xbc";
const char invalid_2[] = "\xc0\x80";
soft_assert ( utf8_validate (valid_1, sizeof valid_1));
soft_assert ( utf8_validate (valid_2, sizeof valid_2));
soft_assert (!utf8_validate (invalid_1, sizeof invalid_1));
soft_assert (!utf8_validate (invalid_2, sizeof invalid_2));
struct utf8_iter iter = utf8_iter_make ("fóọ");
size_t ch_len;
@@ -604,6 +613,78 @@ test_connector (const void *user_data, struct test_connector_fixture *self)
connector_free (&connector);
}
// --- Configuration -----------------------------------------------------------
static void
on_test_config_foo_change (struct config_item *item)
{
*(bool *) item->user_data = item->value.boolean;
}
static bool
test_config_validate_nonnegative
(const struct config_item *item, struct error **e)
{
if (item->type == CONFIG_ITEM_NULL)
return true;
hard_assert (item->type == CONFIG_ITEM_INTEGER);
if (item->value.integer >= 0)
return true;
error_set (e, "must be non-negative");
return false;
}
static struct config_schema g_config_test[] =
{
{ .name = "foo",
.comment = "baz",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off",
.on_change = on_test_config_foo_change },
{ .name = "bar",
.type = CONFIG_ITEM_INTEGER,
.validate = test_config_validate_nonnegative,
.default_ = "1" },
{ .name = "foobar",
.type = CONFIG_ITEM_STRING,
.default_ = "\"qux\\x01\"" },
{}
};
static void
test_config_load (struct config_item *subtree, void *user_data)
{
config_schema_apply_to_object (g_config_test, subtree, user_data);
}
static void
test_config (void)
{
struct config config = config_make ();
bool b = true;
config_register_module (&config, "top", test_config_load, &b);
config_load (&config, config_item_object ());
config_schema_call_changed (config.root);
hard_assert (b == false);
struct config_item *invalid = config_item_integer (-1);
hard_assert (!config_item_set_from (config_item_get (config.root,
"top.bar", NULL), invalid, NULL));
config_item_destroy (invalid);
struct str s = str_make ();
config_item_write (config.root, true, &s);
struct config_item *parsed = config_item_parse (s.str, s.len, false, NULL);
hard_assert (parsed);
config_item_destroy (parsed);
str_free (&s);
config_free (&config);
}
// --- Main --------------------------------------------------------------------
int
@@ -622,6 +703,7 @@ main (int argc, char *argv[])
test_add_simple (&test, "/utf-8", NULL, test_utf8);
test_add_simple (&test, "/base64", NULL, test_base64);
test_add_simple (&test, "/async", NULL, test_async);
test_add_simple (&test, "/config", NULL, test_config);
test_add (&test, "/connector", struct test_connector_fixture, NULL,
test_connector_fixture_init,

View File

@@ -77,7 +77,7 @@ test_irc (void)
static void
test_http_parser (void)
{
struct str_map parameters = str_map_make (NULL);
struct str_map parameters = str_map_make (free);
parameters.key_xfrm = tolower_ascii_strxfrm;
char *type = NULL;
@@ -88,9 +88,11 @@ test_http_parser (void)
soft_assert (!strcasecmp_ascii (subtype, "html"));
soft_assert (parameters.len == 1);
soft_assert (!strcmp (str_map_find (&parameters, "charset"), "utf-8"));
free (type);
free (subtype);
str_map_free (&parameters);
struct http_protocol *protocols;
struct http_protocol *protocols = NULL;
soft_assert (http_parse_upgrade ("websocket, HTTP/2.0, , ", &protocols));
soft_assert (!strcmp (protocols->name, "websocket"));