IMO there are multiple different categories of UB in C/C++.
Some UB just reprepresents pig-headedness on the part of compiler writers and standards organisations. The optimisation benefits are massively outweiged by the fact that they make even the simplest operations (like addition) into potential footguns.
Some UB is arguable, stuff like the aliasing rules. On the one hand i'm not convinced the optimisation opertunities justify the extra mental load, on the other hand they relate to things (like pointer typecasts) that should be used sparingly anyway.
These first two categories could be got rid of quite easily if there was the will to do so. Indeed in gcc there are actually compiler flags to turn many of them into defined behaviour for those who can be bothered to actually RTFM.
But there is a third category of UB. UB that represents fundamental consequences of the language and library design and of low level programming in general. This last category includes stuff like buffer overflows (and sometimes underflows), stale pointer de-references, double frees.
I don't think this third category can ever be done away with completely in low level programming. The best you can hope for is to do what rust tries to do and compartmentalize it but I can't see how you can do even that without language and library changes radical enough that you may as well call it a new language.
A particular problem is the combination of sharing and mutability. Lets take for example I have a type that represents a string, it has a pointer to the string data, a length and a capacity. I read the pointer and start a loop of read character, process character, increment pointer until I reach the end of the string.
However, while the code working through the string some other peice of code comes along, maybe another thread, maybe an interrupt handler, maybe a callback function. This other piece of code appends a character to the string, triggering reallocation of the string. Suddenly the first piece of code has a stale pointer.
Rust gets around this by forbidding shared mutability in safe code except under very specific circumstances but I really don't see how you can solve this problem for an existing language and library ecosystem.