Pointer Encoding


Pointer encoding is neither an SDL requirement nor a Windows Vista security quality gate requirement, but we worked very closely with Windows Vista developers to make sure they were educated about the value of pointer encoding. Many long-lived pointers in Windows Vista are encoded to help protect them from becoming buffer-overrun victims.

Technically, pointer encoding isn’t a code quality benefit; it’s a defensive measure only. It’s very common to use pointers in C and C++. In fact, one of the major benefits of C and C++ is that the languages expose pointers to arbitrary memory locations and code can read from and write to arbitrary computer memory. Accessing memory directly is a double-edged sword: it’s powerful, but failure can be catastrophic because this can be a source of buffer-overrun vulnerabilities.

Introduced in Windows Server 2003 SP1 and Windows XP SP2, pointer encoding allows developers to make it harder for an attacker to successfully overwrite a pointer with a valid value. Take a look at the following contrived code:

 class Stuff { public:    Stuff() {       m_dest = new char[32];       m_data = new char[1]; } ~Stuff() {     delete [] m_dest;     delete [] m_data; } const char *WriteData(const char *src) { if (!src) return NULL;    if (m_dest) strcpy(m_dest,src);    if (m_data) *m_data = src[0];    return src; } private:    char *m_dest, *m_data; };

Assuming the attacker controls src when calling the WriteData method, she can overflow m_dest, and in doing so overwrite the m_data pointer (because it’s a few bytes higher in memory) with whatever value she wants. Because the attacker controls src, and therefore m_dest, and because the vulnerable code calls strcpy and overflows m_dest, she controls m_data. The code then writes the first byte of src to m_data, and now the attacker can very effectively write one byte anywhere in memory. This is a classic heap-based buffer overrun (see Figure 9-3). Of course, the code is bad because it uses strcpy, which is banned as we pointed out in Chapter 1, “Code Quality.”

image from book
Figure 9-3: Overflowing a heap-based buffer to corrupt another buffer.

Now take a look at the same contrived C++ class code using pointer encoding.

 class Stuff { public:   Stuff() {      m_dest = (char*)EncodePointer(new char[32]);      m_data = (char*)EncodePointer(new char[1]); } ~Stuff() {      delete [] DecodePointer(m_dest);      delete [] DecodePointer(m_data);      m_data = m_dest = NULL; } const char *WriteData(const char *src) {      if (!src) return NULL;      char *dest = (char*)DecodePointer(m_dest);      if (dest) strcpy(dest,src);      char *data = (char*)DecodePointer(m_data);      if (data) *data = src[0];      return src; } private:      char *m_dest, *m_data; };

This code is very similar to the original code, except that the pointers are deemed long-lived and are encoded as soon as they are created and then decoded prior to use. You will also notice that the class destructor sets the pointers to NULL after the memory is deleted; I’m in the habit of setting free’d pointers to NULL; it can help mitigate exploitable double-free bugs as described in Chapter 3, “Buffer Overrun Defenses.”

Follow these steps when dealing with long-lived pointers:

  1. Allocate memory or initialize the pointer, and assign the pointer to the address.

  2. Encode the pointer.

  3. In the line above the code where the pointer is used, decode the pointer into a temporary variable.

  4. When the pointer is no longer needed, decode the pointer and free it.

  5. Set the pointer to NULL.

Note that DecodePointer won’t fail; it will just give you back a bad pointer. If you’d like to make this really safe, wrap this in a class and keep a reference copy of the pointer. Here’s an example:

 class EncodedPointer { public:    EncodedPointer() : m_pv( NULL ), m_pReference( NULL ){}    void Set( void* pIn ) {       m_pv = EncodePointer( pIn );       m_pReference = MakeRefPtr( pIn ); } void* Get() {    if( m_pv == NULL )       return NULL;    void* pRet = DecodePointer( m_pv );    // Now double-check for tampering    if( MakeRefPtr( pRet ) != m_pReference )       return NULL;    return pRet; } #ifdef _DEBUG    // testing only    void Corrupt() {       m_pv = (void*)((char*)m_pv + 1); } #endif private:    void* MakeRefPtr( void* pIn ){    return reinterpret_cast< void* >(       reinterpret_cast< size_t >( pIn ) ^       reinterpret_cast< size_t >( this ) ); }    void* m_pv;    void* m_pReference; };

The following code shows how to use this simple class:

 char buf[2]; EncodedPointer ePtr; ePtr.Set( buf ); if( ePtr.Get() == NULL )   printf("Ptr is corrupt or NULL\n"); else   printf("Valid!\n");

Before you use this class a great deal, remember that you ought to do some work to implement the copy constructor and assignment operator. If you assigned one of these classes to another instance of the class, it would be invalid because the C++ this pointer changes. What the class does is to make it very unlikely that a corrupted pointer would get returned as anything other than NULL, and dereferencing a null pointer won’t typically be exploitable. This also gives you a way to check for success or failure, or even throw an exception if it does fail.



Writing Secure Code for Windows Vista
Writing Secure Code for Windows Vista (Best Practices (Microsoft))
ISBN: 0735623937
EAN: 2147483647
Year: 2004
Pages: 122

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