From 833b0a7c8833c9b9724ced01a2e2d22bca0118ba 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 | 531 +++++++++++++++++++++++++++++++ src/utils/http.c | 3 + src/utils/neu_path.c | 122 +++++++ src/utils/tpy.c | 13 +- 22 files changed, 966 insertions(+), 53 deletions(-) create mode 100644 include/neuron/utils/neu_path.h create mode 100644 src/utils/ede.c create mode 100644 src/utils/neu_path.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 58f62b900..9e6c7b8f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ set(NEURON_BASE_SOURCES src/utils/log.c src/utils/cid.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 new file mode 100644 index 000000000..54e3a36c6 --- /dev/null +++ b/src/utils/ede.c @@ -0,0 +1,531 @@ +/** + * NEURON IIoT System for Industry 4.0 + * Copyright (C) 2020-2022 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 + +#include "utils/ede.h" +#include "utils/neu_path.h" + +typedef struct { + int object_type; + const char *area; + neu_type_e present_value_type; +} object_map_t; + +typedef struct { + const char *property_id; + neu_type_e type; +} property_map_t; + +static const object_map_t g_object_map[] = { + { 0, "AI", NEU_TYPE_FLOAT }, { 1, "AO", NEU_TYPE_FLOAT }, + { 2, "AV", NEU_TYPE_FLOAT }, { 3, "BI", NEU_TYPE_BIT }, + { 4, "BO", NEU_TYPE_BIT }, { 5, "BV", NEU_TYPE_BIT }, + { 8, "DEV", NEU_TYPE_ERROR }, { 13, "MSI", NEU_TYPE_UINT8 }, + { 14, "MSO", NEU_TYPE_UINT8 }, { 19, "MSV", NEU_TYPE_UINT8 }, + { 23, "ACC", NEU_TYPE_UINT8 }, +}; + +static const property_map_t g_property_map[] = { + { "Object_Name", NEU_TYPE_STRING }, + { "Object_Tyep", NEU_TYPE_UINT8 }, + { "Object_Type", NEU_TYPE_UINT8 }, + { "Description", NEU_TYPE_STRING }, + { "Device_Type", NEU_TYPE_STRING }, + { "Status_Flags", NEU_TYPE_STRING }, + { "Event_State", NEU_TYPE_UINT8 }, + { "Out_Of_Service", NEU_TYPE_BOOL }, + { "Update_Interval", NEU_TYPE_UINT8 }, + { "Min_Pres_Value", NEU_TYPE_FLOAT }, + { "Max_Pres_Value", NEU_TYPE_FLOAT }, + { "Resolution", NEU_TYPE_FLOAT }, + { "COV_Increment", NEU_TYPE_FLOAT }, + { "Time_Delay", NEU_TYPE_UINT8 }, + { "Notification_Class", NEU_TYPE_UINT8 }, + { "Notify_Type", NEU_TYPE_UINT8 }, + { "Units", NEU_TYPE_UINT8 }, + { "High_Limit", NEU_TYPE_FLOAT }, + { "Low_Limit", NEU_TYPE_FLOAT }, + { "Deadband", NEU_TYPE_FLOAT }, + { "Reliability", NEU_TYPE_UINT8 }, + { "Polarity", NEU_TYPE_UINT8 }, + { "System_Status", NEU_TYPE_UINT8 }, + { "Vendor_Name", NEU_TYPE_STRING }, + { "Vendor_Identifier", NEU_TYPE_UINT8 }, + { "Model_Name", NEU_TYPE_STRING }, + { "Firmware_Revision", NEU_TYPE_STRING }, + { "Application_Software_Version", NEU_TYPE_STRING }, + { "Location", NEU_TYPE_STRING }, + { "Protocol_Version", NEU_TYPE_UINT16 }, + { "Protocol_Conformance_Class", NEU_TYPE_UINT8 }, + { "Protocol_Service_Supported", NEU_TYPE_STRING }, + { "Protocol_Object_Types_Supported", NEU_TYPE_STRING }, + { "Serial_Number", NEU_TYPE_STRING }, + { "Max_APDU_Length_Accepted", NEU_TYPE_UINT16 }, + { "Segmentation_Supported", NEU_TYPE_UINT8 }, + { "LOCAL_TIME", NEU_TYPE_STRING }, + { "LOCAL_DATE", NEU_TYPE_STRING }, + { "UTC_Offset", NEU_TYPE_INT8 }, + { "Daylight_Savings_Status", NEU_TYPE_BOOL }, + { "APUD_Segment_Timeout", NEU_TYPE_UINT8 }, + { "APUD_Timeout", NEU_TYPE_UINT16 }, + { "Number_Of_APDU_Retries", NEU_TYPE_UINT8 }, + { "Max_Master", NEU_TYPE_UINT8 }, + { "Max_Info_Frame", NEU_TYPE_UINT8 }, + { "Profile_Name", NEU_TYPE_STRING }, + { "Pulse_Rate", NEU_TYPE_UINT8 }, + { "Scale", NEU_TYPE_FLOAT }, + { "Prescale", NEU_TYPE_FLOAT }, + { "Value_Before_Change", NEU_TYPE_UINT8 }, + { "Value_Change_Time", NEU_TYPE_STRING }, +}; + +static neu_attribute_e commandable_to_attribute(const char *commandable) +{ + if (commandable == NULL || commandable[0] == '\0') { + return NEU_ATTRIBUTE_READ; + } + + if (strcmp(commandable, "N") == 0 || strcmp(commandable, "n") == 0 || + strcmp(commandable, "0") == 0 || strcmp(commandable, "false") == 0 || + strcmp(commandable, "FALSE") == 0) { + return NEU_ATTRIBUTE_READ; + } + + return (neu_attribute_e)(NEU_ATTRIBUTE_READ | NEU_ATTRIBUTE_WRITE); +} + +static void trim_right(char *s) +{ + size_t len = strlen(s); + + while (len > 0) { + const char ch = s[len - 1]; + if (ch == '\n' || ch == '\r' || isspace((unsigned char) ch)) { + s[len - 1] = '\0'; + --len; + continue; + } + break; + } +} + +static int read_field(const char *line, int field_index, char *out, + size_t out_size) +{ + const char *start = line; + const char *end = line; + int idx = 0; + + if (out_size == 0) { + return -1; + } + + while (*end != '\0' && idx < field_index) { + if (*end == ';') { + ++idx; + start = end + 1; + } + ++end; + } + + if (idx != field_index) { + return -1; + } + + end = start; + while (*end != '\0' && *end != ';') { + ++end; + } + + size_t copy_len = (size_t)(end - start); + if (copy_len >= out_size) { + copy_len = out_size - 1; + } + + memcpy(out, start, copy_len); + out[copy_len] = '\0'; + + return 0; +} + +static const object_map_t *find_object_map(int object_type) +{ + size_t i = 0; + for (i = 0; i < sizeof(g_object_map) / sizeof(g_object_map[0]); ++i) { + if (g_object_map[i].object_type == object_type) { + return &g_object_map[i]; + } + } + return NULL; +} + +static const char *get_file_name(const char *path) +{ + const char *name = NULL; + + if (path == NULL) { + return NULL; + } + + name = strrchr(path, '/'); + if (name == NULL) { + return path; + } + + return name + 1; +} + +static int push_entry(neu_ede_result_t *result, const char *address, + const char *name, neu_type_e data_type, + const char *description, neu_attribute_e attribute) +{ + neu_ede_entry_t *new_entries = + realloc(result->entries, (result->count + 1) * sizeof(neu_ede_entry_t)); + if (new_entries == NULL) { + return -1; + } + + result->entries = new_entries; + + if (name == NULL) { + result->entries[result->count].name[0] = '\0'; + } else { + snprintf(result->entries[result->count].name, + sizeof(result->entries[result->count].name), "%s", name); + } + snprintf(result->entries[result->count].address, + sizeof(result->entries[result->count].address), "%s", address); + result->entries[result->count].data_type = data_type; + if (description == NULL) { + result->entries[result->count].description[0] = '\0'; + } else { + snprintf(result->entries[result->count].description, + sizeof(result->entries[result->count].description), "%s", + description); + } + result->entries[result->count].attribute = attribute; + + ++result->count; + return 0; +} + +int neu_ede_parse_file(const char *file_path, neu_ede_result_t *result) +{ + FILE *fp = NULL; + char line[4096] = { 0 }; + bool object_header_seen = false; + + if (file_path == NULL || result == NULL) { + return -1; + } + + result->entries = NULL; + result->count = 0; + + 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; + } + + while (fgets(line, sizeof(line), fp) != NULL) { + char object_name_buf[256] = { 0 }; + char object_type_buf[32] = { 0 }; + char object_instance_buf[32] = { 0 }; + char description_buf[512] = { 0 }; + char commandable_buf[32] = { 0 }; + char address[128] = { 0 }; + char *end_ptr = NULL; + long object_type = 0; + long object_instance = 0; + + trim_right(line); + + if (line[0] == '\0') { + continue; + } + + if (line[0] == '#') { + if (strstr(line, "object-type") != NULL && + strstr(line, "object-instance") != NULL) { + object_header_seen = true; + } + continue; + } + + if (!object_header_seen) { + continue; + } + + if (read_field(line, 2, object_name_buf, sizeof(object_name_buf)) != + 0) { + object_name_buf[0] = '\0'; + } + if (read_field(line, 3, object_type_buf, sizeof(object_type_buf)) != + 0) { + continue; + } + if (read_field(line, 4, object_instance_buf, + sizeof(object_instance_buf)) != 0) { + continue; + } + if (read_field(line, 5, description_buf, sizeof(description_buf)) != + 0) { + description_buf[0] = '\0'; + } + if (read_field(line, 9, commandable_buf, sizeof(commandable_buf)) != + 0) { + commandable_buf[0] = '\0'; + } + + errno = 0; + object_type = strtol(object_type_buf, &end_ptr, 10); + if (errno != 0 || end_ptr == object_type_buf || *end_ptr != '\0') { + continue; + } + + errno = 0; + object_instance = strtol(object_instance_buf, &end_ptr, 10); + if (errno != 0 || end_ptr == object_instance_buf || *end_ptr != '\0' || + object_instance < 0) { + continue; + } + + const object_map_t *map = find_object_map((int) object_type); + if (map == NULL) { + continue; + } + + snprintf(address, sizeof(address), "%s%ld", map->area, object_instance); + if (push_entry(result, address, object_name_buf, + map->present_value_type, description_buf, + commandable_to_attribute(commandable_buf)) != 0) { + neu_ede_result_uninit(result); + fclose(fp); + return -1; + } + } + + fclose(fp); + return 0; +} + +void neu_ede_result_uninit(neu_ede_result_t *result) +{ + if (result == NULL) { + return; + } + + free(result->entries); + result->entries = NULL; + result->count = 0; +} + +int neu_ede_parse_file_to_tags(const char *file_path, neu_datatag_t **tags, + size_t *count) +{ + neu_ede_result_t result = { 0 }; + neu_datatag_t * out = NULL; + size_t parsed_count = 0; + size_t i = 0; + + if (tags == NULL || count == NULL) { + return -1; + } + + *tags = NULL; + *count = 0; + + if (neu_ede_parse_file(file_path, &result) != 0) { + 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); + return -1; + } + + parsed_count = result.count; + + for (i = 0; i < parsed_count; ++i) { + out[i].name = strdup(result.entries[i].name); + out[i].address = strdup(result.entries[i].address); + out[i].attribute = result.entries[i].attribute; + out[i].type = result.entries[i].data_type; + out[i].description = strdup(result.entries[i].description); + out[i].unit = strdup(""); + + if (out[i].name == NULL || out[i].address == NULL || + out[i].description == NULL || out[i].unit == NULL) { + neu_ede_tags_uninit(out, parsed_count); + neu_ede_result_uninit(&result); + return -1; + } + } + + neu_ede_result_uninit(&result); + *tags = out; + *count = parsed_count; + return 0; +} + +void neu_ede_tags_uninit(neu_datatag_t *tags, size_t count) +{ + size_t i = 0; + + if (tags == NULL) { + return; + } + + for (i = 0; i < count; ++i) { + neu_tag_fini(&tags[i]); + } + + free(tags); +} + +void neu_ede_to_msg(char *driver, const char *path, neu_req_add_gtag_t *cmd) +{ + neu_datatag_t *tags = NULL; + size_t count = 0; + const char * base_name = NULL; + char * ext = NULL; + + if (driver == NULL || path == NULL || cmd == NULL) { + return; + } + + if (neu_ede_parse_file_to_tags(path, &tags, &count) != 0) { + return; + } + + strncpy(cmd->driver, driver, NEU_NODE_NAME_LEN - 1); + cmd->n_group = 1; + cmd->groups = calloc(1, sizeof(neu_gdatatag_t)); + if (cmd->groups == NULL) { + neu_ede_tags_uninit(tags, count); + return; + } + + base_name = get_file_name(path); + if (base_name == NULL || base_name[0] == '\0') { + strncpy(cmd->groups[0].group, "ede_import", NEU_GROUP_NAME_LEN - 1); + } else { + strncpy(cmd->groups[0].group, base_name, NEU_GROUP_NAME_LEN - 1); + ext = strrchr(cmd->groups[0].group, '.'); + if (ext != NULL) { + *ext = '\0'; + } + } + + cmd->groups[0].interval = 1000; + cmd->groups[0].n_tag = (int) count; + cmd->groups[0].tags = tags; +} + +int neu_ede_format_address(const char *area, uint32_t address, + const char *property_id, bool custom, + uint32_t custom_id, char *out, size_t out_size) +{ + if (area == NULL || out == NULL || out_size == 0) { + return -1; + } + + if (custom) { + if (snprintf(out, out_size, "%s%u.custom.%u", area, address, + custom_id) >= (int) out_size) { + return -1; + } + return 0; + } + + if (property_id == NULL || property_id[0] == '\0') { + if (snprintf(out, out_size, "%s%u", area, address) >= (int) out_size) { + return -1; + } + return 0; + } + + if (strcmp(property_id, "NULL") == 0) { + return -1; + } + + if (snprintf(out, out_size, "%s%u.%s", area, address, property_id) >= + (int) out_size) { + return -1; + } + + return 0; +} + +neu_type_e neu_ede_get_data_type(const char *area, const char *property_id, + bool custom) +{ + size_t i = 0; + + if (custom) { + return NEU_TYPE_CUSTOM; + } + + if (property_id != NULL && property_id[0] != '\0') { + if (strcmp(property_id, "NULL") == 0) { + return NEU_TYPE_ERROR; + } + + for (i = 0; i < sizeof(g_property_map) / sizeof(g_property_map[0]); + ++i) { + if (strcmp(property_id, g_property_map[i].property_id) == 0) { + return g_property_map[i].type; + } + } + return NEU_TYPE_ERROR; + } + + if (area == NULL) { + return NEU_TYPE_ERROR; + } + + for (i = 0; i < sizeof(g_object_map) / sizeof(g_object_map[0]); ++i) { + if (strcmp(area, g_object_map[i].area) == 0) { + if (strcmp(area, "DEV") == 0) { + return NEU_TYPE_ERROR; + } + return g_object_map[i].present_value_type; + } + } + + return NEU_TYPE_ERROR; +} \ No newline at end of file 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);