Is it possible for the Arduino's digital pin mode to flip from OUTPUT to INPUT unexpectedly?
The fact that it is possible to set them incorrectly is why, in some projects where I'm concerned about this, I'll add a small series resistance to an inbound signal.  Should the pin become OUTPUT and driven in an opposing direction to the signal, the resistance serves to prevent a short through both devices.  The possibility is always there, whether you have an outright bug, or instability, or flash corruption.
With pinMode the opportunity to accidentally set your port to the wrong direction is maybe a bit worse than for code that directly manipulates the data direction register.  pinMode calls typically don't get inlined, because pinMode on the Arduino is a fairly complicated function at runtime for what it actually does.  So, there's this generic code that can be jumped to that will set various ports to either direction, as opposed to smaller chunks of code that contain instructions that set specific ports to specific settings.
INPUT roughly takes your digital pin out of the circuit.  And INPUT_PULLUP sort of does that, with the exception that internal to the AVR there's going to be 20k - 50k of resistance going to &quot;5V&quot;.  Knowing that, you can test your hypothesis three.  When you find your project in the state where the LED isn't lit, take some some resistance (say 1K) and use it to pull the pin toward &quot;5V&quot;.  Then pull it to GND.  Watch the voltage at the pin with a meter.  Based on what you have attached and what you see for a voltage, you can determine whether it has entered the INPUT or INPUT_PULLUP state.  You'll also be able to see if it's just being driven to whatever state you're calling &quot;not working&quot; (&quot;off&quot; I'm guessing).
You can take all the guess work out of what voltages to see in each scenario if you upload a sketch that sets the pin in question to the INPUT, INPUT_PULLUP, OUTPUT/HIGH, and OUTPUT/LOW states and measure the voltage at the pin when you pull it to each rail with your resistor.  Later you can consult the table you've built to see what has happened.
There's only so much to say about this, particularly your first two ideas, without seeing the code and other details.  Just that sometimes otherwise simple code has bugs in it.  In an ATMega328P project, 40 bytes is not a lot of free space at all.  To give you an idea what sort of problem this can trigger: The Arduino implementation of new that's in the 1.8.3 core will happily blow away your register file and whatever else it can get to after that, if the underlying malloc reports not enough memory.  Basically new continues to initialize the object it thinks was allocated even when the pointed returned is nullptr.  The __malloc_margin is 128 in the 1.8.3.  So if you try to malloc with 40 bytes distance between the stack and heap, you will trash your program state, perhaps even setting a data direction register in the process.