|
Message-ID: <CAEVAO01=mdLq+xchM5ThOWPrHOaP+WxwxuStfiQQ+7voV92oJQ@mail.gmail.com> Date: Tue, 18 Jan 2022 18:17:41 +0800 From: Kaihang Zhang <kaihang.zhang@...rtx.com> To: Rich Felker <dalias@...c.org> Cc: musl@...ts.openwall.com, care <2010267516@...com> Subject: Re: [PATCH v2] fix: Truncate the too-long mntent in function getmntent_r On Sun, Jan 9, 2022 at 11:12 AM Rich Felker <dalias@...c.org> wrote: > > On Fri, Oct 15, 2021 at 08:20:00AM -0400, Kaihang Zhang wrote: > > In function getmntent_r in source misc/mntent.c, entry that is too long > > will be truncated rather than discarded. The caller can tell by errno > > whether the supplied buffer is too small, and retry from the beginning > > of the file. > > --- > > src/misc/mntent.c | 53 +++++++++++++++++++++++++++++------------------ > > 1 file changed, 33 insertions(+), 20 deletions(-) > > > > diff --git a/src/misc/mntent.c b/src/misc/mntent.c > > index eabb8200..085ce45d 100644 > > --- a/src/misc/mntent.c > > +++ b/src/misc/mntent.c > > @@ -21,12 +21,12 @@ int endmntent(FILE *f) > > > > struct mntent *getmntent_r(FILE *f, struct mntent *mnt, char *linebuf, int buflen) > > { > > - int cnt, n[8], use_internal = (linebuf == SENTINEL); > > - > > - mnt->mnt_freq = 0; > > - mnt->mnt_passno = 0; > > + int use_internal = (linebuf == SENTINEL); > > + char *sub; > > > > do { > > + char *end_ptr; > > + > > if (use_internal) { > > getline(&internal_buf, &internal_bufsize, f); > > linebuf = internal_buf; > > @@ -34,25 +34,38 @@ struct mntent *getmntent_r(FILE *f, struct mntent *mnt, char *linebuf, int bufle > > fgets(linebuf, buflen, f); > > } > > if (feof(f) || ferror(f)) return 0; > > - if (!strchr(linebuf, '\n')) { > > + > > + end_ptr = strchr(linebuf, '\n'); > > + if (end_ptr != NULL) { > > + while ((end_ptr[-1] == ' ' || end_ptr[-1] == '\t') && end_ptr != linebuf) end_ptr--; > > + *end_ptr = '\0'; > > Unless I'm misreading, this seems to invoke UB by reading the [-1] > index before checking if that's valid. It could be fixed by just > changing the order of the comparison expressions, but I'm not clear > why this needs to be done anyway. > > > > + } else { > > fscanf(f, "%*[^\n]%*[\n]"); > > errno = ERANGE; > > - return 0; > > } > > - cnt = sscanf(linebuf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d", > > - n, n+1, n+2, n+3, n+4, n+5, n+6, n+7, > > - &mnt->mnt_freq, &mnt->mnt_passno); > > - } while (cnt < 2 || linebuf[n[0]] == '#'); > > - > > - linebuf[n[1]] = 0; > > - linebuf[n[3]] = 0; > > - linebuf[n[5]] = 0; > > - linebuf[n[7]] = 0; > > - > > - mnt->mnt_fsname = linebuf+n[0]; > > - mnt->mnt_dir = linebuf+n[2]; > > - mnt->mnt_type = linebuf+n[4]; > > - mnt->mnt_opts = linebuf+n[6]; > > + > > + linebuf += strspn(linebuf, " \t"); > > + } while (linebuf[0] == '\0' || linebuf[0] == '#'); > > + > > + mnt->mnt_fsname = strsep(&linebuf, " \t"); > > + > > + if (linebuf) linebuf += strspn(linebuf, " \t"); > > + sub = strsep(&linebuf, " \t"); > > + mnt->mnt_dir = sub ? sub : (char *) ""; > > "" already has type char[1] and decays to char *; no cast is needed > here. > > > + > > + if (linebuf) linebuf += strspn(linebuf, " \t"); > > + sub = strsep (&linebuf, " \t"); > > + mnt->mnt_type = sub ? sub : (char *) ""; > > + > > + if (linebuf) linebuf += strspn(linebuf, " \t"); > > + sub = strsep(&linebuf, " \t"); > > + mnt->mnt_opts = sub ? sub : (char *) ""; > > + > > + switch (linebuf ? sscanf(linebuf, " %d %d", &mnt->mnt_freq, &mnt->mnt_passno) : 0) { > > + case 0: mnt->mnt_freq = 0; > > + case 1: mnt->mnt_passno = 0; > > + case 2: break; > > + } > > > > return mnt; > > } > > -- > > 2.25.4 > > This is gratuitously rewriting a lot of parsing logic in a form that > doesn't seem like it's better, and even if it were, the change is > orthogonal to fixing the behavior. I'm sorry for taking so long to get > back to you and say this clearly. > > I do want to move forward on this because I know folks have been > waiting on an upstream fix for a long time. But I need a patch > accompanied by a clear explanation of the behavioral changes it's > making, and just those changes. Or if we're in agreement on what the > behavioral changes should be, I can just write the patch. > > The patch by Alyssa Ross is more minimal and better documented, but > I'm not sure it covers everything you're concerned about. Could you > let me know whether it does? I'll follow up on that thread about > whether there are open issues with it. > > Rich Thanks for taking time to check out this patch and give helpful suggestions! I want to know if cgroup is enabled by using the information in /proc/mounts, the contents are shown below (some unimportant entries have been omitted). --- > overlay / overlay rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4083/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4084/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4084/work 0 0 > cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0 > cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpuacct,cpu 0 0 --- In glibc, we (at least I) usually use the following methods to get cgroup. --- method1.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <mntent.h> #include <errno.h> void main() { struct mntent m; FILE *fd = NULL; char linebuf[256]; fd = setmntent("/proc/mounts", "r"); if (!fd) { printf("error: %s\n", strerror(errno)); exit(-1); } while (getmntent_r(fd, &m, linebuf, sizeof(linebuf))) { if (!strcmp(m.mnt_type, "cgroup")) { printf("cgroup is enabled\n"); exit(0); } } endmntent(fd); } --- The result was as I expected, cgroup is enabled, but when using musl libc, I got nothing! It's because when linebuf is small for the too-long entry 'overlay', the function getmntent_r in musl libc will return 0 and the entry 'cgroup' will not be read. Increasing the linebuf size will not help, beacause in a real scenario, the length of the entry 'overlay' is unpredictable and could be 512 bytes, 1024 bytes, or longer. I have to change the method1.c in musl libc. --- method2.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <mntent.h> #include <errno.h> void main() { struct mntent m; FILE *fd = NULL; char linebuf[256]; fd = setmntent("/proc/mounts", "r"); if (!fd) { printf("error: %s\n", strerror(errno)); exit(-1); } again: errno = 0; while (getmntent_r(fd, &m, linebuf, sizeof(linebuf))) { if (!strcmp(m.mnt_type, "cgroup")) { printf("cgroup is enabled\n"); exit(0); } } if (errno == ERANGE) { goto again; } endmntent(fd); } --- Or a more graceful way without 'goto'. --- method3.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <mntent.h> #include <errno.h> void main() { struct mntent m; FILE *fd = NULL; char linebuf[256]; fd = setmntent("/proc/mounts", "r"); if (!fd) { printf("error: %s\n", strerror(errno)); exit(-1); } for(;;) { errno = 0; if(getmntent_r(fd, &m, linebuf, sizeof(linebuf)) == NULL) { if (errno == ERANGE) { continue; } exit(0); } if (!strcmp(m.mnt_type, "cgroup")) { printf("cgroup is enabled\n"); exit(0); } } endmntent(fd); } --- In musl libc, I have to look at errno to make sure I know if the system has cgroup enabled. In fact, I'm sure linebuf is big enough to hold mnt_fsname, mnt_dir, and mnt_type, and I can tell from mnt_type whether the entry is a cgroup. Simply put, for a mount entry that is too long, I want musl libc to truncate it rather than return 0 immediately, because I don't care about discarded contents or even the entire mount entry that is too long. This way I don't have to pay attention to errno to make sure I know if the system has cgroup enabled. And I can use 'method1.c' in both glibc and musl libc to get the result I want. Errno is also necessary in some scenarios, such as when using the hasmntopt function to ensure that too long mount entries are read to linebuf. Kaihang --
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.