From 711c3953024386d0c25427afbf75c3ce6a141f56 Mon Sep 17 00:00:00 2001 From: Narrat Date: Fri, 26 Sep 2025 12:15:18 +0200 Subject: [PATCH 1/2] gpg_decrypt: drop GnuPG 1.4.11 workaround Remove last remnant of 9e820f3de6a1b3ecab0662d0ae30c0b79d6d4e43 The latest release of gpg1 is 1.4.23 from 2018. It is safe to assume, that there is no modern distribution, which ships this specific gpg1 version. As the workaround itself only applies to 1.4.11, any version before or after seem to be safe. This removal doesn't remove support for gpg1 in general as it isn't end-of-life. All the options in use are available with gpg1 and only a broken --status-fd seemed to be of concern. Closes #581 --- tomb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tomb b/tomb index 58ab90e0..921ddc8b 100755 --- a/tomb +++ b/tomb @@ -1294,7 +1294,7 @@ _load_key() { # contains tweaks for different gpg versions # support both symmetric and asymmetric encryption gpg_decrypt() { - # fix for gpg 1.4.11 where the --status-* options don't work ;^/ + # gpg version check necessary for <2.1.x (although those aren't supported anymore) local gpgver=$(gpg --version --no-permission-warning | awk '/^gpg/ {print $3}') local gpgpass="$1\n$TOMBKEY" local tmpres ret @@ -1316,15 +1316,6 @@ gpg_decrypt() { } } - [[ $gpgver == "1.4.11" ]] && { - _verbose "GnuPG is version 1.4.11 - adopting status fix." - TOMBSECRET=`print - "$gpgpass" | \ - gpg --decrypt ${gpgpopt[@]}` - ret=$? - unset gpgpass - return $ret - } - _tmp_create tmpres=$TOMBTMP TOMBSECRET=`print - "$gpgpass" | \ @@ -1338,7 +1329,6 @@ gpg_decrypt() { [[ "$i" =~ "DECRYPTION_OKAY" ]] && ret=0; done return $ret - } From 974784fba2d1280a10f56e63f8a094cabc2dfc8f Mon Sep 17 00:00:00 2001 From: Narrat Date: Thu, 25 Jun 2026 21:53:17 +0200 Subject: [PATCH 2/2] Change password input for gpg operations GPG was used in way, that the password for the payload and the payload was piped into GPG separated by a newline ("--passphrase-fd 0" where 0 is STDIN). It will then read the input until a newline is encountered and strip it from the pipe. After which only the encrypted payload remains. This has some consequences for passwords in general, as the running shell is involved and may interpret ANSI Escape Sequences. ANSI Escape sequences can be used for various tasks and are non-printable characters. Build with a backslash ("\"), they create control sequences like \n (newline), \t (horizontal tab) and \f (formfeed). If those are used in passwords various stuff can happen. \n for example will shorten the password depending on its position. \ at the end of password will nullify the newline separator between the password and the encrypted payload. See KNOWN_BUGS.md for a table with some cases. To solve those unintended changes the file descriptor is changed from 0 (STDIN) to 3. Which is the next free one after 1 (STDOUT) and 2 (STDERR). The password will be redirected via "3<<<$password". This solution won against a switch to --passphrase-file and the use of an anonymous pipe <(print -R -n - "$password"). In general the use of --passphrase-file is discouraged, but that should only apply if a real file is used. With the pipe only the respective file descriptor should be seen and not its content. For which only gpg is allowed to have access. And if gpg is completed the pipe and its content is gone. What changes: pinentry or more accuratly assuan will still output a (percent)formated password, but the shell won't interfere anymore. This does not address passwords that contain % and the difference between entering the password via pinentry and --tomb-pwd. Addresses #304 --- ChangeLog.md | 16 ++++++++++++++++ KNOWN_BUGS.md | 38 ++++++++++++++++++++++++++++++++++++++ doc/tomb.1 | 7 ++++++- tomb | 25 +++++++++++-------------- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4b113c08..dac5df65 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,20 @@ # Tomb ChangeLog + +## Unreleased +### TBA + +Special character sequences like newline (`\n`), tabfeed (`\t`) and +similar from ANSI Escape Sequences caused unexpected behaviour when +setting passwords. The safe way to pipe the passwords into `gnupg` +would go through the shell and if sequences were encountered +they would be processed. +Which brings unintended changes in the entered password. In the case +of a newline the user could end up with a much weaker password +than expected and not notice it necessarily. +With this release the password won't be changed by the shell pipe +anymore. `Pinentry` and `libassuan` still do their thing though. +For an overview see [Known Bugs](./KNOWN_BUGS.md). + ## 2.13 ### July 2025 diff --git a/KNOWN_BUGS.md b/KNOWN_BUGS.md index f76bfc80..ddfbe012 100644 --- a/KNOWN_BUGS.md +++ b/KNOWN_BUGS.md @@ -1,3 +1,41 @@ +# Passwords getting silently altered +## Issues with Tomb version x.x to 2.13 + +The safe way of piping a password into gnupg would be interpret by the shell +and therefore character sequences like `ANSI Escape Sequences` could cause +unintended changes to the password and not necessarily be noticeable. +But also escaping characters can introduce changes to the final password. +In general a tomb created with 2.13 and before can still be unlocked, but +it needs to happen with unsafe options. +Below is a table with some examples: + +| Sequence | Theoretical | Real | Unlock 2.13 | Unlock >2.13 | Notes | +|----------|-------------|------|-------------|--------------|-------| +| newline | `test\ntest` | `test` | `test` or `test\ntest` or `test\nwhatever` | `test` | Position matters greatly +| horizontal tab | `test\test` | `testest` | `test\test` | `--tomb-pwd "test\test"` | +| formfeed | `test\ftest` | `test\xCtest` | `test\ftest` | `--tomb-pwd "test\ftest"` | dm-crypt will throw a warning | +| bell | `test\atest` | `test\x07test` | `test\atest` | `--tomb-pwd "test\atest"` | bell will appear in output +| backspace | `test\btest` | theoretically `testest`? | `test\btest` | `--tomb-pwd "test\btest"` | behaves unexpectedly +| vertical tab | `test\vtest` | `testtest` | `test\vtest` | `--tomb-pwd "test\vtest"` | +| carriage return | `test\rtest` | `testtest` | `test\rtest` | `--tomb-pwd "test\rtest"` | +| escape character (wrong) | `test\etest` | in shell `testest` | `test\etest` | `--tomb-pwd "test\etest"` | behaves unexpectedly +| escape character | `test\e[1Ktest` | in shell ` test` | `test\e[1Ktest` | `--tomb-pwd "test\e[1Ktest"` | erase from start to cursor +| escpape backslash | `testtest\` | - | - | - | created key is safely protected... + +It isn't explored what happened in cases like `\ntest` or `test\`. +In the first case would `test` be discarded or end up as the `LUKS` key? +Essentially the same question for the second case: +What does the `LUKS` key look like after the password was appended with the +intended key? +The general advice is to generate a new tomb to be on the safe side. + +Of note: This doesn't address the quirk with `%`s in a password. +Pinentry or rather Assuan have the requirement, that some special sequences +need to be percent escaped (`%` as `%25`, `CR` as `%0D` and `LF` as `%0A`). +If such a password was set via pinentry and is later supplied via +`--unsafe --tomb-pwd` it will need to be set as `--tomb-pwd test%25test`. + + # Password bug failing to open tombs ## Issue with Tomb version 2.12 (short lived) diff --git a/doc/tomb.1 b/doc/tomb.1 index 19631791..5a1ea85b 100644 --- a/doc/tomb.1 +++ b/doc/tomb.1 @@ -384,7 +384,8 @@ the non-blocking source of Linux kernel doesn't degrades the quality of random. .B .IP "--tomb-pwd " -Use string as password when needed on tomb. +Use string as password when needed on tomb. Refer to Password Input +for edge cases. .B .IP "--tomb-old-pwd " Use string as old password when needed in tomb commands requiring @@ -473,6 +474,10 @@ When using it from a remote ssh connection it might be necessary to force use of pinentry-tty for instance by unsetting the DISPLAY (X11) or WAYLAND_DISPLAY (Wayland) environment var. +If using \fI--tomb-pwd\fR special care must be taken with passwords that +contain what pinentry/assuan considers special sequences. One of those is +the percent symbol (\fI%\fR) which needs to be manually escaped as (\fI%25\fR). + .SH SWAP On execution of certain commands Tomb will complain about swap memory diff --git a/tomb b/tomb index 921ddc8b..8f359174 100755 --- a/tomb +++ b/tomb @@ -581,7 +581,7 @@ ask_password() { return 1 } - print "$password" + print -R -n - "$password" return 0 } @@ -1296,10 +1296,10 @@ _load_key() { gpg_decrypt() { # gpg version check necessary for <2.1.x (although those aren't supported anymore) local gpgver=$(gpg --version --no-permission-warning | awk '/^gpg/ {print $3}') - local gpgpass="$1\n$TOMBKEY" + local gpgpass="$1" local tmpres ret typeset -a gpgopt - gpgpopt=(--batch --no-tty --passphrase-fd 0 --no-options) + gpgpopt=(--batch --no-tty --passphrase-fd 3 --no-options --no-mdc-warning --no-permission-warning --no-secmem-warning) { option_is_set -g } && { gpgpass="$TOMBKEY" @@ -1318,10 +1318,9 @@ gpg_decrypt() { _tmp_create tmpres=$TOMBTMP - TOMBSECRET=`print - "$gpgpass" | \ - gpg --decrypt ${gpgpopt[@]} \ - --status-fd 2 --no-mdc-warning --no-permission-warning \ - --no-secmem-warning 2> $tmpres` + TOMBSECRET=`print - "$TOMBKEY" | \ + gpg --decrypt ${gpgpopt[@]} --status-fd 2 \ + 3<<<"$gpgpass" 2> $tmpres` unset gpgpass ret=1 for i in ${(f)"$(cat $tmpres)"}; do @@ -1850,18 +1849,16 @@ gen_key() { fi # Set gpg inputs and options - gpgpass="${tombpass}\n$TOMBSECRET" - gpgopt=(--passphrase-fd 0 --symmetric --no-options) + gpgopt=(--passphrase-fd 3 --symmetric --no-options) opt='-n' fi _tmp_create local tmpres=$TOMBTMP - print $opt - "$gpgpass" \ + print $opt - "$TOMBSECRET" \ | gpg --openpgp --force-mdc --cipher-algo ${algo} \ - --batch --no-tty ${gpgopt} \ - --status-fd 2 -o - --armor 2> $tmpres >> "$1" - unset gpgpass + --batch --no-tty ${gpgopt} --status-fd 2 -o - \ + --armor 3<<<"$tombpass" 2> $tmpres >> "$1" # check result of gpg operation for i in ${(f)"$(cat $tmpres)"}; do _verbose "$i" @@ -1869,7 +1866,7 @@ gen_key() { # print -n "${tombpass}" \ # | gpg --openpgp --force-mdc --cipher-algo ${algo} \ - # --batch --no-options --no-tty --passphrase-fd 0 --status-fd 2 \ + # --batch --no-options --no-tty --passphrase-fd 3 --status-fd 2 \ # -o - -c -a ${lukskey} TOMBPASSWORD="$tombpass" # Set global variable