Subject: | Crash in Convert::IBM390::packeb |
'make test' was failing with a segmentation fault on packeb. Also, unpackeb('p5.2') was returning the wrong numbers. The problem turned out to be a conflict with a library function named pow10, which was causing pow10[ndec] to return garbage instead of the proper power of 10.
This wouldn't have caused a segfault, except that CF_num2packed & CF_num2zoned don't check for numbers that are too big. If you do something like
packeb('p16', 1.0e99);
you get a segfault.
The attached patch:
1) renames pow10 to pows_of_10 and makes it static. This fixes the actual problem I was having.
2) changes CF_num2packed & CF_num2zoned to check the size of the number they're about to pass to sprintf. If it's too big, they exit with croak instead of segfaulting.
The patch also includes two new tests to make sure behavior 2 is working.
Even with this problem, Convert::IBM390 has been really useful. Thanks.
Summary of my perl5 (revision 5.0 version 8 subversion 3) configuration:
Platform:
osname=linux, osvers=2.6.4, archname=i586-linux-thread-multi
uname='linux d209 2.6.4 #1 smp thu mar 11 17:56:49 utc 2004 i686 i686 i386 gnulinux '
config_args='-ds -e -Dprefix=/usr -Dvendorprefix=/usr -Dinstallusrbinperl -Dusethreads -Di_db -Di_dbm -Di_ndbm -Di_gdbm -Duseshrplib=true -Doptimize=-O2 -march=i586 -mcpu=i686 -fmessage-length=0 -Wall -Wall -pipe'
hint=recommended, useposix=true, d_sigaction=define
usethreads=define use5005threads=undef useithreads=define usemultiplicity=define
useperlio=define d_sfio=undef uselargefiles=define usesocks=undef
use64bitint=undef use64bitall=undef uselongdouble=undef
usemymalloc=n, bincompat5005=undef
Compiler:
cc='cc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -fno-strict-aliasing -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64',
optimize='-O2 -march=i586 -mcpu=i686 -fmessage-length=0 -Wall -Wall -pipe',
cppflags='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -fno-strict-aliasing'
ccversion='', gccversion='3.3.3 (SuSE Linux)', gccosandvers=''
intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=12
ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
alignbytes=4, prototype=define
Linker and Libraries:
ld='cc', ldflags =''
libpth=/lib /usr/lib /usr/local/lib
libs=-lnsl -ldl -lm -lcrypt -lutil -lpthread -lc
perllibs=-lnsl -ldl -lm -lcrypt -lutil -lpthread -lc
libc=, so=so, useshrplib=true, libperl=libperl.so
gnulibc_version='2.3.3'
Dynamic Linking:
dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-rdynamic -Wl,-rpath,/usr/lib/perl5/5.8.3/i586-linux-thread-multi/CORE'
cccdlflags='-fPIC', lddlflags='-shared'
--- Convert-IBM390-0.19/d/IBM390lib.ccc 2004-07-12 14:29:52.000000000 -0500
+++ Convert-IBM390-0.20/d/IBM390lib.ccc 2005-01-07 14:26:49.821436640 -0600
@@ -1,5 +1,10 @@
#include <stdio.h>
#include <time.h>
+
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
/*-------------------------------------------------------------------
Module: Convert::IBM390
The C functions defined here are faster than straight Perl code.
@@ -7,7 +12,7 @@
/* Powers of 10 */
-double pow10[32] = { 1.0, 10.0, 100.0, 1000.0, 10000.0,
+static const double pows_of_10[32] = { 1.0, 10.0, 100.0, 1000.0, 10000.0,
100000.0, 1000000.0, 10000000.0,
1.0E8, 1.0E9, 1.0E10, 1.0E11, 1.0E12, 1.0E13, 1.0E14, 1.0E15,
1.0E16, 1.0E17, 1.0E18, 1.0E19, 1.0E20, 1.0E21, 1.0E22, 1.0E23,
@@ -71,7 +76,7 @@
/* If ndec is 0, we're finished; if it's nonzero,
correct the number of decimal places. */
if ( ndec != 0 ) {
- out_num = out_num / pow10[ndec];
+ out_num = out_num / pows_of_10[ndec];
}
#ifdef DEBUG390
@@ -103,12 +108,14 @@
} else {
perl_absval = 0 - perlnum; signum = 0x0D;
}
+ if (ndec) perl_absval *= pows_of_10[ndec];
+
+ if (perl_absval >= 1.0E31) {
+ croak("Number %g too long for packed decimal", perlnum);
+ }
/* sprintf will round to an "integral" value. */
- if (ndec == 0) {
sprintf(digits, "%031.0f", perl_absval);
- } else {
- sprintf(digits, "%031.0f", perl_absval * pow10[ndec]);
- }
+
outdigits = outbytes * 2 - 1;
digit_ptr = digits;
out_ptr = packed_ptr;
@@ -188,7 +195,7 @@
/* If ndec is 0, we're finished; if it's nonzero,
correct the number of decimal places. */
if ( ndec != 0 ) {
- out_num = out_num / pow10[ndec];
+ out_num = out_num / pows_of_10[ndec];
}
#ifdef DEBUG390
@@ -219,12 +226,15 @@
} else {
perl_absval = 0 - perlnum; signum = 0xD0;
}
+
+ if (ndec) perl_absval *= pows_of_10[ndec];
+
+ if (perl_absval >= 1.0E31) {
+ croak("Number %g too long for zoned decimal", perlnum);
+ }
/* sprintf will round to an "integral" value. */
- if (ndec == 0) {
sprintf(digits, "%031.0f", perl_absval);
- } else {
- sprintf(digits, "%031.0f", perl_absval * pow10[ndec]);
- }
+
digit_ptr = digits;
out_ptr = zoned_ptr;
for (i = 31 - outbytes; i < 31; i++) {
--- Convert-IBM390-0.19/test.pl 2004-07-14 15:38:58.000000000 -0500
+++ Convert-IBM390-0.20/test.pl 2005-01-07 14:24:26.229265968 -0600
@@ -3,7 +3,7 @@
################## We start with some black magic to print on failure.
-BEGIN { $| = 1; print "1..11\n"; }
+BEGIN { $| = 1; print "1..13\n"; }
END {print "not ok 1\n" unless $loaded;}
use Convert::IBM390 qw(:all);
$loaded = 1;
@@ -98,6 +98,15 @@
($pp, $vv) = unpackeb("p2v", $ebrecord);
was_it_ok_b(11, !defined($pp) && !defined($vv));
+#----- packeb with over-large numbers
+print "packeb crash.....";
+eval { packeb('p16', 1.0e99) };
+was_it_ok_b(12, $@ && $@ =~ /too long/);
+
+print " .....";
+eval { packeb('z32', 1.0e99) };
+was_it_ok_b(13, $@ && $@ =~ /too long/);
+
if ($failed == 0) { print "All tests successful.\n"; }
else {
$tt = ($failed == 1) ? "1 test" : "$failed tests";