This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
(perl #132147) don't cache invalid pages
authorTony Cook <tony@develop-help.com>
Tue, 6 Nov 2018 03:23:48 +0000 (14:23 +1100)
committerTony Cook <tony@develop-help.com>
Mon, 19 Nov 2018 00:27:33 +0000 (11:27 +1100)
When sdbm loads its page buffer from disk, in most cases it validates
the page and doesn't continue processing if it fails validation.

Unfortunately, in a few places it still marked the buffer as loaded
from that page, and later calls would then use that cached page,
causing a variety of problems, including buffer read overflows.

sdbm_firstkey() didn't validate the loaded page at all, it now does.

All places that validate the loaded page now on a failed validation:
  - invalidate the cached page (set pagbno to -1)
  - set the I/O error flag on the database object
  - set errno ($!) to EINVAL

The first ensures that later calls don't end up using an invalid cached
page.

The others allow the caller to check whether an error has occurred.

ext/SDBM_File/sdbm.c

index 2099857..bdb5f47 100644 (file)
@@ -398,6 +398,12 @@ sdbm_firstkey(DBM *db)
        if (lseek(db->pagf, OFF_PAG(0), SEEK_SET) < 0
            || read(db->pagf, db->pagbuf, PBLKSIZ) < 0)
                return ioerr(db), nullitem;
+        if (!chkpage(db->pagbuf)) {
+            errno = EINVAL;
+            ioerr(db);
+            db->pagbno = -1;
+            return nullitem;
+        }
        db->pagbno = 0;
        db->blkptr = 0;
        db->keyptr = 0;
@@ -446,8 +452,12 @@ getpage(DBM *db, long int hash)
                if (lseek(db->pagf, OFF_PAG(pagb), SEEK_SET) < 0
                    || read(db->pagf, db->pagbuf, PBLKSIZ) < 0)
                        return 0;
-               if (!chkpage(db->pagbuf))
-                       return 0;
+               if (!chkpage(db->pagbuf)) {
+                    errno = EINVAL;
+                    db->pagbno = -1;
+                    ioerr(db);
+                    return 0;
+                }
                db->pagbno = pagb;
 
                debug(("pag read: %d\n", pagb));
@@ -543,8 +553,12 @@ getnext(DBM *db)
                db->pagbno = db->blkptr;
                if (read(db->pagf, db->pagbuf, PBLKSIZ) <= 0)
                        break;
-               if (!chkpage(db->pagbuf))
-                       break;
+               if (!chkpage(db->pagbuf)) {
+                    errno = EINVAL;
+                    db->pagbno = -1;
+                    ioerr(db);
+                    break;
+                }
        }
 
        return ioerr(db), nullitem;