Okay. So here's what was happening. I was calling read(), and if it failed, throwing an exception. Apparently, something during exception throw was resetting errno to 0. Silly mistake. Checking errno immediately after the call to read() shows EINVAL.

The man page said that this could be due to unaligned access. But the access is aligned. So something else must be causing it.

Turns out, I was able to read() from regions of memory less than 2 GB, but not > 2 GB (in this case, the stack of another process). That's because I was calling lseek(), which takes an off_t, which is a signed type. So my address was sign-extending and becoming negative.

Oddly lseek() does not report it as an error if you seek before the beginning of the file. I don't know if that's always true.

Solution? Switch over to lseek64() which has enough room in its off64_t for values larger than 2 GB.

I still think there is a bug somewhere, either the kernel or the C library. If errno was being set (and I proved it by making sure it was 0 beforehand), then read() should be returning -1. But it's not, it's returning some value which seems to be "random" yet somehow related to the parameters passed.

If I had been checking for error with:

Code:

if(read(...) < 0)

Then I would have completely missed this problem and probably would have wasted several more hours figuring it out.