clamav/libclamav/json_api.c
Val S. d4114e0d2c
Fix static analysis code quality issues; Fix old libjson-c support (#1574)
`clamscan/manager.c`: Fix double-free in an error condition in `scanfile()`.

`common/optparser.c`: Fix uninitialized use of the `numarg` variable when
`arg` is `NULL`.

`libclamav/cache.c`: Don't check if `ctx-fmap` is `NULL` when we've
already dereferenced it.

`libclamav/crypto.c`: The `win_exception` variable and associated logic
is Windows-specific and so needs preprocessor platform checks. Otherwise
it generates unused variable warnings.

`libclamav/crypto.c`: Check for `size_t` overflow of the `byte_read`
variable in the `cl_hash_file_fd_ex()` function.

`libclamav/crypto.c`: Fix a memory leak in the `cl_hash_file_fd_ex()`
function.

`libclamav/fmap.c`: Correctly the `name` and `path` pointer if
`fmap_duplicate()` fails. Also need to clear those variables when
duplicating the parent `map` so that on error it does not free the wrong
`name` or `path`.

`libclamav/fmap.c`: Refine error handling for `hash_string` cleanup in
`cl_fmap_get_hash()`. Coverity's complaint was that `hash_string` could
never be non-NULL if `status` is not `CL_SUCCESS`. I.e., the cleanup is
dead code. I don't think my cleanup actually "fixes" that though it is
definitely a better way to do the error handling.
The `if (NULL != hash_string) {` check is still technically dead code.
It safeguards against future changes that may `goto done` between the
allocation and transfering ownership from `hash_string` to `hash_out`.

`libclamav/others.c`: Fix possible memory leak in `cli_recursion_stack_push()`.

`libclamav/others.c`: Refactor an if/else + switch statement inside
`cli_dispatch_scan_callback()` so that the `CL_SCAN_CALLBACK_ALERT` case
is not dead-code. It's also easier to read now.

`libclamav/pdfdecode.c`: For logging, use the `%zu` to format `size_t`
instead of casting to `long long` and using `%llu`. Simiularly use the
`STDu32` format string macro for `uint32_t`.

`libclamav/pdfdecode.c`: Fix a possible double-free for the `decoded`
pointer in `filter_lzwdecode()`.

`libclamav/pdfdecode.c`: Remove the `if (capacity > UINT_MAX) {`
overflow check inside `filter_lzwdecode()`, which didn't do anything.
The `capacity` variable this point is a fixed value and so I also changed
the `avail_out` to be that fixed `INFLATE_CHUNK_SIZE` value rather than
using `capacity`. It is more straightforward and replicates how similar
logic works later in the file.
I also removed the copy-pasted `(Bytef *)` cast which didn't reaaally do
anything, and was a copypaste from a different algorihm. The lzw
implementation interface doesn't use `Bytef`.

`libclamav/readdb.c`: Fix a possible NULL-deref on the `matcher` variable
in the error handling/cleanup code if the function fails.

`libclamav/scanners.c`: Fix an issue where the return value from some of
the parsers may be lost/overridden by the call to
`cli_dispatch_scan_callback()` just after the `done:` label in
`cli_magic_scan()`.

`libclamav/scanners.c`: Silence an unused-return value warning when
calling `cli_basename()`.

`sigtool/sigtool.c` and `unit_tests/check_regex.c`:
Fix possible NULL-derefs of the `ctx.recursion_stack` pointer in the error
handling for several functions.

Also, and this isn't a Coverity thing:

`libclamav/json_api.c` and `libclamav/others.c`:
Fix support for libjson-c version 0.13 and older.
I don't think we *should* be using the old version, but some environments
such as the current OSS-Fuzz base image are older and still use it.
The issue is that `json_object_new_uint64()` was introduced in a later
libjson-c version, so we have to fallback to use `json_object_new_int64()`
with older libjson-c, provided the int were storing isn't too big.

CLAM-2768
2025-09-26 18:26:00 -04:00

442 lines
12 KiB
C

/*
* JSON Object API
*
* Copyright (C) 2014-2025 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* Authors: Kevin Lin
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 2 as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include "clamav.h"
#include "others.h"
#include "json_api.h"
cl_error_t cli_json_timeout_cycle_check(cli_ctx *ctx, int *toval)
{
if (SCAN_COLLECT_METADATA) {
if (*toval <= 0) {
if (cli_checktimelimit(ctx) != CL_SUCCESS) {
cli_dbgmsg("cli_json_timeout_cycle_check: timeout!\n");
return CL_ETIMEOUT;
}
(*toval)++;
}
if (*toval > JSON_TIMEOUT_SKIP_CYCLES) {
(*toval) = 0;
}
}
return CL_SUCCESS;
}
cl_error_t cli_json_parse_error(json_object *root, const char *errstr)
{
json_object *perr;
if (!root)
return CL_SUCCESS; /* CL_ENULLARG? */
perr = cli_jsonarray(root, "ParseErrors");
if (perr == NULL) {
return CL_EMEM;
}
return cli_jsonstr(perr, NULL, errstr);
}
cl_error_t cli_jsonnull(json_object *obj, const char *key)
{
json_type objty;
json_object *fpobj = NULL;
if (NULL == obj) {
cli_dbgmsg("json: null 'obj' specified to cli_jsonnull\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as key to cli_jsonnull\n");
return CL_ENULLARG;
}
json_object_object_add(obj, key, fpobj);
} else if (objty == json_type_array) {
json_object_array_add(obj, fpobj);
}
return CL_SUCCESS;
}
cl_error_t cli_jsonstr(json_object *obj, const char *key, const char *s)
{
json_type objty;
json_object *fpobj;
if (NULL == obj) {
cli_dbgmsg("json: null 'obj' specified to cli_jsonstr\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as 'key' to cli_jsonstr\n");
return CL_ENULLARG;
}
} else if (objty != json_type_array) {
return CL_EARG;
}
if (NULL == s) {
cli_dbgmsg("json: null string specified as 's' to cli_jsonstr\n");
return CL_ENULLARG;
}
fpobj = json_object_new_string(s);
if (NULL == fpobj) {
cli_errmsg("json: no memory for json string object\n");
return CL_EMEM;
}
if (objty == json_type_object)
json_object_object_add(obj, key, fpobj);
else if (objty == json_type_array)
json_object_array_add(obj, fpobj);
return CL_SUCCESS;
}
cl_error_t cli_jsonstrlen(json_object *obj, const char *key, const char *s, int len)
{
json_type objty;
json_object *fpobj;
if (NULL == obj) {
cli_dbgmsg("json: null 'obj' specified to cli_jsonstr\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as 'key' to cli_jsonstr\n");
return CL_ENULLARG;
}
} else if (objty != json_type_array) {
return CL_EARG;
}
if (NULL == s) {
cli_dbgmsg("json: null string specified as 's' to cli_jsonstr\n");
return CL_ENULLARG;
}
fpobj = json_object_new_string_len(s, len);
if (NULL == fpobj) {
cli_errmsg("json: no memory for json string object\n");
return CL_EMEM;
}
if (objty == json_type_object)
json_object_object_add(obj, key, fpobj);
else if (objty == json_type_array)
json_object_array_add(obj, fpobj);
return CL_SUCCESS;
}
cl_error_t cli_jsonint(json_object *obj, const char *key, int32_t i)
{
json_type objty;
json_object *fpobj;
if (NULL == obj) {
cli_dbgmsg("json: no parent object specified to cli_jsonint\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as key to cli_jsonint\n");
return CL_ENULLARG;
}
} else if (objty != json_type_array) {
return CL_EARG;
}
fpobj = json_object_new_int(i);
if (NULL == fpobj) {
cli_errmsg("json: no memory for json int object\n");
return CL_EMEM;
}
if (objty == json_type_object)
json_object_object_add(obj, key, fpobj);
else if (objty == json_type_array)
json_object_array_add(obj, fpobj);
return CL_SUCCESS;
}
cl_error_t cli_jsonint64(json_object *obj, const char *key, int64_t i)
{
json_type objty;
json_object *fpobj;
if (NULL == obj) {
cli_dbgmsg("json: no parent object specified to cli_jsonint64\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as key to cli_jsonint64\n");
return CL_ENULLARG;
}
} else if (objty != json_type_array) {
return CL_EARG;
}
fpobj = json_object_new_int64(i);
if (NULL == fpobj) {
cli_errmsg("json: no memory for json int object.\n");
return CL_EMEM;
}
if (objty == json_type_object)
json_object_object_add(obj, key, fpobj);
else if (objty == json_type_array)
json_object_array_add(obj, fpobj);
return CL_SUCCESS;
}
cl_error_t cli_jsonuint64(json_object *obj, const char *key, uint64_t i)
{
json_type objty;
json_object *fpobj;
if (NULL == obj) {
cli_dbgmsg("json: no parent object specified to cli_jsonuint64\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as key to cli_jsonuint64\n");
return CL_ENULLARG;
}
} else if (objty != json_type_array) {
return CL_EARG;
}
#if JSON_C_MINOR_VERSION >= 14
fpobj = json_object_new_uint64(i);
#else
if (i > INT64_MAX) {
cli_dbgmsg("json: uint64 value too large for json int64 object\n");
return CL_EARG;
}
fpobj = json_object_new_int64(i);
#endif
if (NULL == fpobj) {
cli_errmsg("json: no memory for json int object.\n");
return CL_EMEM;
}
if (objty == json_type_object)
json_object_object_add(obj, key, fpobj);
else if (objty == json_type_array)
json_object_array_add(obj, fpobj);
return CL_SUCCESS;
}
cl_error_t cli_jsonbool(json_object *obj, const char *key, int i)
{
json_type objty;
json_object *fpobj;
if (NULL == obj) {
cli_dbgmsg("json: no parent object specified to cli_jsonbool\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as key to cli_jsonbool\n");
return CL_ENULLARG;
}
} else if (objty != json_type_array) {
return CL_EARG;
}
fpobj = json_object_new_boolean(i);
if (NULL == fpobj) {
cli_errmsg("json: no memory for json boolean object.\n");
return CL_EMEM;
}
if (objty == json_type_object)
json_object_object_add(obj, key, fpobj);
else if (objty == json_type_array)
json_object_array_add(obj, fpobj);
return CL_SUCCESS;
}
cl_error_t cli_jsondouble(json_object *obj, const char *key, double d)
{
json_type objty;
json_object *fpobj;
if (NULL == obj) {
cli_dbgmsg("json: no parent object specified to cli_jsondouble\n");
return CL_ENULLARG;
}
objty = json_object_get_type(obj);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as key to cli_jsondouble\n");
return CL_ENULLARG;
}
} else if (objty != json_type_array) {
return CL_EARG;
}
fpobj = json_object_new_double(d);
if (NULL == fpobj) {
cli_errmsg("json: no memory for json double object.\n");
return CL_EMEM;
}
if (objty == json_type_object)
json_object_object_add(obj, key, fpobj);
else if (objty == json_type_array)
json_object_array_add(obj, fpobj);
return CL_SUCCESS;
}
json_object *cli_jsonarray(json_object *obj, const char *key)
{
json_type objty;
json_object *newobj;
/* First check to see if this key exists */
if (obj && key && json_object_object_get_ex(obj, key, &newobj)) {
return json_object_is_type(newobj, json_type_array) ? newobj : NULL;
}
newobj = json_object_new_array();
if (!(newobj))
return NULL;
if (obj) {
objty = json_object_get_type(obj);
if (key && objty == json_type_object) {
json_object_object_add(obj, key, newobj);
if (!json_object_object_get_ex(obj, key, &newobj))
return NULL;
} else if (objty == json_type_array) {
json_object_array_add(obj, newobj);
}
}
return newobj;
}
cl_error_t cli_jsonint_array(json_object *obj, int32_t val)
{
return cli_jsonint(obj, NULL, val);
}
json_object *cli_jsonobj(json_object *obj, const char *key)
{
json_type objty;
json_object *newobj;
if (obj && key && json_object_object_get_ex(obj, key, &newobj))
return json_object_is_type(newobj, json_type_object) ? newobj : NULL;
newobj = json_object_new_object();
if (!(newobj))
return NULL;
if (obj) {
objty = json_object_get_type(obj);
if (key && objty == json_type_object) {
json_object_object_add(obj, key, newobj);
if (!json_object_object_get_ex(obj, key, &newobj))
return NULL;
} else if (objty == json_type_array) {
json_object_array_add(obj, newobj);
}
}
return newobj;
}
/* deleting an object DOES decrement reference count */
cl_error_t cli_json_delowner(json_object *owner, const char *key, int idx)
{
json_type objty;
json_object *obj;
if (NULL == owner) {
cli_dbgmsg("json: no owner object specified to cli_json_delowner\n");
return CL_ENULLARG;
}
objty = json_object_get_type(owner);
if (objty == json_type_object) {
if (NULL == key) {
cli_dbgmsg("json: null string specified as key to cli_delowner\n");
return CL_ENULLARG;
}
if (!json_object_object_get_ex(owner, key, &obj)) {
cli_dbgmsg("json: owner array does not have content with key %s\n", key);
return CL_EARG;
}
json_object_object_del(owner, key);
} else if (objty == json_type_array) {
json_object *empty;
if (NULL == json_object_array_get_idx(owner, idx)) {
cli_dbgmsg("json: owner array does not have content at idx %d\n", idx);
return CL_EARG;
}
/* allocate the empty object to replace target object */
empty = cli_jsonobj(NULL, NULL);
if (NULL == empty)
return CL_EMEM;
if (0 != json_object_array_put_idx(owner, idx, empty)) {
/* this shouldn't be possible */
cli_dbgmsg("json: cannot delete idx %d of owner array\n", idx);
return CL_BREAK;
}
} else {
cli_dbgmsg("json: no owner object cannot hold ownership\n");
return CL_EARG;
}
return CL_SUCCESS;
}