fix(validate): fix unzip hanging on password-protected ZIPs via setsid
unzip -tq on a password-protected archive tries to open /dev/tty to
prompt for the password. Inside a tmux session this generates SIGTTIN,
which stops the process (ps state T). A stopped process cannot receive
SIGTERM, so timeout waited indefinitely for a child that would never exit.
Fix: wrap all unzip calls with setsid so the process runs in a new
session without a controlling terminal. The /dev/tty open then fails
immediately with ENXIO and unzip exits with a non-zero code instead of
blocking. Additional hardening:
- timeout -k 5: send SIGKILL 5 s after SIGTERM as a last resort
- < /dev/null: also cut off stdin as a secondary safeguard
- exit 137 (128+9, SIGKILL) treated as timeout alongside 124
- error output matching "password"/"encrypt"/"need PK compat" reported
as UNVERIFIED instead of INVALID
Affects: recover_bad_signature.sh, restore_bad_signature.sh,
recreate_bad_signature.sh
This commit is contained in:
@@ -360,21 +360,33 @@ validate_recovered_file() {
|
|||||||
if [[ "$_head8o" = "d0cf11e0a1b11ae1" ]] ; then
|
if [[ "$_head8o" = "d0cf11e0a1b11ae1" ]] ; then
|
||||||
echo "VALID|OLE2/CFBF container signature ok - this is a password-protected Office file (encrypted package), not a plain zip, so the zip check does not apply; open it with the password to verify content"
|
echo "VALID|OLE2/CFBF container signature ok - this is a password-protected Office file (encrypted package), not a plain zip, so the zip check does not apply; open it with the password to verify content"
|
||||||
elif command -v unzip > /dev/null 2>&1 ; then
|
elif command -v unzip > /dev/null 2>&1 ; then
|
||||||
# Use a 120-second timeout: unzip -tq on a very large or partially
|
# Run unzip inside setsid so it has no controlling terminal.
|
||||||
# corrupt ZIP can block indefinitely otherwise (the process simply
|
# Without setsid, a password-protected ZIP causes unzip to try
|
||||||
# stalls in I/O, unlike a clean CRC error which exits quickly).
|
# opening /dev/tty to prompt for the password. When running in
|
||||||
|
# the background of a tmux session this generates SIGTTIN, which
|
||||||
|
# STOPS the process (ps state T). A stopped process cannot receive
|
||||||
|
# SIGTERM, so timeout hangs indefinitely waiting for a child that
|
||||||
|
# will never exit. With setsid the /dev/tty open fails immediately
|
||||||
|
# with ENXIO and unzip exits with a non-zero code instead.
|
||||||
|
# -k 5: send SIGKILL 5 s after SIGTERM in case the process is
|
||||||
|
# still alive (e.g. stopped or in uninterruptible sleep).
|
||||||
local _unzip_exit
|
local _unzip_exit
|
||||||
timeout 120 unzip -tq "$_f" > /dev/null 2>&1
|
timeout -k 5 120 setsid unzip -tq "$_f" < /dev/null > /dev/null 2>&1
|
||||||
_unzip_exit=$?
|
_unzip_exit=$?
|
||||||
if [[ $_unzip_exit -eq 0 ]] ; then
|
if [[ $_unzip_exit -eq 0 ]] ; then
|
||||||
echo "VALID|zip integrity ok"
|
echo "VALID|zip integrity ok"
|
||||||
elif [[ $_unzip_exit -eq 124 ]] ; then
|
elif [[ $_unzip_exit -eq 124 || $_unzip_exit -eq 137 ]] ; then
|
||||||
|
# 124 = killed by SIGTERM after timeout, 137 = killed by SIGKILL (128+9)
|
||||||
echo "UNVERIFIED|zip integrity check timed out after 120 s (file may be very large or corrupt; check manually with: unzip -t \"$_f\")"
|
echo "UNVERIFIED|zip integrity check timed out after 120 s (file may be very large or corrupt; check manually with: unzip -t \"$_f\")"
|
||||||
else
|
else
|
||||||
local _badentry
|
local _badentry
|
||||||
_badentry="$(trim "$(timeout 120 unzip -t "$_f" 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")"
|
_badentry="$(trim "$(timeout -k 5 120 setsid unzip -t "$_f" < /dev/null 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")"
|
||||||
|
if echo "$_badentry" | grep -qi "password\|encrypt\|need PK compat" ; then
|
||||||
|
echo "UNVERIFIED|zip is password-protected (cannot verify without password${_badentry:+; unzip says: ${_badentry}})"
|
||||||
|
else
|
||||||
echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}"
|
echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "UNVERIFIED|unzip not installed"
|
echo "UNVERIFIED|unzip not installed"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -386,21 +386,33 @@ validate_recovered_file() {
|
|||||||
if [[ "$_head8o" = "d0cf11e0a1b11ae1" ]] ; then
|
if [[ "$_head8o" = "d0cf11e0a1b11ae1" ]] ; then
|
||||||
echo "VALID|OLE2/CFBF container signature ok - this is a password-protected Office file (encrypted package), not a plain zip, so the zip check does not apply; open it with the password to verify content"
|
echo "VALID|OLE2/CFBF container signature ok - this is a password-protected Office file (encrypted package), not a plain zip, so the zip check does not apply; open it with the password to verify content"
|
||||||
elif command -v unzip > /dev/null 2>&1 ; then
|
elif command -v unzip > /dev/null 2>&1 ; then
|
||||||
# Use a 120-second timeout: unzip -tq on a very large or partially
|
# Run unzip inside setsid so it has no controlling terminal.
|
||||||
# corrupt ZIP can block indefinitely otherwise (the process simply
|
# Without setsid, a password-protected ZIP causes unzip to try
|
||||||
# stalls in I/O, unlike a clean CRC error which exits quickly).
|
# opening /dev/tty to prompt for the password. When running in
|
||||||
|
# the background of a tmux session this generates SIGTTIN, which
|
||||||
|
# STOPS the process (ps state T). A stopped process cannot receive
|
||||||
|
# SIGTERM, so timeout hangs indefinitely waiting for a child that
|
||||||
|
# will never exit. With setsid the /dev/tty open fails immediately
|
||||||
|
# with ENXIO and unzip exits with a non-zero code instead.
|
||||||
|
# -k 5: send SIGKILL 5 s after SIGTERM in case the process is
|
||||||
|
# still alive (e.g. stopped or in uninterruptible sleep).
|
||||||
local _unzip_exit
|
local _unzip_exit
|
||||||
timeout 120 unzip -tq "$_f" > /dev/null 2>&1
|
timeout -k 5 120 setsid unzip -tq "$_f" < /dev/null > /dev/null 2>&1
|
||||||
_unzip_exit=$?
|
_unzip_exit=$?
|
||||||
if [[ $_unzip_exit -eq 0 ]] ; then
|
if [[ $_unzip_exit -eq 0 ]] ; then
|
||||||
echo "VALID|zip integrity ok"
|
echo "VALID|zip integrity ok"
|
||||||
elif [[ $_unzip_exit -eq 124 ]] ; then
|
elif [[ $_unzip_exit -eq 124 || $_unzip_exit -eq 137 ]] ; then
|
||||||
|
# 124 = killed by SIGTERM after timeout, 137 = killed by SIGKILL (128+9)
|
||||||
echo "UNVERIFIED|zip integrity check timed out after 120 s (file may be very large or corrupt; check manually with: unzip -t \"$_f\")"
|
echo "UNVERIFIED|zip integrity check timed out after 120 s (file may be very large or corrupt; check manually with: unzip -t \"$_f\")"
|
||||||
else
|
else
|
||||||
local _badentry
|
local _badentry
|
||||||
_badentry="$(trim "$(timeout 120 unzip -t "$_f" 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")"
|
_badentry="$(trim "$(timeout -k 5 120 setsid unzip -t "$_f" < /dev/null 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")"
|
||||||
|
if echo "$_badentry" | grep -qi "password\|encrypt\|need PK compat" ; then
|
||||||
|
echo "UNVERIFIED|zip is password-protected (cannot verify without password${_badentry:+; unzip says: ${_badentry}})"
|
||||||
|
else
|
||||||
echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}"
|
echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "UNVERIFIED|unzip not installed"
|
echo "UNVERIFIED|unzip not installed"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -380,21 +380,33 @@ validate_recovered_file() {
|
|||||||
if [[ "$_head8o" = "d0cf11e0a1b11ae1" ]] ; then
|
if [[ "$_head8o" = "d0cf11e0a1b11ae1" ]] ; then
|
||||||
echo "VALID|OLE2/CFBF container signature ok - this is a password-protected Office file (encrypted package), not a plain zip, so the zip check does not apply; open it with the password to verify content"
|
echo "VALID|OLE2/CFBF container signature ok - this is a password-protected Office file (encrypted package), not a plain zip, so the zip check does not apply; open it with the password to verify content"
|
||||||
elif command -v unzip > /dev/null 2>&1 ; then
|
elif command -v unzip > /dev/null 2>&1 ; then
|
||||||
# Use a 120-second timeout: unzip -tq on a very large or partially
|
# Run unzip inside setsid so it has no controlling terminal.
|
||||||
# corrupt ZIP can block indefinitely otherwise (the process simply
|
# Without setsid, a password-protected ZIP causes unzip to try
|
||||||
# stalls in I/O, unlike a clean CRC error which exits quickly).
|
# opening /dev/tty to prompt for the password. When running in
|
||||||
|
# the background of a tmux session this generates SIGTTIN, which
|
||||||
|
# STOPS the process (ps state T). A stopped process cannot receive
|
||||||
|
# SIGTERM, so timeout hangs indefinitely waiting for a child that
|
||||||
|
# will never exit. With setsid the /dev/tty open fails immediately
|
||||||
|
# with ENXIO and unzip exits with a non-zero code instead.
|
||||||
|
# -k 5: send SIGKILL 5 s after SIGTERM in case the process is
|
||||||
|
# still alive (e.g. stopped or in uninterruptible sleep).
|
||||||
local _unzip_exit
|
local _unzip_exit
|
||||||
timeout 120 unzip -tq "$_f" > /dev/null 2>&1
|
timeout -k 5 120 setsid unzip -tq "$_f" < /dev/null > /dev/null 2>&1
|
||||||
_unzip_exit=$?
|
_unzip_exit=$?
|
||||||
if [[ $_unzip_exit -eq 0 ]] ; then
|
if [[ $_unzip_exit -eq 0 ]] ; then
|
||||||
echo "VALID|zip integrity ok"
|
echo "VALID|zip integrity ok"
|
||||||
elif [[ $_unzip_exit -eq 124 ]] ; then
|
elif [[ $_unzip_exit -eq 124 || $_unzip_exit -eq 137 ]] ; then
|
||||||
|
# 124 = killed by SIGTERM after timeout, 137 = killed by SIGKILL (128+9)
|
||||||
echo "UNVERIFIED|zip integrity check timed out after 120 s (file may be very large or corrupt; check manually with: unzip -t \"$_f\")"
|
echo "UNVERIFIED|zip integrity check timed out after 120 s (file may be very large or corrupt; check manually with: unzip -t \"$_f\")"
|
||||||
else
|
else
|
||||||
local _badentry
|
local _badentry
|
||||||
_badentry="$(trim "$(timeout 120 unzip -t "$_f" 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")"
|
_badentry="$(trim "$(timeout -k 5 120 setsid unzip -t "$_f" < /dev/null 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")"
|
||||||
|
if echo "$_badentry" | grep -qi "password\|encrypt\|need PK compat" ; then
|
||||||
|
echo "UNVERIFIED|zip is password-protected (cannot verify without password${_badentry:+; unzip says: ${_badentry}})"
|
||||||
|
else
|
||||||
echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}"
|
echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "UNVERIFIED|unzip not installed"
|
echo "UNVERIFIED|unzip not installed"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user