This is the mail archive of the
elfutils-devel@sourceware.org
mailing list for the elfutils project.
[RFC] libelf: segment fault on x86-64 while file's bss offset have a large number
- From: Hongxu Jia <hongxu dot jia at windriver dot com>
- To: elfutils-devel at lists dot fedorahosted dot org
- Date: Fri, 18 Oct 2013 19:49:43 +0800
- Subject: [RFC] libelf: segment fault on x86-64 while file's bss offset have a large number
*Environment
cat /etc/issue
Ubuntu 13.04 \n \l
uname -a
Linux pek-hjia-d1 3.8.0-31-generic #46-Ubuntu SMP Tue Sep 10 20:03:44 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
*Problem
1) Here is the test source code
$ cat >> test.c << EOF
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include "libelf.h"
int main(int argc, char *argv[])
{
int fd;
Elf *e;
if (elf_version(EV_CURRENT) == EV_NONE)
{
printf ("library out of date\n");
exit (1);
}
if ((fd = open("test/xB.linkhuge", O_RDWR)) < 0) {
printf("%s %d failed\n", __FUNCTION__, __LINE__);
exit (1);
}
if ((e = elf_begin(fd, ELF_C_RDWR_MMAP, (Elf *) 0)) == 0) {
printf("failed %s", elf_errmsg (-1));
exit (1);
}
elf_flagelf (e, ELF_C_SET, ELF_F_LAYOUT);
elf_update(e, ELF_C_WRITE);
elf_end(e);
close(fd);
}
EOF
2) Download http://kojipkgs.fedoraproject.org/packages/elfutils/0.157/2.fc21/x86_64/elfutils-libelf-devel-static-0.157-2.fc21.x86_64.rpm
to get libelf.a for debug.
Download http://kojipkgs.fedoraproject.org/packages/elfutils/0.157/2.fc21/x86_64/elfutils-libelf-devel-0.157-2.fc21.x86_64.rpm
to get libelf.h for debug.
3) Compile test.c with libelf.a
$ gcc test.c -o test_case -static -L. -lelf
4) Prepare file whose bss offset have a large number '00200000'
Download the attachment from
https://bugzilla.redhat.com/show_bug.cgi?id=1020842
$ ls test/xB.linkhuge -al
-rwxr-xr-x 1 jiahongxu jiahongxu 1221403 Oct 18 18:55 test/xB.linkhuge
$ readelf -a xB.linkhuge
......
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
skip..
[25] .data PROGBITS 00000000005128a0 001128a0
0000000000010168 0000000000000000 WA 0 0 32
[26] .bss NOBITS 0000000001000000 00200000
0000000000010050 0000000000000000 WA 0 0 32
[27] .comment PROGBITS 0000000000000000 00122a08
0000000000000011 0000000000000001 MS 0 0 1
......
5) Run test_case with strace, there was mmap/munmap error.
$ strace ./test_case
execve("./test_case", ["./test_case"], [/* 59 vars */]) = 0
uname({sys="Linux", node="pek-hjia-d1", ...}) = 0
brk(0) = 0x16a6000
brk(0x16a71c0) = 0x16a71c0
arch_prctl(ARCH_SET_FS, 0x16a6880) = 0
brk(0x16c81c0) = 0x16c81c0
brk(0x16c9000) = 0x16c9000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("test/xB.linkhuge", O_RDWR) = 3
fcntl(3, F_GETFL) = 0x8002 (flags O_RDWR|O_LARGEFILE)
fstat(3, {st_mode=S_IFREG|0755, st_size=1221403, ...}) = 0
mmap(NULL, 1221403, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x7ff720fe2000
fstat(3, {st_mode=S_IFREG|0755, st_size=1221403, ...}) = 0
ftruncate(3, 2097152) = 0
msync(0x7ff720fe2000, 1216568, MS_SYNC) = 0
munmap(0x7ff720fe2000, 2097152) = 0
close(3) = 0
exit_group(0) = ?
6) $ ls test/xB.linkhuge -al
-rwxr-xr-x 1 jiahongxu jiahongxu 2097152 Oct 18 19:04 test/xB.linkhuge
*Analysis
1) While ELF_C_RDWR_MMAP was used, elf_begin invoked mmap() to map file
into memory with the size of '1221403'.
...strace log...
mmap(NULL, 1221403, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x7ff720fe2000
................
2) While 'xB.linkhuge' bss Offset has a large number '00200000', elf_update
caculated file size by __elf64_updatenull_wrlock and the size was
enlarged from '1221403' to '2097152'
3) In this situation, elf_update invoked ftruncate to enlarge the file,
and memory size (elf->maximum_size) also was incorrectly updated.
...strace log...
ftruncate(3, 2097152)
................
4) There was segment fault in elf_end which invoked munmap with the
length is the enlarged file size '2097152', not the length of
mmap '1216568'.
...strace log...
munmap(0x7ff720fe2000, 2097152) = 0
................
*Solution
1) I tried to modify elf_update.c, don't update memory size
(elf->maximum_size) in this situation. It fixed this issue
and everything looks ok, but I am not sure the modification
is necessary.
......
11 diff --git a/libelf/elf_update.c b/libelf/elf_update.c
12 --- a/libelf/elf_update.c
13 +++ b/libelf/elf_update.c
14 @@ -120,7 +120,9 @@ write_file (Elf *elf, off_t size, int change_bo, size_t shnum)
15 size = -1;
16 }
17
18 - if (size != -1 && elf->parent == NULL)
19 + /* If the file is enlarged by truncate, we should not update maximum_size to
20 + avoid segment fault while invoking munmap in elf_end */
21 + if (size != -1 && elf->parent == NULL && (size_t) size <= elf->maximum_size)
22 elf->maximum_size = size;
......
2) I also tried to add check before munmap in elf_end by msync with
the length of elf->maximum_size, if msync return error, munmap
should not be invoked, this could avoid segment fault.
--- a/libelf/elf_end.c
+++ b/libelf/elf_end.c
@@ -217,7 +217,10 @@ elf_end (elf)
if ((elf->flags & ELF_F_MALLOCED) != 0)
free (elf->map_address);
else if ((elf->flags & ELF_F_MMAPPED) != 0)
- munmap (elf->map_address, elf->maximum_size);
+ {
+ if (msync (elf->map_address, elf->maximum_size, MS_SYNC) == 0)
+ munmap (elf->map_address, elf->maximum_size);
+ }
}
3) Any suggestion is welcomed.
--
1.8.1.2