On Tue, Aug 12, 2025 at 06:55:15PM -0500, Aaron Rainbolt wrote: > On Tue, 12 Aug 2025 19:02:11 +0200 Daniel Kiper <dki...@net-space.pl> wrote: > > On Mon, Aug 04, 2025 at 11:50:09PM -0500, Aaron Rainbolt wrote: > > > Xen traditionally allows customizing guest behavior by passing > > > arguments to the VM kernel via the kernel command line. This is no > > > longer possible when using GRUB with Xen, as the kernel command > > > line is decided by the GRUB configuration file within the guest, > > > not data passed to the guest by Xen. > > > > > > To work around this limitation, enable GRUB to parse a command line > > > passed to it by Xen, and expose data from the command line to the > > > GRUB configuration as environment variables. These variables can be > > > used in the GRUB configuration for any desired purpose, such as > > > extending the kernel command line passed to the guest. The command > > > line format is inspired by the Linux kernel's command line format. > > > > > > To reduce the risk of misuse, abuse, or accidents in production, the > > > command line will only be parsed if it consists entirely of 7-bit > > > ASCII characters, only alphabetical characters and underscores are > > > permitted in variable names, and all variable names must start with > > > the string "xen_grub_env_". This also allows room for expanding the > > > command line arguments accepted by GRUB in the future, should other > > > arguments end up becoming desirable in the future. > > > > > > Signed-off-by: Aaron Rainbolt <arraybo...@gmail.com>
[...] > > > diff --git a/grub-core/kern/i386/xen/pvh.c > > > b/grub-core/kern/i386/xen/pvh.c index 91fbca8..a8988d2 100644 > > > --- a/grub-core/kern/i386/xen/pvh.c > > > +++ b/grub-core/kern/i386/xen/pvh.c > > > @@ -321,6 +321,8 @@ void > > > grub_xen_setup_pvh (void) > > > { > > > grub_addr_t par; > > > + const char *xen_cmdline; > > > + int i; > > > > > > grub_xen_cpuid_base (); > > > grub_xen_setup_hypercall_page (); > > > @@ -352,6 +354,27 @@ grub_xen_setup_pvh (void) > > > grub_xen_mm_init_regions (); > > > > > > grub_rsdp_addr = pvh_start_info->rsdp_paddr; > > > + > > > + xen_cmdline = (const char *) pvh_start_info->cmdline_paddr; > > > + for (i = 0; i < GRUB_XEN_MAX_GUEST_CMDLINE; i++) > > > + { > > > + if (xen_cmdline[i] == '\0') > > > > This code still does not make a lot of sense for me. You have NUL > > check in grub_parse_xen_cmdline(). So, you duplicate the check here... > > > > I would just fire grub_strncpy() here and forget... > > I guess it depends on how you view grub_xen_start_page_addr->cmd_line's > semantics. In my mind, cmd_line is a NUL-terminated string, always. If > you boot in PV mode, Xen ensures it's a NUL-terminated string, so it's > reasonable for code in GRUB to assume it will be one. If you boot in > PVH mode, it starts out initialized to all zeros which is technically a > NUL-terminated string, and the code that exists here ensures that if we > copy the kernel command line to cmd_line, it will still be a > NUL-terminated string. If we use a "bare" grub_strncpy() here, then if > someone passes a kernel command line larger than > GRUB_XEN_MAX_GUEST_CMDLINE - 1, cmd_line will end up not being > NUL-terminated anymore, and any code added to GRUB in the future that > assumes it is a NUL-terminated string may buffer overflow. > > One could argue "let's keep the NUL check here and remove it from > grub_parse_xen_cmdline()", but that doesn't work either because we only > get to control the contents of cmd_line if we boot in PVH mode. If > instead we boot in PV mode, cmd_line is initialized by Xen itself. GRUB > receives a pre-populated and ready-to-use start_info struct directly > from the hypervisor in this scenario. Xen is supposed to ensure that > start_info is always NUL-terminated, but if there's ever a bug in Xen > that breaks that assumption, that could result in bad things happening, > the same as if we didn't do the NUL check in grub_xen_setup_pvh(). Now > of course there's nothing we can do about Xen possibly being buggy (if > it gives us a GRUB_XEN_MAX_GUEST_CMDLINE-long buffer with no NUL > terminator, too bad), but we can at least make sure that we're ready > for that eventuality. That's why I like having both NUL checks - we're > ready for if Xen does things wrong, and any future code that isn't > ready for that eventuality will still work if things go wrong, at least > in PVH mode. OK, it looks like this explanation, in shortened form, should land around this code... Or not... Please look below... > (Arguably any new code that depends on cmd_line *should* check it for a > NUL terminator. But I don't want to assume that all new code *will* do > so.) > > > > + { > > > + grub_strncpy ((char *) > > > grub_xen_start_page_addr->cmd_line, > > > + (char *) pvh_start_info->cmdline_paddr, > > > + GRUB_XEN_MAX_GUEST_CMDLINE); > > > + > > > + if > > > (grub_xen_start_page_addr->cmd_line[GRUB_XEN_MAX_GUEST_CMDLINE - 1] > > > != '\0') > > > > If you convince me this code is still needed then I am afraid that > > this is not what you meant here... > > grub_strncpy doesn't just NUL-terminate the command line, it also fills > the entire remainder of the buffer with NUL characters. Therefore if I am afraid we are looking at different grub_strncpy() functions... > grub_strncpy NUL-terminated the copied string, the last character of > the buffer will always be NUL. This is a redundant check since the code > above goes to great lengths to avoid ever putting anything > non-NUL-terminated into the buffer, but I was under the impression you > wanted the check added just in case. I'm happy to remove it if it's not > desirable. I think we misunderstood each other. This code is redundant now... [...] > > > +is_key_safe (char *key, grub_size_t len) > > > +{ > > > + grub_size_t i; > > > + > > > + for (i = 0; i < len; i++) > > > + { > > > + if (! (grub_isalpha (key[i]) || key[i] == '_')) > > > > Please drop space after "!"... > > Will change. > > > > + return false; > > > + } > > > > You can drop curly braces from here... > > Will change. > > (I initially kept this written the way it was though because the GNU > coding standards seemed to indicate I should keep the braces. "When > you have an if-else statement nested in another if statement, always > put braces around the if-else." [1] This isn't a nested if, but it's an > if within a for which is very similar.) > > Is there any recommended coding style documentation I should be looking > at other than the GNU coding standards and the GRUB coding style > guidelines? It seems I'm making an awful lot of style mistakes and > would like to avoid that going forward. Yes, it is but sadly it is not fully/properly/.. documented [1]... [...] > > > + for (i = 0; i < GRUB_XEN_MAX_GUEST_CMDLINE; i++) > > > + { > > > + if (cmdline[i] == '\0') > > > + { > > > + cmdline_valid = true; > > > + break; > > > + } > > > + } > > > + > > > + if (cmdline_valid == false) > > > + { > > > + grub_error (GRUB_ERR_BAD_ARGUMENT, > > > + "command line from Xen is not NUL-terminated"); > > > + grub_print_error (); > > > > grub_fatal()? > > That would probably be a bad idea here. We use (or, at least, will use) > grub_fatal() in grub_xen_setup_pvh() because if grub_strncpy() doesn't > NUL-terminate the string it copies, it indicates a bug in GRUB (either > in grub_strncpy, or more likely in the nearby NUL-checking code). On > the other hand, cmdline_valid may equal false if we boot in PV mode and > Xen incorrectly hands us a non-NUL-terminated string. That's bad and > prevents us from parsing the command line safely, but I'd argue it > shouldn't entirely block boot. (Then again, maybe if it does block > boot, that will make this kind of theoretical Xen bug be much more > noticeable and help it get fixed quicker. If you'd prefer that we bail > out entirely here, I'm happy to change it.) As I said earlier, I would simply call grub_strncpy() in grub_xen_setup_pvh() without additional NUL-check there and fail if non-NUL-terminated string is detected here... > > > + goto cleanup_main; > > > + } > > > + > > > + cmdline_len = grub_strlen (cmdline); > > > + for (i = 0; i < cmdline_len; i++) > > > + { > > > + current_char = cmdline[i]; > > > + > > > + /* > > > + * If the previous character was a backslash, append the current > > > + * character to the word verbatim > > > + */ > > > + if (splitter_state & SPLITTER_HIT_BACKSLASH) > > > + { > > > + splitter_state &= ~SPLITTER_HIT_BACKSLASH; > > > + if (append_char_to_word (s, current_char, false) == false) > > > + goto cleanup_main; > > > + continue; > > > + } > > > + > > > + switch (current_char) > > > + { > > > + case '\\': > > > + /* Backslashes escape arbitrary characters. */ > > > + splitter_state |= SPLITTER_HIT_BACKSLASH; > > > + continue; > > > + > > > + case '\'': > > > + /* > > > + * Single quotes suppress word splitting and double quoting > > > until > > > + * the next single quote is encountered. > > > + */ > > > + if (splitter_state & SPLITTER_IN_DOUBLE_QUOTES) > > > + { > > > + if (append_char_to_word (s, current_char, false) == false) > > > + goto cleanup_main; > > > + continue; > > > + } > > > + > > > + splitter_state ^= SPLITTER_IN_SINGLE_QUOTES; > > > + continue; > > > + > > > + case '"': > > > + /* > > > + * Double quotes suppress word splitting and single quoting > > > until > > > + * the next double quote is encountered. > > > + */ > > > + if (splitter_state & SPLITTER_IN_SINGLE_QUOTES) > > > + { > > > + if (append_char_to_word (s, current_char, false) == false) > > > + goto cleanup_main; > > > + continue; > > > + } > > > + > > > + splitter_state ^= SPLITTER_IN_DOUBLE_QUOTES; > > > + continue; > > > + > > > + case ' ': > > > + /* Spaces separate words in the command line from each other. > > > */ > > > + if (splitter_state & SPLITTER_IN_SINGLE_QUOTES || > > > + splitter_state & SPLITTER_IN_DOUBLE_QUOTES) > > > + { > > > + if (append_char_to_word (s, current_char, false) == false) > > > + goto cleanup_main; > > > + continue; > > > + } > > > + > > > + if (append_word_to_list (s) == false) > > > + goto cleanup_main; > > > > I think this is not fully correct. You should not run > > append_word_to_list() until the closing quote. So, here you should > > have "else" for the first "if", i.e., "if (splitter_state & ..." and > > call append_word_to_list() for closing \" and \' above. > > What closing quote? If we're in quotes, the `if (splitter_state &...` > block will run. That block runs `continue`, restarting the loop before > we get to the `append_word_to_list (s)` call. If we hit a closing > quote, either the `case '\''` or the `case '"'` block will be > triggered, changing the splitter's state so that it knows it's no > longer within a quote block, and then the next space (or the end of the > string) will cause `append_word_to_list()` to be called, adding the > quoted word to the list. Even if this `continue` changes to a `break` > like suggested below, the logic and control flow will remain the same. > > Calling `append_word_to_list()` for closing quotes would break the > splitter. I'm trying to implement splitting behavior similar to Bash or > GRUB's configuration language, both of which parse > `xen_grub_env_var="abc def"ghi` into the variable `xen_grub_env_var` > with the value `abc defghi`. If we split on closing quotes too, the > input above will be parsed into two words, `xen_grub_env_var=abc def` > and an extra word `ghi`, resulting in the environment variable > `xen_grub_env_var` being set to `abc def` and the `ghi` being lost > entirely. One could argue that this is reasonable behavior, but it > isn't consistent with the other splitting behavior in GRUB. OK, makes sense for me... Maybe you should mention somewhere in the code current splitter behavior... Daniel [1] https://www.gnu.org/software/grub/manual/grub-dev/grub-dev.html#Coding-style _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel