Ha, this is a classic. e.lund has the cause. I'll add the part that explains your "out of memory" message.
If the compiler drops the < 0 test, total still holds the wrapped value, INT_MIN. Then malloc(total) converts it to size_t. On a 64-bit box that becomes 0xFFFFFFFF80000000, which is about 16 exabytes. malloc says no, and your OOM branch fires. So malloc isn't odd. It's just being asked for something absurd. (Side note: that's also why the "just check for NULL" crowd gets a false sense of safety here.)
My question is about the declarations. What types are count and rec_size? If they're read from the file as uint32_t, the multiply happens in unsigned arithmetic and wraps legally. Then the int total assignment is implementation-defined, not UB. That would change the story a lot.
Have you tried -fsanitize=undefined at -O2? It should flag the multiply directly. And does -fwrapv make the two builds agree again?