This may be a dumb question, as I feel like I must be overlooking something fairly obvious, but how do functions like getenv(3) access the environment when my program doesn't have any references to the environment?
Your program doesn't have a reference to the environment, but a whole copy of it.
The command line arguments and environment strings (as they were passed to the
execve(2) system call) are all packed together and copied in the address space of the process .
In a typical implementation , two NULL-terminated lists of pointers to them (representing the argument lists and the environment) are made available on the stack to the entry point of the program (
_start), where the startup code (run before
main()) will point the
char **environ global variable to the beginning of the latter.
getenv(3) function is simply looking through that
environ list and comparing each entry in turn.
When some new entry has to be added to the environment (as with
environ list will be relocated elsewhere.
 On linux, the addresses of the argument list and environment variables are accessible as the 48th and 50th fields of
/proc/PID/stat, see procfs(5).