You also get bugs introduced into a program by changes to shared libraries. I've even seen a vulnerability introduced when glibc was upgraded (glibc changed the direction of memory, and the program was using memcpy on overlapping memory).
The memcpy API says that is Undefined Behavior, that program was never valid. Not much different from bitbanging specific virtual addresses and expecting they never change.
C language makes a difference between Undefined Behavior and Implementation Defined Behavior. In this case it's the former (n1570 section 7.24.2.1).
Any code that invokes UB is not a valid C program, regardless of implemention.
More practically, ignoring the bug that did happen, libc also has multiple implementations of these functions and picks one based on HW it is running on. So even a statically linked glibc could behave differently on different HW. Always read the docs, this is well defined in the standard.
The source code may have had UB, but the compiled program could nevertheless have been bug-free.
> Any code that invokes UB is not a valid C program, regardless of implemention.
I disagree. UB is defined (3.4.3) merely as behavior the standard imposes no requirements upon. The definition does not preclude an implementation from having reasonable behavior for situations the standard considers undefined.
This nuance is very important for the topic at had because many programs are written for specific implementations, not specs, and doing this is completely reasonable.
> You also get bugs introduced into a program by changes to shared libraries. I've even seen a vulnerability introduced when glibc was upgraded (glibc changed the direction of memory, and the program was using memcpy on overlapping memory).
Did glibc change the behavior of memcpy (the ABI symbol) or memcpy (the C API symbol) which currently maps to the memcpy@GLIBC_2.14 ABI symbol?