The threat it protects against is uncommon
Encryption protects against an attacker who gains hold of the data. The typical threat is the theft of a laptop or a backup tape, the compromise of a remote backup server, etc. Who cares whether the attacker can modify the data? You aren't going to use that copy of it anyway.
Once the attacker has gained access to the compromised system, that system is no longer trustworthy. Once they've had access to your computer, it isn't really your computer anymore. Even if the data on it is integrity-protected, the integrity of the system itself is at risk. The attacker may have inserted a hardware keylogger or other spying device, for example. So usually it is not necessary to try to recover data from a compromised system.
Integrity protection protects against evil maid types of attack, where the attacker gains access to a system without being detected. These are a lot rarer than simple theft or (detected) remote compromise. It isn't worth protecting against a rare threat if it has a significant cost, and it's even less worth attempting to protect against a threat if you can't do it properly. And as I wrote above, in many scenarios, the integrity hardware itself may be compromised, which makes data integrity meaningless. But let's assume that the hardware isn't compromised. Can you protect the integrity of the data? That's not so easy!
It's usually impossible anyway
Confidentiality protection can be done with cryptography alone. The same does not hold for integrity protection.
How do you trust trust?
Consider a system with full-disk encryption. Despite the literal meaning of “full-disk”, there actually has to be some data in there that isn't encrypted, namely the necessary code to boot the system, disk and console drivers, code prompt for a password or other credentials and decrypt the data. This code doesn't need to be confidential, it's a part of the operating system that anybody can see anyway.
But when the goal is integrity or authenticity rather than confidentiality, you have a problem: the bootstrap code does need to be protected. Otherwise the attacker can replace the bootstrap code by a wrapper that pretends that everything is fine but in fact forwards all your data to big-brother.gov.oceania
.
How do you do protect the bootstrap code? You can't use cryptography, because something has to verify the security the verification code before it can be used to verify the code. Catch-22.
If you do trust the hardware, then you can get integrity protection by putting the bootstrap code in ROM. Cryptography isn't needed since ROM code is physically protected. (This assumes that ROM is really ROM — that's the case on ARM platforms, but x86 typically boots from flash, which makes it more of a problem.) Typically, the ROM code is a tiny piece of code that loads a second bigger piece of code from disk or flash and verifies it before executing it, and that code in turn verifies and executes another piece, etc.
This is called a secure boot chain.
Authenticty is not integrity
Assuming that you have ROM code that verifies the signature of a bootloader loaded from disk/flash before executing it, that only verifies the authenticity of the bootloader, not its integrity. Typically the ROM verifies a signature and not a hash in order to allow the bootloader to be upgraded — if the ROM verifies a hash then it might as well contain the bootloader code since only one version of the bootloader code will every be possible. But if the bootloader can be upgraded, then it can also be downgraded. In particular, if there is a security vulnerability in an older version of the bootloader, then the adversary can install that version and exploit the vulnerability.
With ROM alone, it's impossible to protect against such a downgrade attack. You need to have at least a little bit of mutable, persistent storage to record which versions of the bootloader are authorized. Flash memory is fine, if you can prevent the attacker from accessing that flash memory, which is not always possible depending on the environment. A TPM is the usual solution on a PC. Some devices such as some smartphones have a special flash partition called RPMB (Replay-Protected Memory Block, see e.g. 1) for this purpose.
If you don't have this mutable persistent storage then the best you can do is protect the authenticity of the data, not its integrity.
The cost is not just space overhead
Let's assume that you've solved the trust bootstrap problem. You assume that the attacker can't modify your hardware. It may or may not be a realistic assumption depending on the scenario, but let's go with it. And either you have the protected storage to detect integrity violations, or you've resigned yourself to only have authenticity protection. Now you want to work on your system and modify files, which requires updating their authenticity/integrity data. How does this work?
When the threat model is that an attacker steals the storage device and then accesses it, you only need resistance against chosen-plaintext attacks at most (the adversary may convince you to store certain files prior to stealing your disk), which is easy to achieve. In particular, a cipher mode where the IV is implicitly derived from the block location is ok, and that saves the cost of having to save the IV somewhere. That cost isn't just a matter of disk space, it's also a matter of having to update the location where the IV is stored, or having filesystem blocks that are smaller than the blocks of the underlying storage media.
When the attacker can see multiple versions of the storage (for example, they steal multiple disk-level backups), things get more complicated, even for confidentiality protection; for example you must never reuse an IV, because that leaks information about what data gets changed. Even letting the adversary know which blocks on the disk are changing leaks information; there's no miracle remedy for that.
For integrity or authenticity, the integrity data has to be stored somewhere, so you can't escape the overhead. But you need more than that. You also need to tie the integrity data to what it protects and to the overall version of the filesystem. So you can't just take a MAC of block_number || block_content
: that would allow the attacker to substitute an older copy of the block — a block-level downgrade attack.
There's a standard solution to this: hash trees. Organize the blocks in a tree structure, where a block's parent contains the integrity data of its children. Every time a block is updated, its parent needs to be updated as well, and so on up to the root of the tree. If you have physically-protected storage, use it to store the integrity data for the root; otherwise the best you can to is to authenticate the root, which means that the filesystem can be downgraded (rollback attack), but at least it can only be rolled back to a valid older version.
The need to update integrity data has a cost. This goes beyond the simple arithmetic cost of having more data to store and load. It also seriously hurts parallelization and reordering. It's insecure to start processing data in a block before the integrity of that block has been verified (or if you do, you have to keep track of data dependencies and roll back any dependency on data that turned out to have been corrupted). Writes can't be reordered for performance anymore unless you fully trust the stability of the system and its power supply, because write reordering is based on the assumption that the data makes sense either way. If integrity data is written out of order and the system crashes, there's no way to distinguish legitimate not-quite-up-to-date data from an outright attack, unless you keep some information about old data around in which case you pay the cost of maintaining that extra information.
In most scenarios, it isn't worth paying that cost since you can't guarantee integrity or even authenticity anyway. In some scenarios, such as remote backup, there's a local trusted system that can verify the integrity of the backup, but that's a very different use case which typically isn't accessed through a filesystem interface. That's why encrypted filesystems rarely offer integrity protection.