Thanks, applied as 670e4f1259e813706d2f0d73b8431f69b708a9c7.

Michael

[sent from post-receive hook]

On Mon, 22 Sep 2025 13:04:27 +0200, Alexander Dahl <[email protected]> wrote:
> 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 <[email protected]>
> Message-Id: <[email protected]>
> Signed-off-by: Michael Olbrich <[email protected]>
> 
> 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 000000000000..631d0f1ef7fb
> --- /dev/null
> +++ b/patches/libubootenv-0.3.6/0001-Make-libyaml-optional.patch
> @@ -0,0 +1,1507 @@
> +From: Stefano Babic <[email protected]>
> +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 <[email protected]>
> +---
> + 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, <[email protected]>
> ++ *
> ++ * 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, <[email protected]>
> ++ *
> ++ * 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, <[email protected]>
> ++ *
> ++ * 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 000000000000..a4574ff5fef6
> --- /dev/null
> +++ 
> b/patches/libubootenv-0.3.6/0002-extended_config-fix-segfault-on-empty-config.patch
> @@ -0,0 +1,40 @@
> +From: James Hilliard <[email protected]>
> +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 <[email protected]>
> +---
> + 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 000000000000..1ead8954860b
> --- /dev/null
> +++ 
> b/patches/libubootenv-0.3.6/0003-BUG-Fix-warning-when-copying-UBI-volume.patch
> @@ -0,0 +1,35 @@
> +From: Stefano Babic <[email protected]>
> +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 <[email protected]>
> +---
> + 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 000000000000..e7ae92fb8562
> --- /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 <[email protected]>
> +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 <[email protected]>
> +---
> + 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 000000000000..9c89141c7a90
> --- /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 <[email protected]>
> +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 <[email protected]>
> +Acked-by: Stefano Babic <[email protected]>
> +---
> + 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 000000000000..b9356ee68463
> --- /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 74bfaaf1965d..d6753cd87081 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

Reply via email to