Do posix functions like getpeername or getsockname completely rewrite their buffers?
No. The standard says:
The getpeername() function shall retrieve the peer address of the specified socket, store this address in the sockaddr structure pointed to by the address argument, and store the length of this address in the object pointed to by the address_len argument.
What it does hence is store something at address address. Not more. (doing more would not only be wasted time, imho, but also a breach of contract. "I store N bytes at A" means that the byte at A+N+1 is untouched, to me.)
While I can understand this helps the functions to understand the type such as casting …
struct sockaddr contains (at least) the the two fields sa_family_t sa_family and char sa_data[] Socket address (variable-length data). In other words, the size of the C struct is impossible to know intrinsically. You need to know the length of of either the variable-length sa_data array or the overall size (including that).
So, I'm technically of the opinion that getpeername and co should never change more than the updated address_len bytes (anything else would be in conflict with "don't have side effects you don't declare").
Practically, you pass a buffer and its size to a function that modifies it. Don't rely on anything not happening within that length in that buffer.
Here's a short code example where I am reusing the buffer across multiple socket read calls under the assumption that the implementations fully zeroize the buffer before writing to it so that I am not reading corrupt data
You never read corrupt data, because you only read min(address_len, sizeof(addr)) bytes, and address_len gets explicitly filled in by getpeername. This problem hence doesn't really occur, unless you make massive mistakes when handling the "output" of that function by ignoring the length it declared it worked on!
Checking the return code to be 0 is not sufficient: you also need to check that the filled in (sockaddr)addr.sa_family is as you expect it to be, and that the value of len == sizeof(sockaddr_in).
Generally, this isn't 1975. Don't reuse objects unless it really makes sense to. Your compiler knows how long an object is used – it will not even use extra stack space to just use a new object of the desired type when you need it, if the previous one isn't used anymore. Furthermore, we're talking of objects that are 32 bytes in length or shorter. I can't think of any case where the additional memory usage would even be close to mattering, considering the code you use that memory with does multiple syscalls, which does much (as in: 3 orders of magnitude) worse things to your caches.
Rant: The sockaddr.* type family is a nightmare, and so is the getsockname and friends API; you can't write provably correct C code that uses these without doing extra copies. Why? Because this assumes that pointers to structs of different types but starting with the same elements can be cast around arbitrarily, which is wrong. C, since 1989 (that's 36 years!) says (in C89, §3.3 EXPRESSIONS) that you cannot just access fields of a sockaddr_in that is just a pointercast of sockaddr. It's simply undefined behaviour to do so (and there's good reasons for that; by ensuring that, the compiler can assume that objects of different types don't live in the same memory, which makes a a whole lot of errors impossible and a whole lot of optimizations possible (with strict aliasing enabled: 30% faster on arm, 70% faster on x86_64!)).
The only way around here would be to memcpy the sockaddr filled in by getsockname to a sockaddr_in, and then access the fields of that. In fact, that's what you should do here. And it's stupid that it's necessary; a better-designed getsockaddr would take the desired address family as separate argument, which, well, would even be more efficient to check against, and would take a void* argument that it guarantees to memcpy (or equivalent) construct the reply to request for information at. Or, and quite frankly, POSIX would acknowledge that this is an address family-specific function and having the same function for UNIX domain, IP, IPv6, X.25 links, IPX, AS numbers (whyever these are an address family??) and things like BGP transports.
The good news is that this is coding-wise an extra copy only. Functionally, your compiler sees what you're doing here – copying from one struct to a different one. And if, by chance, the structs are "compatible" in all the fields you might access afterwards, it elides that copy. (Which, by the way, is an awesome ability only made possible by the strict aliasing rules mentioned above.) In the getsockname and sockaddr.* case, that avoidance of an actual copy will happen, because that "compatible by chance" has been forced by developers of operating systems and standards libraries making sure manually that they are compatible (by padding fields, and restricting themselves to "strange" types).