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 <gargaviral99@gmail.com>
This commit is contained in:
aviralgarg05 2026-08-15 15:56:41 +05:30 committed by Alan C. Assis
parent d0c15c8c87
commit 0717e05248
2 changed files with 357 additions and 58 deletions

View file

@ -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;
}

View file

@ -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);
if (leftend != lhs && rightend != rhs)
leftdigits = lhs;
rightdigits = rhs;
while (isdigit((unsigned char)*leftdigits))
{
if (leftnum < rightnum)
leftdigits++;
}
while (isdigit((unsigned char)*rightdigits))
{
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));
if (manifest->description[0] != '\0')
{
cJSON_AddStringToObject(root, "description", manifest->description);
}
if (manifest->category[0] != '\0')
{
cJSON_AddStringToObject(root, "category", manifest->category);
}
if (manifest->launch_argc > 0)
{
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);
}
}
return root;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int pkg_metadata_load_index(FAR struct pkg_index_s *index)
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;
FAR char *text;
char path[PATH_MAX];
size_t count = 0;
size_t textlen;
int ret;
if (index == NULL)
{
return -EINVAL;
}
memset(index, 0, sizeof(*index));
ret = pkg_store_format_index_path(path, sizeof(path));
if (ret < 0)
{
return ret;
}
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);
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++;