Menu

#496 P_fd_set core dumps with FORTIFY_SOURCE on Linux with raised file number limits.

Stable_Branch
closed-fixed
nobody
None
5
2015-02-13
2013-12-04
No

I think PTlib has a bug in P_fd_set when running with a raised file limit ( above FD_SET_SIZE ) on glibc with _FORTIFY_SOURCE=2 ( which seems to be the default now ). Description is a bit lengthy as it's a complex issue.

Note I found the problem in PTLIB 2.10.9 with other programs / libraries, but I've checked the trunk to the best of my limited knowledge and the problem seems to be there too and I think it will affect opal users too, so I'm reporting it here.

I encountered it with a program ( yate ) which uses h323plus and PTlib 2.10.9 running under Gentoo Linux with glibc 2.15 and under Ubuntu 1304 with glibc 2.17, with the process file limit raised to 8192 ( it's a high traffic instance ). As soon as I had about 130 simultaneous calls my program died with the following stack trace:

  Program terminated with signal 6, Aborted.
  #0  0x00007fd155b60f35 in raise () from /lib64/libc.so.6
  (gdb) bt
  #0  0x00007fd155b60f35 in raise () from /lib64/libc.so.6
  #1  0x00007fd155b623b8 in abort () from /lib64/libc.so.6
  #2  0x00007fd155b9fc2b in ?? () from /lib64/libc.so.6
  #3  0x00007fd155c28147 in __fortify_fail () from /lib64/libc.so.6
  #4  0x00007fd155c26130 in __chk_fail () from /lib64/libc.so.6
  #5  0x00007fd155c280f7 in __fdelt_warn () from /lib64/libc.so.6
  #6  0x00007fd15488d227 in P_fd_set::operator=(int) () from /home/h323plus/lib/libpt.so.2.10.9
  #7  0x00007fd154873a23 in PThread::PXBlockOnIO(int, int, PTimeInterval const&) ()
     from /home/h323plus/lib/libpt.so.2.10.9
  #8  0x00007fd15486de84 in PChannel::PXSetIOBlock(PChannel::PXBlockType, PTimeInterval const&) ()
     from /home/h323plus/lib/libpt.so.2.10.9
  #9  0x00007fd154869b5f in PTCPSocket::Read(void*, int) () from /home/h323plus/lib/libpt.so.2.10.9
  #10 0x00007fd15486cae0 in PIndirectChannel::Read(void*, int) ()
     from /home/h323plus/lib/libpt.so.2.10.9
  #11 0x00007fd15486c054 in PChannel::ReadChar() () from /home/h323plus/lib/libpt.so.2.10.9
  #12 0x00007fd154f4f6b7 in H323TransportTCP::ReadPDU(PBYTEArray&) ()
    from /home/h323plus/lib/libh323_linux_x86_64_.so.1.25.0
  #13 0x00007fd154f291b6 in H323Connection::HandleControlChannel() ()
     from /home/h323plus/lib/libh323_linux_x86_64_.so.1.25.0
  #14 0x00007fd154873819 in PThread::PX_ThreadStart(void*) () from /home/h323plus/lib/libpt.so.2.10.9
  #15 0x00007fd156a43f4a in start_thread () from /lib64/libpthread.so.0
  #16 0x00007fd155c1298d in clone () from /lib64/libc.so.6
 (gdb)

After lots of digging thorugh PTlib's sources and system include files I think this problem is because P_fd_set uses the usual idiom for enlarging the fd_set:

 void P_fd_set::Construct()
 {
   max_fd = PProcess::Current().GetMaxHandles();
   set = (fd_set *)malloc((max_fd+7)>>3);
 }

but then uses FD_SET to set bits, which, when FORTIFY_SOURCE is active, checks the descriptor index with the following code ( from /usr/include/bits/select2.h ):

 extern long int __fdelt_warn (long int __d)
   __warnattr ("bit outside of fd_set selected");
 #undef __FD_ELT
 #define __FD_ELT(d) \
   __extension__                                                             \
   ({ long int __d = (d);                                                    \
      (__builtin_constant_p (__d)                                            \
       ? (0 <= __d && __d < __FD_SETSIZE                                     \
          ? (__d / __NFDBITS)                                                \
          : __fdelt_warn (__d))                                              \
       : __fdelt_chk (__d)); })

Leading to my problem as soon as I have and fd > FD_SETSIZE.

P_fd_set::Zero() uses a memset, which is not catched with FORTIFY_SOURCE because GCC does not know the chunk size int it.

I have a couple of workarounds, one is rebuilding PTlib without FORTIFY_SOURCE, the other, for which I'm developing and testing a patch, is using an array of fd_sets in construct ( using set = calloc(sizeof(fd_set), (max_fd+FD_SET_SIZE-1)/FD_SETSIZE ) and using FD_SET(fd % FD_SETSIZE) on element fd / FD_SETSIZE, which I think should work with FORTIFY_SOURCE. This can lead to some extra used space, but in my experience FD_SETSIZE tends to be 1024 or a similar power of two, and when the limit is raised it's typically done to a large value, and also to a multiple of it, so it shouldn't be a big issue. If there is interest in it I can contribute it as soon as I have it tested.

Also, I think the initialization in ::Construct uses an incorrect size, as normally fd_sets are arrays of int or longs, in fact in my /usr/include/sys/select.h file I have:

 /* The fd_set member is required to be an array of longs.  */
 typedef long int __fd_mask;

And the FD_SET/CLEAR/ISSET macros use __fd_mask elements, so the byte aligned malloc

   set = (fd_set *)malloc((max_fd+7)>>3);

in construct allocates less than needed, specially in big-endian architectures, although this problem is masked because normally malloc pads & aligns for long or greater. Problem will typically be solved by using ((max_fd+31)>>5)<<2, but as long must be at least 32 bits, but can be greater, I'll suggest to play it safe and use 64 or even 128 bits chunks.

Discussion


Log in to post a comment.