On 7/27/26 7:50 AM, Christian Borntraeger wrote:
> From: Joshua Daley <[email protected]>
>
> menu_get_zipl_boot_index() calls strlen() on a pointer into the middle
> of _s2 with no upper bound, so a stage-2 image whose blocks contain no
> NUL bytes causes strlen() to walk beyond _s2. The resulting length
> is then used to size a stack VLA in zipl_print_entry(), risking a stack
> overflow.
>
> Fix by:
>
> - Implementing strnlen(), a bounded version of strlen().
>
> - Adding a menu_data_end parameter to menu_get_zipl_boot_index() and
> replacing both strlen() calls with strnlen() bounded by the remaining
> buffer space. The loop guard also checks that the pointer has not
> reached menu_data_end. For robustness, the function returns 0
> (boot default) if somehow menu_data is initially >= menu_data_end.
>
> - Replacing the VLA char buf[len + 2] in zipl_print_entry() with a fixed
> ZIPL_ENTRY_MAX + 2 (82-byte) buffer and truncating len before use.
>
> - Passing s2_end (_s2 + sizeof(_s2)) as menu_data_end at the one call
> site in eckd_get_boot_menu_index(), so the bound is exactly the end of
> the buffer.
>
> Fixes: f7178910845a ("s390-ccw: print zipl boot menu")
> Signed-off-by: Joshua Daley <[email protected]>
> ---
> pc-bios/s390-ccw/bootmap.c | 4 +++-
> pc-bios/s390-ccw/helper.h | 10 ++++++++++
> pc-bios/s390-ccw/menu.c | 23 ++++++++++++++++++-----
> pc-bios/s390-ccw/s390-ccw.h | 2 +-
> 4 files changed, 32 insertions(+), 7 deletions(-)
>
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index ed6e8cbbc7..81512265ce 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -61,6 +61,7 @@ static uint8_t _s2[MAX_SECTOR_SIZE * 3]
> __attribute__((__aligned__(PAGE_SIZE)));
> static void *s2_prev_blk = _s2;
> static void *s2_cur_blk = _s2 + MAX_SECTOR_SIZE;
> static void *s2_next_blk = _s2 + MAX_SECTOR_SIZE * 2;
> +static void *s2_end = _s2 + sizeof(_s2);
>
> static inline int verify_boot_info(BootInfo *bip)
> {
> @@ -308,7 +309,8 @@ static int eckd_get_boot_menu_index(block_number_t
> s1b_block_nr)
> }
> }
>
> - return menu_get_zipl_boot_index(s2_cur_blk + banner_offset);
> + return menu_get_zipl_boot_index(s2_cur_blk + banner_offset,
> + s2_end);
> }
>
> prev_block_nr = cur_block_nr;
> diff --git a/pc-bios/s390-ccw/helper.h b/pc-bios/s390-ccw/helper.h
> index 8e3dfcb6d6..d9b7da444a 100644
> --- a/pc-bios/s390-ccw/helper.h
> +++ b/pc-bios/s390-ccw/helper.h
> @@ -45,4 +45,14 @@ static inline void sleep(unsigned int seconds)
> }
> }
>
> +static inline size_t strnlen(const char *s, size_t maxlen)
> +{
> + size_t len = 0;
> +
> + while (len < maxlen && s[len]) {
> + len++;
> + }
> + return len;
> +}
> +
> #endif
> diff --git a/pc-bios/s390-ccw/menu.c b/pc-bios/s390-ccw/menu.c
> index b6a9a56d46..af96a83de9 100644
> --- a/pc-bios/s390-ccw/menu.c
> +++ b/pc-bios/s390-ccw/menu.c
> @@ -16,6 +16,7 @@
> #include "s390-ccw.h"
> #include "sclp.h"
> #include "s390-time.h"
> +#include "helper.h"
>
> #define KEYCODE_NO_INP '\0'
> #define KEYCODE_ESCAPE '\033'
> @@ -26,6 +27,9 @@
> #define ZIPL_TIMEOUT_OFFSET 138
> #define ZIPL_FLAG_OFFSET 140
>
> +/* Max printable chars for a zipl boot menu entry */
> +#define ZIPL_ENTRY_MAX 80
> +
> #define TOD_CLOCK_MILLISECOND 0x3e8000
>
> #define LOW_CORE_EXTERNAL_INT_ADDR 0x86
> @@ -179,9 +183,13 @@ int menu_get_boot_index(bool *valid_entries)
> /* Returns the entry number that was printed, or -1 on invalid entry */
> static int zipl_print_entry(const char *data, size_t len)
> {
> - char buf[len + 2];
> + char buf[ZIPL_ENTRY_MAX + 2];
> const char *p;
>
> + if (len > ZIPL_ENTRY_MAX) {
> + len = ZIPL_ENTRY_MAX;
> + }
> +
> ebcdic_to_ascii(data, buf, len);
> buf[len] = '\n';
> buf[len + 1] = '\0';
> @@ -196,7 +204,7 @@ static int zipl_print_entry(const char *data, size_t len)
> return atoi(p);
> }
>
> -int menu_get_zipl_boot_index(const char *menu_data)
> +int menu_get_zipl_boot_index(const char *menu_data, const char
> *menu_data_end)
> {
> size_t len;
> int entry;
> @@ -212,13 +220,18 @@ int menu_get_zipl_boot_index(const char *menu_data)
> timeout = zipl_timeout * 1000;
> }
>
> + if (menu_data >= menu_data_end) {
> + return 0;
> + }
> +
> /* Print banner */
> puts("s390-ccw zIPL Boot Menu\n");
> - menu_data += strlen(menu_data) + 1;
> + len = strnlen(menu_data, menu_data_end - menu_data);
> + menu_data += len + 1;
If we've already hit menu_data_end at this point should we bail rather
than skipping the while() loop and immediately proceeding into
menu_get_boot_index(valid_entries); with an all-false array?
AFAICT the current implementation would print no options and wait for
user input.
e.g. something like
if (menu_data >= menu_data_end)
return 0;
>
> /* Print entries */
> - while (*menu_data) {
> - len = strlen(menu_data);
> + while (menu_data < menu_data_end && *menu_data) {
> + len = strnlen(menu_data, menu_data_end - menu_data);
> entry = zipl_print_entry(menu_data, len);
> menu_data += len + 1;
>
> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
> index 1e1f71775e..f6030a6071 100644
> --- a/pc-bios/s390-ccw/s390-ccw.h
> +++ b/pc-bios/s390-ccw/s390-ccw.h
> @@ -76,7 +76,7 @@ void jump_to_low_kernel(void);
>
> /* menu.c */
> void menu_set_parms(uint8_t boot_menu_flag, uint32_t boot_menu_timeout);
> -int menu_get_zipl_boot_index(const char *menu_data);
> +int menu_get_zipl_boot_index(const char *menu_data, const char
> *menu_data_end);
> bool menu_is_enabled_zipl(void);
> int menu_get_enum_boot_index(bool *valid_entries);
> bool menu_is_enabled_enum(void);