
Now when a new object is allocated there, we end up using the corrupted data and invoking the evil pointer, and the rest is history. Now, as you mention, if calloc didn't zero, an attacker could scribble over what they think will be the next allocated slot in the heap (either by targeting a partially used page or an entirely unused page that's just being kept in malloc's retainer) and write a value into the callback pointer field. In a single threaded application, you would not be able to target the function pointer here because nothing interesting happens between calloc and the invocation since the if check will always fail due to it being zero'd from calloc. It's contrived for simplicity, but suppose obj had a function pointer in it and just after allocation the object is passed to another routine which does some work on the object and checks if the callback exists and calls it if it does. Yeah, more or less! This closes some paths and opens brand new ones, like more of the former and less of the latter.
