The way it works normally is to have a guard page -- virtual memory that is mapped with no access permission -- and then stack probes that poke memory whenever the stack usage grows by a large amount. Then you get a page fault when that probe goes too far, into the guard page.
The probing part should be fine for no_std, but it would be up to you to arrange a mechanism like the guard page for your stack.
What do you mean by "no_std program" ? Typically running a program means defining how it interacts with the environment in some way; and that's the same way you would set up stack protection. Rust, out of the box, doesn't offer a way to compile a "no_std userspace Linux executable".
Embedded devices don't normally have any memory protection or use virtual memory manager, so you won't get a nice segfault like you do when running on an OS.
Depending on where you place the stack in physical memory, you can make sure a stack overflow starts writing over heap memory or global variables, or it could run past the end of RAM and trigger a bus fault (an interrupt you can catch and use to trigger a reboot).
Something else you can do is play with the linker script to set aside some memory immediately after the stack that can be used to detect when a stack overflow has occurred (the purple area in the image below).
Because it's not something injected by the compiler you would still need to check that memory manually every now and then, either explicitly or using a timer+interrupt. If you've heard of stack canaries, this is a very similar concept.
Personally, I'd prefer the bus fault version. Checking if your "stack canary" has been overwritten will add some performance overhead and complication, and the end result will be the same - your device will either reset (in production) or pause and wait for you to attach a debugger. It would also be compatible with stack probes because you get a bus fault when both reading and writing to physical memory that doesn't exist, whereas a canary only detects writes.