123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- Description: patch for CVE-2020-14315
- A memory corruption vulnerability is present in bspatch as shipped in
- Colin Percival’s bsdiff tools version 4.3. Insufficient checks when
- handling external inputs allows an attacker to bypass the sanity checks
- in place and write out of a dynamically allocated buffer boundaries.
- Source: https://svnweb.freebsd.org/base/head/usr.bin/bsdiff/bspatch/bspatch.c?revision=352742&view=co
- Author: tony mancill <tmancill@debian.org>
- Comment: The patch was created by comparing the Debian sources to the
- "Confirmed Patched Version" [1] documented in the
- X41 D-SEC GmbH Security Advisory: X41-2020-006 [2].
- References to FreeBSD capsicum have been dropped. Definitions for
- TYPE_MINIMUM and TYPE_MAXIMUM have been borrowed from the Debian
- coreutils package sources but originate in gnulib [3] and are used to
- define OFF_MIN and OFF_MAX (limits of off_t). Whitespace changes from
- the confirmed patched version are also included and keep the difference
- between the Debian sources and the confirmed patched version minimal.
- .
- [1] https://svnweb.freebsd.org/base/head/usr.bin/bsdiff/bspatch/bspatch.c?revision=352742&view=co
- [2] https://www.openwall.com/lists/oss-security/2020/07/09/2
- [3] https://www.gnu.org/software/gnulib/
- Last-Update: 2021-04-03
- Forwarded: not-needed
- Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=964796
- --- a/bspatch.c
- +++ b/bspatch.c
- @@ -1,4 +1,6 @@
- /*-
- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
- + *
- * Copyright 2003-2005 Colin Percival
- * All rights reserved
- *
- @@ -25,55 +27,147 @@
- */
-
- #if 0
- -__FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:06 cperciva Exp $");
- +__FBSDID("$FreeBSD$");
- #endif
-
- #include <bzlib.h>
- -#include <stdlib.h>
- +#include <err.h>
- +#include <fcntl.h>
- +#include <libgen.h>
- +#include <limits.h>
- +#include <stdint.h>
- #include <stdio.h>
- +#include <stdlib.h>
- #include <string.h>
- -#include <err.h>
- #include <unistd.h>
- -#include <fcntl.h>
- +
- +#ifndef O_BINARY
- +#define O_BINARY 0
- +#endif
- +#define HEADER_SIZE 32
- +
- +/* TYPE_MINIMUM and TYPE_MAXIMUM taken from coreutils */
- +#ifndef TYPE_MINIMUM
- +#define TYPE_MINIMUM(t) \
- + ((t) ((t) 0 < (t) -1 ? (t) 0 : ~ TYPE_MAXIMUM (t)))
- +#endif
- +#ifndef TYPE_MAXIMUM
- +#define TYPE_MAXIMUM(t) \
- + ((t) ((t) 0 < (t) -1 \
- + ? (t) -1 \
- + : ((((t) 1 << (sizeof (t) * CHAR_BIT - 2)) - 1) * 2 + 1)))
- +#endif
- +
- +#ifndef OFF_MAX
- +#define OFF_MAX TYPE_MAXIMUM(off_t)
- +#endif
- +
- +#ifndef OFF_MIN
- +#define OFF_MIN TYPE_MINIMUM(off_t)
- +#endif
- +
- +static char *newfile;
- +static int dirfd = -1;
- +
- +static void
- +exit_cleanup(void)
- +{
- +
- + if (dirfd != -1 && newfile != NULL)
- + if (unlinkat(dirfd, newfile, 0))
- + warn("unlinkat");
- +}
- +
- +static inline off_t
- +add_off_t(off_t a, off_t b)
- +{
- + off_t result;
- +
- +#if __GNUC__ >= 5 || \
- + (defined(__has_builtin) && __has_builtin(__builtin_add_overflow))
- + if (__builtin_add_overflow(a, b, &result))
- + errx(1, "Corrupt patch");
- +#else
- + if ((b > 0 && a > OFF_MAX - b) || (b < 0 && a < OFF_MIN - b))
- + errx(1, "Corrupt patch");
- + result = a + b;
- +#endif
- + return result;
- +}
-
- static off_t offtin(unsigned char *buf)
- {
- off_t y;
-
- - y=buf[7]&0x7F;
- - y=y*256;y+=buf[6];
- - y=y*256;y+=buf[5];
- - y=y*256;y+=buf[4];
- - y=y*256;y+=buf[3];
- - y=y*256;y+=buf[2];
- - y=y*256;y+=buf[1];
- - y=y*256;y+=buf[0];
- + y = buf[7] & 0x7F;
- + y = y * 256; y += buf[6];
- + y = y * 256; y += buf[5];
- + y = y * 256; y += buf[4];
- + y = y * 256; y += buf[3];
- + y = y * 256; y += buf[2];
- + y = y * 256; y += buf[1];
- + y = y * 256; y += buf[0];
-
- - if(buf[7]&0x80) y=-y;
- + if (buf[7] & 0x80)
- + y = -y;
-
- - return y;
- + return (y);
- }
-
- -int main(int argc,char * argv[])
- +static void
- +usage(void)
- {
- - FILE * f, * cpf, * dpf, * epf;
- - BZFILE * cpfbz2, * dpfbz2, * epfbz2;
- +
- + fprintf(stderr, "usage: bspatch oldfile newfile patchfile\n");
- + exit(1);
- +}
- +
- +int main(int argc, char *argv[])
- +{
- + FILE *f, *cpf, *dpf, *epf;
- + BZFILE *cpfbz2, *dpfbz2, *epfbz2;
- + char *directory, *namebuf;
- int cbz2err, dbz2err, ebz2err;
- - int fd;
- - ssize_t oldsize,newsize;
- - ssize_t bzctrllen,bzdatalen;
- - unsigned char header[32],buf[8];
- + int newfd, oldfd;
- + off_t oldsize, newsize;
- + off_t bzctrllen, bzdatalen;
- + unsigned char header[HEADER_SIZE], buf[8];
- unsigned char *old, *new;
- - off_t oldpos,newpos;
- + off_t oldpos, newpos;
- off_t ctrl[3];
- - off_t lenread;
- - off_t i;
- + off_t i, lenread, offset;
-
- - if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
- + if (argc != 4)
- + usage();
-
- /* Open patch file */
- - if ((f = fopen(argv[3], "r")) == NULL)
- + if ((f = fopen(argv[3], "rb")) == NULL)
- + err(1, "fopen(%s)", argv[3]);
- + /* Open patch file for control block */
- + if ((cpf = fopen(argv[3], "rb")) == NULL)
- + err(1, "fopen(%s)", argv[3]);
- + /* open patch file for diff block */
- + if ((dpf = fopen(argv[3], "rb")) == NULL)
- err(1, "fopen(%s)", argv[3]);
- + /* open patch file for extra block */
- + if ((epf = fopen(argv[3], "rb")) == NULL)
- + err(1, "fopen(%s)", argv[3]);
- + /* open oldfile */
- + if ((oldfd = open(argv[1], O_RDONLY | O_BINARY, 0)) < 0)
- + err(1, "open(%s)", argv[1]);
- + /* open directory where we'll write newfile */
- + if ((namebuf = strdup(argv[2])) == NULL ||
- + (directory = dirname(namebuf)) == NULL ||
- + (dirfd = open(directory, O_DIRECTORY)) < 0)
- + err(1, "open %s", argv[2]);
- + free(namebuf);
- + if ((newfile = basename(argv[2])) == NULL)
- + err(1, "basename");
- + /* open newfile */
- + if ((newfd = openat(dirfd, newfile,
- + O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666)) < 0)
- + err(1, "open(%s)", argv[2]);
- + atexit(exit_cleanup);
-
- /*
- File format:
- @@ -90,104 +184,104 @@ int main(int argc,char * argv[])
- */
-
- /* Read header */
- - if (fread(header, 1, 32, f) < 32) {
- + if (fread(header, 1, HEADER_SIZE, f) < HEADER_SIZE) {
- if (feof(f))
- - errx(1, "Corrupt patch\n");
- + errx(1, "Corrupt patch");
- err(1, "fread(%s)", argv[3]);
- }
-
- /* Check for appropriate magic */
- if (memcmp(header, "BSDIFF40", 8) != 0)
- - errx(1, "Corrupt patch\n");
- + errx(1, "Corrupt patch");
-
- /* Read lengths from header */
- - bzctrllen=offtin(header+8);
- - bzdatalen=offtin(header+16);
- - newsize=offtin(header+24);
- - if((bzctrllen<0) || (bzdatalen<0) || (newsize<0))
- - errx(1,"Corrupt patch\n");
- + bzctrllen = offtin(header + 8);
- + bzdatalen = offtin(header + 16);
- + newsize = offtin(header + 24);
- + if (bzctrllen < 0 || bzctrllen > OFF_MAX - HEADER_SIZE ||
- + bzdatalen < 0 || bzctrllen + HEADER_SIZE > OFF_MAX - bzdatalen ||
- + newsize < 0 || newsize > SSIZE_MAX)
- + errx(1, "Corrupt patch");
-
- /* Close patch file and re-open it via libbzip2 at the right places */
- if (fclose(f))
- err(1, "fclose(%s)", argv[3]);
- - if ((cpf = fopen(argv[3], "r")) == NULL)
- - err(1, "fopen(%s)", argv[3]);
- - if (fseeko(cpf, 32, SEEK_SET))
- - err(1, "fseeko(%s, %lld)", argv[3],
- - (long long)32);
- + offset = HEADER_SIZE;
- + if (fseeko(cpf, offset, SEEK_SET))
- + err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset);
- if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)
- errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
- - if ((dpf = fopen(argv[3], "r")) == NULL)
- - err(1, "fopen(%s)", argv[3]);
- - if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
- - err(1, "fseeko(%s, %lld)", argv[3],
- - (long long)(32 + bzctrllen));
- + offset = add_off_t(offset, bzctrllen);
- + if (fseeko(dpf, offset, SEEK_SET))
- + err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset);
- if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)
- errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
- - if ((epf = fopen(argv[3], "r")) == NULL)
- - err(1, "fopen(%s)", argv[3]);
- - if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
- - err(1, "fseeko(%s, %lld)", argv[3],
- - (long long)(32 + bzctrllen + bzdatalen));
- + offset = add_off_t(offset, bzdatalen);
- + if (fseeko(epf, offset, SEEK_SET))
- + err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset);
- if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)
- errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
-
- - if(((fd=open(argv[1],O_RDONLY,0))<0) ||
- - ((oldsize=lseek(fd,0,SEEK_END))==-1) ||
- - ((old=malloc(oldsize+1))==NULL) ||
- - (lseek(fd,0,SEEK_SET)!=0) ||
- - (read(fd,old,oldsize)!=oldsize) ||
- - (close(fd)==-1)) err(1,"%s",argv[1]);
- - if((new=malloc(newsize+1))==NULL) err(1,NULL);
- -
- - oldpos=0;newpos=0;
- - while(newpos<newsize) {
- + if ((oldsize = lseek(oldfd, 0, SEEK_END)) == -1 ||
- + oldsize > SSIZE_MAX ||
- + (old = malloc(oldsize)) == NULL ||
- + lseek(oldfd, 0, SEEK_SET) != 0 ||
- + read(oldfd, old, oldsize) != oldsize ||
- + close(oldfd) == -1)
- + err(1, "%s", argv[1]);
- + if ((new = malloc(newsize)) == NULL)
- + err(1, NULL);
- +
- + oldpos = 0;
- + newpos = 0;
- + while (newpos < newsize) {
- /* Read control data */
- - for(i=0;i<=2;i++) {
- + for (i = 0; i <= 2; i++) {
- lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
- if ((lenread < 8) || ((cbz2err != BZ_OK) &&
- (cbz2err != BZ_STREAM_END)))
- - errx(1, "Corrupt patch\n");
- - ctrl[i]=offtin(buf);
- - };
- + errx(1, "Corrupt patch");
- + ctrl[i] = offtin(buf);
- + }
-
- /* Sanity-check */
- - if ((ctrl[0] < 0) || (ctrl[1] < 0))
- - errx(1,"Corrupt patch\n");
- + if (ctrl[0] < 0 || ctrl[0] > INT_MAX ||
- + ctrl[1] < 0 || ctrl[1] > INT_MAX)
- + errx(1, "Corrupt patch");
-
- /* Sanity-check */
- - if(newpos+ctrl[0]>newsize)
- - errx(1,"Corrupt patch\n");
- + if (add_off_t(newpos, ctrl[0]) > newsize)
- + errx(1, "Corrupt patch");
-
- /* Read diff string */
- lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]);
- if ((lenread < ctrl[0]) ||
- ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
- - errx(1, "Corrupt patch\n");
- + errx(1, "Corrupt patch");
-
- /* Add old data to diff string */
- - for(i=0;i<ctrl[0];i++)
- - if((oldpos+i>=0) && (oldpos+i<oldsize))
- - new[newpos+i]+=old[oldpos+i];
- + for (i = 0; i < ctrl[0]; i++)
- + if (add_off_t(oldpos, i) < oldsize)
- + new[newpos + i] += old[oldpos + i];
-
- /* Adjust pointers */
- - newpos+=ctrl[0];
- - oldpos+=ctrl[0];
- + newpos = add_off_t(newpos, ctrl[0]);
- + oldpos = add_off_t(oldpos, ctrl[0]);
-
- /* Sanity-check */
- - if(newpos+ctrl[1]>newsize)
- - errx(1,"Corrupt patch\n");
- + if (add_off_t(newpos, ctrl[1]) > newsize)
- + errx(1, "Corrupt patch");
-
- /* Read extra string */
- lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]);
- if ((lenread < ctrl[1]) ||
- ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
- - errx(1, "Corrupt patch\n");
- + errx(1, "Corrupt patch");
-
- /* Adjust pointers */
- - newpos+=ctrl[1];
- - oldpos+=ctrl[2];
- - };
- + newpos = add_off_t(newpos, ctrl[1]);
- + oldpos = add_off_t(oldpos, ctrl[2]);
- + }
-
- /* Clean up the bzip2 reads */
- BZ2_bzReadClose(&cbz2err, cpfbz2);
- @@ -197,12 +291,13 @@ int main(int argc,char * argv[])
- err(1, "fclose(%s)", argv[3]);
-
- /* Write the new file */
- - if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,0666))<0) ||
- - (write(fd,new,newsize)!=newsize) || (close(fd)==-1))
- - err(1,"%s",argv[2]);
- + if (write(newfd, new, newsize) != newsize || close(newfd) == -1)
- + err(1, "%s", argv[2]);
- + /* Disable atexit cleanup */
- + newfile = NULL;
-
- free(new);
- free(old);
-
- - return 0;
- + return (0);
- }
|