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++;

Reply via email to