7

I have the following summarized pseudo code in C89 from an ssh server library that provide only access to things like git‑shell (/bin/bashis replaced with the program to run, so it’s not possible to do something else) :

struct _raw_uncapped_ssh_string { // no limit on the size of the string;
    uint32_t len;
    char non_null_terminated_string[]; // by protocol design it have a minimum length of 1
};
typedef struct _raw_uncapped_ssh_string raw_ssh_string;
union buffer {
       void * uncapped_zlib_decompressed_network_data; 
       // yes, the size is uncapped, so it’s possible to put 4Gb of
       // data in it that would be copied later into memory. zlib 
       // allow easily to turn some Mb in Gb of data, but it’s not 
       // the point of the question.

       raw_ssh_string st;
};
get_command (compressed_network_data) {
    size_t len;
    char * command;
    buffer string=uncompress_to_buffer(compressed_network_data);
    len=ntohl(string.st.len)+1;
    command=malloc(len+1);
    command[len]=0;

    // here’s the point, both the string length and content as 
    // well it’s supplied size is controlled by the attacker.
    memcpy(command,string.st.data,len); 

    return command;
}

Here’s how the command is executed later (the stringcommandis unchanged afterget_command()).

const char *args[]={"/bin/bash",command,NULL}; // /bin/bash isn’t the shell, it has been replaced by git‑shell.

// redirect the program output to the network.
dup2(stdin, 0);
dup2(stdout,1);
dup2(stdout,2);
close(stdin);
close(stdout);

//if this return execution failed and print an error message
return execv(args[0],(char * const *)args); // I don’t know which is the system, so I can’t know about the libc behaviour.

I can’t domemcpy(command,string.st.data,0)since the third member ofmemcpyhas a minimum size of 1, and in my context,size_tuses a 64 bit integer, I can’t perform a buffer overflow, since there islen.

All I can do is to setlento a value larger than the one allocated tostring.st.data. This is a buffer underflow enabling me to read unallocated memory.
I can read the server memory, however, I can’t see what sensitive data a public ssh server can hold (in my case the list of users that can perform ssh is public).

So does a buffer underflow onmemcpyallow remote code execution ?

  • If the code really does command[len] = 0 then that is a buffer overflow, since the max index for a buffer of length len is len-1. Alternatively, if the actual code does a malloc(len+1) instead of a malloc(len), then you can make a massive buffer overflow by setting the len value to 0xFFFFFFFF. – Thomas Pornin Oct 30 '15 at 15:25
  • @ThomasPornin : No this an error from my part, in fact it calls several function to do this. Assume the remaining is true. – user2284570 Oct 30 '15 at 15:26
  • @ThomasPornin : I’m talking about 64 bits, since len is size_t, it use a 64 bits integer. So there’s no way for integer overflow. – user2284570 Oct 30 '15 at 15:39
  • @ThomasPornin They allocate len+1 so the setting to 0 should be valid. – RoraΖ Oct 30 '15 at 16:06
  • @RoraΖ : He was talking about settingstring.st.lento ‑1. – user2284570 Oct 30 '15 at 16:19
2

In general, no, a buffer underflow cannot be used for remote code execution. Since attacker-controlled data never leaves the space allocated for it, it never has the ability to take over the program's execution flow.

A buffer underflow does have the potential for other types of attacks, such as information disclosure (if the program is counting on the buffer's original contents being wiped out by the new data).

  • Or as I saw recently in git, be turned later into a buffer overflow. – user2284570 Dec 17 '15 at 11:26
2

When I originally wrote this response, it appears I glossed over some misunderstandings that you hold. These misunderstandings would likely prevent you from understanding my answer. To be clear, I'm going to make this text large to draw emphasis, not to be disrespectful:

Many of the terms you use don't mean what you seem to think they mean.

For example:

  • "the third member of memcpy" is non-existent because memcpy is a function, not a struct or union.
  • "I can’t perform a buffer overflow, since there is len" and I can't run out of petrol, since there is petrol. Am I begging the question? It seems like a fitting analogy to me, especially since theoretically speaking, your code could invoke a buffer overflow.
  • "All I can do is to set len to a value larger than the one allocated to string.st.data. This is a buffer underflow..." No, that is not the definition of a buffer underflow. A buffer underflow occurs when you access out-of-bounds an array using a negative index, or an index that would cause pointer arithmetic wrapping to occur. The latter may be possible for some configurations (even on "64-bit systems", whatever that means), but I doubt this is what you meant when you were writing these words, because you then followed them up with:
  • "... enabling me to read unallocated memory." I think perhaps you meant "uninitialised memory". To be clear, I think you meant to state that you've allocated an excess and left the excess uninitialised, and perhaps you want to do something about that (see calloc or memset).

Let us consider char *fubar = NULL; for a moment... this kind of pointer generally has a zero value. Dereferencing it is considered a null pointer dereference, but if we write something like 0["hello"] we get the same as "hello"[0] (that is 'h'). Thus, a null pointer dereference can be used in attacks when the attacker controls the expression between the square braces (as they do in your situation).

Coming back to the fubar thing; let's say we memset(&fubar, UCHAR_MAX, sizeof fubar);, now fubar is all 1-bits, meaning it might be an address that is the largest address our system could (theoretically) accommodate. What if we access fubar[1]? How can you access the element after the largest address? Does the address then wrap back around? Technically, all of this is undefined behaviour, but if I were to name that on common architecture, it would be:

  1. Arithmetic overflow of a pointer, leading into...
  2. Null pointer dereference, and/or potentially a buffer underflow.

does a buffer underflow on memcpy allow remote code execution ?

A buffer underflow may allow an attacker to overwrite function pointers located in the regions of memory before the array in question. If those function pointers are made to point at shellcode, that shellcode may be executed when they're invoked later on.

In this somewhat obscure piece of code, it appears there's a risk of buffer underflow when int has a wider domain than uint32_t, as ntohl(string.st.len)+1 would cause the uint32_t value to be converted to an int type. Consider, for example, if INT_MIN is -4294967296 (which is one less than 0 - UINT32_MAX) and INT_MAX is 4294967295... this would essentially be a 33-bit int with padding to pad out to the width of a byte; uncommon, but possible. In this circumstance, the expression ntohl(string.st.len)+1 is not uint32_t in type; it's int in type, and rather than wrapping back to 0 when unsigned integer overflow occurs, it probably wraps to -4294967296 when signed integer overflow occurs.

If you're looking for a guarantee against buffer underflow, use the U integer literal suffix (i.e. ntohl(string.st.len)+1U). Then in that situation, you'll end up with the expression being either a uint32_t or an unsigned int (depending on which type has the largest domain).

If you consider that ntohl(string.st.len) may return one less than the maximum value for that unsigned type (whatever it is), then len=ntohl(string.st.len)+1 will result in the maximum value, malloc(len+1) will cause unsigned wrapping so you'll end up invoking malloc(0) and then command[len]=0 will write well and truly beyond the end of the array. Then, of course, you'll also have a problem with memcpy(command,string.st.data,len); (this is a buffer overflow).

Buffer underflows and overflows aren't the only risk. If you don't check the return value of malloc, and malloc returns NULL then a null pointer dereference can be used to cause arbitrary code execution. This implies that you should have a method to communicate malloc failures back to the caller. It also implies that you could use that same method to check and communicate that wrapping problem back to the caller.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.