Q. What stops an assembly program from crashing the operating system?
A. Nothing.
However, a lot of very clever programmers have tried very hard over the years to make it more and more difficult. Unfortunately, for every clever programmer, there are many, many others who between them are more creative, more ambitious, and sometimes just luckier then the clever ones. Every single time a clever programmer says that nobody should, would or could do something, someone out there will find a way to do it. Microsoft Windows (as an example) has been around for almost 35 years and we still have BSoD (Blue Screens of Death), which are just instructions that crashed the operating system.
Lets start with a little terminology. Everything that runs on a computer does so in machine code. The bit that reads the keystrokes or the movement of the mouse pointer, the bit that changes the colour of a pixel on the display or reads a byte from a file and the bit that calculates whether your bullet hit the bad guy or the bit that decides if your credit card application will be accepted are all executed as a sequence of machine code instructions. Some jobs are so common and are done so often that it is makes sense to assemble the instructions required to do them and have everybody use that assembly. The bunch of these jobs that allow or help others to use the computer tend to be called the operating system but there is nothing inherently different between them and any other programs. They are all just sequences of machine code instructions. You will hear about protected modes and privilege rings but these are just more sequences of the same machine code telling the computer how to behave.
What makes operating systems more complicated (and therefore prone to crashing) is that they have to account for things that you don't normally have to think about. Take the simplest of jobs as an example. I want to write a message to the end of a file. In a high level language you would write something like:
with open("myFile.txt", "w+") as f:
# do some really clever things
f.write("Goodbye cruel world!")
Lets ignore all the details about how the physical states are accessed and changed or how they are interpreted as bits and bytes or how those bytes are transferred to and from the memory and CPU, and trust that all that is handled by programs that the OS provides behind the scenes. Lets just think about the how you append to the end of a file. 1) Find out where the end of the file is, 2) write something at that position. What could possibly go wrong? Actually, quite a lot. Think about what else is happening on the computer while you are doing clever stuff. If anything else being done by anybody else (including the operating system itself) changes the file you are working on in any way, then this really simple job suddenly gets a whole lot more complicated. The file is longer, the file is shorter. The file is not there anymore. The disk is full, the disk would be full if all the bytes are written, the disk is locked, the disk is faulty... There are now so many different conditions and exceptions that the operating system has to deal with that it is amazing it ever gets anything done at all.