diff --git a/recover_bad_signature.sh b/recover_bad_signature.sh index f33d01f..74ef762 100755 --- a/recover_bad_signature.sh +++ b/recover_bad_signature.sh @@ -360,20 +360,32 @@ validate_recovered_file() { 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" elif command -v unzip > /dev/null 2>&1 ; then - # Use a 120-second timeout: unzip -tq on a very large or partially - # corrupt ZIP can block indefinitely otherwise (the process simply - # stalls in I/O, unlike a clean CRC error which exits quickly). + # Run unzip inside setsid so it has no controlling terminal. + # Without setsid, a password-protected ZIP causes unzip to try + # 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 - 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=$? if [[ $_unzip_exit -eq 0 ]] ; then 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\")" else local _badentry - _badentry="$(trim "$(timeout 120 unzip -t "$_f" 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")" - echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}" + _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})}" + fi fi else echo "UNVERIFIED|unzip not installed" diff --git a/recreate_bad_signature.sh b/recreate_bad_signature.sh index b4196cc..54e7da5 100755 --- a/recreate_bad_signature.sh +++ b/recreate_bad_signature.sh @@ -386,20 +386,32 @@ validate_recovered_file() { 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" elif command -v unzip > /dev/null 2>&1 ; then - # Use a 120-second timeout: unzip -tq on a very large or partially - # corrupt ZIP can block indefinitely otherwise (the process simply - # stalls in I/O, unlike a clean CRC error which exits quickly). + # Run unzip inside setsid so it has no controlling terminal. + # Without setsid, a password-protected ZIP causes unzip to try + # 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 - 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=$? if [[ $_unzip_exit -eq 0 ]] ; then 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\")" else local _badentry - _badentry="$(trim "$(timeout 120 unzip -t "$_f" 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")" - echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}" + _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})}" + fi fi else echo "UNVERIFIED|unzip not installed" diff --git a/restore_bad_signature.sh b/restore_bad_signature.sh index 9ec6063..c043c10 100755 --- a/restore_bad_signature.sh +++ b/restore_bad_signature.sh @@ -380,20 +380,32 @@ validate_recovered_file() { 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" elif command -v unzip > /dev/null 2>&1 ; then - # Use a 120-second timeout: unzip -tq on a very large or partially - # corrupt ZIP can block indefinitely otherwise (the process simply - # stalls in I/O, unlike a clean CRC error which exits quickly). + # Run unzip inside setsid so it has no controlling terminal. + # Without setsid, a password-protected ZIP causes unzip to try + # 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 - 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=$? if [[ $_unzip_exit -eq 0 ]] ; then 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\")" else local _badentry - _badentry="$(trim "$(timeout 120 unzip -t "$_f" 2>&1 | grep -v '^Archive:' | grep -v '^[[:space:]]*$' | head -1)")" - echo "INVALID|zip integrity check failed${_badentry:+ (${_badentry})}" + _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})}" + fi fi else echo "UNVERIFIED|unzip not installed"