Description
tar-rs unpack_in can chmod arbitrary directories by following symlinks
Summary
When unpacking a tar archive, the tar crate's unpack_dir function uses fs::metadata() to check whether a path that already exists is a directory. Because fs::metadata() follows symbolic links, a crafted tarball containing a symlink entry followed by a directory entry with the same name causes the crate to treat the symlink target as a valid existing directory — and subsequently apply chmod to it. This allows an attacker to modify the permissions of arbitrary directories outside the extraction root.
Reproducer
A malicious tarball contains two entries: (1) a symlink foo pointing to an arbitrary external directory, and (2) a directory entry foo/. (or just foo). When unpacked, create_dir("foo") fails with EEXIST because the symlink is already on disk. The fs::metadata() check then follows the symlink, sees a directory at the target, and allows processing to continue. The directory entry's mode bits are then applied via chmod, which also follows the symlink — modifying the permissions of the external target directory.
Fix
The fix is very simple, we now use fs::symlink_metadata() in unpack_dir, so symlinks are detected and rejected rather than followed.
Credit
This issue was reported by @xokdvium - thank you!
Mitigation
Minimal update. May introduce new vulnerabilities or breaking changes.
|
 alpine v3.24 | | | 1.94.0-r1 |
 debian 12 | | =1.63.0+dfsg1-2 || =1.64.0+dfsg1-1 || =1.64.0+dfsg1-1~exp1 || =1.64.0+dfsg1-1~exp2 || =1.64.0+dfsg1-1~exp3 || =1.64.0+dfsg1-1~exp4 || =1.65.0+dfsg1-1 || =1.65.0+dfsg1-1~exp1 || =1.65.0+dfsg1-1~exp2 || =1.65.0+dfsg1-1~exp3 || =1.65.0+dfsg1-2 || =1.66.0+dfsg1-1 || =1.66.0+dfsg1-1~exp1 || =1.67.1+dfsg1-1 || =1.67.1+dfsg1-1~exp1 || =1.68.2+dfsg1-1 || =1.68.2+dfsg1-1~exp1 || =1.69.0+dfsg1-1 || =1.69.0+dfsg1-1~exp1 || =1.69.0+dfsg1-1~exp2 || =1.70.0+dfsg1-1 || =1.70.0+dfsg1-1~exp1 || =1.70.0+dfsg1-1~exp2 || =1.70.0+dfsg1-1~exp3 || =1.70.0+dfsg1-2 || =1.70.0+dfsg1-2~exp1 || =1.70.0+dfsg1-3 || =1.70.0+dfsg1-4 || =1.70.0+dfsg1-5 || =1.70.0+dfsg1-6 || =1.70.0+dfsg1-7 || =1.70.0+dfsg1-8 || =1.70.0+dfsg1-8.1 || =1.70.0+dfsg1-9 || =1.70.0+dfsg2-1 || =1.70.0+dfsg2-1+loong64 || =1.70.0+dfsg2-1~exp1 || =1.70.0+dfsg2-1~exp2 || =1.70.0+dfsg2-1~exp3 || =1.71.1+dfsg1-1 || =1.71.1+dfsg1-1~exp1 || =1.71.1+dfsg1-1~exp2 || =1.71.1+dfsg1-2 || =1.72.1+dfsg1-1 || =1.72.1+dfsg1-1+hurd.1 || =1.72.1+dfsg1-1~exp1 || =1.72.1+dfsg1-1~exp2 || =1.73.0+dfsg1-1 || =1.73.0+dfsg1-1+hurd.1 || =1.73.0+dfsg1-1+loong64 || =1.73.0+dfsg1-1~exp1 || =1.74.1+dfsg1-1 || =1.74.1+dfsg1-1+hurd.1 || =1.74.1+dfsg1-1~exp1 || =1.75.0+dfsg1-1 || =1.75.0+dfsg1-1~exp1 || =1.75.0+dfsg1-2 || =1.75.0+dfsg1-3 || =1.75.0+dfsg1-4 || =1.75.0+dfsg1-4+hurd.1 || =1.75.0+dfsg1-5 || =1.76.0+dfsg1-1 || =1.76.0+dfsg1-1+hurd.1 || =1.76.0+dfsg1-1~exp1 || =1.77.2+dfsg1-1 || =1.77.2+dfsg1-1~exp1 || =1.78.0+dfsg1-1~exp1 || =1.78.0+dfsg1-2 || =1.79.0+dfsg1-1 || =1.79.0+dfsg1-1~exp1 || =1.79.0+dfsg1-2 || =1.79.0+dfsg1-2+hurd.1 || =1.80.0+dfsg1-1~exp1 || =1.80.1+dfsg1-1 || =1.80.1+dfsg1-1+hurd.1 || =1.80.1+dfsg1-1~exp1 || =1.81.0+dfsg1-1 || =1.81.0+dfsg1-1~exp1 || =1.81.0+dfsg1-1~exp2 || =1.81.0+dfsg1-2 || =1.82.0+dfsg1-1 || =1.82.0+dfsg1-1~exp1 || =1.82.0+dfsg1-1~exp2 || =1.82.0+dfsg1-1~exp3 || =1.82.0+dfsg1-2 || =1.83.0+dfsg1-1 || =1.83.0+dfsg1-1~exp1 || =1.84.0+dfsg1-1 || =1.84.0+dfsg1-1~exp1 || =1.84.0+dfsg1-2 || =1.85.0+dfsg1-1 || =1.85.0+dfsg2-1 || =1.85.0+dfsg2-2 || =1.85.0+dfsg2-3 || =1.85.0+dfsg3-1 || =1.85.0~beta.9+dfsg1-1~exp1 || =1.86.0+dfsg1-1 || =1.86.0+dfsg1-1~bpo13+1 || =1.86.0+dfsg1-1~bpo13+2 || =1.86.0+dfsg1-1~exp1 || =1.86.0+dfsg1-1~exp2 || =1.86.0+dfsg1-1~exp2+x32 || =1.86.0+dfsg1-1~exp3 || =1.86.0+dfsg1-1~exp4 || =1.87.0+dfsg1-1 || =1.87.0+dfsg1-1~bpo13+1 || =1.87.0+dfsg1-1~bpo13+2 || =1.87.0+dfsg1-1~exp1 || =1.88.0+dfsg1-1 || =1.88.0+dfsg1-1~exp1 || =1.88.0+dfsg1-2 || =1.88.0+dfsg1-2~bpo13+1 || =1.89.0+dfsg1-1 || =1.89.0+dfsg1-1~bpo13+1 || =1.89.0+dfsg1-1~bpo13+2 || =1.89.0+dfsg1-1~exp1 || =1.90.0+dfsg1-1 || =1.90.0+dfsg1-1~bpo13+1 || =1.90.0+dfsg1-1~bpo13+2 || =1.90.0+dfsg1-1~exp1 || =1.91.1+dfsg1-1 || =1.91.1+dfsg1-1~bpo13+1 || =1.91.1+dfsg1-1~bpo13+2 || =1.91.1+dfsg1-1~exp1 || =1.91.1+dfsg1-1~exp2 || =1.91.1+dfsg1-1~exp3 || =1.92.0+dfsg1-1 || =1.92.0+dfsg1-1~exp1 || =1.92.0+dfsg1-2 || =1.92.0+dfsg1-2~bpo13+1 || =1.93.1+dfsg1-1 || =1.93.1+dfsg1-1~exp1 || =1.93.1+dfsg1-2 || =1.93.1+dfsg1-2~bpo13+1 || =1.94.1+dfsg1-1 || =1.94.1+dfsg1-1~bpo13+1 || =1.94.1+dfsg1-1~bpo13+2 || =1.94.1+dfsg1-1~bpo13+3 || =1.94.1+dfsg1-1~exp1 || =1.94.1+dfsg1-1~exp2 || =1.94.1+dfsg1-1~exp3 || =1.95.0+dfsg1-1 || =1.95.0+dfsg1-2 || =1.95.0+dfsg1-2~bpo13+1 || =1.96.0+dfsg1-1~exp1 || =1.96.1+dfsg1-1 | - |
 debian 12 | | =0.4.38-1 || =0.4.38-2 || =0.4.40-1 || =0.4.43-1 || =0.4.43-2 || =0.4.43-3 || =0.4.43-4 || =0.4.44-1 || =0.4.45-1 || =0.4.45-2 || =0.4.46-1 | - |
 debian 13 | | =0.4.43-4 || =0.4.44-1 || =0.4.45-1 || =0.4.45-2 || =0.4.46-1 | - |
 debian 14 | | =0.4.43-4 || =0.4.44-1 || >=0 <0.4.45-1 | 0.4.45-1 |
 rpm rhel10 | | - | - |
 rpm rhel9 | | - | - |
 rpm rhel10 | | - | - |
 rpm rhel10 | | - | - |
 rpm rhel10 | | - | - |