CVE-2020-36279: Out-of-bounds Read (CWE-125)
value: Leptonica before 1.80.0 allows a heap-based buffer over-read in rasteropGeneralLow, related to adaptmap_reg.c and adaptmap.c.
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.5
Published: 2021-03-12T00:15Z
Repository: https://github.com/DanBloomberg/leptonica
Language: C
Files changed: prog/adaptmap_reg.c, src/adaptmap.c
Commit date: 2020-05-26 22:24:40 -0700
Commit message: Fixing oss-fuzz issue 22512: Heap-buffer-overflow in rasteropGeneralLow()
* Simplified the hole-filling function
`

Diff: diff --git a/prog/adaptmap_reg.c b/prog/adaptmap_reg.c
index 65309770..ef8dbfd3 100644
--- a/prog/adaptmap_reg.c
+++ b/prog/adaptmap_reg.c
@@ -55,11 +55,11 @@ int main(int    argc,
          char **argv)
 {
 l_int32       w, h;
 PIX          *pixs, *pixg, *pixim, *pixgm, *pixmi, *pix1, *pix2;
 PIX          *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
-PIXA         *pixa;
+PIXA         *pixa, *pixa2;
 L_REGPARAMS  *rp;
 
     if (regTestSetup(argc, argv, &rp))
         return 1;
 
@@ -157,14 +157,35 @@ L_REGPARAMS  *rp;
     pixGammaTRCMasked(pix2, pix2, pixim, 1.0, 60, 190);
     regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG);  /* 13 */
     pixaAddPix(pixa, pix2, L_INSERT);
     pixDestroy(&pixim);
 
+        /* Check pixFillMapHoles() */
+    pixa2 = pixaCreate(3);
+    pix1 = pixRead("weasel8.png");  /* use this as the map */
+    pixGammaTRC(pix1, pix1, 1.0, 0, 270);  /* darken white pixels */
+    pixaAddPix(pixa2, pix1, L_COPY);
+    pixGetDimensions(pix1, &w, &h, NULL);
+    pixRasterop(pix1, 0, 0, 5, h, PIX_SET, NULL, 0, 0);  /* add white holes */
+    pixRasterop(pix1, 20, 0, 2, h, PIX_SET, NULL, 0, 0);
+    pixRasterop(pix1, 40, 0, 3, h, PIX_SET, NULL, 0, 0);
+    pixRasterop(pix1, 0, 0, w, 3, PIX_SET, NULL, 0, 0);
+    pixRasterop(pix1, 0, 15, w, 3, PIX_SET, NULL, 0, 0);
+    pixRasterop(pix1, 0, 35, w, 2, PIX_SET, NULL, 0, 0);
+    pixaAddPix(pixa2, pix1, L_COPY);
+    pixFillMapHoles(pix1, w, h, L_FILL_WHITE);
+    pixaAddPix(pixa2, pix1, L_INSERT);
+    pix2 = pixaDisplayTiledInColumns(pixa2, 3, 1.0, 20, 1);
+    regTestWritePixAndCheck(rp, pix2, IFF_PNG);  /* 14 */
+    pixDisplayWithTitle(pix2, 50, 850, NULL, rp->display);
+    pixaDestroy(&pixa2);
+    pixDestroy(&pix2);
+    
         /* Display results */
     pix1 = pixaDisplayTiledAndScaled(pixa, 32, 400, 4, 0, 20, 2);
     pixWrite("/tmp/lept/adapt/results.jpg", pix1, IFF_JFIF_JPEG);
-    pixDisplayWithTitle(pix1, 100, 0, NULL, rp->display);
+    pixDisplayWithTitle(pix1, 50, 0, NULL, rp->display);
     pixDestroy(&pix1);
     pixaDestroy(&pixa);
 
     return regTestCleanup(rp);
 }
diff --git a/src/adaptmap.c b/src/adaptmap.c
index 634be33b..4f5ec4b6 100644
--- a/src/adaptmap.c
+++ b/src/adaptmap.c
@@ -1468,11 +1468,10 @@ pixFillMapHoles(PIX     *pix,
                 l_int32  filltype)
 {
 l_int32   w, h, y, nmiss, goodcol, i, j, found, ival, valtest;
 l_uint32  val, lastval;
 NUMA     *na;  /* indicates if there is any data in the column */
-PIX      *pixt;
 
     PROCNAME("pixFillMapHoles");
 
     if (!pix || pixGetDepth(pix) != 8)
         return ERROR_INT("pix not defined or not 8 bpp", procName, 1);
@@ -1520,35 +1519,30 @@ PIX      *pixt;
         return 1;
     }
 
     /* ---------- Fill in missing columns by replication ----------- */
     if (nmiss > 0) {  /* replicate columns */
-        pixt = pixCopy(NULL, pix);
             /* Find the first good column */
         goodcol = 0;
         for (j = 0; j < w; j++) {
             numaGetIValue(na, j, &ival);
             if (ival == 1) {
                 goodcol = j;
                 break;
             }
         }
         if (goodcol > 0) {  /* copy cols backward */
-            for (j = goodcol - 1; j >= 0; j--) {
-                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j + 1, 0);
-                pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
-            }
+            for (j = goodcol - 1; j >= 0; j--)
+                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j + 1, 0);
         }
         for (j = goodcol + 1; j < w; j++) {   /* copy cols forward */
             numaGetIValue(na, j, &ival);
             if (ival == 0) {
                     /* Copy the column to the left of j */
-                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j - 1, 0);
-                pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
+                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j - 1, 0);
             }
         }
-        pixDestroy(&pixt);
     }
     if (w > nx) {  /* replicate the last column */
         for (i = 0; i < h; i++) {
             pixGetPixel(pix, w - 2, i, &val);
             pixSetPixel(pix, w - 1, i, val);

Vulnerable code: PIXA         *pixa;
    pixDisplayWithTitle(pix1, 100, 0, NULL, rp->display);
PIX      *pixt;
        pixt = pixCopy(NULL, pix);
            for (j = goodcol - 1; j >= 0; j--) {
                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j + 1, 0);
                pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
            }
                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j - 1, 0);
                pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
        pixDestroy(&pixt);
Fixed code: PIXA         *pixa, *pixa2;
        /* Check pixFillMapHoles() */
    pixa2 = pixaCreate(3);
    pix1 = pixRead("weasel8.png");  /* use this as the map */
    pixGammaTRC(pix1, pix1, 1.0, 0, 270);  /* darken white pixels */
    pixaAddPix(pixa2, pix1, L_COPY);
    pixGetDimensions(pix1, &w, &h, NULL);
    pixRasterop(pix1, 0, 0, 5, h, PIX_SET, NULL, 0, 0);  /* add white holes */
    pixRasterop(pix1, 20, 0, 2, h, PIX_SET, NULL, 0, 0);
    pixRasterop(pix1, 40, 0, 3, h, PIX_SET, NULL, 0, 0);
    pixRasterop(pix1, 0, 0, w, 3, PIX_SET, NULL, 0, 0);
    pixRasterop(pix1, 0, 15, w, 3, PIX_SET, NULL, 0, 0);
    pixRasterop(pix1, 0, 35, w, 2, PIX_SET, NULL, 0, 0);
    pixaAddPix(pixa2, pix1, L_COPY);
    pixFillMapHoles(pix1, w, h, L_FILL_WHITE);
    pixaAddPix(pixa2, pix1, L_INSERT);
    pix2 = pixaDisplayTiledInColumns(pixa2, 3, 1.0, 20, 1);
    regTestWritePixAndCheck(rp, pix2, IFF_PNG);  /* 14 */
    pixDisplayWithTitle(pix2, 50, 850, NULL, rp->display);
    pixaDestroy(&pixa2);
    pixDestroy(&pix2);
    
    pixDisplayWithTitle(pix1, 50, 0, NULL, rp->display);
            for (j = goodcol - 1; j >= 0; j--)
                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j + 1, 0);
                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j - 1, 0);