This is an automated email from the ASF dual-hosted git repository.
acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push:
new 0717e0524 system/nxpkg: validate catalog and database contents
0717e0524 is described below
commit 0717e052485ceb19c1e32cff67b6282ff3e8245a
Author: aviralgarg05 <[email protected]>
AuthorDate: Sat Aug 15 15:56:41 2026 +0530
system/nxpkg: validate catalog and database contents
Validate package fields before using them and ensure version pointers refer
to installed entries. Keep installed manifests available for rollback.
Signed-off-by: aviralgarg05 <[email protected]>
---
system/nxpkg/pkg_manifest.c | 58 ++++++++
system/nxpkg/pkg_metadata.c | 353 +++++++++++++++++++++++++++++++++++++-------
2 files changed, 355 insertions(+), 56 deletions(-)
diff --git a/system/nxpkg/pkg_manifest.c b/system/nxpkg/pkg_manifest.c
index 3e7b56a26..5ee23e2e5 100644
--- a/system/nxpkg/pkg_manifest.c
+++ b/system/nxpkg/pkg_manifest.c
@@ -78,6 +78,41 @@ static bool pkg_validate_hex(FAR const char *value)
* Public Functions
****************************************************************************/
+/****************************************************************************
+ * Name: pkg_validate_path_component
+ *
+ * Description:
+ * Check that a value is safe as one path component.
+ *
+ ****************************************************************************/
+
+bool pkg_validate_path_component(FAR const char *value)
+{
+ FAR const char *p;
+
+ if (pkg_validate_required(value) < 0)
+ {
+ return false;
+ }
+
+ /* Reject dot names and parent traversal. */
+
+ if (value[0] == '.')
+ {
+ return false;
+ }
+
+ for (p = value; *p != '\0'; p++)
+ {
+ if (*p == '/' || *p == '\\')
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
const char *pkg_manifest_type_str(enum pkg_payload_type_e type)
{
switch (type)
@@ -95,6 +130,8 @@ const char *pkg_manifest_type_str(enum pkg_payload_type_e
type)
int pkg_manifest_validate(FAR const struct pkg_manifest_s *manifest)
{
+ size_t i;
+
if (manifest == NULL)
{
return -EINVAL;
@@ -110,6 +147,14 @@ int pkg_manifest_validate(FAR const struct pkg_manifest_s
*manifest)
return -EINVAL;
}
+ /* Names and versions become package-store path components. */
+
+ if (!pkg_validate_path_component(manifest->name) ||
+ !pkg_validate_path_component(manifest->version))
+ {
+ return -EINVAL;
+ }
+
if (strlen(manifest->sha256) != PKG_HASH_HEX_LEN)
{
return -EINVAL;
@@ -126,6 +171,19 @@ int pkg_manifest_validate(FAR const struct pkg_manifest_s
*manifest)
return -EINVAL;
}
+ if (manifest->launch_argc > PKG_LAUNCH_ARGS_MAX)
+ {
+ return -EINVAL;
+ }
+
+ for (i = 0; i < manifest->launch_argc; i++)
+ {
+ if (pkg_validate_required(manifest->launch_args[i]) < 0)
+ {
+ return -EINVAL;
+ }
+ }
+
return 0;
}
diff --git a/system/nxpkg/pkg_metadata.c b/system/nxpkg/pkg_metadata.c
index aadb4692e..370ac5f69 100644
--- a/system/nxpkg/pkg_metadata.c
+++ b/system/nxpkg/pkg_metadata.c
@@ -24,6 +24,7 @@
* Included Files
****************************************************************************/
+#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
@@ -100,6 +101,54 @@ static FAR cJSON *pkg_metadata_packages_array(FAR cJSON
*root)
return cJSON_GetObjectItemCaseSensitive(root, "packages");
}
+static int pkg_metadata_parse_launch_args(
+ FAR cJSON *item, FAR struct pkg_manifest_s *manifest)
+{
+ FAR cJSON *field;
+ FAR cJSON *arg;
+ size_t argc = 0;
+ FAR const char *value;
+ int ret;
+
+ field = cJSON_GetObjectItemCaseSensitive(item, "launch_args");
+ if (field == NULL)
+ {
+ manifest->launch_argc = 0;
+ return 0;
+ }
+
+ if (!cJSON_IsArray(field))
+ {
+ return -EINVAL;
+ }
+
+ cJSON_ArrayForEach(arg, field)
+ {
+ if (argc >= PKG_LAUNCH_ARGS_MAX)
+ {
+ return -E2BIG;
+ }
+
+ value = cJSON_GetStringValue(arg);
+ if (value == NULL)
+ {
+ return -EINVAL;
+ }
+
+ ret = pkg_copy_string(manifest->launch_args[argc],
+ sizeof(manifest->launch_args[argc]), value);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ argc++;
+ }
+
+ manifest->launch_argc = argc;
+ return 0;
+}
+
static int pkg_metadata_parse_manifest(FAR cJSON *item,
FAR struct pkg_manifest_s *manifest)
{
@@ -170,6 +219,37 @@ static int pkg_metadata_parse_manifest(FAR cJSON *item,
return -EINVAL;
}
+ /* Missing optional UI fields remain empty. */
+
+ field = cJSON_GetObjectItemCaseSensitive(item, "description");
+ value = cJSON_GetStringValue(field);
+ if (value != NULL)
+ {
+ pkg_copy_string(manifest->description, sizeof(manifest->description),
+ value);
+ }
+
+ field = cJSON_GetObjectItemCaseSensitive(item, "category");
+ value = cJSON_GetStringValue(field);
+ if (value != NULL)
+ {
+ pkg_copy_string(manifest->category, sizeof(manifest->category),
+ value);
+ }
+
+ field = cJSON_GetObjectItemCaseSensitive(item, "icon");
+ value = cJSON_GetStringValue(field);
+ if (value != NULL)
+ {
+ pkg_copy_string(manifest->icon, sizeof(manifest->icon), value);
+ }
+
+ ret = pkg_metadata_parse_launch_args(item, manifest);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
return pkg_manifest_validate(manifest);
}
@@ -221,6 +301,9 @@ static int pkg_metadata_parse_installed_entry(
{
FAR cJSON *field;
FAR const char *value;
+ bool current_found = false;
+ bool previous_found = false;
+ size_t i;
int ret;
memset(entry, 0, sizeof(*entry));
@@ -285,38 +368,82 @@ static int pkg_metadata_parse_installed_entry(
return ret;
}
+ if (!pkg_validate_path_component(entry->name) ||
+ !pkg_validate_path_component(entry->current) ||
+ (entry->previous[0] != '\0' &&
+ !pkg_validate_path_component(entry->previous)))
+ {
+ return -EINVAL;
+ }
+
+ for (i = 0; i < entry->version_count; i++)
+ {
+ if (!pkg_validate_path_component(entry->versions[i]))
+ {
+ return -EINVAL;
+ }
+
+ current_found |= strcmp(entry->versions[i], entry->current) == 0;
+ previous_found |= strcmp(entry->versions[i], entry->previous) == 0;
+ }
+
+ if (!current_found ||
+ (entry->previous[0] != '\0' && !previous_found))
+ {
+ return -EINVAL;
+ }
+
return 0;
}
static int pkg_metadata_version_token_cmp(FAR const char *lhs,
FAR const char *rhs)
{
- long leftnum;
- long rightnum;
- FAR char *leftend;
- FAR char *rightend;
+ FAR const char *cmpleft;
+ FAR const char *cmpright;
+ FAR const char *leftdigits;
+ FAR const char *rightdigits;
+ size_t leftlen;
+ size_t rightlen;
+ int ret;
- leftnum = strtol(lhs, &leftend, 10);
- rightnum = strtol(rhs, &rightend, 10);
+ leftdigits = lhs;
+ rightdigits = rhs;
+ while (isdigit((unsigned char)*leftdigits))
+ {
+ leftdigits++;
+ }
- if (leftend != lhs && rightend != rhs)
+ while (isdigit((unsigned char)*rightdigits))
{
- if (leftnum < rightnum)
+ rightdigits++;
+ }
+
+ if (leftdigits != lhs && rightdigits != rhs)
+ {
+ while (*lhs == '0' && lhs + 1 < leftdigits)
+ {
+ lhs++;
+ }
+
+ while (*rhs == '0' && rhs + 1 < rightdigits)
+ {
+ rhs++;
+ }
+
+ leftlen = (size_t)(leftdigits - lhs);
+ rightlen = (size_t)(rightdigits - rhs);
+ if (leftlen < rightlen)
{
return -1;
}
- if (leftnum > rightnum)
+ if (leftlen > rightlen)
{
return 1;
}
- }
- else
- {
- int ret;
- ret = pkg_string_cmp(lhs, PKG_VERSION_MAX + 1,
- rhs, PKG_VERSION_MAX + 1);
+ ret = memcmp(lhs, rhs, leftlen);
if (ret < 0)
{
return -1;
@@ -326,6 +453,26 @@ static int pkg_metadata_version_token_cmp(FAR const char
*lhs,
{
return 1;
}
+
+ cmpleft = leftdigits;
+ cmpright = rightdigits;
+ }
+ else
+ {
+ cmpleft = lhs;
+ cmpright = rhs;
+ }
+
+ ret = pkg_string_cmp(cmpleft, PKG_VERSION_MAX + 1,
+ cmpright, PKG_VERSION_MAX + 1);
+ if (ret < 0)
+ {
+ return -1;
+ }
+
+ if (ret > 0)
+ {
+ return 1;
}
return 0;
@@ -395,6 +542,8 @@ static FAR cJSON *pkg_metadata_manifest_to_json(
FAR const struct pkg_manifest_s *manifest)
{
FAR cJSON *root;
+ FAR cJSON *launch_args;
+ size_t i;
root = cJSON_CreateObject();
if (root == NULL)
@@ -410,51 +559,55 @@ static FAR cJSON *pkg_metadata_manifest_to_json(
cJSON_AddStringToObject(root, "sha256", manifest->sha256);
cJSON_AddStringToObject(root, "type",
pkg_manifest_type_str(manifest->type));
- return root;
-}
-/****************************************************************************
- * Public Functions
- ****************************************************************************/
-
-int pkg_metadata_load_index(FAR struct pkg_index_s *index)
-{
- FAR cJSON *root;
- FAR cJSON *packages;
- FAR cJSON *item;
- FAR char *text;
- char path[PATH_MAX];
- size_t count = 0;
- size_t textlen;
- int ret;
-
- if (index == NULL)
+ if (manifest->description[0] != '\0')
{
- return -EINVAL;
+ cJSON_AddStringToObject(root, "description", manifest->description);
}
- memset(index, 0, sizeof(*index));
-
- ret = pkg_store_format_index_path(path, sizeof(path));
- if (ret < 0)
+ if (manifest->category[0] != '\0')
{
- return ret;
+ cJSON_AddStringToObject(root, "category", manifest->category);
}
- pkg_info("loading index from %s", path);
-
- ret = pkg_store_read_text(path, &text);
- if (ret < 0)
+ if (manifest->launch_argc > 0)
{
- return ret;
+ launch_args = cJSON_AddArrayToObject(root, "launch_args");
+ if (launch_args == NULL)
+ {
+ cJSON_Delete(root);
+ return NULL;
+ }
+
+ for (i = 0; i < manifest->launch_argc; i++)
+ {
+ FAR cJSON *arg;
+
+ arg = cJSON_CreateString(manifest->launch_args[i]);
+ if (arg == NULL)
+ {
+ cJSON_Delete(root);
+ return NULL;
+ }
+
+ cJSON_AddItemToArray(launch_args, arg);
+ }
}
- textlen = strlen(text);
- pkg_info("index read complete (%zu bytes)", textlen);
+ return root;
+}
+
+static int pkg_metadata_parse_index_text(FAR const char *text,
+ FAR struct pkg_index_s *index)
+{
+ FAR cJSON *root;
+ FAR cJSON *packages;
+ FAR cJSON *item;
+ size_t count = 0;
+ int ret;
root = cJSON_Parse(text);
pkg_info("cJSON_Parse returned %s", root != NULL ? "success" : "failure");
- free(text);
if (root == NULL)
{
return -EINVAL;
@@ -471,15 +624,21 @@ int pkg_metadata_load_index(FAR struct pkg_index_s *index)
{
if (count >= PKG_INDEX_MAX)
{
- cJSON_Delete(root);
- return -E2BIG;
+ /* Keep entries parsed before the catalog limit. */
+
+ pkg_error("index has more than %d packages, truncating",
+ PKG_INDEX_MAX);
+ break;
}
ret = pkg_metadata_parse_manifest(item, &index->manifests[count]);
if (ret < 0)
{
- cJSON_Delete(root);
- return ret;
+ /* Skip malformed entries without rejecting the catalog. */
+
+ pkg_error("skipping malformed package entry %zu: %d", count,
+ ret);
+ continue;
}
pkg_info("parsed manifest %s %s",
@@ -493,6 +652,84 @@ int pkg_metadata_load_index(FAR struct pkg_index_s *index)
return 0;
}
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+int pkg_metadata_load_index_path(FAR const char *path,
+ FAR struct pkg_index_s *index)
+{
+ FAR char *text;
+ size_t textlen;
+ int ret;
+
+ if (path == NULL || index == NULL)
+ {
+ return -EINVAL;
+ }
+
+ memset(index, 0, sizeof(*index));
+
+ pkg_info("loading index from %s", path);
+
+ ret = pkg_store_read_text(path, &text);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ textlen = strlen(text);
+ pkg_info("index read complete (%zu bytes)", textlen);
+
+ ret = pkg_metadata_parse_index_text(text, index);
+ pkg_free(text);
+ return ret;
+}
+
+int pkg_metadata_load_index(FAR struct pkg_index_s *index)
+{
+ char path[PATH_MAX];
+ int ret;
+
+ ret = pkg_store_format_index_path(path, sizeof(path));
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ return pkg_metadata_load_index_path(path, index);
+}
+
+int pkg_metadata_load_manifest_path(FAR const char *path,
+ FAR struct pkg_manifest_s *manifest)
+{
+ FAR cJSON *root;
+ FAR char *text;
+ int ret;
+
+ if (path == NULL || manifest == NULL)
+ {
+ return -EINVAL;
+ }
+
+ ret = pkg_store_read_text(path, &text);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ root = cJSON_Parse(text);
+ pkg_free(text);
+ if (root == NULL)
+ {
+ return -EINVAL;
+ }
+
+ ret = pkg_metadata_parse_manifest(root, manifest);
+ cJSON_Delete(root);
+ return ret;
+}
+
FAR const struct pkg_manifest_s *
pkg_metadata_find_latest(FAR const struct pkg_index_s *index,
FAR const char *name)
@@ -573,7 +810,7 @@ int pkg_metadata_load_installed(FAR struct
pkg_installed_db_s *db)
}
root = cJSON_Parse(text);
- free(text);
+ pkg_free(text);
if (root == NULL)
{
return -EINVAL;
@@ -590,15 +827,19 @@ int pkg_metadata_load_installed(FAR struct
pkg_installed_db_s *db)
{
if (count >= PKG_INSTALLED_MAX)
{
- cJSON_Delete(root);
- return -E2BIG;
+ pkg_error("installed db has more than %d entries, truncating",
+ PKG_INSTALLED_MAX);
+ break;
}
ret = pkg_metadata_parse_installed_entry(item, &db->entries[count]);
if (ret < 0)
{
- cJSON_Delete(root);
- return ret;
+ /* Keep valid installed entries when one is corrupt. */
+
+ pkg_error("skipping malformed installed entry %zu: %d", count,
+ ret);
+ continue;
}
count++;