util/noidle.c: audit and update, improve security.
authorMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 4 Aug 2023 21:23:44 +0000 (21:23 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 4 Aug 2023 21:23:44 +0000 (21:23 +0000)
mmsoftware/util/hdnoidle.c

index be6576c..abbc33c 100644 (file)
@@ -116,6 +116,9 @@ int disk_getsize(int, off_t *);
 ssize_t        disk_getblocksize(int);
 
 
+uint8_t        *membuf = NULL;
+
+
 /*
  * Splits columns of a string delimited by spaces and/or tabs, and fills
  * char **argv with pointers to each column. Returns the number of columns
@@ -149,8 +152,8 @@ straspl(char **argv, char *str, int maxcols)
 void
 conf_read(noidledisk_t **disks, int *dn)
 {
+       noidledisk_t *dmem = NULL;
        FILE *fh;
-       noidledisk_t *d = NULL;
        int n = 0, i;
        char *cols[3] = { NULL, NULL, NULL };
        char line[CONFIG_LZ + 1];
@@ -168,82 +171,87 @@ conf_read(noidledisk_t **disks, int *dn)
        (void)fclose(fh);
 
        /* Allocate structs */
-       if ((d = malloc(sizeof(noidledisk_t) * n)) == NULL)
+       if ((dmem = malloc(sizeof(noidledisk_t) * n)) == NULL)
                err(EXIT_FAILURE, "malloc()");
 
-       /* Reopen and fill structs */
+       /*
+        * Reopen and fill structs.  There's a possible race condition between
+        * the above and this, but we ensure not to configure more than <n>.
+        * If there are more lines than expected, we ignore them.  If there
+        * are less, we adjust <n>.
+        */
        if ((fh = fopen(CONFIG, "r")) == NULL)
                err(EXIT_FAILURE, "fopen(%s)", CONFIG);
-       i = 0;
-       while (fgets(line, CONFIG_LZ, fh) != NULL) {
+       for (i = 0; i < n && fgets(line, CONFIG_LZ, fh) != NULL; ) {
                line[CONFIG_LZ] = '\0';
-               if (*line != '#') {
-                       if (straspl(cols, line, 2) == 2) {
-                               if ((d[i].dev = strdup(cols[0])) == NULL)
-                                       err(EXIT_FAILURE, "strdup()");
-                               if ((d[i].delay = atoi(cols[1])) < 1)
-                                       err(EXIT_FAILURE, "atoi(delay) < 1");
-                               i++;
-                       }
+               if (*line == '#')
+                       continue;
+               if (straspl(cols, line, 2) == 2) {
+                       noidledisk_t *d = &dmem[i];
+
+                       if ((d->dev = strdup(cols[0])) == NULL)
+                               err(EXIT_FAILURE, "strdup()");
+                       if ((d->delay = atoi(cols[1])) < 1)
+                               err(EXIT_FAILURE, "atoi(delay) < 1");
+                       i++;
                }
        }
        (void)fclose(fh);
+       if (i < 1)
+               err(EXIT_FAILURE, "hdnoidle: Quitting, no disk configured.");
 
-       if (i != n)
-               err(EXIT_FAILURE, "conf_read() - i != n");
-
-       *disks = d;
-       *dn = n;
+       *disks = dmem;
+       *dn = i;
 }
 
 void
-conf_setup(noidledisk_t *d, int nd)
+conf_setup(noidledisk_t *disks, int nd)
 {
        int i;
 
+       /*
+        * Large buffer in which we'll custom-align smaller regions that can
+        * overlap and will only be used one at a time then discarded.
+        */
+       if ((membuf = malloc(65536)) == NULL)
+               err(EXIT_FAILURE, "malloc(65536)");
+
        for (i = 0; i < nd; i++) {
-               uint8_t *membuf;
                int pad;
+               noidledisk_t *d = &disks[i];
 
                /*
                 * O_DIRECT has no effect when already using a raw device but
                 * Linux now lacks real raw devices and apparently relies on
                 * this to hopefully offer similar behavior.
                 */
-               if ((d[i].fd = open(d[i].dev, O_RDONLY | O_DIRECT)) == -1)
-                       err(EXIT_FAILURE, "open(%s)", d[i].dev);
-               if (disk_getsize(d[i].fd, &(d[i].size)) == -1)
-                       err(EXIT_FAILURE, "disk_getsize(%s)", d[i].dev);
-               if ((d[i].bsize = disk_getblocksize(d[i].fd)) == -1)
-                       err(EXIT_FAILURE, "disk_getblocksize(%s)", d[i].dev);
+               if ((d->fd = open(d->dev, O_RDONLY | O_DIRECT)) == -1)
+                       err(EXIT_FAILURE, "open(%s)", d->dev);
+               if (disk_getsize(d->fd, &(d->size)) == -1)
+                       err(EXIT_FAILURE, "disk_getsize(%s)", d->dev);
+               if ((d->bsize = disk_getblocksize(d->fd)) == -1)
+                       err(EXIT_FAILURE, "disk_getblocksize(%s)", d->dev);
 
-               d[i].t = d[i].delay;
-               d[i].offset = 0;
+               d->t = d->delay;
+               d->offset = 0;
 
                /*
                 * We need memory to also be block-aligned.  Since malloc(3)
                 * will only guarantee 32-bit or 64-bit aligned memory, let's
-                * allocate a larger buffer and use an aligned sub-buffer.
+                * assign them an aligned sub-buffer in membuf.
                 */
-               if ((membuf = malloc(65536)) == NULL)
-                       err(EXIT_FAILURE, "malloc(65536)");
-               d[i].sbuf = membuf;
-
-               if ((d[i].sbuf = malloc(d[i].bsize)) == NULL)
-                       err(EXIT_FAILURE, "malloc(%lu)",
-                           (unsigned long)d[i].bsize);
-               if ((pad = (uintptr_t)d[i].sbuf % d->bsize) > 0)
+               d->sbuf = membuf;
+               if ((pad = (uintptr_t)d->sbuf % d->bsize) > 0)
                        pad = d->bsize - pad;
-               d[i].sbuf += pad;
-               if (d[i].sbuf + d->bsize > &membuf[65536])
+               d->sbuf += pad;
+               if (&d->sbuf[d->bsize] > &membuf[65535])
                        err(EXIT_FAILURE,
                            "Device block size for %s larger than expected!",
-                           d[i].dev);
+                           d->dev);
 
                (void)printf("hdnoidle: device=%s, size=%" PRIu64 ", "
                    "blocksize=%lu, delay=%u\n",
-                   d[i].dev, d[i].size, (unsigned long)d[i].bsize,
-                   d[i].delay);
+                   d->dev, d->size, (unsigned long)d->bsize, d->delay);
        }
 }
 
@@ -305,19 +313,28 @@ main(void)
 
                        if (--d->t < 1) {
                                off_t pad;
+                               ssize_t rz;
 
+                               /* Reset timer */
                                d->t = d->delay;
+
+                               /* Seek to current offset */
                                if (lseek(d->fd, d->offset, SEEK_SET) == -1) {
                                        warn("seek(%s, %" PRIu64 ")", d->dev,
                                            d->offset);
                                        continue;
                                }
-                               if (read(d->fd, d->sbuf, d->bsize) == -1)
+
+                               /* Read and discard by zeroing. */
+                               if ((rz = read(d->fd, d->sbuf, d->bsize))
+                                   == -1)
                                        warn("read(dev=%s (fd=%d), buf=%p, "
                                            "sz=%lu) off=%" PRIu64,
                                            d->dev, d->fd, d->sbuf,
                                            (unsigned long)d->bsize,
                                            d->offset);
+                               (void)memset(d->sbuf, '\0', rz);
+
                                /*
                                 * Skip forward somewhat arbitrarily hoping to
                                 * hit a non-cached block and to defeat