tags:

views:

388

answers:

5

I have a program written in C++ which uses dlopen to load a dynamic library (Linux, i386, .so). When the library file is subsequently modified, my program tends to crash. This is understandable, since presumably the file is simply mapped into memory.

My question is: other than simply creating myself a copy of the file and dlopening that, is there way for me to load a shared object which is safe against subsequent modifications, or any way to recover from modifications to a shared object that I have loaded?

Clarification: The question is not "how can I install a new library without crashing the program", it is "if someone who I don't control is copying libraries around, is it possible for me to defend against that?"

+14  A: 

If you rm the library prior to installing the new one, I think your system will keep the inode allocated, the file open, and your program running. (And when your program finally exits, then the mostly-hidden-but-still-there file resources are released.)

Update: Ok, post-clarification. The dynamic linker actually completely "solves" this problem by passing the MAP_COPY flag, if available, to mmap(2). However, MAP_COPY does not exist on Linux and is not a planned future feature. Second best is MAP_DENYWRITE, which I believe the loader does use, and which is in the Linux API, and which Linux used to do. It errors-out writes while a region is mapped. It should still allow an rm and replace. The problem here is that anyone with read-access to a file can map it and block writes, which opens a local DoS hole. (Consider /etc/utmp. There is a proposal to use the execute permission bit to fix this.)

You aren't going to like this, but there is a trivial kernel patch that will restore MAP_DENYWRITE functionality. Linux still has the feature, it just clears the bit in the case of mmap(2). You have to patch it in code that is duplicated per-architecture, for ia32 I believe the file is arch/x86/ia32/sys_ia32.c.

asmlinkage long sys32_mmap2(unsigned long addr, unsigned long len,
                            unsigned long prot, unsigned long flags,
                            unsigned long fd, unsigned long pgoff)
{
        struct mm_struct *mm = current->mm;
        unsigned long error;
        struct file *file = NULL;

        flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); // fix this line to not clear MAP_DENYWRITE

This should be OK as long as you don't have any malicious local users with credentials. It's not a remote DoS, just a local one.

DigitalRoss
This is correct.
David Joyner
+6  A: 

If you install a new version of the library, the correct procedure is to create a new file in the same directory, then rename it over the old one. The old file will remain while it's open, and continue to be used.

Package managers like RPM do this automatically - so you can update shared libraries and executables while they're running - but the old versions keep running.

In the case where you need to take a new version, restart the process or reload the library - restarting the process sounds better - your program can exec itself. Even init can do this.

MarkR
+1  A: 

If you can figure out where your library is mapped into memory, then you might be able to mprotect it writeable and do a trivial write to each page (e.g. read and write back the first byte of each page). That should get you a private copy of every page.

If 'mprotect' doesn't work (it may not, the original file was probably opened read-only), then you can copy the region out to another location, remap the region (using mmap) to a private, writeable region, then copy the region back.

I wish the OS had a "transform this read-only region to a copy-on-write region". I don't think something like that exists, though.

In any of these scenarios, there is still a window of vulnerability - someone can modify the library while dlopen is calling initializers or before your remap call happens. You're not really safe unless you can fix the dynamic linker like @DigitalRoss describes.

Who is editing your libraries out from under you, anyway? Find that person and hit him over the head with a frying pan.

Keith Randall
Thanks for the suggestions -- unfortunately I get in a lot of trouble if I frying-pan people too often :-)
kdt
A: 

This is an intriguing question. I hate finding holes like this in Linux, and love finding ways to fix them.

My suggestion is inspired by the @Paul Tomblin answer to this question about temporary files on Linux. Some of the other answers here have suggested the existence of this mechanism, but have not described a method of exploiting it from the client application as you requested.

I have not tested this, so I have no idea how well it will work. Also, there may be minor security concerns associated with a race condition related to the brief period of time between when the temporary file is created and when it is unlinked. Also, you already noted the possibility of creating a copy of the library, which is what I am proposing. My twist on this is that your temporary copy exists as an entry in the file system for only an instant, regardless of how long you actually hold the library open.

When you want to load a library follow these steps:

  1. copy the file to a temporary location, probably starting with mkstemp()
  2. load the temporary copy of the library using dlopen()
  3. unlink() the temporary file
  4. proceed as normal, the file's resources will be automatically removed when you dlclose()

It would be nice if there were a really easy way to achieve the "copy the file" step without requiring you to actually copy the file. Hard-linking comes to mind, but I don't think that it would work for these purposes. It would be ideal if Linux had a copy-on-write mechanism which was as easy to use as link(), but I am not aware of such a facility.

Edit: The @Zan Lynx answer points out that creating custom copies of dynamic libraries can be wasteful if they are replicated into multiple processes. So my suggestion probably only makes sense if it is applied judiciously -- to only those libraries which are at risk of being stomped (presumably a small subset of all libraries which does not include files in /lib or /usr/lib).

nobar
+3  A: 

It is not possible to defend against someone overwriting your library if they have file write permission.

Because dlopen memory maps the library file, all changes to the file are visible in every process that has it open.

The dlopen function uses memory mapping because it is the most memory efficient way to use shared libraries. A private copy would waste memory.

As others have said, the proper way to replace a shared library in a Unix is to use unlink or rename, not to overwrite the library with a new copy. The install command will do this properly.

Zan Lynx