Redemption Steps

One of the first steps toward redemption is to understand how to correctly write reentrant code. Even if you dont think the application will be running in a threaded environment, if people ever try to port the application, or overcome application hangs by using multiple threads, theyll appreciate it when you dont program with side effects. One portability consideration is that Windows doesnt properly implement fork(), creating new processes under Windows is very expensive, and creating new threads is very cheap.

While the choice of using processes or threads varies depending on the operating system you choose, and the application, code that doesnt depend on side effects will be more portable and much less prone to race conditions.

If youre trying to deal with concurrent execution contexts, whether through forked processes or threads, you need to carefully guard against both the lack of locking shared resources, and incorrectly locking resources. This subject has been covered in much more detail elsewhere, so well only deal with it briefly here. Things to consider:

  • If your code throws an unhandled exception while holding a lock, youll deadlock any other code that requires the lock. One way out of this is to turn the acquisition and release of the lock into a C++ object so that as the stack unwinds, the destructor will release the lock. Note that you may leave the locked resource in an unstable state; in some cases, it may be better to deadlock than to continue in an undefined state.

  • Always acquire multiple locks in the same order, and release them in the opposite order from how they were acquired . If you think you need multiple locks to do something, think for a while longer. A more elegant design may solve the problem with less complexity.

  • Do as little while holding a lock as possible. To contradict the advice of the previous bullet point, sometimes multiple locks can allow you to use a fine level of granularity, and actually reduce the chance of a deadlock and substantially improve the performance of your application. This is an art, not a science. Design carefully, and get advice from other developers.

  • Do not ever depend on a system call to complete execution before another application or thread is allowed to execute. System calls can range anywhere from thousands to millions of instructions. Since its wrong to expect one system call to complete, dont even start to think that two system calls will complete together.

If youre executing a signal handler or exception handler, the only really safe thing to do may be to call exit(). The best advice weve seen on the subject is from Michal Zalewskis paper, Delivering Signals for Fun and Profit: Understanding, Exploiting and Preventing Signal-Handling Related Vulnerabilities:

  • Use only reentrant-safe libcalls in signal handlers. This requires major rewrites of numerous programs. Another half-solution is to implement a wrapper around every insecure libcall used, having special global flag checked to avoid reentry.

  • Block signal delivery during all nonatomic operations and/or construct signal handlers in the way that would not rely on internal program state (for example, unconditional setting of specific flag and nothing else).

  • Block signal delivery in signal handlers.

In order to deal with TOCTOU issues, one of the best defenses is to create files in places where ordinary users do not have write access. In the case of directories, you may not always have this option. When programming for Windows platforms, remember that a security descriptor can be attached to a file (or any other object) at the time of creation. Supplying the access controls at the time of creation eliminates race conditions between creation and applying the access controls. In order to avoid race conditions between checking to see if an object exists and creating a new one, you have a couple of options, depending on the type of object. The best option, which can be used with files, is to specify the CREATE_NEW flag to the CreateFile API. If the file exists, the call will fail. Creating directories is simpler: all calls to CreateDirectory will fail if the directory already exists. Even so, there is an opportunity for problems. Lets say that you put your app in C:\Program Files\MyApp, but an attacker has already created the directory. The attacker will now have full control access to the directory, which includes the right to delete files within the directory, even if the file itself doesnt grant delete access to that user . The API calls to create several other types of objects do not allow passing in a parameter to determine create new versus open always semantics, and these APIs will succeed but return ERROR_ ALREADY_EXISTS to GetLastError. The correct way to deal with this if you want to ensure that you do not open an existing object is to write code like this:

 HANDLE hMutex = CreateMutex(...args...); if(hMutex == NULL)  return false; if(GetLastError() == ERROR_ALREADY_EXISTS) {  CloseHandle(hMutex);  return false; } 


19 Deadly Sins of Software Security. Programming Flaws and How to Fix Them
Writing Secure Code
ISBN: 71626751
EAN: 2147483647
Year: 2003
Pages: 239

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net