CPAN PR Challenge – Text::BibTex

This month I ended up with Text::BibTex.  Conveniently, this distro came with an actual bug that needed fixing.

This, however, was not a Perl problem at all but rather a C problem.  The module was causing an actual crash with this stack trace:

/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f9f39cc5f37] /lib/x86_64-linux-gnu/libc.so.6(+0xebdf0)[0x7f9f39cc4df0] /lib/x86_64-linux-gnu/libc.so.6(+0xead37)[0x7f9f39cc3d37] /usr/lib/libbtparse.so.1(zzFAIL+0xe4)[0x7f9f38eabdc4] /usr/lib/libbtparse.so.1(body+0xdf)[0x7f9f38eab56f] /usr/lib/libbtparse.so.1(entry+0x1ea)[0x7f9f38eab98a] /usr/lib/libbtparse.so.1(bt_parse_entry+0x100)[0x7f9f38ea9d40] /usr/lib/perl5/auto/Text/BibTeX/BibTeX.so(XS_Text__BibTeX__Entry__parse+0x 135)[0x7f9f390c1a45] /usr/lib/libperl.so.5.14(Perl_pp_entersub+0x58c)[0x7f9f3a6ba3cc] /usr/lib/libperl.so.5.14(Perl_runops_standard+0x16)[0x7f9f3a6b19a6] /usr/lib/libperl.so.5.14(perl_run+0x3a5)[0x7f9f3a6535b5] /usr/bin/perl(main+0x149)[0x400f89] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd)[0x7f9f39bf7ead] /usr/bin/perl[0x400fc1]

I don’t actually know anything about BibTex, so the first thing I did was strip down the problem data to the smallest thing that would still break and make a test case.  As it turned out, the smallest thing was about 2000 bytes of any text inside a comment that wasn’t closed.  This was a bit of a clue:

./btparse/pccts/antlr.h:74:#define ZZLEXBUFSIZE 2000

The culprit in this case was an improper use of strncat() in this block:

#ifdef LL_K
 static char text[LL_K*ZZLEXBUFSIZE+1];
 SetWordType *f[LL_K];
#else
 static char text[ZZLEXBUFSIZE+1];
 SetWordType *f[1];
#endif
 SetWordType **miss_set;
 char **miss_text;
 int *bad_tok;
 char **bad_text;
 int *err_k;
 int i;
 va_list ap;
#ifndef __USE_PROTOS
 int k;
#endif
#ifdef __USE_PROTOS
 va_start(ap, k);
#else
 va_start(ap);
 k = va_arg(ap, int); /* how many lookahead sets? */
#endif
 text[0] = '\0';
 for (i=1; i<=k; i++) /* collect all lookahead sets */
 {
 f[i-1] = va_arg(ap, SetWordType *);
 }
 for (i=1; i<=k; i++) /* look for offending token */
 {
#ifdef LL_K
 int freeSpace = (LL_K*ZZLEXBUFSIZE+1) - strlen(text);
#else
 int freeSpace = (ZZLEXBUFSIZE+1) - strlen(text);
#endif
 if ( i>1 ) strcat(text, " ");
 strncat(text, LATEXT(i), freeSpace);
 if ( !zzset_el((unsigned)LA(i), f[i-1]) ) break;
 }

strncat(a,b,n) looks like it safely avoids overflowing the destination buffer but in fact according to its specification:

 If src contains n or more bytes, strncat() writes n+1 bytes to dest (n from src plus the terminating null byte). Therefore, the size of dest must be at least strlen(dest)+n+1.

So to fix, we just allocate more space:

#ifdef LL_K
 static char text[LL_K*ZZLEXBUFSIZE+1+1];
 SetWordType *f[LL_K];
#else
 static char text[ZZLEXBUFSIZE+1+1];
 SetWordType *f[1];
#endif

Leave a Reply

Your email address will not be published. Required fields are marked *