To fix this problem, see Nasm segmentation fault on RET in _start for correct ways to make an exit system-call in 32 or 64-bit x86 code for Linux or Windows. (The entry point, _start, isn't a function in Linux; the stack pointer points at argc, not a return address, so ret doesn't work either.) For other ISAs or OSes, check their manuals or look at existing examples for how to exit.
See also What is the correct constant for the exit system call? re: #include <sys/syscall.h> in a .S file to get constants like SYS_exit( or SYS_exit_group) on Unix-like OSes.
Or in assemblers that can't use C headers directly, look for asm/unistd.h on Linux; for x86-64 vs. -32, see unistd_64.h vs. unistd_32.h. (And/or see this Q&A for tools to make .inc files from C headers with just the constants, also useful for getting stuff like O_RDWR or MAP_PRIVATE constants for syscall args.)
If you're using any libc functions, especially printf, you should call exit to flush stdio buffers. Or ret from main, not _start, and let the CRT startup code call exit. See also
As @fuz explains, execution will continue into whatever bytes come next in memory where your executable is loaded / mapped. The CPU doesn't know where your source ended, it just fetches and decodes bytes from memory.
Often there are some zero bytes of padding at the end of the .text section. On 32-bit x86, 00 00 decodes as add [eax], al, a memory-destination add. It's add [rax], al in 64-bit mode. That will fault1 if RAX doesn't point to a writeable page.
RISC-V specifically chose its opcodes so 00 00 00 00 (and 00 00 compressed instructions) would be invalid instructions that fault, definitely not a NOP, so regions of zero-padding can't work as NOP sleds for exploits which send execution nearby instead of exactly to the bytes they want to execute. Some older RISCs do run all-zero bytes as a NOP or non-faulting ALU instruction, but AArch64 similarly avoids this problem with 0000xxxx as udf #imm16, an illegal instruction.
If execution gets past whatever 00 or non-zero garbage bytes are in memory, eventually it'll come to an unmapped or non-executable page. This will also lead to an invalid page fault, just like a data access for a bad pointer, so you also get SIGSEGV on Unix-like systems.
(Or on primitive CPUs without memory protection, the instruction pointer could wrap. e.g. on 8086, code-fetch from CS:IP wraps IP without affecting CS, so execution implicitly loops over a 64KiB region if it's all straight-line code with no jumps.)
If you're curious, run under a debugger and look at disassembly of the faulting instruction, and the hexdump of its machine code in case you recognize it as ASCII data or 00 padding. (Don't put data in the path of execution either.)
Footnote 1: hardware #PF exception -> software SIGSEGV
The x86 CPU exception is #PF, a page fault. The CPU will run the kernel's page-fault handler, which checks whether the process should be allowed to access that virtual address.
If so, it can allocate a new page for copy-on-write or just zeroed, or find an existing page of file data in the pagecache for regions mapped to files. Then set up the page-table entry to "wire" that page into the process's address-space to handle a "minor" or "soft" page fault. Or do I/O to get the page from disk, waking up to wire the page when the I/O is done (major or hard page fault).
But in this case, we're talking about a page the process doesn't have mapped, so it's an invalid page fault. The kernel's page-fault handler will deliver a SIGSEGV segmentation-fault signal if this is a Unix-like OS, or do something similar for other OSes like Windows.
The default action for SIGSEGV signal is killing your process, if it hasn't set up a handler for that signal. Not that you should; unless you know why your process should be raising SIGSEGV, usually that's unrecoverable.
Note that "segmentation fault" is unrelated to x86 segments like CS and DS, because modern OSes use paging for memory protection, not x86 segments. 32-bit or 64-bit process on x86 Linux runs with CS base=0 / limit = unlimited. The name is historical.
add al, [eax]on x86 which will probably fault.00 00decodes asadd [eax], al: memory destination, not memory source, so EAX (or RAX in 64-bit code) has to be pointing at writeable memory, but repeated execution doesn't change the low byte of the address._startisn't a function, there's nothing to return to. You need to make an exit system-call. That Q&A has actual code examples for x86 / x86-64.