On the contrary, it is some special magic of getContents. getContents is ultimately implemented in terms of lazyRead, which itself is implemented using unsafeInterleaveIO. That’s the magic. I’d say it’s black magic, and to be completely avoided.
The rest of your description is correct @Torinthiel. The thing that’s special in the case of getContents (and other forms of lazy I/O) is the black magic that permits the compiler to do I/O during “asking” (as you call “evaluating”) a thunk (which should be a pure operation). That’s not technically wrong, but it is very, very confusing. It comes from an era where “lazy all the things” was the hot topic in Haskell. It was worth exploring but ultimately failed.
I recommend not trying to understand lazy I/O, ignore everything that uses it, completely forget it exists, and use proper streaming abstractions instead (pipes/conduit/streaming/streamly/Bluefin).