From: wyu <97944796+wyu71@users.noreply.github.com> Date: Wed, 12 Aug 2026 01:43:55 +0000 (+0800) Subject: fstab-filter: preserve fstab option escaping in the filtered output X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=eb5ee83c1f7e0b8287c9ca9b1535d3e94845476a;p=thirdparty%2Fsystemd.git fstab-filter: preserve fstab option escaping in the filtered output fstab_filter_options() used strv_split_full(..., EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX), which unescapes "\," and "\\" while splitting, and then strv_join_full(escape_separator=true) to re-escape the kept options. The round-trip was lossy for invalid escapes kept verbatim by RELAX (e.g. an option value "\xff" became "\\xff"), so the generated Options= line diverged from the input. A trailing backslash was also collapsed: "val1\\" in the fstab became "val1\" in the split word, which let the backslash escape the option separator in Options= (issue #42787). Rework fstab_filter_options() to split with EXTRACT_UNESCAPE_RELAX only, so the escape sequences of kept options are preserved verbatim and the filtered output is assembled with the plain strv_join(); the matching option values are unescaped manually so callers keep getting the unescaped form they had before. Fixes a bug introduced by d4d9f034b13acef37375daec5074d9d271e21eed. Fixes #42787. --- diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c index 25e229bf3e2..a4f5e348ab0 100644 --- a/src/shared/fstab-util.c +++ b/src/shared/fstab-util.c @@ -200,6 +200,34 @@ int fstab_is_mount_point_full(const char *where, const char *path) { #endif } +static char* fstab_unescape_option(const char *s) { + /* Undo the fstab option escaping: "\," -> ",", "\\" -> "\", and keep any + * other "\x" verbatim (matches EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX). */ + char *ret, *e; + + assert(s); + + ret = new(char, strlen(s) + 1); + if (!ret) + return NULL; + + e = ret; + for (const char *p = s; *p; p++) { + if (*p == '\\' && p[1] != '\0') { + if (IN_SET(p[1], ',', '\\')) { + *e++ = p[1]; + p++; + continue; + } + /* Keep the backslash and the following character verbatim. */ + *e++ = *p++; + } + *e++ = *p; + } + *e = '\0'; + return ret; +} + int fstab_filter_options( const char *opts, const char *names, @@ -234,8 +262,10 @@ int fstab_filter_options( _cleanup_free_ char **filtered_strv = NULL; /* strings are owned by 'opts_split' */ /* For backwards compatibility, we need to pass-through escape characters. - * The only ones we "consume" are the ones used as "\," or "\\". */ - r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX); + * The only ones we "consume" are the ones used as "\," or "\\". The option + * values are not unescaped by strv_split_full() here; matching option values are + * unescaped manually below so that callers keep getting the unescaped form. */ + r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_RELAX); if (r < 0) return r; @@ -257,10 +287,18 @@ int fstab_filter_options( } if (found) { + _cleanup_free_ char *unescaped = NULL; + + if ((ret_value && *x == '=') || ret_values) { + unescaped = fstab_unescape_option(x + 1); + if (!unescaped) + return -ENOMEM; + } + if (ret_value) - r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL); + r = free_and_replace(value, unescaped); else if (ret_values) - r = strv_extend(&values, x + 1); + r = strv_extend(&values, unescaped); else r = 0; } else @@ -270,7 +308,7 @@ int fstab_filter_options( } if (ret_filtered) { - filtered = strv_join_full(filtered_strv, ",", NULL, /* escape_separator= */ true); + filtered = strv_join(filtered_strv, ","); if (!filtered) return -ENOMEM; } diff --git a/src/test/test-fstab-util.c b/src/test/test-fstab-util.c index 773b1f9799f..63b70863e61 100644 --- a/src/test/test-fstab-util.c +++ b/src/test/test-fstab-util.c @@ -125,7 +125,7 @@ TEST(fstab_filter_options) { /* escaped characters */ do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt1\0", 1, 1, "opt1", "\\", "\\", "opt2=\\xff"); - do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt2\0", 1, 1, "opt2", "\\xff", "\\xff", "opt1=\\"); + do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt2\0", 1, 1, "opt2", "\\xff", "\\xff", "opt1=\\\\"); } TEST(fstab_find_pri) {