Gentoo Websites Logo
Go to: Gentoo Home Documentation Forums Lists Bugs Planet Store Wiki Get Gentoo!
Bug 197191 - mmap() does not seem to zero-fill end of maps of files that are ~PAGE_SIZE in length
Summary: mmap() does not seem to zero-fill end of maps of files that are ~PAGE_SIZE in...
Status: RESOLVED INVALID
Alias: None
Product: Gentoo Linux
Classification: Unclassified
Component: [OLD] Core system (show other bugs)
Hardware: AMD64 Linux
: High major (vote)
Assignee: Gentoo Kernel Bug Wranglers and Kernel Maintainers
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-10-27 02:55 UTC by Paul Price
Modified: 2007-10-30 11:19 UTC (History)
2 users (show)

See Also:
Package list:
Runtime testing required: ---


Attachments
Test program to reproduce (test.c,605 bytes, text/plain)
2007-10-27 02:57 UTC, Paul Price
Details
Example text file (ppImage.config,8.00 KB, text/plain)
2007-10-27 02:58 UTC, Paul Price
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Paul Price 2007-10-27 02:55:06 UTC
We are using mmap to map a text file into memory, that we then parse.  We map a larger size than the file, and use the fact that bytes following the file are set to 0.  This ensures that a \0 terminator exists, when we treat the memory as a char*.  However, when the file size is a multiple of the page size (in our case, the file is 8192 bytes), the length argument to mmap is not respected.  This results in bus errors when attempting to do any string handling of the returned pointer (e.g., strlen).
Either the man page is wrong ("The length argument specifies the length of the mapping" and "The contents of a file mapping ... are  initialized using length bytes" and "For a file that is not a  multiple  of  the  page  size,  the  remaining memory is zeroed when mapped") or the implementation is wrong.

Reproducible: Always

Steps to Reproduce:
1. Compile and run the attached file: gcc -g test.c -o test ; ./test
Actual Results:  
price@alala:/home/panstarrs/price/temp>./test
Bus error (core dumped)


Expected Results:  
The test program should behave like "cat", dumping the data file to stdout.
Comment 1 Paul Price 2007-10-27 02:57:41 UTC
Created attachment 134462 [details]
Test program to reproduce
Comment 2 Paul Price 2007-10-27 02:58:21 UTC
Created attachment 134464 [details]
Example text file
Comment 3 Joshua Hoblitt 2007-10-27 03:05:53 UTC
The strace of this code is really interesting.  The second mmap() happens when passing either buf.st_size (8192) or buf.st_size + 1 (8193) to mmap(2) in the test example. It looks like glibc is trying to do the right thing by giving us a page of padding but for some reason this page is not being zero'd out and contains garbage.  The results are consistent across multiple kernel revs so it looks like we're getting a recycled page and it's glibc's responsibility to zero it.

open("ppImage.config", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=8192, ...}) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=8192, ...}) = 0
mmap(NULL, 8193, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x2b7fea18c000
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b7fea18f000
Comment 4 SpanKY gentoo-dev 2007-10-27 20:13:39 UTC
the only time you're guaranteed the return from mmap() is zero-ed is when MAP_ANONYMOUS is used ... you are not using MAP_ANONYMOUS

the mmap page states that when a fd is given, the specified file is used to initialize the mapping

i see no mention of a guarantee that a non-anonymous mapping should be zero-filled beyond the length of the file given as backing to the mmap()

really though, i think your mention of different kernels giving different behavior should be a hint ... glibc does not do zeroing *anywhere* with mmap() regardless of flags ... glibc merely passes your request on to the kernel and the kernel takes care of things
Comment 5 Paul Price 2007-10-29 19:04:42 UTC
Please refer to "The Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition" definition of mmap (http://www.opengroup.org/onlinepubs/009695399/functions/mmap.html) where the following statements are made:

* "The mmap() function shall establish a mapping between the address space of the process at an address pa for len bytes to the memory object represented by the file descriptor fildes at offset off for len bytes."

* "while the argument len need not meet a size or alignment constraint, the implementation shall include, in any mapping operation, any partial page specified by the range [pa,pa+len)."

* "The system shall always zero-fill any partial page at the end of an object."

I submit that the glibc implementation of mmap does not result in a mapping "for len bytes".  Instead, it continues only as far as the file size (these two are NOT the same).  This is demonstrated by the attached test program, which attempts to access the mapping at a place between the length of the file and "len", resulting in SIGBUS.  Making "len" larger than the size of the file should result in a mapping valid for "len" bytes (because "len" is not constrained according to the standard).  Moreover, bytes beyond the end of the file should be zero-filled, since they belong to the partial page at the end of an object.  Therefore, this implementation violates POSIX.1b.

Please note further that we have NOT stated that different kernels give different results, but "The results are consistent across multiple kernel revs" (comment 3).

Please reconsider the "INVALID" resolution.
Comment 6 SpanKY gentoo-dev 2007-10-29 19:58:35 UTC
the mmap() manpage does not mention that stipulation.

however, i still dont think it is a glibc problem.  look at the source code and you'll see that glibc merely calls the kernel.

i'm pretty sure the second mmap() in your strace is irrelevant ... it isnt being done on the fd and it's MAP_ANONYMOUS which means there is no guarantee whatsoever as to the location of the mapping.
Comment 7 Joshua Hoblitt 2007-10-29 20:58:11 UTC
This issue is also present in sys-libs/glibc-2.6.1 which is the latest stable glibc for amd64.
Comment 8 Duane Griffin 2007-10-30 02:23:11 UTC
The standard says the mapping will return zeros for any *partial* page following the end of the object. It also says:

"References within the address range starting at pa and continuing for len bytes to whole pages following the end of an object shall result in delivery of a SIGBUS signal."

In other words, if you read past the end of the file but within the final partial page you will get back zeros. If you read past the final partial page you will get a SIGBUS. Of course, in your test example, there isn't a partial page, since the file size is an exact multiple of the page size. Hence the string is unterminated and it blows up.

Here is an old LKML post discussing this, BTW:
http://marc.info/?l=linux-kernel&m=95954229317867&w=2

So I think this bug should indeed be closed as INVALID.

However, see: http://bugs.gentoo.org/show_bug.cgi?id=197483
Comment 9 Paul Price 2007-10-30 02:37:36 UTC
Fair enough, though I thought the standard seemed unclear about where the mapping is supposed to end: pa+len or pa+file_size.
Comment 10 Duane Griffin 2007-10-30 11:19:49 UTC
The mapping itself does end at (pa + len), however it is not accessible beyond the last page containing (pa + file_size).

Just because a memory region is mapped doesn't necessarily make it OK to access :)