and it's not sufficient anyways. even with NX you could have the attacker control a function pointer you call. in any sufficiently complex program you'll have enough widgets to build anything you want just out of data.
code and data are the same thing. whatever inputs your system receives is "code" that your application interprets, just like your application is the code that the CPU interprets. turtles all the way down.
There is no fundamental distinction between code and data. This separation does not exist as category in actual reality (and I vividly remember that being one of the bigger points GEB made, early on). They're fundamentally one and the same, and classifying some bits as data and some as code is a point of view that really depends on what you're doing and why.
As for Harvard architecture - every time you branch on your "data", you're effectively executing it, and your "code" can be seen as emulating a system where your "data" is the actual code.
Says you. I wrote a program for a Hardvard architecture chip (PIC) that read bytecode from the data memory and executed it. Updates were made by replacing the data block with a new data block.
In practice, there is no way to separate data and instructions. After all, JS downloaded inside a webpage is literally data, and it gets executed.
any respective machine doesn't require full Turing completeness?
any ad-hoc machine allows for mutations and flexibility, which I'm sure is an added strength.
2nd edit,
Also, didn't we have a weaker model of this with segmented non-flat memory models on i386-
That doesn't even make sense, to be honest. Interpreted languages like Python aren't running machine code. They are taking data and executing machine code based on the data. Is Python safe?
It's not that hard to imagine that in a specific embedded device the kernel / compiler have access to write to the instruction memory space while user space programs don't have any API / permissions to do that.
In practice, it's usually possible to write to the embedded flash memory via software, but it's much more difficult and disruptive than a simple CPU memory access.
I don't know any details about Python's architecture, but it's totally possible to write an interpreter for machine code; that's basically what an emulator is. The trade off is that pure interpreters tend to be very slow.