Why a Deleted File Isn't Gone: Unlink, Unallocated Space & Slack
- Explain what rm actually removes — the directory entry and a link count — and why the data blocks are not touched
- Describe unallocated space: freed blocks keep their old contents until the filesystem reuses them
- Define file slack and explain why a file's last block leaks data from whatever was there before
- Connect data persistence to how recovery and file carving work, and when they succeed or fail
- Choose a real deletion method (shred, or encrypt-and-destroy-the-key) and name the cases where shred is not enough
What rm actually removes
See that deleting unlinks a name, and that the data is a separate thing that is left behind.
The command is called
rmfor remove, and almost everyone reads that as 'erase.' It is not. On a Linux filesystem a file is really two separate things: the data (bytes sitting in disk blocks) plus an inode (a small record holding the file's size, permissions, timestamps, and the list of which blocks hold its data). A filename is neither of those — it is just a link: an entry in a directory that maps a name to an inode number. You can even have several names for the same file (hard links), all pointing at the one inode.So when you
rm notes.txt, the kernel does something narrower than 'erase': it removes the directory entry (that name-to-inode link) and decrements the inode's link count. Only when that count hits zero — no names left pointing at the inode — does the filesystem mark the inode and its data blocks as free. And 'free' means available for reuse; it does not mean overwritten. The bytes are still lying in those blocks, untouched, exactly as they were. That gap between 'freed' and 'erased' is the whole subject of this lesson, and it is why an accidentalrmis sometimes recoverable and a deliberate one is sometimes a false sense of security.printf 'Q3 revenue figures — draft\n' > notes.txt ls -i notes.txt131074 notes.txt
ls -iprints the inode number in front of the name — the ID of the underlying record the filename points at. (The number you see will differ from this sample; it is whichever inode the filesystem handed your new file.) The name and the inode are separate: this is the link. Renaming or deleting the name does not touch the inode's data blocks; it only changes which names point where.ln notes.txt copy.txt && stat -c 'links=%h inode=%i' notes.txtlinks=2 inode=131074
A hard link proves a filename is just a link: copy.txtnow points at the same inode asnotes.txt, and the link count is2.rm notes.txthere would drop the count to1and free nothing —copy.txtstill holds the data. Only removing the last name frees the inode and blocks.rmoperates on names and counts, not on bytes.Freed is not erased: unallocated space
Understand that freed blocks keep their contents until something reuses them.
When the last link is gone and the blocks are marked free, they become unallocated space — space the filesystem considers available, but which still physically contains the old file's bytes. Nothing goes back and zeroes them; that would be enormously wasteful, so no filesystem does it on delete. The old content simply sits there until the filesystem happens to hand one of those blocks to a new file and writes over it.
That 'until' is the important part, because the window can be minutes or months. On a nearly empty disk, deleted data can survive for a very long time — the allocator has plenty of fresh blocks to use and rarely revisits the freed ones. On a nearly full, busy disk it may be overwritten within seconds. This is why forensic advice is always to stop writing to a disk the moment you realize you need to recover something: every new file, every log line, every browser cache write is a chance to land on the blocks you want back. Unallocated space is not empty — it is a graveyard of everything recently deleted, slowly being paved over.
rm copy.txt notes.txt; df -h /Filesystem Size Used Avail Use% Mounted on /dev/root 20G 6.1G 14G 31% /
After removing the last name, dfreports the space as available again — the accounting says 'free.' But 'free' is a bookkeeping state, not a physical wipe: the blocks that held the file still contain its bytes until the filesystem reuses them for something new.dfgoing up is exactly the moment the data becomes recoverable-but-doomed — present, but living on borrowed time.Slack space: the tail of the last block
Define file slack and see why a file's final block leaks old data.
Disks hand out storage in fixed-size blocks (commonly 4096 bytes on ext4). A file almost never divides evenly into whole blocks, so its last block is only partly used — and the leftover tail of that block has to contain something. That unused remainder is file slack, and it is a second place old data hides.
Picture a 5000-byte file on a 4096-byte-block filesystem: it needs two blocks. The first is full; the second holds only 904 bytes of the file, leaving 3192 bytes of slack. What is in that slack? Whatever occupied that block before — the tail of some earlier, now-deleted file. The new file's metadata says it is 5000 bytes, so normal tools only ever show you those 5000 bytes; the slack is invisible to
catandlsbut plainly readable to a tool that reads the raw block. Slack is why 'I overwrote that area with a new file' is not a reliable wipe: a smaller new file leaves the back of the block untouched, still carrying the old bytes.printf 'hello' > tiny.txt; echo "size=$(stat -c %s tiny.txt) bytes, allocated=$(( $(stat -c %b tiny.txt) * $(stat -c %B tiny.txt) )) bytes"size=5 bytes, allocated=4096 bytes
A 5-byte file still consumes a whole filesystem block. stat -c %breports 8 allocated units and%Breports that each unit is 512 bytes, so the file is charged 8 × 512 = 4096 bytes on disk for 5 bytes of content. Five bytes are yours; the other 4091 are slack — space charged to this file but not written by it, still holding whatever the block contained before. Multiply that across every file on a disk and slack is a large, quietly persistent hiding place for old data.Why recovery and carving work
Connect persistence to the techniques that exploit it.
Put the two together — unallocated blocks that still hold old files, plus slack — and recovery stops being magic. There are two broad approaches. Metadata-based recovery uses filesystem structures that survive a delete: on ext4 the journal or a freed inode may still list which blocks a file used, so a tool like The Sleuth Kit can reassemble it precisely. File carving ignores filesystem metadata entirely and reads the raw bytes of unallocated space looking for file signatures — the fixed 'magic' bytes that mark the start (and sometimes end) of known formats:
%PDFfor a PDF,\xFF\xD8\xFFfor a JPEG,PKfor a ZIP. When a carver finds a signature, it copies from there until the file's end marker or a size limit, and hands you the file back with no help from the filesystem at all. Tools likeforemost,scalpel, andphotorecare carvers.Carving is powerful precisely because it needs no intact metadata — it works on a wiped directory, a reformatted partition, or a raw disk image. Its limits follow directly from the mechanics above: it can only recover what has not been overwritten, it struggles with fragmented files (whose blocks are scattered, not contiguous), and it recovers the bytes without the original filename or timestamps (those lived in the inode). This is the exact skill practiced in the Recover a Deleted File room, where the notes are gone from the directory listing but the blocks are still in the image.
printf '%%PDF-1.7' | xxd | head -100000000: 2550 4446 2d31 2e37 %PDF-1.7
A file's leading bytes are its signature — here the %PDFmagic that marks a PDF. Carvers scan raw unallocated space for exactly these patterns, then copy forward to reconstruct the file with no filesystem metadata. That a format announces itself in its first bytes is what makes carving from a graveyard of freed blocks possible.A carver hands back bytes, not meaning: the recovered file has no name, no timestamps and no path, just a number for where it was found. So the first two moves on anything carved are the same as on any unknown file. `file` confirms the signature really produced the type you wanted (a carver that over-ran a fragment gives you a broken or truncated file). `strings` then prints every run of printable characters, which on a photo or a document surfaces the text people hide in them: a JPEG comment, EXIF fields, a name or an address.
printf '\xff\xd8\xff\xfe\x00\x1enote: drop-site 203.0.113.80\xff\xd9' > carved.jpg file carved.jpg strings carved.jpg | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}'carved.jpg: JPEG image data 203.0.113.80
- strings
- Print runs of 4 or more printable characters found anywhere in a file (change the minimum with -n).
- grep -oE
- Print only the part of each line that matches the extended regex, here a dotted IPv4 address.
The printf builds a tiny stand-in for a carved photo: the JPEG start marker ( FF D8), a comment segment (FF FE, a two-byte length, then the text) and the end marker (FF D9), the same signature pair a carver looks for.filerecognises it as a JPEG from those leading bytes.stringspulls out the comment text, and the grep keeps only the IPv4 address in it. Runstringson the recovered file, never on the whole disk image: on an image it returns every printable run on the disk, and you can no longer say which file the text came from.So how do you actually delete something?
Pick a real erase method and know where shred stops being enough.
If
rmonly unlinks, real deletion means overwriting the bytes so there is nothing left to recover. For a single file on a traditional disk,shreddoes this: it overwrites the file's blocks with several passes of data (and with-za final pass of zeros), optionally removing the name afterward with-u. Overwrite the blocks and the old contents are genuinely gone.But
shredrests on one assumption — that writing to the file overwrites the same physical blocks — and modern storage breaks that assumption in three important ways, so be honest about them:- Journaling and copy-on-write filesystems (ext4's journal, and especially btrfs/ZFS, which never overwrite in place) may keep copies of the data elsewhere that
shrednever touches. - SSDs and flash use wear-leveling: the drive silently redirects writes to different physical cells, so 'overwriting' a file may leave the original cells intact and unreachable by any normal command.
- Copies you forgot — backups, snapshots, slack in other files, RAM, swap.
Because of this, the assured way to make data unrecoverable at scale is not to chase every block but to encrypt the whole disk from the start and destroy the key (a few bytes to erase makes terabytes unreadable — 'crypto-erase'), or to wipe/destroy the entire device.
shreda sensitive file, yes — but know that on an SSD or a CoW filesystem, full-disk encryption plus key destruction is the honest answer.printf 'top secret' > secret.txt; shred -uvz secret.txtshred: secret.txt: pass 1/4 (random)... shred: secret.txt: pass 2/4 (random)... shred: secret.txt: pass 3/4 (random)... shred: secret.txt: pass 4/4 (000000)... shred: secret.txt: removing shred: secret.txt: renamed to 0000000000 shred: 0000000000: renamed to 000000000 shred: 000000000: renamed to 00000000 shred: 00000000: renamed to 0000000 shred: 0000000: renamed to 000000 shred: 000000: renamed to 00000 shred: 00000: renamed to 0000 shred: 0000: renamed to 000 shred: 000: renamed to 00 shred: 00: renamed to 0 shred: secret.txt: removed
shredoverwrites the file's blocks (several random passes, then zeros with-z) and removes the name with-u— so the data, not just the link, is gone on a traditional in-place filesystem. The cascade of renames beforeremovedis-ushortening the filename one character at a time: the name lives in the directory entry, not in the file's blocks, so shredding the contents does nothing to hide what the file was called. On an SSD or a copy-on-write filesystem, treat all of this as best-effort per the caveats above, and reach for full-disk encryption + key destruction when the stakes are real.Check yourself
You rm a sensitive file on a busy server, then realize it must be truly unrecoverable. Why is the rm not enough, and what actually erases the data?
- Journaling and copy-on-write filesystems (ext4's journal, and especially btrfs/ZFS, which never overwrite in place) may keep copies of the data elsewhere that
The assumption that leaks data
Stop trusting rm, empty-trash, or a quick format to erase sensitive data.
- A filename is a link to an inode; rm removes the directory entry and drops the link count, and only when the last link is gone are the inode and data blocks marked free. 'Free' means reusable, not overwritten — the bytes stay put.
- Freed blocks are unallocated space that still holds the old contents until the filesystem reuses them; on an idle disk that can be a long time, which is why you stop writing to a disk the moment you need to recover from it.
- File slack — the unused tail of a file's last block — is a second place old data survives, invisible to normal tools but readable raw. Persistence in unallocated space plus slack is exactly what recovery and file carving (foremost/scalpel/photorec; The Sleuth Kit) exploit, limited by overwrites and fragmentation.
- To actually delete, overwrite the bytes: shred works on in-place filesystems, but journaling/CoW filesystems and SSD wear-leveling can leave copies, so full-disk encryption plus destroying the key is the reliable erase. Deleting the name is never deleting the data.
