Daniel Bittman

Github
Twitter

The Magically Changing errno

A while ago, I wrote a post complaining about GCC changing my memset function into a recursive call to itself. I found that very funny. So, I messed around with this stuff a bit, and found something even funnier.

If you have a simple loop to zero a set of values, you may think that it's essentially equivalent to a call to memset. But it isn't. GCC will commonly convert such a loop into a memset call for various optimization reasons. But they're both just zeroing memory, so what's the problem? Say you call some library function and you have a value in errno that you care about. You then loop over an array, zeroing each element. You wouldn't have expected the value of errno to change over the course of that loop - after all, you never mention it directly...

...but can it change anyway? This optimization into a call to memset could pose a problem, because as it turns out, a small detail in the C99 standard allows the C library to change the value of errno during a call to memset/memcpy/etc. I wrote a small proof-of-concept that shows how errno could change without a program mentioning it directly nor calling any library functions that would change it.

Proof! Because that's important. Here is a replacement memset function which can be compiled into a shared library.

		void *memset(void *s, int c, size_t n)
		{
			fprintf(stderr, "replacement memset called\n");
			unsigned char *ptr = s;
			while(n--) {
				*(ptr++) = (unsigned char)c;
			}
			errno = 99;
			return s;
		}

			

...and here is a simple program that demonstrates the issue.

		void manipulate_array(char *buffer, char c, int n)
		{
			errno = 0;
			fprintf(stderr, "errno value: %d\n", errno);
			while(n--) {
				*buffer++ = c;
				// other code here maybe
			}
			fprintf(stderr, "new value of errno: %d\n", errno);
		}
		int main(int argc, char **argv)
		{
			char buffer[1024];
			manipulate_array(buffer, 0, 1024);
			return 0;
		}
			

Put together:

# LD_PRELOAD=./memset_errno.so ./test
errno value: 0
replacement memset called
new value of errno: 99
Ha. Funny how that goes, cheeky little errno changing itself around without me even touching it! Yeah, yeah, I know, I change it in my memset replacement. But that's not me! That's totally the standard library!

Practicality?

So, turns out none of the C libraries I looked at (glibc, newlib, dietlibc, etc) do this. I mean, why would they - changing errno inside memset is completely insane. What's memset gonna do, fail? The only real error condition would result in a segfault anyway. So, whatever, right?

It's not like gcc claims full compliance with the standard anyway. Furthermore, a bug report was filed where they discussed some of the implications of this. Gcc emits calls to memcpy and friends to deal with structs anyway, so if a C library decides to mess with errno, it's totally possible for code to break.

Again, this isn't considered a bug. This one, which stems from the same issue that caused my memset function to become recursive, has a worse side effect in a way - silent changes to a variable. In practice, it doesn't matter, because in practice, this doesn't ever happen. But we should not have to settle for that unsatisfying answer.

posted 2015-06-16 by Daniel Bittman (send me an email or follow me on twitter!)