mmap does not seem to like both MAP_ANONYMOUS and MAP_SHARED being set, but instead of returning ((void*)-1)/EINVAL, it seg faults. It should just ignore the MAP_SHARED bit, but if it must pay attention, then it should not fault.
Do you happen to have a simple testcase you can attach to this bug?
Subject: Re: mmap seg faults on marginally improper arguments decimal at us dot ibm dot com wrote: > ------- Additional Comments From decimal at us dot ibm dot com 2005-12-12 19:14 ------- > Do you happen to have a simple testcase you can attach to this bug? Not really. Attached is a small library of text_mmap and text_munmap that allows you to mmap a text file and always know you will not seg fault looking for a terminating NUL byte. Lines 162 thru 166 read: pNuls = mmap( (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED, 0, 0 ); Add "MAP_SHARED" to the collection of flag bits and invoke text_mmap() with the first argument naming a file that is a multiple of a page size. The fourth struct argument points to this: typedef struct { void* txt_data; /* text file data */ size_t txt_size; /* actual file size */ size_t txt_full_size; /* mmaped mem size */ int txt_fd; /* file descriptor */ int txt_zero_fd; /* fd for /dev/zero */ int txt_errno; /* warning code */ int txt_prot; /* "prot" flags */ int txt_flags; /* mapping type */ int txt_alloc; /* if we malloced memory */ } tmap_info_t; Do not initialize it before the call. text_mmap opens the file with the open mode being a function of the flags and protection bits. This should be sufficient (on the few failing platforms), as long as the file size is a multiple of the page size: int main(int argc, char** argv) { tmap_info_t map_info; void* ptr = text_mmap(argv[1],PROT_READ|PROT_WRITE,MAP_PRIVATE,&map_info); text_munmap( &map_info ); return 0; } ========= my original email (sorry it was not included in the original bug report) ==================== An issue in my code reveals a bug in mmap(): /lib/libc.so.6.1 Linux/alpha 2.6.13.2 > My mapping descriptor structure shows this: > > (gdb) print *pMI > $2 = {txt_data = 0x2000001c000, txt_size = 8192, txt_full_size = 16384, > txt_fd = 7, txt_zero_fd = -1, txt_errno = 0, txt_prot = 0, txt_flags = 0, > txt_alloc = 0} > > Immediately before this call: > > pNuls = mmap( > (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz, > PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED, 0, 0 ); > > The address passed is: > > (gdb) print 0x2000001c000+8192 > $5 = 0x2000001e000 > > It seg faults instead of returning ((void*)-1). So, the "|MAP_SHARED" dinkleberry is left over from the copied text where I am mapping a real file. Anyway, this call should either work or return ((void*)-1). Not seg fault. Handling a seg fault is painful. This could be a kernel issue, of course, but libc is between my code and there, so I am reporting it to you....Thanks - Bruce /* * $Id: text_mmap.c,v 4.8 2005/12/08 17:54:39 bkorb Exp $ * * Time-stamp: "2005-12-05 13:40:56 bkorb" */ #define FILE_WRITABLE(_prt,_flg) \ ((_prt & PROT_WRITE) && (_flg & (MAP_SHARED|MAP_PRIVATE) == MAP_SHARED)) #define MAP_FAILED_PTR ((void*)MAP_FAILED) /*=export_func text_mmap * private: * * what: map a text file with terminating NUL * * arg: const char*, pzFile, name of the file to map * arg: int, prot, mmap protections (see mmap(2)) * arg: int, flags, mmap flags (see mmap(2)) * arg: tmap_info_t*, mapinfo, returned info about the mapping * * ret-type: void* * ret-desc: The mmaped data address * * doc: * * This routine will mmap a file into memory ensuring that there is at least * one @file{NUL} character following the file data. It will return the * address where the file contents have been mapped into memory. If there is a * problem, then it will return @code{MAP_FAILED} and set @file{errno} * appropriately. * * The named file does not exist, @code{stat(2)} will set @file{errno} as it * will. If the file is not a regular file, @file{errno} will be * @code{EINVAL}. At that point, @code{open(2)} is attempted with the access * bits set appropriately for the requested @code{mmap(2)} protections and flag * bits. On failure, @file{errno} will be set according to the documentation * for @code{open(2)}. If @code{mmap(2)} fails, @file{errno} will be set as * that routine sets it. If @code{text_mmap} works to this point, a valid * address will be returned, but there may still be ``issues''. * * If the file size is not an even multiple of the system page size, then * @code{text_map} will return at this point and @file{errno} will be zero. * Otherwise, an anonymous map is attempted. If not available, then an attempt * is made to @code{mmap(2)} @file{/dev/zero}. If any of these fail, the * address of the file's data is returned, bug @code{no} @file{NUL} characters * are mapped after the end of the data. * * see: mmap(2), open(2), stat(2) * * err: Any error code issued by mmap(2), open(2), stat(2) is possible. * Additionally, if the specified file is not a regular file, then * errno will be set to @code{EINVAL}. * * example: * #include <mylib.h> * tmap_info_t mi; * int no_nul; * void* data = text_mmap( "file", PROT_WRITE, MAP_PRIVATE, &mi ); * if (data == MAP_FAILED) return; * no_nul = (mi.txt_size == mi.txt_full_size); * << use the data >> * text_munmap( &mi ); =*/ void* text_mmap( const char* pzFile, int prot, int flags, tmap_info_t* pMI ) { memset( pMI, 0, sizeof(*pMI) ); #ifdef HAVE_MMAP pMI->txt_zero_fd = -1; #endif pMI->txt_fd = -1; /* * Make sure we can stat the regular file. Save the file size. */ { struct stat sb; if (stat( pzFile, &sb ) != 0) { pMI->txt_errno = errno; return MAP_FAILED_PTR; } if (! S_ISREG( sb.st_mode )) { pMI->txt_errno = errno = EINVAL; return MAP_FAILED_PTR; } pMI->txt_size = sb.st_size; } /* * Map mmap flags and protections into open flags and do the open. */ { int o_flag; /* * See if we will be updating the file. If we can alter the memory * and if we share the data and we are *not* copy-on-writing the data, * then our updates will show in the file, so we must open with * write access. */ if (FILE_WRITABLE(prot,flags)) o_flag = O_RDWR; else o_flag = O_RDONLY; /* * If you're not sharing the file and you are writing to it, * then don't let anyone else have access to the file. */ if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE)) o_flag |= O_EXCL; pMI->txt_fd = open( pzFile, o_flag ); } if (pMI->txt_fd < 0) { pMI->txt_errno = errno; return MAP_FAILED_PTR; } #ifdef HAVE_MMAP /* * * * * WITH MMAP * * * * * */ /* * do the mmap. If we fail, then preserve errno, close the file and * return the failure. */ pMI->txt_data = mmap( NULL, pMI->txt_size, prot, flags, pMI->txt_fd, 0 ); if (pMI->txt_data == MAP_FAILED_PTR) { pMI->txt_errno = errno; goto fail_return; } /* * Most likely, everything will turn out fine now. The only difficult * part at this point is coping with files with sizes that are a multiple * of the page size. Handling that is what this whole thing is about. */ pMI->txt_zero_fd = -1; pMI->txt_errno = 0; { void* pNuls; #ifdef _SC_PAGESIZE size_t pgsz = sysconf(_SC_PAGESIZE); #else size_t pgsz = getpagesize(); #endif /* * Compute the pagesize rounded mapped memory size. * IF this is not the same as the file size, then there are NUL's * at the end of the file mapping and all is okay. */ pMI->txt_full_size = (pMI->txt_size + (pgsz - 1)) & ~(pgsz - 1); if (pMI->txt_size != pMI->txt_full_size) return pMI->txt_data; /* * Still here? We have to append a page of NUL's */ pMI->txt_full_size += pgsz; #if defined(MAP_ANONYMOUS) pNuls = mmap( (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED, 0, 0 ); if (pNuls != MAP_FAILED_PTR) return pMI->txt_data; pMI->txt_errno = errno; #endif #if defined(HAVE_DEV_ZERO) pMI->txt_zero_fd = open( "/dev/zero", O_RDONLY ); if (pMI->txt_zero_fd < 0) { pMI->txt_errno = errno; } else { pNuls = mmap( (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz, PROT_READ, MAP_PRIVATE|MAP_FIXED, pMI->txt_zero_fd, 0 ); if (pNuls != MAP_FAILED_PTR) return pMI->txt_data; pMI->txt_errno = errno; close( pMI->txt_zero_fd ); pMI->txt_zero_fd = -1; } #endif pMI->txt_full_size = pMI->txt_size; } { void* p = AGALOC( pMI->txt_size+1, "file text" ); if (pMI->txt_data == NULL) { pMI->txt_errno = ENOMEM; goto fail_return; } memcpy( p, pMI->txt_data, pMI->txt_size ); ((char*)p)[pMI->txt_size] = NUL; munmap(pMI->txt_data, pMI->txt_size ); pMI->txt_data = p; } pMI->txt_alloc = 1; return pMI->txt_data; #else /* * * * * * no HAVE_MMAP * * * * * */ pMI->txt_data = AGALOC( pMI->txt_size+1, "file text" ); if (pMI->txt_data == NULL) { pMI->txt_errno = ENOMEM; goto fail_return; } { size_t sz = pMI->txt_size; char* pz = pMI->txt_data; while (sz > 0) { ssize_t rdct = read( pMI->txt_fd, pz, sz ); if (rdct <= 0) { pMI->txt_errno = errno; fprintf( stderr, zFSErrReadFile, errno, strerror( errno ), pzFile ); free( pMI->txt_data ); goto fail_return; } pz += rdct; sz -= rdct; } *pz = NUL; } /* * We never need a dummy page mapped in */ pMI->txt_zero_fd = -1; pMI->txt_errno = 0; return pMI->txt_data; #endif /* * * * * * no HAVE_MMAP * * * * * */ fail_return: if (pMI->txt_fd >= 0) { close( pMI->txt_fd ); pMI->txt_fd = -1; } errno = pMI->txt_errno; pMI->txt_data = MAP_FAILED_PTR; return pMI->txt_data; } /*=export_func text_munmap * private: * * what: unmap the data mapped in by text_mmap * * arg: tmap_info_t*, mapinfo, info about the mapping * * ret-type: int * ret-desc: -1 or 0. @file{errno} will have the error code. * * doc: * * This routine will unmap the data mapped in with @code{text_mmap} and close * the associated file descriptors opened by that function. * * see: munmap(2), close(2) * * err: Any error code issued by munmap(2) or close(2) is possible. =*/ int text_munmap( tmap_info_t* pMI ) { #ifdef HAVE_MMAP int res = 0; if (pMI->txt_alloc) { /* * IF the user has write permission and the text is not mapped private, * then write back any changes. Hopefully, nobody else has modified * the file in the mean time. */ if ( ((pMI->txt_prot & PROT_WRITE) != 0) && ((pMI->txt_flags & MAP_PRIVATE) == 0)) { if (lseek( pMI->txt_fd, 0, SEEK_SET) != 0) goto error_return; res = (write( pMI->txt_fd, pMI->txt_data, pMI->txt_size ) < 0) ? errno : 0; } AGFREE( pMI->txt_data ); errno = res; } else { res = munmap( pMI->txt_data, pMI->txt_full_size ); } if (res != 0) goto error_return; res = close( pMI->txt_fd ); if (res != 0) goto error_return; pMI->txt_fd = -1; errno = 0; if (pMI->txt_zero_fd != -1) { res = close( pMI->txt_zero_fd ); pMI->txt_zero_fd = -1; } error_return: pMI->txt_errno = errno; return res; #else /* HAVE_MMAP */ errno = 0; /* * IF the memory is writable *AND* it is not private (copy-on-write) * *AND* the memory is "sharable" (seen by other processes) * THEN rewrite the data. */ if ( FILE_WRITABLE(pMI->txt_prot, pMI->txt_flags) && (lseek( pMI->txt_fd, 0, SEEK_SET ) >= 0) ) { write( pMI->txt_fd, pMI->txt_data, pMI->txt_size ); } close( pMI->txt_fd ); pMI->txt_fd = -1; pMI->txt_errno = errno; free( pMI->txt_data ); return pMI->txt_errno; #endif /* HAVE_MMAP */ } /* * Local Variables: * mode: C * c-file-style: "stroustrup" * tab-width: 4 * indent-tabs-mode: nil * End: * end of autoopts/text_mmap.c */
That's no test case. Send a self-contained piece of code which can be executed. There is no indicated whatsoever that anything is wrong in glibc. mmap is nothing but a simple syscall wrapper. I'll close the bug unless we get a test case which shows the problem.
I do not have an Alpha platform and the problem does not exist on my Linux/i386 platform. I was pretty sure at the time that I wrote this report that the cause was more likely to be in the kernel than in glibc. Leastwise, I was guessing that the mmap code for Linux/Alpha was not going to be much different from any other platform. If that code is not any different, then some variation in kernel behavior triggered the fault. Is that a kernel problem or a glibc problem? I don't know. I don't want to be in the middle. #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <stdint.h> #include <unistd.h> typedef struct { void* txt_data; /* text file data */ size_t txt_size; /* actual file size */ size_t txt_full_size; /* mmaped mem size */ int txt_fd; /* file descriptor */ int txt_zero_fd; /* fd for /dev/zero */ int txt_errno; /* warning code */ int txt_prot; /* "prot" flags */ int txt_flags; /* mapping type */ int txt_alloc; /* if we malloced memory */ } tmap_info_t; int main(int argc, char** argv) { { int fd = open( "/tmp/FOO", O_CREAT | O_WRONLY, 0666 ); long sz = sysconf( _SC_PAGESIZE ); char z[ 16 ]; if (fd < 0) exit(1); while (sz > 0) { sprintf( z, "0x%4X..", sz ); write( fd, z, 8 ); sz -= 8; } close(fd); } { tmap_info_t mi; void* ptr = text_mmap( "/tmp/FOO", PROT_READ|PROT_WRITE, MAP_PRIVATE, &mi ); fputs( "This platform does not fail\n", stdout ); text_munmap( &mi ); } return 1; }
Subject: Re: mmap seg faults on marginally improper arguments We don't know how your text_mmap() does to work. On Dec 24, 2005, at 8::32::56GMT+04:00, bkorb at gnu dot org wrote: > > ------- Additional Comments From bkorb at gnu dot org 2005-12-24 > 16:32 ------- > I do not have an Alpha platform and the problem does not exist > on my Linux/i386 platform. I was pretty sure at the time that > I wrote this report that the cause was more likely to be in the > kernel than in glibc. Leastwise, I was guessing that the mmap > code for Linux/Alpha was not going to be much different from > any other platform. If that code is not any different, then some > variation in kernel behavior triggered the fault. Is that a > kernel problem or a glibc problem? I don't know. I don't want > to be in the middle. > > > > #include <sys/types.h> > #include <sys/mman.h> > #include <sys/stat.h> > > #include <stdio.h> > #include <fcntl.h> > #include <stdint.h> > #include <unistd.h> > > typedef struct { > void* txt_data; /* text file data */ > size_t txt_size; /* actual file size */ > size_t txt_full_size; /* mmaped mem size */ > int txt_fd; /* file descriptor */ > int txt_zero_fd; /* fd for /dev/zero */ > int txt_errno; /* warning code */ > int txt_prot; /* "prot" flags */ > int txt_flags; /* mapping type */ > int txt_alloc; /* if we malloced memory */ > } tmap_info_t; > > int > main(int argc, char** argv) > { > { > int fd = open( "/tmp/FOO", O_CREAT | O_WRONLY, 0666 ); > long sz = sysconf( _SC_PAGESIZE ); > char z[ 16 ]; > > if (fd < 0) exit(1); > while (sz > 0) { > sprintf( z, "0x%4X..", sz ); > write( fd, z, 8 ); > sz -= 8; > } > close(fd); > } > > { > tmap_info_t mi; > void* ptr = > text_mmap( "/tmp/FOO", PROT_READ|PROT_WRITE, MAP_PRIVATE, &mi ); > fputs( "This platform does not fail\n", stdout ); > text_munmap( &mi ); > } > > return 1; > } > > -- > > > http://sourceware.org/bugzilla/show_bug.cgi?id=2007 > > ------- You are receiving this mail because: ------- > You are on the CC list for the bug, or are watching someone who is.
Subject: Re: mmap seg faults on marginally improper arguments The function was included in a previous message. Fundamentally, it mmaps a given file. If that file size is a multiple of a page size, then it does a one page anonymous map for the next page, thus assuring that the str*() functions won't seg fault looking for the terminating NUL byte. The original anonymous mapping call: pNuls = mmap( (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED, 0, 0 ); works fine on most all platforms. On Linux/Alpha it seg faults. OK responses are either successful or (void*)-1 (i.e. MAP_FAILED). The latter is okay because anonymous and shared conflict. So, I've modified the code below to simply make the two calls instead of using the library I provided before. Be forwarned: I've only tested the failure using my "text_mmap" function, not the following code. "It ought to behave the same." YMMV. mv at binarysec dot com wrote: > ------- Additional Comments From mv at binarysec dot com 2005-12-25 17:52 ------- >>#include <sys/types.h> >>#include <sys/mman.h> >>#include <sys/stat.h> >> >>#include <stdio.h> >>#include <fcntl.h> >>#include <stdint.h> >>#include <unistd.h> >> >>int >>main(int argc, char** argv) >>{ >> long sz = sysconf( _SC_PAGESIZE ); >> { >> int fd = open( "/tmp/FOO", O_CREAT | O_WRONLY, 0666 ); >> char z[ 16 ]; >> >> if (fd < 0) exit(1); >> while (sz > 0) { >> sprintf( z, "0x%4X..", sz ); >> write( fd, z, 8 ); >> sz -= 8; >> } >> close(fd); >> } >> >> { >> int fd = open( "/tmp/FOO", O_RDONLY ); >> void* ptr = mmap( NULL, sz, fd, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0 ); >> (void)mmap( (void*)(((long)ptr) + sz), sz, -1, >> PROT_READ|PROT_WRITE, >> MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED, 0, 0 ); >> fputs( "This platform does not fail\n", stdout ); >> } >> >> return 1; >>}
I think you have a programming error. You can't use MAP_ANON with MAP_PRIVATE or MAP_SHARED. MAP_ANON, MAP_PRIVATE or MAP_SHARED *must* be used distinctly. MAP_ANON : No file MAP_PRIVATE : Read sync MAP_SHARED : Read sync + write sync (when you write datas in the mem map the kernel will sync asap the mem content into the file... You can force the write operation with sync()). When i see MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED i say to me that you miss understanding the mmap() behavior (no offence). And as Ulrich Drepper said mmap() is just a syscall wrapper so the return value is handled by kernel devels so you may ask the kernel ml. I suggest to you to make this bug INVALID. mv-
Subject: Re: mmap seg faults on marginally improper arguments mv at binarysec dot com wrote: > When i see MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED i say to me that you miss understanding the > mmap() behavior (no offence). When I see this response, it seems like the bug report was not carefully read. I completely acknowledge the fact that the input flags are inconsistent. The issue is whether or not it is appropriate for mmap to segfault because of that, or if it should return ((void*)-1). Segfaults with often incomprehensible stack traces are much harder to figure out than an EINVAL error return. I've fixed my code and I promise I will never make another cut-n-paste error ever again. ;-) Meanwhile, it would be nice to future typo-prone users to make the error response more friendly than a seg fault. Thanks - Bruce
Okay i get you. As i said a small kernel patch can be done to fix the return value. The problem is that many free software use MAP_ANON with MAP_PRIVATE or with MAP_SHARED (Apache for example). Thus the patch risk to prevent the good behavior of these software. So the patch have to wait maybe the version 2.7 or 2.8 of the linux kernel. You may ask kernel devels.
Please report this to the Linux kernel folks. Since glibc has just a wrapper, we cannot do anything here.
I am reopening the bug for reconsideration. It may be the case that invalid args were not the trigger for the fault. Consider this profile for mmap in glibc: extern void *mmap (void *__addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset) __THROW; against this implementation (from Linux sources for alpha): asmlinkage unsigned long osf_mmap(unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long fd, unsigned long off) I do not know if the arguments wind up getting placed correctly anyway, but they certainly do not match for the alpha platform. There should be an alpha-specific profile for the function that matches the implementation. I would like to know if this could be the cause of continued seg faults, even with corrected calling flag values. Thanks!
THe parameter types used in the kernel are almost always different. By design.