Hi, the attached patch fixes two vulnerabilities in file(1):
CVE-2014-2270: A specifically crafted Portable Executable (PE) can trigger out-of-bounds read. CVE-2014-1943: A malicious input file could trigger infinite recursion in libmagic(3). The patch is based on a FreeBSD security advisory and fixes from the file developers upstream. I had to do some adaptions because our version of file is a bit older. We are not affected by the two other CVEs (CVE-2012-1571, CVE-2012-1571) referred by the FreeBSD SA. For further Information see: https://www.freebsd.org/security/advisories/FreeBSD-SA-14:16.file.asc http://security.FreeBSD.org/patches/SA-14:16/file-8.4.patch I have ignored the 80 characters limit sometimes to keep the diff to upstream smaller. The regression tests for file were successful. I have another patch which fixes the vulnerabilities described in https://www.freebsd.org/security/advisories/FreeBSD-SA-14:28.file.asc . I will submit it if the first part is committed to make reviewers job easier. Regards Florian Riehm Index: ascmagic.c =================================================================== RCS file: /cvs/src/usr.bin/file/ascmagic.c,v retrieving revision 1.12 diff -u -p -r1.12 ascmagic.c --- ascmagic.c 18 May 2014 17:50:11 -0000 1.12 +++ ascmagic.c 14 Dec 2014 14:10:55 -0000 @@ -175,7 +175,8 @@ file_ascmagic(struct magic_set *ms, cons } if ((utf8_end = encode_utf8(utf8_buf, mlen, ubuf, ulen)) == NULL) goto done; - if (file_softmagic(ms, utf8_buf, utf8_end - utf8_buf, TEXTTEST) != 0) { + if (file_softmagic(ms, utf8_buf, utf8_end - utf8_buf, + 0, TEXTTEST) != 0) { rv = 1; goto done; } Index: file.h =================================================================== RCS file: /cvs/src/usr.bin/file/file.h,v retrieving revision 1.24 diff -u -p -r1.24 file.h --- file.h 18 May 2014 17:50:11 -0000 1.24 +++ file.h 14 Dec 2014 14:10:55 -0000 @@ -332,7 +332,8 @@ protected int file_zmagic(struct magic_s const unsigned char *, size_t); protected int file_ascmagic(struct magic_set *, const unsigned char *, size_t); protected int file_is_tar(struct magic_set *, const unsigned char *, size_t); -protected int file_softmagic(struct magic_set *, const unsigned char *, size_t, int); +protected int file_softmagic(struct magic_set *, const unsigned char *, size_t, + size_t, int); protected struct mlist *file_apprentice(struct magic_set *, const char *, int); protected uint64_t file_signextend(struct magic_set *, struct magic *, uint64_t); Index: funcs.c =================================================================== RCS file: /cvs/src/usr.bin/file/funcs.c,v retrieving revision 1.8 diff -u -p -r1.8 funcs.c --- funcs.c 18 May 2014 17:50:11 -0000 1.8 +++ funcs.c 14 Dec 2014 14:10:55 -0000 @@ -181,7 +181,7 @@ file_buffer(struct magic_set *ms, int fd (m = file_is_tar(ms, buf, nb)) == 0) { /* try tests in /etc/magic (or surrogate magic file) */ if ((ms->flags & MAGIC_NO_CHECK_SOFT) != 0 || - (m = file_softmagic(ms, buf, nb, BINTEST)) == 0) { + (m = file_softmagic(ms, buf, nb, 0, BINTEST)) == 0) { /* try known keywords, check whether it is ASCII */ if ((ms->flags & MAGIC_NO_CHECK_ASCII) != 0 || (m = file_ascmagic(ms, buf, nb)) == 0) { Index: softmagic.c =================================================================== RCS file: /cvs/src/usr.bin/file/softmagic.c,v retrieving revision 1.17 diff -u -p -r1.17 softmagic.c --- softmagic.c 17 Apr 2013 15:01:26 -0000 1.17 +++ softmagic.c 14 Dec 2014 14:10:56 -0000 @@ -39,9 +39,9 @@ private int match(struct magic_set *, struct magic *, uint32_t, - const unsigned char *, size_t, int); + const unsigned char *, size_t, int, int); private int mget(struct magic_set *, const unsigned char *, - struct magic *, size_t, unsigned int); + struct magic *, size_t, unsigned int, int); private int magiccheck(struct magic_set *, struct magic *); private int32_t mprint(struct magic_set *, struct magic *); private void mdebug(uint32_t, const char *, size_t); @@ -54,6 +54,7 @@ private void cvt_16(union VALUETYPE *, c private void cvt_32(union VALUETYPE *, const struct magic *); private void cvt_64(union VALUETYPE *, const struct magic *); +#define OFFSET_OOB(n, o, i) ((n) < (o) || (i) > ((n) - (o))) /* * Macro to give description string according to whether we want plain * text or MIME type @@ -66,12 +67,13 @@ private void cvt_64(union VALUETYPE *, c */ /*ARGSUSED1*/ /* nbytes passed for regularity, maybe need later */ protected int -file_softmagic(struct magic_set *ms, const unsigned char *buf, size_t nbytes, int mode) +file_softmagic(struct magic_set *ms, const unsigned char *buf, size_t nbytes, + size_t level, int mode) { struct mlist *ml; int rv; for (ml = ms->mlist->next; ml != ms->mlist; ml = ml->next) - if ((rv = match(ms, ml->magic, ml->nmagic, buf, nbytes, mode)) != 0) + if ((rv = match(ms, ml->magic, ml->nmagic, buf, nbytes, mode, level)) != 0) return rv; return 0; @@ -106,7 +108,7 @@ file_softmagic(struct magic_set *ms, con */ private int match(struct magic_set *ms, struct magic *magic, uint32_t nmagic, - const unsigned char *s, size_t nbytes, int mode) + const unsigned char *s, size_t nbytes, int mode, int recursion_level) { uint32_t magindex = 0; unsigned int cont_level = 0; @@ -134,7 +136,7 @@ match(struct magic_set *ms, struct magic ms->line = m->lineno; /* if main entry matches, print it... */ - flush = !mget(ms, s, m, nbytes, cont_level); + flush = !mget(ms, s, m, nbytes, cont_level, recursion_level + 1); if (flush) { if (m->reln == '!') flush = 0; @@ -205,7 +207,7 @@ match(struct magic_set *ms, struct magic continue; } #endif - flush = !mget(ms, s, m, nbytes, cont_level); + flush = !mget(ms, s, m, nbytes, cont_level, recursion_level + 1); if (flush && m->reln != '!') continue; @@ -885,12 +887,17 @@ mcopy(struct magic_set *ms, union VALUET private int mget(struct magic_set *ms, const unsigned char *s, - struct magic *m, size_t nbytes, unsigned int cont_level) + struct magic *m, size_t nbytes, unsigned int cont_level, int recursion_level) { uint32_t offset = ms->offset; uint32_t count = m->str_range; union VALUETYPE *p = &ms->ms_value; + if (recursion_level >= 20) { + file_error(ms, 0, "recursion nesting exceeded"); + return -1; + } + if (mcopy(ms, p, m->type, m->flag & INDIR, s, offset, nbytes, count) == -1) return -1; @@ -936,7 +943,7 @@ mget(struct magic_set *ms, const unsigne } switch (m->in_type) { case FILE_BYTE: - if (nbytes < (offset + 1)) + if (OFFSET_OOB(nbytes, offset, 1)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -971,7 +978,7 @@ mget(struct magic_set *ms, const unsigne offset = ~offset; break; case FILE_BESHORT: - if (nbytes < (offset + 2)) + if (OFFSET_OOB(nbytes, offset, 2)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -1023,7 +1030,7 @@ mget(struct magic_set *ms, const unsigne offset = ~offset; break; case FILE_LESHORT: - if (nbytes < (offset + 2)) + if (OFFSET_OOB(nbytes, offset, 2)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -1075,7 +1082,7 @@ mget(struct magic_set *ms, const unsigne offset = ~offset; break; case FILE_SHORT: - if (nbytes < (offset + 2)) + if (OFFSET_OOB(nbytes, offset, 2)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -1111,7 +1118,7 @@ mget(struct magic_set *ms, const unsigne offset = ~offset; break; case FILE_BELONG: - if (nbytes < (offset + 4)) + if (OFFSET_OOB(nbytes, offset, 4)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -1181,7 +1188,7 @@ mget(struct magic_set *ms, const unsigne offset = ~offset; break; case FILE_LELONG: - if (nbytes < (offset + 4)) + if (OFFSET_OOB(nbytes, offset, 4)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -1251,7 +1258,7 @@ mget(struct magic_set *ms, const unsigne offset = ~offset; break; case FILE_MELONG: - if (nbytes < (offset + 4)) + if (OFFSET_OOB(nbytes, offset, 4)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -1321,7 +1328,7 @@ mget(struct magic_set *ms, const unsigne offset = ~offset; break; case FILE_LONG: - if (nbytes < (offset + 4)) + if (OFFSET_OOB(nbytes, offset, 4)) return 0; if (off) { switch (m->in_op & FILE_OPS_MASK) { @@ -1373,14 +1380,14 @@ mget(struct magic_set *ms, const unsigne /* Verify we have enough data to match magic type */ switch (m->type) { case FILE_BYTE: - if (nbytes < (offset + 1)) /* should alway be true */ + if (OFFSET_OOB(nbytes, offset, 1)) return 0; break; case FILE_SHORT: case FILE_BESHORT: case FILE_LESHORT: - if (nbytes < (offset + 2)) + if (OFFSET_OOB(nbytes, offset, 2)) return 0; break; @@ -1399,26 +1406,26 @@ mget(struct magic_set *ms, const unsigne case FILE_FLOAT: case FILE_BEFLOAT: case FILE_LEFLOAT: - if (nbytes < (offset + 4)) + if (OFFSET_OOB(nbytes, offset, 4)) return 0; break; case FILE_DOUBLE: case FILE_BEDOUBLE: case FILE_LEDOUBLE: - if (nbytes < (offset + 8)) + if (OFFSET_OOB(nbytes, offset, 8)) return 0; break; case FILE_STRING: case FILE_PSTRING: case FILE_SEARCH: - if (nbytes < (offset + m->vallen)) + if (OFFSET_OOB(nbytes, offset, m->vallen)) return 0; break; case FILE_REGEX: - if (nbytes < offset) + if (OFFSET_OOB(nbytes, offset, 0)) return 0; break;