From 3156933f2c0c12cb1929adfabb950c734837c5ab Mon Sep 17 00:00:00 2001 From: fengzero Date: Wed, 1 Jul 2026 10:15:29 +0000 Subject: [PATCH] limit path & fix memory check --- CMakeLists.txt | 1 + include/neuron/neuron.h | 1 + include/neuron/utils/neu_path.h | 50 +++++++++++++ plugins/file/file_plugin.c | 18 +++-- plugins/monitor/mqtt_handle.c | 22 +++++- plugins/mqtt/mqtt_handle.c | 38 +++++++++- plugins/restful/adapter_handle.c | 4 +- src/base/group.c | 24 +++++- src/base/metrics.c | 7 +- src/base/tag.c | 9 ++- src/connection/connection.c | 3 + src/core/manager.c | 16 ++++ src/otel/otel_manager.c | 15 ++++ src/parser/neu_json_rw.c | 26 +++++++ src/parser/neu_json_tag.c | 8 +- src/persist/sqlite.c | 84 ++++++++++++++------- src/utils/base64.c | 6 ++ src/utils/cid.c | 18 ++++- src/utils/ede.c | 15 +++- src/utils/http.c | 3 + src/utils/neu_path.c | 122 +++++++++++++++++++++++++++++++ src/utils/tpy.c | 13 +++- tests/ut/CMakeLists.txt | 4 +- tests/{ => ut}/EDE_test.csv | 0 tests/ut/ede_test.cc | 24 +----- 25 files changed, 451 insertions(+), 80 deletions(-) create mode 100644 include/neuron/utils/neu_path.h create mode 100644 src/utils/neu_path.c rename tests/{ => ut}/EDE_test.csv (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d850a1670..271a66add 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ set(NEURON_BASE_SOURCES src/utils/cid.c src/utils/ede.c src/utils/tpy.c + src/utils/neu_path.c ${PERSIST_SOURCES}) if (SMART_LINK) diff --git a/include/neuron/neuron.h b/include/neuron/neuron.h index 65f50481f..75c5f25cb 100644 --- a/include/neuron/neuron.h +++ b/include/neuron/neuron.h @@ -38,6 +38,7 @@ extern "C" { #include "utils/zlog.h" #include "utils/neu_jwt.h" +#include "utils/neu_path.h" #include "utils/time.h" #include "utils/utarray.h" #include "utils/utextend.h" diff --git a/include/neuron/utils/neu_path.h b/include/neuron/utils/neu_path.h new file mode 100644 index 000000000..f1f059f65 --- /dev/null +++ b/include/neuron/utils/neu_path.h @@ -0,0 +1,50 @@ +/** + * NEURON IIoT System for Industry 4.0 + * Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + **/ + +#ifndef NEURON_UTILS_PATH_H +#define NEURON_UTILS_PATH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// Reject an untrusted path component (e.g. a node/plugin/library name that is +// used to build a filesystem path). Returns true only when `name` is a single, +// safe path segment: non-empty, contains no '/' and is not "." or "..". +bool neu_path_is_valid_component(const char *name); + +// Confine an untrusted `path` to the trusted base directory `base`. +// +// The result is the canonical absolute path when it resolves inside `base` +// (either a relative path joined under `base`, or an absolute path that already +// lies within `base`). Returns a newly heap-allocated string the caller must +// free(), or NULL if the path escapes `base`, is invalid, or cannot be +// resolved. A not-yet-existing final component (e.g. a file about to be +// created) is handled by resolving its parent directory. +// +// If `base` is NULL or empty, the current working directory is used. +char *neu_path_confine(const char *base, const char *path); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/file/file_plugin.c b/plugins/file/file_plugin.c index d48b51988..d4156ce53 100644 --- a/plugins/file/file_plugin.c +++ b/plugins/file/file_plugin.c @@ -181,7 +181,9 @@ static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group) { neu_dvalue_t dvalue = { 0 }; - FILE *fp = fopen(tag->address, "r"); + char *safe_path = neu_path_confine(NULL, tag->address); + FILE *fp = safe_path ? fopen(safe_path, "r") : NULL; + free(safe_path); if (fp == NULL) { dvalue.type = NEU_TYPE_ERROR; if (errno == ENOENT) { @@ -190,14 +192,16 @@ static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group) dvalue.value.i32 = NEU_ERR_FILE_OPEN_FAILURE; } } else { - char *buf = calloc(1, 1024); - int ret = fread(buf, 1, 1024, fp); - if (ret < 0) { + char * buf = calloc(1, 1024); + size_t ret = fread(buf, 1, 1024, fp); + if (ret == 0 && ferror(fp)) { dvalue.type = NEU_TYPE_ERROR; dvalue.value.i32 = NEU_ERR_FILE_READ_FAILURE; } else { + size_t n = ret < NEU_VALUE_SIZE - 1 ? ret : NEU_VALUE_SIZE - 1; dvalue.type = NEU_TYPE_STRING; - strncpy(dvalue.value.str, buf, NEU_VALUE_SIZE - 1); + strncpy(dvalue.value.str, buf, n); + dvalue.value.str[n] = '\0'; } fclose(fp); @@ -219,7 +223,9 @@ static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group) static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag, neu_value_u value) { - FILE *fp = fopen(tag->address, "w"); + char *safe_path = neu_path_confine(NULL, tag->address); + FILE *fp = safe_path ? fopen(safe_path, "w") : NULL; + free(safe_path); if (fp == NULL) { if (errno == ENOENT) { plugin->common.adapter_callbacks->driver.write_response( diff --git a/plugins/monitor/mqtt_handle.c b/plugins/monitor/mqtt_handle.c index e4b9b18e9..87545611f 100644 --- a/plugins/monitor/mqtt_handle.c +++ b/plugins/monitor/mqtt_handle.c @@ -41,9 +41,11 @@ static char *generate_heartbeat_json(neu_plugin_t *plugin, UT_array *states) char * json_str = NULL; json.n_state = utarray_len(states); - json.states = calloc(json.n_state, sizeof(neu_json_node_state_t)); - if (NULL == json.states) { - return NULL; + if (json.n_state > 0) { + json.states = calloc(json.n_state, sizeof(neu_json_node_state_t)); + if (NULL == json.states) { + return NULL; + } } utarray_foreach(states, neu_nodes_state_t *, state) @@ -210,6 +212,10 @@ static char *generate_event_json(neu_plugin_t *plugin, neu_reqresp_type_e event, json_req.add_gtag.n_group = add_gtag->n_group; json_req.add_gtag.groups = calloc(add_gtag->n_group, sizeof(*json_req.add_gtag.groups)); + if (NULL == json_req.add_gtag.groups) { + plog_error(plugin, "calloc fail"); + break; + } for (u_int16_t i = 0; i < add_gtag->n_group; i++) { json_req.add_gtag.groups[i].group = strdup(add_gtag->groups[i].group); @@ -218,6 +224,10 @@ static char *generate_event_json(neu_plugin_t *plugin, neu_reqresp_type_e event, json_req.add_gtag.groups[i].tags = calloc(add_gtag->groups[i].n_tag, sizeof(*json_req.add_gtag.groups->tags)); + if (NULL == json_req.add_gtag.groups[i].tags) { + plog_error(plugin, "calloc fail"); + break; + } for (u_int16_t j = 0; j < add_gtag->groups[i].n_tag; j++) { json_req.add_gtag.groups[i].tags[j].type = add_gtag->groups[i].tags[j].type; @@ -239,7 +249,11 @@ static char *generate_event_json(neu_plugin_t *plugin, neu_reqresp_type_e event, neu_json_encode_by_fn(&json_req, neu_json_encode_add_gtags_req, &json_str); - free(json_req.add_tags.tags); + for (u_int16_t i = 0; i < add_gtag->n_group; i++) { + free(json_req.add_gtag.groups[i].group); + free(json_req.add_gtag.groups[i].tags); + } + free(json_req.add_gtag.groups); break; } case NEU_REQ_ADD_PLUGIN_EVENT: { diff --git a/plugins/mqtt/mqtt_handle.c b/plugins/mqtt/mqtt_handle.c index eab8a9d5e..da6510f99 100644 --- a/plugins/mqtt/mqtt_handle.c +++ b/plugins/mqtt/mqtt_handle.c @@ -604,7 +604,12 @@ void handle_write_req(neu_mqtt_qos_e qos, const char *topic, } } - mqtt = calloc(1, sizeof(neu_json_mqtt_t)); + mqtt = calloc(1, sizeof(neu_json_mqtt_t)); + if (NULL == mqtt) { + plog_error(plugin, "calloc mqtt fail"); + model__write_request__free_unpacked(wr, NULL); + return; + } mqtt->uuid = strdup(wr->uuid); mqtt->payload = NULL; mqtt->traceparent = NULL; @@ -620,6 +625,14 @@ void handle_write_req(neu_mqtt_qos_e qos, const char *topic, cmd.n_tag = wr->n_tags; if (cmd.n_tag > 0) { cmd.tags = calloc(cmd.n_tag, sizeof(neu_resp_tag_value_t)); + if (NULL == cmd.tags) { + plog_error(plugin, "calloc cmd.tags fail"); + free(cmd.driver); + free(cmd.group); + neu_json_decode_mqtt_req_free(mqtt); + model__write_request__free_unpacked(wr, NULL); + return; + } } for (int i = 0; i < cmd.n_tag; i++) { @@ -930,7 +943,12 @@ void handle_driver_action_req(neu_mqtt_qos_e qos, const char *topic, return; } - mqtt = calloc(1, sizeof(neu_json_mqtt_t)); + mqtt = calloc(1, sizeof(neu_json_mqtt_t)); + if (NULL == mqtt) { + plog_error(plugin, "calloc mqtt fail"); + model__driver_action_request__free_unpacked(dar, NULL); + return; + } mqtt->uuid = strdup(dar->uuid); mqtt->payload = NULL; mqtt->traceparent = NULL; @@ -1004,7 +1022,12 @@ void handle_read_req(neu_mqtt_qos_e qos, const char *topic, neu_reqresp_head_t header = { 0 }; - mqtt = calloc(1, sizeof(neu_json_mqtt_t)); + mqtt = calloc(1, sizeof(neu_json_mqtt_t)); + if (NULL == mqtt) { + plog_error(plugin, "calloc mqtt fail"); + model__read_request__free_unpacked(read_req, NULL); + return; + } mqtt->uuid = strdup(read_req->uuid); mqtt->payload = NULL; mqtt->traceparent = NULL; @@ -1021,6 +1044,14 @@ void handle_read_req(neu_mqtt_qos_e qos, const char *topic, cmd.n_tag = read_req->n_tags; if (cmd.n_tag > 0) { cmd.tags = calloc(cmd.n_tag, sizeof(char *)); + if (NULL == cmd.tags) { + plog_error(plugin, "calloc cmd.tags fail"); + free(cmd.driver); + free(cmd.group); + neu_json_decode_mqtt_req_free(mqtt); + model__read_request__free_unpacked(read_req, NULL); + return; + } } for (int i = 0; i < cmd.n_tag; i++) { cmd.tags[i] = strdup(read_req->tags[i]); @@ -1029,6 +1060,7 @@ void handle_read_req(neu_mqtt_qos_e qos, const char *topic, if (0 != neu_plugin_op(plugin, header, &cmd)) { neu_req_read_group_fini(&cmd); plog_error(plugin, "neu_plugin_op(NEU_REQ_READ_GROUP) fail"); + model__read_request__free_unpacked(read_req, NULL); return; } diff --git a/plugins/restful/adapter_handle.c b/plugins/restful/adapter_handle.c index c7980c77a..c555aed48 100644 --- a/plugins/restful/adapter_handle.c +++ b/plugins/restful/adapter_handle.c @@ -706,7 +706,9 @@ void handle_put_node_tag(nng_aio *aio) header.ctx = aio; header.type = NEU_REQ_UPDATE_NODE_TAG; strncpy(cmd.node, req->name, NEU_NODE_NAME_LEN - 1); - strncpy(cmd.tags, req->tags, NEU_NODE_TAGS_LEN - 1); + if (req->tags != NULL) { + strncpy(cmd.tags, req->tags, NEU_NODE_TAGS_LEN - 1); + } int ret = neu_plugin_op(plugin, header, &cmd); if (ret != 0) { NEU_JSON_RESPONSE_ERROR(NEU_ERR_IS_BUSY, { diff --git a/src/base/group.c b/src/base/group.c index d2f08f021..84640b62d 100644 --- a/src/base/group.c +++ b/src/base/group.c @@ -49,8 +49,15 @@ static void update_timestamp(neu_group_t *group); neu_group_t *neu_group_new(const char *name, uint32_t interval) { neu_group_t *group = calloc(1, sizeof(neu_group_t)); + if (NULL == group) { + return NULL; + } - group->name = strdup(name); + group->name = strdup(name); + if (NULL == group->name) { + free(group); + return NULL; + } group->interval = interval; pthread_mutex_init(&group->mtx, NULL); @@ -142,9 +149,22 @@ int neu_group_add_tag(neu_group_t *group, const neu_datatag_t *tag) return NEU_ERR_TAG_NAME_CONFLICT; } - el = calloc(1, sizeof(tag_elem_t)); + el = calloc(1, sizeof(tag_elem_t)); + if (NULL == el) { + pthread_mutex_unlock(&group->mtx); + return NEU_ERR_EINTERNAL; + } el->name = strdup(tag->name); el->tag = neu_tag_dup(tag); + if (NULL == el->name || NULL == el->tag) { + free(el->name); + if (el->tag) { + neu_tag_free(el->tag); + } + free(el); + pthread_mutex_unlock(&group->mtx); + return NEU_ERR_EINTERNAL; + } HASH_ADD_STR(group->tags, name, el); update_timestamp(group); diff --git a/src/base/metrics.c b/src/base/metrics.c index e1e9f47be..02eaa78f9 100644 --- a/src/base/metrics.c +++ b/src/base/metrics.c @@ -76,7 +76,7 @@ static void find_os_info() } buf[strcspn(buf, "\n")] = 0; strncpy(g_metrics_.machine, buf, sizeof(g_metrics_.machine)); - g_metrics_.kernel[sizeof(g_metrics_.machine) - 1] = 0; + g_metrics_.machine[sizeof(g_metrics_.machine) - 1] = 0; pclose(f); @@ -87,6 +87,7 @@ static void find_os_info() strncpy(g_metrics_.clib, "glibc", sizeof(g_metrics_.clib)); strncpy(g_metrics_.clib_version, gnu_get_libc_version(), sizeof(g_metrics_.clib_version)); + g_metrics_.clib_version[sizeof(g_metrics_.clib_version) - 1] = 0; #endif } @@ -96,7 +97,7 @@ static size_t parse_memory_fields(int col) char buf[64] = {}; size_t val = 0; - sprintf(buf, "free -b | awk 'NR==2 {print $%i}'", col); + snprintf(buf, sizeof(buf), "free -b | awk 'NR==2 {print $%i}'", col); f = popen(buf, "r"); if (NULL == f) { @@ -131,7 +132,7 @@ static inline size_t neuron_memory_used() size_t val = 0; pid_t pid = getpid(); - sprintf(buf, "ps -o rss= %ld", (long) pid); + snprintf(buf, sizeof(buf), "ps -o rss= %ld", (long) pid); f = popen(buf, "r"); if (NULL == f) { diff --git a/src/base/tag.c b/src/base/tag.c index c49c3d561..ae7b7cbe5 100644 --- a/src/base/tag.c +++ b/src/base/tag.c @@ -37,9 +37,9 @@ static void tag_array_copy(void *_dst, const void *_src) dst->decimal = src->decimal; dst->bias = src->bias; dst->option = src->option; - dst->address = strdup(src->address); - dst->name = strdup(src->name); - dst->description = strdup(src->description); + dst->address = strdup(src->address ? src->address : ""); + dst->name = strdup(src->name ? src->name : ""); + dst->description = strdup(src->description ? src->description : ""); if (src->unit == NULL) { dst->unit = strdup(""); } else { @@ -107,6 +107,9 @@ UT_icd *neu_tag_get_icd() neu_datatag_t *neu_tag_dup(const neu_datatag_t *tag) { neu_datatag_t *new = calloc(1, sizeof(*new)); + if (NULL == new) { + return NULL; + } tag_array_copy(new, tag); return new; } diff --git a/src/connection/connection.c b/src/connection/connection.c index 62a10ae06..6b7039f91 100644 --- a/src/connection/connection.c +++ b/src/connection/connection.c @@ -1408,6 +1408,9 @@ int neu_conn_stream_tcp_server_consume(neu_conn_t *conn, int fd, void *context, int neu_conn_wait_msg(neu_conn_t *conn, void *context, uint16_t n_byte, neu_conn_process_msg fn) { + if (n_byte > conn->buf_size) { + n_byte = conn->buf_size; + } ssize_t ret = neu_conn_recv(conn, conn->buf, n_byte); neu_protocol_unpack_buf_t pbuf = { 0 }; conn->offset = 0; diff --git a/src/core/manager.c b/src/core/manager.c index 4e614d5d0..60d4ef33c 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -31,6 +31,7 @@ #include "utils/base64.h" #include "utils/http.h" #include "utils/log.h" +#include "utils/neu_path.h" #include "utils/time.h" #include "adapter.h" @@ -303,6 +304,21 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) char schema[64] = { 0 }; char buffer[65] = { 0 }; + // The library name is used to build a path under the plugin dir; reject + // any name with a path separator or '..' so it cannot escape that dir. + if (!neu_path_is_valid_component(cmd->library)) { + nlog_warn("reject library name with path separator: %s", + cmd->library); + free(cmd->so_file); + free(cmd->schema_file); + header->type = NEU_RESP_ERROR; + e.error = NEU_ERR_LIBRARY_NAME_NOT_CONFORM; + snprintf(header->receiver, sizeof(header->receiver), "%s", + header->sender); + reply(manager, header, &e); + break; + } + if (sscanf(cmd->library, "libplugin-%64s.so", buffer) != 1) { nlog_warn("library %s no conform", cmd->library); free(cmd->so_file); diff --git a/src/otel/otel_manager.c b/src/otel/otel_manager.c index 1d6cb09bf..b2e439e51 100644 --- a/src/otel/otel_manager.c +++ b/src/otel/otel_manager.c @@ -785,7 +785,15 @@ void neu_otel_scope_set_parent_span_id2(neu_otel_scope_ctx ctx, { trace_scope_t *scope = (trace_scope_t *) ctx; + if (len < 0) { + len = 0; + } else if (len > 8) { + len = 8; // parent_span_id buffer is 8 bytes + } uint8_t *p_sp_id = calloc(1, 8); + if (NULL == p_sp_id) { + return; + } memcpy(p_sp_id, parent_span_id, len); scope->span->parent_span_id.len = len; scope->span->parent_span_id.data = p_sp_id; @@ -1036,6 +1044,13 @@ void neu_otel_split_traceparent(const char *in, char *trace_id, char *span_id, const size_t trace_id_cap = 64; const size_t span_id_cap = 32; + if (NULL == copy) { + trace_id[0] = '\0'; + span_id[0] = '\0'; + *flags = 0; + return; + } + token = strtok_r(copy, delimiter, &saveptr); token = strtok_r(NULL, delimiter, &saveptr); diff --git a/src/parser/neu_json_rw.c b/src/parser/neu_json_rw.c index 6d7a87aef..765c5d308 100644 --- a/src/parser/neu_json_rw.c +++ b/src/parser/neu_json_rw.c @@ -600,6 +600,12 @@ static int decode_write_tags_req_json(void * json_obj, } req->tags = calloc(req->n_tag, sizeof(neu_json_write_tags_elem_t)); + if (NULL == req->tags) { + req->n_tag = 0; + free(req->node); + free(req->group); + return -1; + } for (int i = 0; i < req->n_tag; i++) { neu_json_elem_t v_elems[] = { { @@ -1315,6 +1321,11 @@ static int decode_write_gtags_req_json(void * json_obj, } req->groups = calloc(req->n_group, sizeof(neu_json_write_gtags_elem_t)); + if (NULL == req->groups) { + req->n_group = 0; + free(req->node); + return -1; + } for (int i = 0; i < req->n_group; i++) { neu_json_elem_t g_elems[] = { { @@ -1329,6 +1340,13 @@ static int decode_write_gtags_req_json(void * json_obj, ret = neu_json_decode_array_by_json( json_obj, "groups", i, NEU_JSON_ELEM_SIZE(g_elems), g_elems); + if (ret != 0) { + for (int x = i - 1; x >= 0; x--) { + free(req->groups[x].tags); + } + free(req->groups); + return -1; + } req->groups[i].group = g_elems[0].v.val_str; req->groups[i].n_tag = json_array_size(g_elems[1].v.val_object); @@ -1338,6 +1356,14 @@ static int decode_write_gtags_req_json(void * json_obj, req->groups[i].tags = calloc(req->groups[i].n_tag, sizeof(neu_json_write_tags_elem_t)); + if (NULL == req->groups[i].tags) { + req->groups[i].n_tag = 0; + for (int x = i - 1; x >= 0; x--) { + free(req->groups[x].tags); + } + free(req->groups); + return -1; + } for (int k = 0; k < req->groups[i].n_tag; k++) { neu_json_elem_t v_elems[] = { diff --git a/src/parser/neu_json_tag.c b/src/parser/neu_json_tag.c index e1c9a276d..06c10c051 100644 --- a/src/parser/neu_json_tag.c +++ b/src/parser/neu_json_tag.c @@ -259,7 +259,7 @@ int neu_json_decode_tag_array_json(void *json_obj, neu_json_tag_array_t *arr) return 0; decode_fail: - while (--i > 0) { + while (i-- > 0) { neu_json_decode_tag_fini(&tags[i]); } free(tags); @@ -595,7 +595,7 @@ int neu_json_decode_gtag_array_json(void *json_obj, neu_json_gtag_array_t *arr) return 0; decode_fail: - while (--i > 0) { + while (i-- > 0) { neu_json_decode_gtag_fini(>ags[i]); } free(gtags); @@ -814,6 +814,10 @@ int neu_json_decode_del_tags_req(char *buf, neu_json_del_tags_req_t **result) } req->tags = calloc(req->n_tags, sizeof(neu_json_del_tags_req_name_t)); + if (NULL == req->tags) { + req->n_tags = 0; + goto decode_fail; + } neu_json_del_tags_req_name_t *p_tag = req->tags; for (int i = 0; i < req->n_tags; i++) { neu_json_elem_t id_elems[] = { { diff --git a/src/persist/sqlite.c b/src/persist/sqlite.c index ded4771e0..dacac4738 100644 --- a/src/persist/sqlite.c +++ b/src/persist/sqlite.c @@ -536,12 +536,15 @@ int neu_sqlite_persister_load_nodes(neu_persister_t *self, int step = sqlite3_step(stmt); while (SQLITE_ROW == step) { neu_persist_node_info_t info = {}; - char *name = strdup((char *) sqlite3_column_text(stmt, 0)); + const char *name_txt = (const char *) sqlite3_column_text(stmt, 0); + char * name = name_txt ? strdup(name_txt) : NULL; if (NULL == name) { break; } - char *plugin_name = strdup((char *) sqlite3_column_text(stmt, 3)); + const char *plugin_name_txt = + (const char *) sqlite3_column_text(stmt, 3); + char *plugin_name = plugin_name_txt ? strdup(plugin_name_txt) : NULL; if (NULL == plugin_name) { free(name); break; @@ -971,12 +974,16 @@ int neu_sqlite_persister_load_subscriptions(neu_persister_t *self, int step = sqlite3_step(stmt); while (SQLITE_ROW == step) { - char *driver_name = strdup((char *) sqlite3_column_text(stmt, 0)); + const char *driver_name_txt = + (const char *) sqlite3_column_text(stmt, 0); + char *driver_name = driver_name_txt ? strdup(driver_name_txt) : NULL; if (NULL == driver_name) { break; } - char *group_name = strdup((char *) sqlite3_column_text(stmt, 1)); + const char *group_name_txt = + (const char *) sqlite3_column_text(stmt, 1); + char *group_name = group_name_txt ? strdup(group_name_txt) : NULL; if (NULL == group_name) { free(driver_name); break; @@ -1091,7 +1098,8 @@ static int collect_group_info(sqlite3_stmt *stmt, UT_array **group_infos) int step = sqlite3_step(stmt); while (SQLITE_ROW == step) { neu_persist_group_info_t info = {}; - char *name = strdup((char *) sqlite3_column_text(stmt, 0)); + const char *name_txt = (const char *) sqlite3_column_text(stmt, 0); + char * name = name_txt ? strdup(name_txt) : NULL; if (NULL == name) { break; } @@ -1207,7 +1215,8 @@ int neu_sqlite_persister_load_node_setting(neu_persister_t * self, goto end; } - char *s = strdup((char *) sqlite3_column_text(stmt, 0)); + const char *setting_txt = (const char *) sqlite3_column_text(stmt, 0); + char * s = setting_txt ? strdup(setting_txt) : NULL; if (NULL == s) { nlog_error("strdup fail"); rv = NEU_ERR_EINTERNAL; @@ -1240,14 +1249,17 @@ static int collect_user_info(sqlite3_stmt *stmt, UT_array **user_infos) int step = sqlite3_step(stmt); while (SQLITE_ROW == step) { neu_persist_user_info_t info = {}; - char *name = strdup((char *) sqlite3_column_text(stmt, 0)); + const char *name_txt = (const char *) sqlite3_column_text(stmt, 0); + char * name = name_txt ? strdup(name_txt) : NULL; if (NULL == name) { break; } - info.name = name; - info.hash = strdup((char *) sqlite3_column_text(stmt, 1)); + info.name = name; + const char *hash_txt = (const char *) sqlite3_column_text(stmt, 1); + info.hash = hash_txt ? strdup(hash_txt) : NULL; if (NULL == info.hash) { + free(name); break; } utarray_push_back(*user_infos, &info); @@ -1342,7 +1354,8 @@ int neu_sqlite_persister_load_user(neu_persister_t *self, const char *user_name, goto error; } - user->hash = strdup((char *) sqlite3_column_text(stmt, 0)); + const char *hash_txt = (const char *) sqlite3_column_text(stmt, 0); + user->hash = hash_txt ? strdup(hash_txt) : NULL; if (NULL == user->hash) { nlog_error("strdup fail"); goto error; @@ -1481,8 +1494,10 @@ int neu_sqlite_persister_load_server_cert( // Read all fields cert_info->id = sqlite3_column_int(stmt, 0); - cert_info->app_name = strdup((char *) sqlite3_column_text(stmt, 1)); - cert_info->common_name = strdup((char *) sqlite3_column_text(stmt, 2)); + char *app_name_db = (char *) sqlite3_column_text(stmt, 1); + cert_info->app_name = app_name_db ? strdup(app_name_db) : NULL; + char *common_name = (char *) sqlite3_column_text(stmt, 2); + cert_info->common_name = common_name ? strdup(common_name) : NULL; char *subject = (char *) sqlite3_column_text(stmt, 3); cert_info->subject = subject ? strdup(subject) : NULL; @@ -1543,13 +1558,16 @@ static int collect_client_cert_info(sqlite3_stmt *stmt, UT_array **cert_infos) info.id = sqlite3_column_int(stmt, 0); - char *app_name = strdup((char *) sqlite3_column_text(stmt, 1)); + const char *app_name_col = (const char *) sqlite3_column_text(stmt, 1); + char * app_name = app_name_col ? strdup(app_name_col) : NULL; if (NULL == app_name) { break; } info.app_name = app_name; - char *common_name = strdup((char *) sqlite3_column_text(stmt, 2)); + const char *common_name_col = + (const char *) sqlite3_column_text(stmt, 2); + char *common_name = common_name_col ? strdup(common_name_col) : NULL; if (NULL == common_name) { free(app_name); break; @@ -1838,7 +1856,8 @@ int neu_sqlite_persister_load_security_policy( info->id = sqlite3_column_int(stmt, 0); - char *app_name_col = strdup((char *) sqlite3_column_text(stmt, 1)); + const char *app_name_txt = (const char *) sqlite3_column_text(stmt, 1); + char * app_name_col = app_name_txt ? strdup(app_name_txt) : NULL; if (NULL == app_name_col) { free(info); sqlite3_finalize(stmt); @@ -1846,7 +1865,9 @@ int neu_sqlite_persister_load_security_policy( } info->app_name = app_name_col; - char *policy_name = strdup((char *) sqlite3_column_text(stmt, 2)); + const char *policy_name_txt = + (const char *) sqlite3_column_text(stmt, 2); + char *policy_name = policy_name_txt ? strdup(policy_name_txt) : NULL; if (NULL == policy_name) { free(info->app_name); free(info); @@ -1899,13 +1920,16 @@ static int collect_security_policy_info(sqlite3_stmt *stmt, info.id = sqlite3_column_int(stmt, 0); - char *app_name = strdup((char *) sqlite3_column_text(stmt, 1)); + const char *app_name_txt = (const char *) sqlite3_column_text(stmt, 1); + char * app_name = app_name_txt ? strdup(app_name_txt) : NULL; if (NULL == app_name) { break; } info.app_name = app_name; - char *policy_name = strdup((char *) sqlite3_column_text(stmt, 2)); + const char *policy_name_txt = + (const char *) sqlite3_column_text(stmt, 2); + char *policy_name = policy_name_txt ? strdup(policy_name_txt) : NULL; if (NULL == policy_name) { free(app_name); break; @@ -2027,7 +2051,8 @@ int neu_sqlite_persister_load_auth_setting( auth_info->id = sqlite3_column_int(stmt, 0); auth_info->enabled = sqlite3_column_int(stmt, 2); - auth_info->app_name = strdup((char *) sqlite3_column_text(stmt, 1)); + const char *auth_app_name = (const char *) sqlite3_column_text(stmt, 1); + auth_info->app_name = auth_app_name ? strdup(auth_app_name) : NULL; if (NULL == auth_info->app_name) { nlog_error("strdup fail"); goto error; @@ -2064,20 +2089,25 @@ static int collect_auth_user_info(sqlite3_stmt *stmt, UT_array **user_infos) info.id = sqlite3_column_int(stmt, 0); - char *app_name = strdup((char *) sqlite3_column_text(stmt, 1)); + const char *app_name_txt = (const char *) sqlite3_column_text(stmt, 1); + char * app_name = app_name_txt ? strdup(app_name_txt) : NULL; if (NULL == app_name) { break; } info.app_name = app_name; - char *username = strdup((char *) sqlite3_column_text(stmt, 2)); + const char *username_txt = (const char *) sqlite3_column_text(stmt, 2); + char * username = username_txt ? strdup(username_txt) : NULL; if (NULL == username) { free(app_name); break; } info.username = username; - char *password_hash = strdup((char *) sqlite3_column_text(stmt, 3)); + const char *password_hash_txt = + (const char *) sqlite3_column_text(stmt, 3); + char *password_hash = + password_hash_txt ? strdup(password_hash_txt) : NULL; if (NULL == password_hash) { free(app_name); free(username); @@ -2170,19 +2200,23 @@ int neu_sqlite_persister_load_auth_user( user_info->id = sqlite3_column_int(stmt, 0); - user_info->app_name = strdup((char *) sqlite3_column_text(stmt, 1)); + const char *ui_app_name = (const char *) sqlite3_column_text(stmt, 1); + user_info->app_name = ui_app_name ? strdup(ui_app_name) : NULL; if (NULL == user_info->app_name) { nlog_error("strdup fail"); goto error; } - user_info->username = strdup((char *) sqlite3_column_text(stmt, 2)); + const char *ui_username = (const char *) sqlite3_column_text(stmt, 2); + user_info->username = ui_username ? strdup(ui_username) : NULL; if (NULL == user_info->username) { nlog_error("strdup fail"); goto error; } - user_info->password_hash = strdup((char *) sqlite3_column_text(stmt, 3)); + const char *ui_password_hash = (const char *) sqlite3_column_text(stmt, 3); + user_info->password_hash = + ui_password_hash ? strdup(ui_password_hash) : NULL; if (NULL == user_info->password_hash) { nlog_error("strdup fail"); goto error; diff --git a/src/utils/base64.c b/src/utils/base64.c index 19a25416c..21c234267 100644 --- a/src/utils/base64.c +++ b/src/utils/base64.c @@ -18,6 +18,7 @@ **/ #include +#include #include #include #include @@ -57,6 +58,11 @@ int Base64Decode(const char *b64message, unsigned char **buffer, int *length) { // Decodes a base64 encoded string BIO *bio, *b64; + // BIO_read() takes an int length; reject oversized input so neither the + // read length nor the derived decodeLen can overflow/truncate to int. + if (strlen(b64message) > INT_MAX) { + return -1; + } int decodeLen = calcDecodeLength(b64message); *buffer = (unsigned char *) malloc(decodeLen + 1); if (NULL == *buffer) { diff --git a/src/utils/cid.c b/src/utils/cid.c index b0663f0c2..1e51d2fa6 100644 --- a/src/utils/cid.c +++ b/src/utils/cid.c @@ -24,6 +24,7 @@ #include "define.h" #include "utils/cid.h" #include "utils/log.h" +#include "utils/neu_path.h" #include "json/json.h" static int parse_ied(xmlNode *xml_ied, cid_ied_t *ied); @@ -40,7 +41,14 @@ static void fill_doi_ctls(cid_t *cid); int neu_cid_parse(const char *path, cid_t *cid) { memset(cid, 0, sizeof(cid_t)); - xmlDoc *doc = xmlReadFile(path, NULL, 0); + char *safe_path = neu_path_confine(NULL, path); + if (safe_path == NULL) { + nlog_warn("reject icd file path outside working dir: %s", path); + return -1; + } + // XML_PARSE_NONET blocks network access for external entities/DTDs (XXE). + xmlDoc *doc = xmlReadFile(safe_path, NULL, XML_PARSE_NONET); + free(safe_path); if (doc == NULL) { nlog_warn("Failed to read icd file %s", path); return -1; @@ -1059,8 +1067,14 @@ static int parse_template(xmlNode *xml_template, cid_template_t *template) } if (count != NULL) { + int cnt = atoi(count); + if (cnt < 0) { + cnt = 0; + } else if (cnt > 255) { + cnt = 255; // array_size is uint8_t + } tm_da->is_array = true; - tm_da->array_size = atoi(count); + tm_da->array_size = (uint8_t) cnt; } else { tm_da->is_array = false; tm_da->array_size = 0; diff --git a/src/utils/ede.c b/src/utils/ede.c index 8d780209d..54e3a36c6 100644 --- a/src/utils/ede.c +++ b/src/utils/ede.c @@ -24,6 +24,7 @@ #include #include "utils/ede.h" +#include "utils/neu_path.h" typedef struct { int object_type; @@ -242,7 +243,12 @@ int neu_ede_parse_file(const char *file_path, neu_ede_result_t *result) result->entries = NULL; result->count = 0; - fp = fopen(file_path, "r"); + char *safe_path = neu_path_confine(NULL, file_path); + if (safe_path == NULL) { + return -1; + } + fp = fopen(safe_path, "r"); + free(safe_path); if (fp == NULL) { return -1; } @@ -359,6 +365,13 @@ int neu_ede_parse_file_to_tags(const char *file_path, neu_datatag_t **tags, return -1; } + if (result.count == 0) { + neu_ede_result_uninit(&result); + *tags = NULL; + *count = 0; + return 0; + } + out = calloc(result.count, sizeof(neu_datatag_t)); if (out == NULL) { neu_ede_result_uninit(&result); diff --git a/src/utils/http.c b/src/utils/http.c index b410d816d..bfe770636 100644 --- a/src/utils/http.c +++ b/src/utils/http.c @@ -67,6 +67,9 @@ ssize_t neu_url_decode(const char *s, size_t len, char *buf, size_t size) size_t i = 0, j = 0; int n = 0; unsigned int c; + if (size == 0) { + return -1; + } while (i < len && j < size) { c = s[i++]; if ('+' == c) { diff --git a/src/utils/neu_path.c b/src/utils/neu_path.c new file mode 100644 index 000000000..10c9e9363 --- /dev/null +++ b/src/utils/neu_path.c @@ -0,0 +1,122 @@ +/** + * NEURON IIoT System for Industry 4.0 + * Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + **/ + +#include +#include +#include +#include + +#include "utils/neu_path.h" + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +bool neu_path_is_valid_component(const char *name) +{ + if (name == NULL || name[0] == '\0') { + return false; + } + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + return false; + } + // A safe component must not contain a path separator. + if (strchr(name, '/') != NULL) { + return false; + } + return true; +} + +// Return true if `resolved` is `base` or lies below `base/`. +static bool is_within(const char *base, const char *resolved) +{ + size_t bl = strlen(base); + if (strncmp(resolved, base, bl) != 0) { + return false; + } + // Exact match, or the next char is a separator (a real sub-path). + return resolved[bl] == '\0' || resolved[bl] == '/'; +} + +char *neu_path_confine(const char *base, const char *path) +{ + if (path == NULL || path[0] == '\0') { + return NULL; + } + + char base_real[PATH_MAX]; + if (base == NULL || base[0] == '\0') { + base = "."; + } + if (realpath(base, base_real) == NULL) { + return NULL; + } + + // Build an absolute candidate: absolute paths are taken as-is (and must + // still resolve inside base), relative paths are joined under base. + char candidate[PATH_MAX]; + if (path[0] == '/') { + if (strlen(path) >= sizeof(candidate)) { + return NULL; + } + strcpy(candidate, path); + } else { + if ((size_t) snprintf(candidate, sizeof(candidate), "%s/%s", base_real, + path) >= sizeof(candidate)) { + return NULL; + } + } + + char resolved[PATH_MAX]; + if (realpath(candidate, resolved) == NULL) { + // The final component may not exist yet (e.g. a file about to be + // created). Resolve the parent directory and re-append the basename. + char tmp[PATH_MAX]; + strncpy(tmp, candidate, sizeof(tmp)); + tmp[sizeof(tmp) - 1] = '\0'; + + char *slash = strrchr(tmp, '/'); + if (slash == NULL) { + return NULL; + } + *slash = '\0'; + const char *bname = slash + 1; + if (!neu_path_is_valid_component(bname)) { + return NULL; + } + + char parent_real[PATH_MAX]; + if (realpath(tmp[0] != '\0' ? tmp : "/", parent_real) == NULL) { + return NULL; + } + if (!is_within(base_real, parent_real)) { + return NULL; + } + if ((size_t) snprintf(resolved, sizeof(resolved), "%s/%s", parent_real, + bname) >= sizeof(resolved)) { + return NULL; + } + return strdup(resolved); + } + + if (!is_within(base_real, resolved)) { + return NULL; + } + return strdup(resolved); +} diff --git a/src/utils/tpy.c b/src/utils/tpy.c index c3d2506e2..2a8b0fbfb 100644 --- a/src/utils/tpy.c +++ b/src/utils/tpy.c @@ -20,6 +20,7 @@ #include #include "utils/log.h" +#include "utils/neu_path.h" #include "utils/tpy.h" static int parse_vars(xmlNode *xml_ied, tpy_t *tpy); @@ -27,7 +28,14 @@ static int parse_vars(xmlNode *xml_ied, tpy_t *tpy); int neu_tpy_parse(const char *path, tpy_t *tpy) { memset(tpy, 0, sizeof(tpy_t)); - xmlDoc *doc = xmlReadFile(path, NULL, 0); + char *safe_path = neu_path_confine(NULL, path); + if (safe_path == NULL) { + nlog_warn("reject tpy file path outside working dir: %s", path); + return -1; + } + // XML_PARSE_NONET blocks network access for external entities/DTDs (XXE). + xmlDoc *doc = xmlReadFile(safe_path, NULL, XML_PARSE_NONET); + free(safe_path); if (doc == NULL) { nlog_warn("Failed to read tpy file %s", path); return -1; @@ -101,7 +109,8 @@ static int parse_vars(xmlNode *xml_ied, tpy_t *tpy) attr, (const xmlChar *) "TaskPrio"); snprintf(static_name, sizeof(static_name), "%s_%s", - name_static, name_task); + name_static != NULL ? name_static : "", + name_task != NULL ? name_task : ""); strncpy(tpy_var.name, content, sizeof(tpy_var.name) - 1); xmlFree(name_static); diff --git a/tests/ut/CMakeLists.txt b/tests/ut/CMakeLists.txt index 902baafb7..1f1afa6c1 100644 --- a/tests/ut/CMakeLists.txt +++ b/tests/ut/CMakeLists.txt @@ -93,14 +93,12 @@ target_include_directories(mqtt_schema_test PRIVATE ) target_link_libraries(mqtt_schema_test neuron-base gtest_main gtest) +file(COPY ${CMAKE_SOURCE_DIR}/tests/ut/EDE_test.csv DESTINATION ${UT_DIRECTORY}/config) add_executable(ede_test ede_test.cc ${CMAKE_SOURCE_DIR}/src/utils/ede.c) target_include_directories(ede_test PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/include ) -target_compile_definitions(ede_test PRIVATE - EDE_TEST_FILE_PATH="${CMAKE_SOURCE_DIR}/tests/EDE_test.csv" -) target_link_libraries(ede_test neuron-base gtest_main gtest) include(GoogleTest) diff --git a/tests/EDE_test.csv b/tests/ut/EDE_test.csv similarity index 100% rename from tests/EDE_test.csv rename to tests/ut/EDE_test.csv diff --git a/tests/ut/ede_test.cc b/tests/ut/ede_test.cc index a13abf0c6..2c2323287 100644 --- a/tests/ut/ede_test.cc +++ b/tests/ut/ede_test.cc @@ -14,6 +14,7 @@ zlog_category_t *neuron = NULL; static constexpr size_t kExpectedEdeTagCount = 1499; +#define EDE_TEST_FILE_PATH "./config/EDE_test.csv" static std::vector split_semicolon(const std::string &line) { @@ -267,29 +268,6 @@ TEST(EdeTest, EdeToMsg) free(cmd.groups); } -TEST(EdeTest, CommandableToRw) -{ - const char * tmp_file = "/tmp/ede_commandable_test.csv"; - std::ofstream ofs(tmp_file); - ofs << "PROJECT_NAME;X\n"; - ofs << "# keyname;device obj.-instance;object-name;object-type;" - "object-instance;description;present-value-default;" - "min-present-value;max-present-value;commandable\n"; - ofs << "k1;2;obj;1;10;desc;;;;Y\n"; - ofs << "k2;2;obj;1;11;desc;;;;N\n"; - ofs.close(); - - neu_ede_result_t result = { 0 }; - ASSERT_EQ(neu_ede_parse_file(tmp_file, &result), 0); - ASSERT_EQ(result.count, 2U); - EXPECT_EQ(result.entries[0].attribute, - (neu_attribute_e)(NEU_ATTRIBUTE_READ | NEU_ATTRIBUTE_WRITE)); - EXPECT_EQ(result.entries[1].attribute, NEU_ATTRIBUTE_READ); - - neu_ede_result_uninit(&result); - std::remove(tmp_file); -} - int main(int argc, char **argv) { zlog_init("./config/dev.conf");