Subject: Re: bug#35137: [df] incorrect parsing of /proc/self/mountinfo with \r in mount path

From: Bernhard Voelker <[email-masked]>
Date: Fri, 5 Apr 2019 09:01:00 +0200

On 4/4/19 9:52 AM, Zbigniew Jędrzejewski-Szmek wrote:
> See https://github.com/systemd/systemd/issues/12018 and
> https://github.com/karelzak/util-linux/issues/780 for additional context.
> 
> $ mkdir "$(echo -e foo\\rbar)"
> $ sudo mount -t tmpfs tmpfs foo^Mbar/
> $ cat -v /proc/self/mountinfo|grep foo
> 865 39 0:59 / /tmp/foo^Mbar rw,relatime shared:462 - tmpfs tmpfs rw,seclabel
> $ df -h | grep foo
> $ df -h /tmp/foo$'\r'bar
> Filesystem      Size  Used Avail Use% Mounted on
> -               3.9G     0  3.9G   0% /tmp/foo?bar
> 
> When asked to show all filesystems, the mount point is not shown at all.
> When asked to show just that one, df parses the mount point correctly,
> but it gets the filesystem type wrong.

Thanks for the report.

I see the issue is not yet solved in util-linux as well.
For coreutils, the fix is in gnulib.  Parsing the line is starting
to get ugly ... dirty patch attached.

This also caters for the issue that df(1) totally skips a file system
if the source is an empty string which is allowed for e.g. tmpfs:

  $ mount -t tmpfs '' /mnt
  $ df -h | grep mnt

At least util-linux' findmnt has already worked around that case, see
https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/commit/?id=18a52a5094f8

Finally, other users of gnulib/lib/mountlist.c are also affected.

Have a nice day,
Berny
diff --git a/lib/mountlist.c b/lib/mountlist.c
index 9b54a2cf7..75a195ea1 100644
--- a/lib/mountlist.c
+++ b/lib/mountlist.c
@@ -456,7 +456,7 @@ read_file_system_list (bool need_fs_type)
             int target_s, target_e, type_s, type_e;
             int source_s, source_e, mntroot_s, mntroot_e;
             char test;
-            char *dash;
+            char *dash, *source;
             int rc;
 
             rc = sscanf(line, "%*u "        /* id - discarded  */
@@ -470,6 +470,12 @@ read_file_system_list (bool need_fs_type)
                               &target_s, &target_e,
                               &test);
 
+            /* Cater for unusual target names where %*s stopped too early,
+               e.g. "mount -t tmpfs tmpfs foo$'\r'bar".  */
+            if (test != ' ')
+              while (*(line + target_e) && *(line + target_e) != ' ')
+                target_e++;
+
             if (rc != 3 && rc != 7)  /* 7 if %n included in count.  */
               continue;
 
@@ -480,26 +486,42 @@ read_file_system_list (bool need_fs_type)
 
             rc = sscanf(dash, " - "
                               "%n%*s%n "    /* FS type, start and end  */
-                              "%n%*s%n "    /* source, start and end  */
                               "%c",         /* more data...  */
                               &type_s, &type_e,
-                              &source_s, &source_e,
                               &test);
-            if (rc != 1 && rc != 5)  /* 5 if %n included in count.  */
+            if (rc != 1 && rc != 3)  /* 3 if %n included in count.  */
               continue;
 
+            source = dash + type_e + 1;
+            if (*source == ' ')
+              {
+                /* The source is an empty string, which is e.g. allowed for
+                   tmpfs: "mount -t tmpfs '' /mnt".  */
+                source_e = 0;
+              }
+            else
+              {
+                rc = sscanf(source, " "
+                            "%n%*s%n "    /* source, start and end  */
+                            "%c",         /* more data...  */
+                            &source_s, &source_e,
+                            &test);
+                if (rc != 1 && rc != 3)  /* 3 if %n included in count.  */
+                  continue;
+              }
+
             /* manipulate the sub-strings in place.  */
             line[mntroot_e] = '\0';
             line[target_e] = '\0';
             dash[type_e] = '\0';
-            dash[source_e] = '\0';
+            source[source_e] = '\0';
             unescape_tab (dash + source_s);
             unescape_tab (line + target_s);
             unescape_tab (line + mntroot_s);
 
             me = xmalloc (sizeof *me);
 
-            me->me_devname = xstrdup (dash + source_s);
+            me->me_devname = xstrdup (source);
             me->me_mountdir = xstrdup (line + target_s);
             me->me_mntroot = xstrdup (line + mntroot_s);
             me->me_type = xstrdup (dash + type_s);