I am writing sandboxing code for Google Chrome that has the potential to trigger execution of a hook function on any arbitrary system call that the program makes. Inside of the hook function, it performs a read-only operation on a global data structure that describes some of the sandboxing policies. This works fine once sandbox initialization has completed, but it requires special care when initially setting up this data structure as we never want to present invalid data (e.g. use after free) to the hook function. And as system calls are somewhat out of the scope of the C++ language specification, we have to assume that they can happen at (almost) any time.<div>
<br></div><div>A very simplified version of our code looks like this:</div><div><br></div><div><div><font face="courier new, monospace">#include <string.h></font></div><div><font face="courier new, monospace"><br></font></div>
<div><font face="courier new, monospace">// We have some low-level code that can trigger on any system call and that</font></div><div><font face="courier new, monospace">// performs a read-only access to "data". It is guaranteed to only ever access</font></div>
<div><font face="courier new, monospace">// indices between [0..n_data) and it doesn't need to look at "n_data". So, it</font></div><div><font face="courier new, monospace">// is OK if a) "data" points to either new or old memory locations as long as</font></div>
<div><font face="courier new, monospace">// they contain valid data, and b) the value of "n_data" does not need to be</font></div><div><font face="courier new, monospace">// consistent.</font></div><div><font face="courier new, monospace">// We are not concerned about multi-threaded code. Our code is guaranteed to be</font></div>
<div><font face="courier new, monospace">// single-threaded at this time.</font></div><div><font face="courier new, monospace">// We are also not concerned about asynchronous signals. Our code will only ever</font></div>
<div>
<font face="courier new, monospace">// execute synchronously as a direct result of a system call.</font></div><div><font face="courier new, monospace">static char *data;</font></div><div><font face="courier new, monospace">static size_t n_data;</font></div>
<div><font face="courier new, monospace"><br></font></div><div><font face="courier new, monospace">void AddToData(char ch) {</font></div><div><font face="courier new, monospace"> // Copy data from "data" to "new_data". Don't use realloc() as it could be</font></div>
<div><font face="courier new, monospace"> // making system calls at unexpected times.</font></div><div><font face="courier new, monospace"> // Then switch pointers without</font><span style="font-family:'courier new',monospace"> making any interleaving system calls.</span></div>
<div><font face="courier new, monospace"> char *old_data = data;</font></div><div><font face="courier new, monospace"> char *new_data = new char[n_data + 1];</font></div><div><span style="font-family:'courier new',monospace"> memcpy(new_data, data, n_data);</span></div>
<div><font face="courier new, monospace"><br></font></div><div><font face="courier new, monospace"> // Do we need a barrier here to make sure memcpy() has completed before</font></div><div><font face="courier new, monospace"> // we switch the pointer in "data"?</font></div>
<div><font face="courier new, monospace"> data = new_data;</font></div><div><font face="courier new, monospace"> data[n_data++] = ch;</font></div><div><font face="courier new, monospace"><br></font></div><div><font face="courier new, monospace"> // We are worried about the compiler moving the deletion of "old_data" prior</font></div>
<div><font face="courier new, monospace"> // to the assignment of "data". As it doesn't know about us trapping all</font></div><div><font face="courier new, monospace"> // system calls, it could very conceivably reason that "delete[]" doesn't</font></div>
<div><font face="courier new, monospace"> // have global side-effect and can thus be moved prior to our assignment.</font></div><div><font face="courier new, monospace"> // We add a optimization barrier to prevent this from happening.</font></div>
<div><font face="courier new, monospace"> // Is this the correct type of barrier?</font></div><div><font face="courier new, monospace"> asm volatile("" : "=r"(data) : "0"(data) : "memory");</font></div>
<div><font face="courier new, monospace"> delete[] old_data;</font></div><div><font face="courier new, monospace">}</font></div><div><font face="courier new, monospace"><br></font></div><div><font face="courier new, monospace">int main(int argc, char *argv[]) {</font></div>
<div><font face="courier new, monospace"> AddToData('X');</font></div><div><font face="courier new, monospace"> return 0;</font></div><div><font face="courier new, monospace">}</font></div></div><div><br></div>
<div>
I realize that we are operating somewhat outside of the guarantees that the language can give us. But we want to make sure we defensively write our code so that it is unlikely to trigger unintended compiler optimizations. Please also note that we have not actually observed any re-ordering that would conflict with the intention of our code; but if we can give hints to the compiler that allow it to better deduce our intentions, then that's what we want to do.</div>
<div><br></div><div>This code is not actually performance critical; so, aggressively disabling optimizations in favor of correctness is fine with us.</div><div><br></div><div>The use of non-portable assembly would be acceptable, but is not our first preference. Using compiler extensions in more recent compilers are also possible, but we have to have a fallback plan for when we compile with older versions of clang or gcc. Maybe, we can just rely on the fact, that their optimizers were sufficiently conservative, that we don't need to prevent reordering.</div>
<div><br></div><div><br></div><div>Markus</div><div><br></div>