since checkpoints are removed, we would be vulnerable to this attack
Where did you see that checkpoints are removed? They're still in
src/chainparams.cpp as of current master (commit
How does the headers-first synchronization prevent the fill-disk attack?
Headers-first prevents a related attack where the client would accept "orphan" blocks---block with no known parent---and store them until their parent was received. This prevented wasted bandwidth and sped up validation in the normal case but made disk-fill attacks easier in the pathological case.
Headers first allows Bitcoin Core to discover the most proof of work block chain (headers chain) that its peers know about before downloading any blocks, which allows it to ensure any blocks it receives are on that chain. This, in turn, means that it never needs to download or store orphan blocks.
A syncing node downloads all headers from a single peer. If that peer only sends headers from its malicious low-difficulty branch, won't the syncing node try to download all those blocks, and get it's disk filled?
That's correct, and that's why checkpoints are still used in the code as far as I know. My understanding is that, for the ultimate removal of checkpoints, three things were desired:
Minimum chainwork: a feature coded into a node that tells it the legitimate chain must have at least X amount of chainwork, with X being set to the value for a recent block near the time of a software release. This replaces the original use of checkpoints in preventing network-level attackers from feeding clients long, low-PoW chains containing valid blocks but which aren't the consensus best block chain. This was deployed in Bicoin Core 0.13.2.
Assumed Valid Blocks: a feature designed to replace the secondary use of checkpoints for (optionally) speeding up Initial Block Download (IBD) by skipping validation of signatures in old blocks. This was deployed in Bitcoin Core 0.14
A minimum difficult soft fork: a change to directly address the block-fill (or header-fill) attack you described by raising the minimum difficulty at various epochs in the block chain to correspond roughly with the actual observed increases in difficulty. This would make it more expensive for an attacker to feed fake blocks to a node. To the best of my knowledge this has not yet reached the BIP stage and I'm not sure it's currently being actively championed.
For reference, this topic was discussed in the 2017-03-02 Bitcoin Core developer meeting: https://bitcoincore.org/en/meetings/2017/03/02/#discussion