mailarchive of the ptxdist mailing list
 help / color / mirror / Atom feed
From: Alexander Dahl via ptxdist <ptxdist@pengutronix.de>
To: ptxdist@pengutronix.de
Cc: Alexander Dahl <ada@thorsis.com>
Subject: [ptxdist] [PATCH v2 4/5] libubootenv: Import some fixes from master
Date: Fri, 19 Sep 2025 12:06:42 +0200	[thread overview]
Message-ID: <20250919100643.199174-5-ada@thorsis.com> (raw)
In-Reply-To: <20250919100643.199174-1-ada@thorsis.com>

Not part of a new libubootenv release yet, but at least the ubi volume
fix is necessary.  Otherwise fw_printenv might fail on arm v7a storing
u-boot env in an ubi volume like this:

    root@DistroKit:/mnt/data/tmp fw_printenv
    *** buffer overflow detected ***: terminated
    Aborted (core dumped)

Picked the other fixes along the way.

Signed-off-by: Alexander Dahl <ada@thorsis.com>
---

Notes:
    v2:
    - new patch, not present in v1

 .../0001-Make-libyaml-optional.patch          | 1507 +++++++++++++++++
 ..._config-fix-segfault-on-empty-config.patch |   40 +
 ...-Fix-warning-when-copying-UBI-volume.patch |   35 +
 ...segfault-due-to-uninitialized-pointe.patch |   32 +
 ...nfig.c-Catch-NULL-pointer-for-calloc.patch |   30 +
 patches/libubootenv-0.3.6/series              |    8 +
 rules/libubootenv.make                        |    3 +-
 7 files changed, 1654 insertions(+), 1 deletion(-)
 create mode 100644 patches/libubootenv-0.3.6/0001-Make-libyaml-optional.patch
 create mode 100644 patches/libubootenv-0.3.6/0002-extended_config-fix-segfault-on-empty-config.patch
 create mode 100644 patches/libubootenv-0.3.6/0003-BUG-Fix-warning-when-copying-UBI-volume.patch
 create mode 100644 patches/libubootenv-0.3.6/0004-libubootenv-fix-segfault-due-to-uninitialized-pointe.patch
 create mode 100644 patches/libubootenv-0.3.6/0005-extended_config.c-Catch-NULL-pointer-for-calloc.patch
 create mode 100644 patches/libubootenv-0.3.6/series

diff --git a/patches/libubootenv-0.3.6/0001-Make-libyaml-optional.patch b/patches/libubootenv-0.3.6/0001-Make-libyaml-optional.patch
new file mode 100644
index 000000000..631d0f1ef
--- /dev/null
+++ b/patches/libubootenv-0.3.6/0001-Make-libyaml-optional.patch
@@ -0,0 +1,1507 @@
+From: Stefano Babic <stefano.babic@swupdate.org>
+Date: Tue, 29 Oct 2024 09:23:20 +0100
+Subject: [PATCH] Make libyaml optional
+
+NewYAML format is required for extended features because the format
+foreseen by U-Boot is very limited. However, some systems due to
+low resources don't want to link to libyaml. Add the option
+NO_YML_SUPPORT to disable YAML configuration file and just use
+fw_env.config in the U-Boot format.
+
+There are no functional changes in this patch - function depending on
+YML are moved in a separate file, and some functions are factorized.
+
+Signed-off-by: Stefano Babic <stefano.babic@swupdate.org>
+---
+ CMakeLists.txt        |   6 +
+ src/CMakeLists.txt    |   9 +-
+ src/common.c          | 248 +++++++++++++++++++
+ src/common.h          |  15 ++
+ src/extended_config.c | 452 ++++++++++++++++++++++++++++++++++
+ src/uboot_env.c       | 655 +-------------------------------------------------
+ 6 files changed, 735 insertions(+), 650 deletions(-)
+ create mode 100644 src/common.c
+ create mode 100644 src/common.h
+ create mode 100644 src/extended_config.c
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 3bb93e16b42b..796d7bcffb29 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -12,6 +12,8 @@ set(VERSION	"0.3.6")
+ SET(SOVERSION "0")
+ add_definitions(-DVERSION="${VERSION}")
+ 
++option(NO_YML_SUPPORT "YML Support")
++
+ if(DEFAULT_CFG_FILE)
+     add_definitions(-DDEFAULT_CFG_FILE="${DEFAULT_CFG_FILE}")
+ endif(DEFAULT_CFG_FILE)
+@@ -20,6 +22,10 @@ if(DEFAULT_ENV_FILE)
+     add_definitions(-DDEFAULT_ENV_FILE="${DEFAULT_ENV_FILE}")
+ endif(DEFAULT_ENV_FILE)
+ 
++if(NO_YML_SUPPORT)
++  add_definitions(-DNO_YAML_SUPPORT)
++endif(NO_YML_SUPPORT)
++
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99")
+ 
+ #set(CMAKE_C_FLAGS_DEBUG "-g")
+diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
+index 63d48225f83b..c56d0c756528 100644
+--- a/src/CMakeLists.txt
++++ b/src/CMakeLists.txt
+@@ -6,6 +6,9 @@ cmake_minimum_required (VERSION 2.6)
+ SET(libubootenv_SOURCES
+   uboot_env.c
+   uboot_mtd.c
++  extended_config.c
++  common.c
++  common.h
+   uboot_private.h
+ )
+ 
+@@ -22,7 +25,11 @@ SET_TARGET_PROPERTIES(ubootenv PROPERTIES VERSION ${VERSION} SOVERSION ${SOVERSI
+ ADD_LIBRARY(ubootenv_static STATIC ${libubootenv_SOURCES} ${include_HEADERS})
+ SET_TARGET_PROPERTIES(ubootenv_static PROPERTIES OUTPUT_NAME ubootenv)
+ add_executable(fw_printenv fw_printenv.c)
+-target_link_libraries(ubootenv z yaml)
++target_link_libraries(ubootenv z)
++if (NOT NO_YML_SUPPORT)
++target_link_libraries(ubootenv yaml)
++endif(NOT NO_YML_SUPPORT)
++
+ target_link_libraries(fw_printenv ubootenv)
+ add_custom_target(fw_setenv ALL ${CMAKE_COMMAND} -E create_symlink fw_printenv fw_setenv)
+ 
+diff --git a/src/common.c b/src/common.c
+new file mode 100644
+index 000000000000..2cbde93a7140
+--- /dev/null
++++ b/src/common.c
+@@ -0,0 +1,248 @@
++/*
++ * (C) Copyright 2024
++ * Stefano Babic, <stefano.babic@swupdate.org>
++ *
++ * SPDX-License-Identifier:     LGPL-2.1-or-later
++ */
++
++#define _GNU_SOURCE
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <stdint.h>
++#include <stddef.h>
++#include <dirent.h>
++#include <unistd.h>
++#include <limits.h>
++#include <string.h>
++#include <errno.h>
++#include <fcntl.h>
++#include <sys/stat.h>
++#include <sys/ioctl.h>
++#ifdef __FreeBSD__
++#include <sys/disk.h>
++#define BLKGETSIZE64 DIOCGMEDIASIZE
++#else
++#include <linux/fs.h>
++#endif
++
++#include "uboot_private.h"
++#include "common.h"
++
++static enum device_type get_device_type(char *device)
++{
++	enum device_type type = DEVICE_NONE;
++
++	if (!strncmp(device, DEVICE_MTD_NAME, strlen(DEVICE_MTD_NAME)))
++		if (strchr(device, DEVNAME_SEPARATOR)) {
++			type = DEVICE_UBI;
++		} else {
++			type = DEVICE_MTD;
++		}
++	else if (!strncmp(device, DEVICE_UBI_NAME, strlen(DEVICE_UBI_NAME)))
++		type = DEVICE_UBI;
++	else if (strlen(device) > 0)
++		type = DEVICE_FILE;
++
++	return type;
++}
++
++int normalize_device_path(char *path, struct uboot_flash_env *dev)
++{
++	char *sep = NULL, *normalized = NULL;
++	size_t normalized_len = 0, volume_len = 0, output_len = 0;
++
++	/*
++	 * if volume name is present, split into device path and volume
++	 * since only the device path needs normalized
++	 */
++	sep = strchr(path, DEVNAME_SEPARATOR);
++	if (sep)
++	{
++		volume_len = strlen(sep);
++		*sep = '\0';
++	}
++
++	if ((normalized = realpath(path, NULL)) == NULL)
++	{
++		/* device file didn't exist */
++		return -EINVAL;
++	}
++
++	normalized_len = strlen(normalized);
++	output_len = sizeof(dev->devname) - 1; /* leave room for null */
++	if ((normalized_len + volume_len) > output_len)
++	{
++		/* full name is too long to fit */
++		free(normalized);
++		return -EINVAL;
++	}
++
++	/*
++	 * save normalized path to device file,
++	 * and possibly append separator char & volume name
++	 */
++	memset(dev->devname, 0, sizeof(dev->devname));
++	strncpy(dev->devname, normalized, output_len);
++	free(normalized);
++
++	if (sep)
++	{
++		*sep = DEVNAME_SEPARATOR;
++		strncpy(dev->devname + normalized_len, sep, output_len - normalized_len);
++	}
++
++	return 0;
++}
++
++void set_var_access_type(struct var_entry *entry, const char *pvarflags)
++{
++	if (entry) {
++		for (int i = 0; i < strlen(pvarflags); i++) {
++			switch (pvarflags[i]) {
++			case 's':
++				entry->type = TYPE_ATTR_STRING;
++				break;
++			case 'd':
++				entry->type = TYPE_ATTR_DECIMAL;
++				break;
++			case 'x':
++				entry->type = TYPE_ATTR_HEX;
++				break;
++			case 'b':
++				entry->type = TYPE_ATTR_BOOL;
++				break;
++			case 'i':
++				entry->type = TYPE_ATTR_IP;
++				break;
++			case 'm':
++				entry->type = TYPE_ATTR_MAC;
++				break;
++			case 'a':
++				entry->access = ACCESS_ATTR_ANY;
++				break;
++			case 'r':
++				entry->access = ACCESS_ATTR_READ_ONLY;
++				break;
++			case 'o':
++				entry->access = ACCESS_ATTR_WRITE_ONCE;
++				break;
++			case 'c':
++				entry->access = ACCESS_ATTR_CHANGE_DEFAULT;
++				break;
++			default: /* ignore it */
++				break;
++			}
++		}
++	}
++}
++
++struct var_entry *create_var_entry(const char *name)
++{
++	struct var_entry *entry;
++
++	entry = (struct var_entry *)calloc(1, sizeof(*entry));
++	if (!entry)
++		return NULL;
++	entry->name = strdup(name);
++	if (!entry->name) {
++		free(entry);
++		return NULL;
++	}
++
++	return entry;
++}
++
++bool check_compatible_devices(struct uboot_ctx *ctx)
++{
++	if (!ctx->redundant)
++		return true;
++
++	if (ctx->envdevs[0].mtdinfo.type != ctx->envdevs[1].mtdinfo.type)
++		return false;
++	if (ctx->envdevs[0].flagstype != ctx->envdevs[1].flagstype)
++		return false;
++	if (ctx->envdevs[0].envsize != ctx->envdevs[1].envsize)
++		return false;
++
++	return true;
++}
++
++int check_env_device(struct uboot_flash_env *dev)
++{
++	int fd, ret;
++	struct stat st;
++
++	dev->device_type = get_device_type(dev->devname);
++	if (dev->device_type == DEVICE_NONE)
++		return -EBADF;
++
++	if (dev->device_type == DEVICE_UBI) {
++		ret = libubootenv_ubi_update_name(dev);
++		if (ret)
++			return ret;
++	}
++
++	ret = stat(dev->devname, &st);
++	if (ret < 0)
++		return -EBADF;
++	fd = open(dev->devname, O_RDONLY);
++	if (fd < 0)
++		return -EBADF;
++
++	if (S_ISCHR(st.st_mode)) {
++		if (dev->device_type == DEVICE_MTD) {
++			ret = libubootenv_mtdgetinfo(fd, dev);
++			if (ret < 0 || (dev->mtdinfo.type != MTD_NORFLASH &&
++					dev->mtdinfo.type != MTD_NANDFLASH)) {
++				close(fd);
++				return -EBADF;
++			}
++			if (dev->sectorsize == 0) {
++				dev->sectorsize = dev->mtdinfo.erasesize;
++			}
++		}
++	}
++
++	switch (dev->device_type) {
++	case DEVICE_FILE:
++		dev->flagstype = FLAGS_INCREMENTAL;
++		break;
++	case DEVICE_MTD:
++		switch (dev->mtdinfo.type) {
++		case MTD_NORFLASH:
++			dev->flagstype = FLAGS_BOOLEAN;
++			break;
++		case MTD_NANDFLASH:
++			dev->flagstype = FLAGS_INCREMENTAL;
++		};
++		break;
++	case DEVICE_UBI:
++		dev->flagstype = FLAGS_INCREMENTAL;
++		break;
++	default:
++		close(fd);
++		return -EBADF;
++	};
++
++	/*
++	 * Check for negative offsets, treat it as backwards offset
++	 * from the end of the block device
++	 */
++	if (dev->offset < 0) {
++		uint64_t blkdevsize;
++		int rc;
++
++		rc = ioctl(fd, BLKGETSIZE64, &blkdevsize);
++		if (rc < 0) {
++			close(fd);
++			return -EINVAL;
++		}
++
++		dev->offset += blkdevsize;
++	}
++
++	close(fd);
++
++	return 0;
++}
+diff --git a/src/common.h b/src/common.h
+new file mode 100644
+index 000000000000..adacd4896741
+--- /dev/null
++++ b/src/common.h
+@@ -0,0 +1,15 @@
++/*
++ * (C) Copyright 2024
++ * Stefano Babic, <stefano.babic@swupdate.org>
++ *
++ * SPDX-License-Identifier:     LGPL-2.1-or-later
++ */
++
++
++#include "uboot_private.h"
++
++struct var_entry *create_var_entry(const char *name);
++void set_var_access_type(struct var_entry *entry, const char *pvarflags);
++int normalize_device_path(char *path, struct uboot_flash_env *dev);
++int check_env_device(struct uboot_flash_env *dev);
++bool check_compatible_devices(struct uboot_ctx *ctx);
+diff --git a/src/extended_config.c b/src/extended_config.c
+new file mode 100644
+index 000000000000..ec814f484efd
+--- /dev/null
++++ b/src/extended_config.c
+@@ -0,0 +1,452 @@
++/*
++ * (C) Copyright 2024
++ * Stefano Babic, <stefano.babic@swupdate.org>
++ *
++ * SPDX-License-Identifier:     LGPL-2.1-or-later
++ */
++
++/**
++ * @file extended_config.c
++ *
++ * @brief Implement the extended config file YAML
++	*
++ */
++#define _GNU_SOURCE
++
++#if !defined(NO_YAML_SUPPORT)
++#include <stdio.h>
++#include <stdlib.h>
++#include <stdint.h>
++#include <stddef.h>
++#include <unistd.h>
++#include <errno.h>
++#include <yaml.h>
++
++#include "uboot_private.h"
++#include "common.h"
++
++/* yaml_* functions return 1 on success and 0 on failure. */
++enum yaml_status {
++    SUCCESS = 0,
++    FAILURE = 1
++};
++
++enum yaml_state {
++	STATE_START,    /* start state */
++	STATE_STREAM,   /* start/end stream */
++	STATE_DOCUMENT, /* start/end document */
++	STATE_SECTION,  /* top level */
++
++	STATE_NAMESPACE,	/* Init Configuration Namespace */
++	STATE_NAMESPACE_FIELDS,	/* namespace key list */
++	STATE_NKEY,		/* Check key names */
++	STATE_NSIZE,		/* Size key-value pair */
++	STATE_NLOCKFILE,	/* Lockfile key-value pair */
++	STATE_DEVVALUES,	/* Devices key names */
++	STATE_WRITELIST,	/* List with vars that are accepted by write
++				 * if list is missing, all vars are accepted
++				 * var is in the format name:flags, see U-Boot
++				 * documentation
++				 */
++
++	STATE_NPATH,
++	STATE_NOFFSET,
++	STATE_NSECTORSIZE,
++	STATE_NUNLOCK,
++	STATE_STOP      /* end state */
++};
++
++typedef enum yaml_parse_error_e {
++	YAML_UNEXPECTED_STATE,
++	YAML_UNEXPECTED_KEY,
++	YAML_BAD_DEVICE,
++	YAML_BAD_DEVNAME,
++	YAML_BAD_VARLIST,
++	YAML_DUPLICATE_VARLIST,
++	YAML_OOM,
++} yaml_parse_error_type_t;
++
++struct parser_state {
++	enum yaml_state state;		/* The current parse state */
++	struct uboot_ctx *ctxsets;	/* Array of vars set ctx */
++	struct uboot_ctx *ctx;		/* Current ctx in parsing */
++	unsigned int nelem;		/* Number of elemets in ctxsets */
++	unsigned int cdev;		/* current device in parsing */
++	yaml_parse_error_type_t error;	/* error causing parser to stop */
++	yaml_event_type_t event_type;	/* event type causing error */
++};
++
++static int consume_event(struct parser_state *s, yaml_event_t *event)
++{
++	char *value;
++	struct uboot_flash_env *dev;
++	struct uboot_ctx *newctx;
++	int cdev;
++
++	switch (s->state) {
++	case STATE_START:
++		switch (event->type) {
++		case YAML_STREAM_START_EVENT:
++			s->state = STATE_STREAM;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_STREAM:
++		switch (event->type) {
++		case YAML_DOCUMENT_START_EVENT:
++			s->state = STATE_DOCUMENT;
++			break;
++		case YAML_STREAM_END_EVENT:
++			s->state = STATE_STOP;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_DOCUMENT:
++		switch (event->type) {
++		case YAML_MAPPING_START_EVENT:
++			s->state = STATE_SECTION;
++			break;
++		case YAML_DOCUMENT_END_EVENT:
++			s->state = STATE_STREAM;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_SECTION:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			value = (char *)event->data.scalar.value;
++			newctx = calloc (s->nelem + 1, sizeof(*newctx));
++			for (int i = 0; i < s->nelem; i++) {
++				newctx[i] = s->ctxsets[i];
++			}
++			if (s->ctxsets) free(s->ctxsets);
++			s->ctxsets = newctx;
++			s->ctx = &newctx[s->nelem];
++			s->ctx->name = strdup(value);
++			s->nelem++;
++			s->state = STATE_NAMESPACE;
++			break;
++		case YAML_MAPPING_END_EVENT:
++			s->state = STATE_DOCUMENT;
++			break;
++		case YAML_DOCUMENT_END_EVENT:
++			s->state = STATE_STREAM;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NAMESPACE:
++		switch (event->type) {
++		case YAML_MAPPING_START_EVENT:
++			s->state = STATE_NAMESPACE_FIELDS;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NAMESPACE_FIELDS:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			value = (char *)event->data.scalar.value;
++			if (!strcmp(value, "size")) {
++				s->state = STATE_NSIZE;
++			} else if (!strcmp(value, "lockfile")) {
++				s->state = STATE_NLOCKFILE;
++			} else if (!strcmp(value, "devices")) {
++				s->state = STATE_DEVVALUES;
++				s->cdev = 0;
++			} else if (!strcmp(value, "writelist")) {
++				s->state = STATE_WRITELIST;
++			} else {
++				s->error = YAML_UNEXPECTED_KEY;
++				s->event_type = event->type;
++				return FAILURE;
++			}
++			break;
++		case YAML_MAPPING_END_EVENT:
++			s->state = STATE_SECTION;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NSIZE:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			value = (char *)event->data.scalar.value;
++			errno = 0;
++			s->ctx->size = strtoull(value, NULL, 0);
++			s->state = STATE_NAMESPACE_FIELDS;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NLOCKFILE:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			value = (char *)event->data.scalar.value;
++			s->ctx->lockfile = strdup(value);
++			s->state = STATE_NAMESPACE_FIELDS;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_DEVVALUES:
++		switch (event->type) {
++		case YAML_MAPPING_START_EVENT:
++		case YAML_SEQUENCE_START_EVENT:
++			break;
++		case YAML_MAPPING_END_EVENT:
++			dev = &s->ctx->envdevs[s->cdev];
++			if (check_env_device(dev) < 0) {
++				s->error = YAML_BAD_DEVICE;
++				s->event_type = event->type;
++				return FAILURE;
++			}
++			s->cdev++;
++			break;
++		case YAML_SEQUENCE_END_EVENT:
++			s->state = STATE_NAMESPACE_FIELDS;
++			break;
++		case YAML_SCALAR_EVENT:
++			value = (char *)event->data.scalar.value;
++			if (s->cdev)
++				s->ctx->redundant = true;
++			if (!strcmp(value, "path")) {
++				s->state = STATE_NPATH;
++			} else if (!strcmp(value, "offset")) {
++				s->state = STATE_NOFFSET;
++			} else if (!strcmp(value, "sectorsize")) {
++				s->state = STATE_NSECTORSIZE;
++				} else if (!strcmp(value, "disablelock")) {
++				s->state = STATE_NUNLOCK;
++			} else {
++				s->error = YAML_UNEXPECTED_KEY;
++				s->event_type = event->type;
++				return FAILURE;
++			}
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_WRITELIST:
++		switch (event->type) {
++
++		char *varflag, *name;
++		struct var_entry *entry;
++
++		case YAML_MAPPING_START_EVENT:
++		case YAML_SEQUENCE_START_EVENT:
++			break;
++		case YAML_MAPPING_END_EVENT:
++			break;
++		case YAML_SEQUENCE_END_EVENT:
++			s->state = STATE_NAMESPACE_FIELDS;
++			break;
++		case YAML_SCALAR_EVENT:
++			value = (char *)event->data.scalar.value;
++
++			/*
++			 * Format is name:flags, split it into two values
++			 */
++			varflag = strchr(value, ':');
++			if (!varflag || varflag > value + (strlen(value) - 1)) {
++				s->error = YAML_BAD_VARLIST;
++				s->event_type = event->type;
++				return FAILURE;
++			}
++			*varflag++ = '\0';
++
++			/*
++			 * Check there is not yet an entry for this variable
++			 */
++			LIST_FOREACH(entry, &s->ctx->writevarlist, next) {
++				if (strcmp(entry->name, value) == 0) {
++					s->error = YAML_DUPLICATE_VARLIST;
++					s->event_type = event->type;
++					return FAILURE;
++				}
++			}
++
++			/*
++			 * Insert variable with its configuration into the list
++			 * of modifiable vars
++			 */
++			entry = create_var_entry(value);
++			if (!entry) {
++				s->error = YAML_OOM;
++				s->event_type = event->type;
++				return FAILURE;
++			}
++			set_var_access_type(entry, varflag);
++			LIST_INSERT_HEAD(&s->ctx->writevarlist, entry, next);
++
++#if !defined(NDEBUG)
++			fprintf(stdout, "Writelist: %s flags %s\n", value, varflag);
++#endif
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NPATH:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			dev = &s->ctx->envdevs[s->cdev];
++			value = (char *)event->data.scalar.value;
++			if (normalize_device_path(value, dev) < 0) {
++				s->error = YAML_BAD_DEVNAME;
++				s->event_type = event->type;
++				return FAILURE;
++			}
++			dev->envsize = s->ctx->size;
++			s->state = STATE_DEVVALUES;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NOFFSET:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			dev = &s->ctx->envdevs[s->cdev];
++			value = (char *)event->data.scalar.value;
++			dev->offset = strtoull(value, NULL, 0);
++			s->state = STATE_DEVVALUES;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NSECTORSIZE:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			dev = &s->ctx->envdevs[s->cdev];
++			value = (char *)event->data.scalar.value;
++			dev->sectorsize = strtoull(value, NULL, 0);
++			s->state = STATE_DEVVALUES;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++	case STATE_NUNLOCK:
++		switch (event->type) {
++		case YAML_SCALAR_EVENT:
++			dev = &s->ctx->envdevs[s->cdev];
++			value = (char *)event->data.scalar.value;
++			if (!strcmp(value, "yes"))
++				dev->disable_mtd_lock = 1;
++			s->state = STATE_DEVVALUES;
++			break;
++		default:
++			s->error = YAML_UNEXPECTED_STATE;
++			s->event_type = event->type;
++			return FAILURE;
++		}
++		break;
++
++    case STATE_STOP:
++        break;
++    }
++    return SUCCESS;
++}
++
++int parse_yaml_config(struct uboot_ctx **ctxlist, FILE *fp)
++{
++	yaml_parser_t parser;
++	yaml_event_t  event;
++	enum yaml_status status;
++	struct parser_state state;
++	struct uboot_ctx *ctx;
++
++	if (!yaml_parser_initialize(&parser))
++		return -ENOMEM;
++
++	 /* Set input file */
++	yaml_parser_set_input_file(&parser, fp);
++	memset(&state, 0, sizeof(state));
++	state.state = STATE_START;
++	do {
++		if (!yaml_parser_parse(&parser, &event)) {
++			status = FAILURE;
++			goto cleanup;
++		}
++		status = consume_event(&state, &event);
++		yaml_event_delete(&event);
++		if (status == FAILURE) {
++			goto cleanup;
++		}
++	} while (state.state != STATE_STOP);
++
++	state.ctxsets[0].nelem = state.nelem;
++
++	for (int i = 0; i < state.nelem; i++) {
++		ctx = &state.ctxsets[i];
++		ctx->ctxlist = &state.ctxsets[0];
++		if (ctx->redundant && !check_compatible_devices(ctx)) {
++			status = FAILURE;
++			break;
++		}
++	}
++
++
++cleanup:
++	yaml_parser_delete(&parser);
++	if (status == FAILURE) {
++		if (state.ctxsets) free (state.ctxsets);
++		state.ctxsets = NULL;
++	}
++	*ctxlist = state.ctxsets;
++	return status;
++}
++#endif
+diff --git a/src/uboot_env.c b/src/uboot_env.c
+index 0b4f9f4d0458..d8b93dac7479 100644
+--- a/src/uboot_env.c
++++ b/src/uboot_env.c
+@@ -21,12 +21,6 @@
+ #include <dirent.h>
+ #include <unistd.h>
+ #include <limits.h>
+-#ifdef __FreeBSD__
+-#include <sys/disk.h>
+-#define BLKGETSIZE64 DIOCGMEDIASIZE
+-#else
+-#include <linux/fs.h>
+-#endif
+ #include <string.h>
+ #include <fcntl.h>
+ #include <errno.h>
+@@ -38,60 +32,15 @@
+ #include <sys/wait.h>
+ #include <sys/ioctl.h>
+ #include <zlib.h>
+-#include <yaml.h>
+ 
+ #include "uboot_private.h"
++#include "common.h"
+ 
+-/* yaml_* functions return 1 on success and 0 on failure. */
+-enum yaml_status {
+-    SUCCESS = 0,
+-    FAILURE = 1
+-};
+-
+-enum yaml_state {
+-	STATE_START,    /* start state */
+-	STATE_STREAM,   /* start/end stream */
+-	STATE_DOCUMENT, /* start/end document */
+-	STATE_SECTION,  /* top level */
+-
+-	STATE_NAMESPACE,	/* Init Configuration Namespace */
+-	STATE_NAMESPACE_FIELDS,	/* namespace key list */
+-	STATE_NKEY,		/* Check key names */
+-	STATE_NSIZE,		/* Size key-value pair */
+-	STATE_NLOCKFILE,	/* Lockfile key-value pair */
+-	STATE_DEVVALUES,	/* Devices key names */
+-	STATE_WRITELIST,	/* List with vars that are accepted by write
+-				 * if list is missing, all vars are accepted
+-				 * var is in the format name:flags, see U-Boot
+-				 * documentation
+-				 */
+-
+-	STATE_NPATH,
+-	STATE_NOFFSET,
+-	STATE_NSECTORSIZE,
+-	STATE_NUNLOCK,
+-	STATE_STOP      /* end state */
+-};
+-
+-typedef enum yaml_parse_error_e {
+-	YAML_UNEXPECTED_STATE,
+-	YAML_UNEXPECTED_KEY,
+-	YAML_BAD_DEVICE,
+-	YAML_BAD_DEVNAME,
+-	YAML_BAD_VARLIST,
+-	YAML_DUPLICATE_VARLIST,
+-	YAML_OOM,
+-} yaml_parse_error_type_t;
+-
+-struct parser_state {
+-	enum yaml_state state;		/* The current parse state */
+-	struct uboot_ctx *ctxsets;	/* Array of vars set ctx */
+-	struct uboot_ctx *ctx;		/* Current ctx in parsing */
+-	unsigned int nelem;		/* Number of elemets in ctxsets */
+-	unsigned int cdev;		/* current device in parsing */
+-	yaml_parse_error_type_t error;	/* error causing parser to stop */
+-	yaml_event_type_t event_type;	/* event type causing error */
+-};
++#if defined(NO_YAML_SUPPORT)
++#define parse_yaml_config(ctx,fp) -1
++#else
++extern int parse_yaml_config(struct uboot_ctx **ctxlist, FILE *fp);
++#endif
+ 
+ #define FREE_ENTRY do { \
+ 	free(entry->name); \
+@@ -152,64 +101,6 @@ static char attr_tostring(type_attribute a)
+ 	return 's';
+ }
+ 
+-static void set_var_access_type(struct var_entry *entry, const char *pvarflags)
+-{
+-	if (entry) {
+-		for (int i = 0; i < strlen(pvarflags); i++) {
+-			switch (pvarflags[i]) {
+-			case 's':
+-				entry->type = TYPE_ATTR_STRING;
+-				break;
+-			case 'd':
+-				entry->type = TYPE_ATTR_DECIMAL;
+-				break;
+-			case 'x':
+-				entry->type = TYPE_ATTR_HEX;
+-				break;
+-			case 'b':
+-				entry->type = TYPE_ATTR_BOOL;
+-				break;
+-			case 'i':
+-				entry->type = TYPE_ATTR_IP;
+-				break;
+-			case 'm':
+-				entry->type = TYPE_ATTR_MAC;
+-				break;
+-			case 'a':
+-				entry->access = ACCESS_ATTR_ANY;
+-				break;
+-			case 'r':
+-				entry->access = ACCESS_ATTR_READ_ONLY;
+-				break;
+-			case 'o':
+-				entry->access = ACCESS_ATTR_WRITE_ONCE;
+-				break;
+-			case 'c':
+-				entry->access = ACCESS_ATTR_CHANGE_DEFAULT;
+-				break;
+-			default: /* ignore it */
+-				break;
+-			}
+-		}
+-	}
+-}
+-
+-static struct var_entry *create_var_entry(const char *name)
+-{
+-	struct var_entry *entry;
+-
+-	entry = (struct var_entry *)calloc(1, sizeof(*entry));
+-	if (!entry)
+-		return NULL;
+-	entry->name = strdup(name);
+-	if (!entry->name) {
+-		free(entry);
+-		return NULL;
+-	}
+-
+-	return entry;
+-}
+-
+ static char access_tostring(access_attribute a)
+ {
+ 	switch(a) {
+@@ -389,166 +280,6 @@ static int __libuboot_set_env(struct uboot_ctx *ctx, const char *varname, const
+ 	return 0;
+ }
+ 
+-static enum device_type get_device_type(char *device)
+-{
+-	enum device_type type = DEVICE_NONE;
+-
+-	if (!strncmp(device, DEVICE_MTD_NAME, strlen(DEVICE_MTD_NAME)))
+-		if (strchr(device, DEVNAME_SEPARATOR)) {
+-			type = DEVICE_UBI;
+-		} else {
+-			type = DEVICE_MTD;
+-		}
+-	else if (!strncmp(device, DEVICE_UBI_NAME, strlen(DEVICE_UBI_NAME)))
+-		type = DEVICE_UBI;
+-	else if (strlen(device) > 0)
+-		type = DEVICE_FILE;
+-
+-	return type;
+-}
+-
+-static int normalize_device_path(char *path, struct uboot_flash_env *dev)
+-{
+-	char *sep = NULL, *normalized = NULL;
+-	size_t normalized_len = 0, volume_len = 0, output_len = 0;
+-
+-	/*
+-	 * if volume name is present, split into device path and volume
+-	 * since only the device path needs normalized
+-	 */
+-	sep = strchr(path, DEVNAME_SEPARATOR);
+-	if (sep)
+-	{
+-		volume_len = strlen(sep);
+-		*sep = '\0';
+-	}
+-
+-	if ((normalized = realpath(path, NULL)) == NULL)
+-	{
+-		/* device file didn't exist */
+-		return -EINVAL;
+-	}
+-
+-	normalized_len = strlen(normalized);
+-	output_len = sizeof(dev->devname) - 1; /* leave room for null */
+-	if ((normalized_len + volume_len) > output_len)
+-	{
+-		/* full name is too long to fit */
+-		free(normalized);
+-		return -EINVAL;
+-	}
+-
+-	/*
+-	 * save normalized path to device file,
+-	 * and possibly append separator char & volume name
+-	 */
+-	memset(dev->devname, 0, sizeof(dev->devname));
+-	strncpy(dev->devname, normalized, output_len);
+-	free(normalized);
+-
+-	if (sep)
+-	{
+-		*sep = DEVNAME_SEPARATOR;
+-		strncpy(dev->devname + normalized_len, sep, output_len - normalized_len);
+-	}
+-
+-	return 0;
+-}
+-
+-static int check_env_device(struct uboot_flash_env *dev)
+-{
+-	int fd, ret;
+-	struct stat st;
+-
+-	dev->device_type = get_device_type(dev->devname);
+-	if (dev->device_type == DEVICE_NONE)
+-		return -EBADF;
+-
+-	if (dev->device_type == DEVICE_UBI) {
+-		ret = libubootenv_ubi_update_name(dev);
+-		if (ret)
+-			return ret;
+-	}
+-
+-	ret = stat(dev->devname, &st);
+-	if (ret < 0)
+-		return -EBADF;
+-	fd = open(dev->devname, O_RDONLY);
+-	if (fd < 0)
+-		return -EBADF;
+-
+-	if (S_ISCHR(st.st_mode)) {
+-		if (dev->device_type == DEVICE_MTD) {
+-			ret = libubootenv_mtdgetinfo(fd, dev);
+-			if (ret < 0 || (dev->mtdinfo.type != MTD_NORFLASH &&
+-					dev->mtdinfo.type != MTD_NANDFLASH)) {
+-				close(fd);
+-				return -EBADF;
+-			}
+-			if (dev->sectorsize == 0) {
+-				dev->sectorsize = dev->mtdinfo.erasesize;
+-			}
+-		}
+-	}
+-
+-	switch (dev->device_type) {
+-	case DEVICE_FILE:
+-		dev->flagstype = FLAGS_INCREMENTAL;
+-		break;
+-	case DEVICE_MTD:
+-		switch (dev->mtdinfo.type) {
+-		case MTD_NORFLASH:
+-			dev->flagstype = FLAGS_BOOLEAN;
+-			break;
+-		case MTD_NANDFLASH:
+-			dev->flagstype = FLAGS_INCREMENTAL;
+-		};
+-		break;
+-	case DEVICE_UBI:
+-		dev->flagstype = FLAGS_INCREMENTAL;
+-		break;
+-	default:
+-		close(fd);
+-		return -EBADF;
+-	};
+-
+-	/*
+-	 * Check for negative offsets, treat it as backwards offset
+-	 * from the end of the block device
+-	 */
+-	if (dev->offset < 0) {
+-		uint64_t blkdevsize;
+-		int rc;
+-
+-		rc = ioctl(fd, BLKGETSIZE64, &blkdevsize);
+-		if (rc < 0) {
+-			close(fd);
+-			return -EINVAL;
+-		}
+-
+-		dev->offset += blkdevsize;
+-	}
+-
+-	close(fd);
+-
+-	return 0;
+-}
+-
+-static bool check_compatible_devices(struct uboot_ctx *ctx)
+-{
+-	if (!ctx->redundant)
+-		return true;
+-
+-	if (ctx->envdevs[0].mtdinfo.type != ctx->envdevs[1].mtdinfo.type)
+-		return false;
+-	if (ctx->envdevs[0].flagstype != ctx->envdevs[1].flagstype)
+-		return false;
+-	if (ctx->envdevs[0].envsize != ctx->envdevs[1].envsize)
+-		return false;
+-
+-	return true;
+-}
+-
+ static int fileread(struct uboot_flash_env *dev, void *data)
+ {
+ 	int ret = 0;
+@@ -1035,380 +766,6 @@ static int libuboot_load(struct uboot_ctx *ctx)
+ 	return ctx->valid ? 0 : -ENODATA;
+ }
+ 
+-static int consume_event(struct parser_state *s, yaml_event_t *event)
+-{
+-	char *value;
+-	struct uboot_flash_env *dev;
+-	struct uboot_ctx *newctx;
+-	int cdev;
+-
+-	switch (s->state) {
+-	case STATE_START:
+-		switch (event->type) {
+-		case YAML_STREAM_START_EVENT:
+-			s->state = STATE_STREAM;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_STREAM:
+-		switch (event->type) {
+-		case YAML_DOCUMENT_START_EVENT:
+-			s->state = STATE_DOCUMENT;
+-			break;
+-		case YAML_STREAM_END_EVENT:
+-			s->state = STATE_STOP;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_DOCUMENT:
+-		switch (event->type) {
+-		case YAML_MAPPING_START_EVENT:
+-			s->state = STATE_SECTION;
+-			break;
+-		case YAML_DOCUMENT_END_EVENT:
+-			s->state = STATE_STREAM;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_SECTION:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			value = (char *)event->data.scalar.value;
+-			newctx = calloc (s->nelem + 1, sizeof(*newctx));
+-			for (int i = 0; i < s->nelem; i++) {
+-				newctx[i] = s->ctxsets[i];
+-			}
+-			if (s->ctxsets) free(s->ctxsets);
+-			s->ctxsets = newctx;
+-			s->ctx = &newctx[s->nelem];
+-			s->ctx->name = strdup(value);
+-			s->nelem++;
+-			s->state = STATE_NAMESPACE;
+-			break;
+-		case YAML_MAPPING_END_EVENT:
+-			s->state = STATE_DOCUMENT;
+-			break;
+-		case YAML_DOCUMENT_END_EVENT:
+-			s->state = STATE_STREAM;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NAMESPACE:
+-		switch (event->type) {
+-		case YAML_MAPPING_START_EVENT:
+-			s->state = STATE_NAMESPACE_FIELDS;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NAMESPACE_FIELDS:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			value = (char *)event->data.scalar.value;
+-			if (!strcmp(value, "size")) {
+-				s->state = STATE_NSIZE;
+-			} else if (!strcmp(value, "lockfile")) {
+-				s->state = STATE_NLOCKFILE;
+-			} else if (!strcmp(value, "devices")) {
+-				s->state = STATE_DEVVALUES;
+-				s->cdev = 0;
+-			} else if (!strcmp(value, "writelist")) {
+-				s->state = STATE_WRITELIST;
+-			} else {
+-				s->error = YAML_UNEXPECTED_KEY;
+-				s->event_type = event->type;
+-				return FAILURE;
+-			}
+-			break;
+-		case YAML_MAPPING_END_EVENT:
+-			s->state = STATE_SECTION;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NSIZE:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			value = (char *)event->data.scalar.value;
+-			errno = 0;
+-			s->ctx->size = strtoull(value, NULL, 0);
+-			s->state = STATE_NAMESPACE_FIELDS;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NLOCKFILE:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			value = (char *)event->data.scalar.value;
+-			s->ctx->lockfile = strdup(value);
+-			s->state = STATE_NAMESPACE_FIELDS;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_DEVVALUES:
+-		switch (event->type) {
+-		case YAML_MAPPING_START_EVENT:
+-		case YAML_SEQUENCE_START_EVENT:
+-			break;
+-		case YAML_MAPPING_END_EVENT:
+-			dev = &s->ctx->envdevs[s->cdev];
+-			if (check_env_device(dev) < 0) {
+-				s->error = YAML_BAD_DEVICE;
+-				s->event_type = event->type;
+-				return FAILURE;
+-			}
+-			s->cdev++;
+-			break;
+-		case YAML_SEQUENCE_END_EVENT:
+-			s->state = STATE_NAMESPACE_FIELDS;
+-			break;
+-		case YAML_SCALAR_EVENT:
+-			value = (char *)event->data.scalar.value;
+-			if (s->cdev)
+-				s->ctx->redundant = true;
+-			if (!strcmp(value, "path")) {
+-				s->state = STATE_NPATH;
+-			} else if (!strcmp(value, "offset")) {
+-				s->state = STATE_NOFFSET;
+-			} else if (!strcmp(value, "sectorsize")) {
+-				s->state = STATE_NSECTORSIZE;
+-				} else if (!strcmp(value, "disablelock")) {
+-				s->state = STATE_NUNLOCK;
+-			} else {
+-				s->error = YAML_UNEXPECTED_KEY;
+-				s->event_type = event->type;
+-				return FAILURE;
+-			}
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_WRITELIST:
+-		switch (event->type) {
+-
+-		char *varflag, *name;
+-		struct var_entry *entry;
+-
+-		case YAML_MAPPING_START_EVENT:
+-		case YAML_SEQUENCE_START_EVENT:
+-			break;
+-		case YAML_MAPPING_END_EVENT:
+-			break;
+-		case YAML_SEQUENCE_END_EVENT:
+-			s->state = STATE_NAMESPACE_FIELDS;
+-			break;
+-		case YAML_SCALAR_EVENT:
+-			value = (char *)event->data.scalar.value;
+-
+-			/*
+-			 * Format is name:flags, split it into two values
+-			 */
+-			varflag = strchr(value, ':');
+-			if (!varflag || varflag > value + (strlen(value) - 1)) {
+-				s->error = YAML_BAD_VARLIST;
+-				s->event_type = event->type;
+-				return FAILURE;
+-			}
+-			*varflag++ = '\0';
+-
+-			/*
+-			 * Check there is not yet an entry for this variable
+-			 */
+-			LIST_FOREACH(entry, &s->ctx->writevarlist, next) {
+-				if (strcmp(entry->name, value) == 0) {
+-					s->error = YAML_DUPLICATE_VARLIST;
+-					s->event_type = event->type;
+-					return FAILURE;
+-				}
+-			}
+-
+-			/*
+-			 * Insert variable with its configuration into the list
+-			 * of modifiable vars
+-			 */
+-			entry = create_var_entry(value);
+-			if (!entry) {
+-				s->error = YAML_OOM;
+-				s->event_type = event->type;
+-				return FAILURE;
+-			}
+-			set_var_access_type(entry, varflag);
+-			LIST_INSERT_HEAD(&s->ctx->writevarlist, entry, next);
+-
+-#if !defined(NDEBUG)
+-			fprintf(stdout, "Writelist: %s flags %s\n", value, varflag);
+-#endif
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NPATH:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			dev = &s->ctx->envdevs[s->cdev];
+-			value = (char *)event->data.scalar.value;
+-			if (normalize_device_path(value, dev) < 0) {
+-				s->error = YAML_BAD_DEVNAME;
+-				s->event_type = event->type;
+-				return FAILURE;
+-			}
+-			dev->envsize = s->ctx->size;
+-			s->state = STATE_DEVVALUES;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NOFFSET:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			dev = &s->ctx->envdevs[s->cdev];
+-			value = (char *)event->data.scalar.value;
+-			dev->offset = strtoull(value, NULL, 0);
+-			s->state = STATE_DEVVALUES;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NSECTORSIZE:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			dev = &s->ctx->envdevs[s->cdev];
+-			value = (char *)event->data.scalar.value;
+-			dev->sectorsize = strtoull(value, NULL, 0);
+-			s->state = STATE_DEVVALUES;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-	case STATE_NUNLOCK:
+-		switch (event->type) {
+-		case YAML_SCALAR_EVENT:
+-			dev = &s->ctx->envdevs[s->cdev];
+-			value = (char *)event->data.scalar.value;
+-			if (!strcmp(value, "yes"))
+-				dev->disable_mtd_lock = 1;
+-			s->state = STATE_DEVVALUES;
+-			break;
+-		default:
+-			s->error = YAML_UNEXPECTED_STATE;
+-			s->event_type = event->type;
+-			return FAILURE;
+-		}
+-		break;
+-
+-    case STATE_STOP:
+-        break;
+-    }
+-    return SUCCESS;
+-}
+-
+-int parse_yaml_config(struct uboot_ctx **ctxlist, FILE *fp)
+-{
+-	yaml_parser_t parser;
+-	yaml_event_t  event;
+-	enum yaml_status status;
+-	struct parser_state state;
+-	struct uboot_ctx *ctx;
+-
+-	if (!yaml_parser_initialize(&parser))
+-		return -ENOMEM;
+-
+-	 /* Set input file */
+-	yaml_parser_set_input_file(&parser, fp);
+-	memset(&state, 0, sizeof(state));
+-	state.state = STATE_START;
+-	do {
+-		if (!yaml_parser_parse(&parser, &event)) {
+-			status = FAILURE;
+-			goto cleanup;
+-		}
+-		status = consume_event(&state, &event);
+-		yaml_event_delete(&event);
+-		if (status == FAILURE) {
+-			goto cleanup;
+-		}
+-	} while (state.state != STATE_STOP);
+-
+-	state.ctxsets[0].nelem = state.nelem;
+-
+-	for (int i = 0; i < state.nelem; i++) {
+-		ctx = &state.ctxsets[i];
+-		ctx->ctxlist = &state.ctxsets[0];
+-		if (ctx->redundant && !check_compatible_devices(ctx)) {
+-			status = FAILURE;
+-			break;
+-		}
+-	}
+-
+-
+-cleanup:
+-	yaml_parser_delete(&parser);
+-	if (status == FAILURE) {
+-		if (state.ctxsets) free (state.ctxsets);
+-		state.ctxsets = NULL;
+-	}
+-	*ctxlist = state.ctxsets;
+-	return status;
+-}
+-
+ #define LINE_LENGTH 2048
+ int libuboot_load_file(struct uboot_ctx *ctx, const char *filename)
+ {
diff --git a/patches/libubootenv-0.3.6/0002-extended_config-fix-segfault-on-empty-config.patch b/patches/libubootenv-0.3.6/0002-extended_config-fix-segfault-on-empty-config.patch
new file mode 100644
index 000000000..a4574ff5f
--- /dev/null
+++ b/patches/libubootenv-0.3.6/0002-extended_config-fix-segfault-on-empty-config.patch
@@ -0,0 +1,40 @@
+From: James Hilliard <james.hilliard1@gmail.com>
+Date: Thu, 24 Apr 2025 19:53:01 -0600
+Subject: [PATCH] extended_config: fix segfault on empty config
+
+We need to validate that we have nelem and bail out if we don't.
+
+Fixes:
+==245== Invalid write of size 4
+==245==    at 0x4849E0C: parse_yaml_config (uboot_env.c:1390)
+==245==    by 0x484A717: libuboot_read_config_ext (uboot_env.c:1484)
+==245==    by 0x13F253: bootloader_initialize.constprop.0 (uboot.c:45)
+==245==    by 0x13F457: do_env_set (uboot.c:72)
+==245==    by 0x12B057: update_transaction_state (stream_interface.c:139)
+==245==    by 0x12C367: extract_files (stream_interface.c:297)
+==245==    by 0x12C367: network_initializer (stream_interface.c:658)
+==245==    by 0x4EA89AF: ??? (in /usr/lib/libc.so.6)
+==245==    by 0x4F0989B: ??? (in /usr/lib/libc.so.6)
+==245==  Address 0x2e8 is not stack'd, malloc'd or (recently) free'd
+
+Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
+---
+ src/extended_config.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/extended_config.c b/src/extended_config.c
+index ec814f484efd..45df3ec7a7ca 100644
+--- a/src/extended_config.c
++++ b/src/extended_config.c
+@@ -428,6 +428,11 @@ int parse_yaml_config(struct uboot_ctx **ctxlist, FILE *fp)
+ 		}
+ 	} while (state.state != STATE_STOP);
+ 
++	if (state.nelem == 0) {
++		status = FAILURE;
++		goto cleanup;
++	}
++
+ 	state.ctxsets[0].nelem = state.nelem;
+ 
+ 	for (int i = 0; i < state.nelem; i++) {
diff --git a/patches/libubootenv-0.3.6/0003-BUG-Fix-warning-when-copying-UBI-volume.patch b/patches/libubootenv-0.3.6/0003-BUG-Fix-warning-when-copying-UBI-volume.patch
new file mode 100644
index 000000000..1ead89548
--- /dev/null
+++ b/patches/libubootenv-0.3.6/0003-BUG-Fix-warning-when-copying-UBI-volume.patch
@@ -0,0 +1,35 @@
+From: Stefano Babic <stefano.babic@swupdate.org>
+Date: Tue, 29 Apr 2025 13:49:15 +0200
+Subject: [PATCH] BUG: Fix warning when copying UBI volume
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fix:
+
+/home/stefano/SWUpdate/libubootenv/src/uboot_mtd.c: In function ‘libubootenv_ubi_update_name’:
+/home/stefano/SWUpdate/libubootenv/src/uboot_mtd.c:283:17: warning: ‘memset’ writing 256 bytes into a region of size 236 overflows the destination [-Wstringop-overflow=]
+  283 |                 memset(volume, 0, DEVNAME_MAX_LENGTH);
+      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+/home/stefano/SWUpdate/libubootenv/src/uboot_mtd.c:223:14: note: destination object ‘volume’ of size 236
+
+This can lead to overwrite the buffer for the volumes.
+
+Signed-off-by: Stefano Babic <stefano.babic@swupdate.org>
+---
+ src/uboot_mtd.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/uboot_mtd.c b/src/uboot_mtd.c
+index 81376e4ddc09..24e817d88944 100644
+--- a/src/uboot_mtd.c
++++ b/src/uboot_mtd.c
+@@ -280,7 +280,7 @@ int libubootenv_ubi_update_name(struct uboot_flash_env *dev)
+ 		memset(device, 0, DEVNAME_MAX_LENGTH);
+ 		memcpy(device, dev->devname, sep - dev->devname);
+ 
+-		memset(volume, 0, DEVNAME_MAX_LENGTH);
++		memset(volume, 0, VOLNAME_MAX_LENGTH);
+ 		sscanf(sep + 1, "%s", &volume[0]);
+ 
+ 		dev_id = ubi_get_dev_id(device);
diff --git a/patches/libubootenv-0.3.6/0004-libubootenv-fix-segfault-due-to-uninitialized-pointe.patch b/patches/libubootenv-0.3.6/0004-libubootenv-fix-segfault-due-to-uninitialized-pointe.patch
new file mode 100644
index 000000000..e7ae92fb8
--- /dev/null
+++ b/patches/libubootenv-0.3.6/0004-libubootenv-fix-segfault-due-to-uninitialized-pointe.patch
@@ -0,0 +1,32 @@
+From: Mohamed-nour Toumi <mohamed.toumi_ext@softathome.com>
+Date: Mon, 28 Jul 2025 22:25:35 +0200
+Subject: [PATCH] libubootenv: fix segfault due to uninitialized pointer in
+ config parser
+
+Issue: The issue was introduced in commit c478e8d9, which replaced the use of %ms in sscanf() with a calloc()-based workaround for platforms where %ms is not supported (e.g., FreeBSD).
+However, this change inadvertently introduced a logic flaw: it uses calloc to emulate %ms% in sscanf() but also added 2 free instructions which could lead to free non-allocated memory on tmp when sscanf()
+is used to perform the dynamic allocation.
+
+Fix: Ensure `tmp` is initialized to NULL before each call to sscanf with `%ms`
+in `libuboot_read_config_ext()`. This prevents `free(tmp)` from crashing
+when sscanf fails to allocate memory (e.g., due to malformed config lines).
+
+Fixes segmentation fault observed when running swupdate with a ubootenv config file.
+
+Signed-off-by: Mohamed-nour Toumi <mohamed.toumi_ext@softathome.com>
+---
+ src/uboot_env.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/uboot_env.c b/src/uboot_env.c
+index d8b93dac7479..9641800f4bdc 100644
+--- a/src/uboot_env.c
++++ b/src/uboot_env.c
+@@ -871,6 +871,7 @@ int libuboot_read_config_ext(struct uboot_ctx **ctxlist, const char *config)
+ 				tmp,
+ #else
+ 		(void)len;
++		tmp = NULL;
+ 		ret = sscanf(line, "%ms %lli %zx %zx %lx %d",
+ 				&tmp,
+ #endif
diff --git a/patches/libubootenv-0.3.6/0005-extended_config.c-Catch-NULL-pointer-for-calloc.patch b/patches/libubootenv-0.3.6/0005-extended_config.c-Catch-NULL-pointer-for-calloc.patch
new file mode 100644
index 000000000..9c89141c7
--- /dev/null
+++ b/patches/libubootenv-0.3.6/0005-extended_config.c-Catch-NULL-pointer-for-calloc.patch
@@ -0,0 +1,30 @@
+From: Steffen Kothe <steffen.kothe@skothe.net>
+Date: Sun, 31 Aug 2025 19:07:56 +0000
+Subject: [PATCH] extended_config.c: Catch NULL pointer for calloc
+
+calloc is not guaranteed to return a valid pointer to a free memory
+area.
+
+Hence check for possible NULL return and fail immediately.
+
+Addresses possible CWE-690.
+
+Signed-off-by: Steffen Kothe <steffen.kothe@skothe.net>
+Acked-by: Stefano Babic <stefano.babic@swupdate.org>
+---
+ src/extended_config.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/extended_config.c b/src/extended_config.c
+index 45df3ec7a7ca..c3782e1fba94 100644
+--- a/src/extended_config.c
++++ b/src/extended_config.c
+@@ -131,6 +131,8 @@ static int consume_event(struct parser_state *s, yaml_event_t *event)
+ 		case YAML_SCALAR_EVENT:
+ 			value = (char *)event->data.scalar.value;
+ 			newctx = calloc (s->nelem + 1, sizeof(*newctx));
++			if (newctx == NULL)
++				return FAILURE;
+ 			for (int i = 0; i < s->nelem; i++) {
+ 				newctx[i] = s->ctxsets[i];
+ 			}
diff --git a/patches/libubootenv-0.3.6/series b/patches/libubootenv-0.3.6/series
new file mode 100644
index 000000000..b9356ee68
--- /dev/null
+++ b/patches/libubootenv-0.3.6/series
@@ -0,0 +1,8 @@
+# generated by git-ptx-patches
+#tag:base --start-number 1
+0001-Make-libyaml-optional.patch
+0002-extended_config-fix-segfault-on-empty-config.patch
+0003-BUG-Fix-warning-when-copying-UBI-volume.patch
+0004-libubootenv-fix-segfault-due-to-uninitialized-pointe.patch
+0005-extended_config.c-Catch-NULL-pointer-for-calloc.patch
+# 8a759d09e861f42f9d568274b3aeb30b  - git-ptx-patches magic
diff --git a/rules/libubootenv.make b/rules/libubootenv.make
index 74bfaaf19..d6753cd87 100644
--- a/rules/libubootenv.make
+++ b/rules/libubootenv.make
@@ -34,7 +34,8 @@ LIBUBOOTENV_LICENSE_FILES := \
 LIBUBOOTENV_CONF_TOOL	:= cmake
 LIBUBOOTENV_CONF_OPT	:=  \
 	$(CROSS_CMAKE_USR) \
-	-DBUILD_DOC=OFF
+	-DBUILD_DOC=OFF \
+	-DNO_YML_SUPPORT=OFF
 
 # ----------------------------------------------------------------------------
 # Target-Install
-- 
2.39.5




  parent reply	other threads:[~2025-09-19 10:07 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-09-19 10:06 [ptxdist] [PATCH v2 0/5] libubootenv: New package to replace target u-boot-tools Alexander Dahl via ptxdist
2025-09-19 10:06 ` [ptxdist] [PATCH v2 1/5] libubootenv: Introduce new package Alexander Dahl via ptxdist
2025-09-22 11:04   ` [ptxdist] [APPLIED] " Michael Olbrich
2025-09-19 10:06 ` [ptxdist] [PATCH v2 2/5] libubootenv: Add option for installing tools Alexander Dahl via ptxdist
2025-09-22 11:04   ` [ptxdist] [APPLIED] " Michael Olbrich
2025-09-19 10:06 ` [ptxdist] [PATCH v2 3/5] libubootenv: Install config file to target Alexander Dahl via ptxdist
2025-09-22 11:04   ` [ptxdist] [APPLIED] " Michael Olbrich
2025-09-19 10:06 ` Alexander Dahl via ptxdist [this message]
2025-09-22 11:04   ` [ptxdist] [APPLIED] libubootenv: Import some fixes from master Michael Olbrich
2025-09-19 10:06 ` [ptxdist] [PATCH v2 5/5] libubootenv: Make yaml support optional Alexander Dahl via ptxdist
2025-09-22 11:04   ` [ptxdist] [APPLIED] " Michael Olbrich

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250919100643.199174-5-ada@thorsis.com \
    --to=ptxdist@pengutronix.de \
    --cc=ada@thorsis.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox