As for how you do this sort of thing in general, fork/exec from a multithreaded POSIX program is a minefield and you're best off not doing it. But, if you insist, it looks like posix_spawn might help, or you can do your setenv in the child before the exec. setenv isn't async signal-safe, so strictly speaking you're not allowed to do this.
If this turns out actually to be a problem, introduce a second wrapper EXE that you spawn, supplying environment stuff and arguments for subchild EXE. Fork, exec the wrapper, have it do its thing, then have it exec the EXE you actually want (no fork).
I haven't done that for setting the environment, but I have for setting CLOEXEC on file descriptors when doing fork/exec as part of a (large, gnarly, zillions of FDs, tons of threads, closed source) program.
It doesn't have to be that complicated. After the fork, in the child, you can construct your own local environment array, and pass it to execve or one of the other exec functions that takes an environ argument. This is perfectly safe behavior in a multi-threaded program.
Never having needed to do exactly this myself in practice, I'd quite forgotten about the ...e functions.
You build up a the environment in a local variable, fork(), then have the child alter its environment based on the said local variable before it goes on to do useful stuff.
If you're just executing another program, do the same, but call execle() which takes the environment as an argument.
Also, if you want to pass an environment variable to a child process from a specific thread, how do you do that without interfering with other threads that might do the same (alter the same variable to pass it to a different child process)? Would that require an ugly mutex around invoking child processes?