gpg: Hang on exit after trustdb error, when keyboxd is in use
Open, HighPublic

Assigned To
None
Authored By
tfry
Mon, Oct 12, 3:23 PM

Description

Reported by Brian Lee on gnupg-devel for gpg version 2.5.24: https://lists.gnupg.org/pipermail/gnupg-devel/2026-October/036494.html
Reported for MacOS, but the problem is reproducible on Linux.

Slightly adjusted test script:

export TESTDIR=$PWD/testdir
export GNUPGHOME=$TESTDIR/.gnupg
mkdir -p -m 700 $GNUPGHOME && echo use-keyboxd > $GNUPGHOME/common.conf
gpg --batch --passphrase '' --quick-gen-key test@example.org ed25519 sign never
echo hello > $TESTDIR/msg
gpg --batch -u test@example.org --detach-sign $TESTDIR/msg
gpg -K
gpg --verify $TESTDIR/msg.sig $TESTDIR/msg

# Now use either:
cp $GNUPGHOME/trustdb.gpg $TESTDIR/tdb.gpg && chmod 000 $TESTDIR/tdb.gpg
gpg --trustdb-name $TESTDIR/tdb.gpg --verify $TESTDIR/msg.sig $TESTDIR/msg
## prints               gpg: Fatal: can't open './tdb.gpg': Permission denied
## then hangs

# Or:
chmod 400 $GNUPGHOME/trustdb.gpg
gpg --check-trustdb
## prints               gpg: Note: trustdb not writable
##                      gpg: trustdb rec 32: write failed (n=-1): Bad file descriptor
##                      gpg: Fatal: /home/thomas/g10code/testgh/.gnupg/trustdb.gpg: failed to append a record: Bad file descriptor
## then hangs

Both variants of the script produce a legitimate error message. However, after that, gpg hangs.

The reporter has provided additional analysis on the mailing list.

Event Timeline

Here is a patch to address the issue:

This patch doesn't change the problem of clean up; It solves the issue by not-locking-the-stream, because the lock is not needed.

While the issue of hang-up will be gone, we would need to consider how to clean up the resources.

This patch is a good idea. In fact it would be better if "samethread" cpould be the default because strerams are rarely used by more than one thread. However, this would be a surprising change and not compatible with the long established [g]libc behaviour.

We need to study more closely our atexit behaviour related to threads.

• werner added projects: Bug Report, gpgrt.

Here is another (non-perfect) solution by libgpg-error:

Since the particular problem is hang with the stream of reading pipe, it will be possible to avoid with the change above.

I say "non-perfect" because it is better to avoid use of atexit by a library in an application with multiple threads if we want to do it cleanly.

• gniibe mentioned this in Unknown Object (Maniphest Task).Mon, Oct 19, 4:07 AM

I applied the libgpg-error patch in: rE2e3b31d565c6: estream: Skip the read-only stream when IN_ATEXIT is true.

Let us keep this ticket to consider use of atexit in a multi-threaded application by a library.

Here are things to consider:

  • It would be good to remove use of atexit in a library. That's because:
    • In an exit handler, to have concrete semantics, access to shared data should be avoided, use of locking should be also avoided, because we don't know how it goes in other threads.
  • If we need to support something (e.g. flushing output buffer of estream) at exit-time (termination of an application by a thread), what we could do it cleanly in a library is:
    • estream could offer a callback-intended function to flush its output buffer, if an application wants to use it
    • it's something like:
      • if the stream is for other thread, terminate the thread or joined the thread
      • do flushing
    • it's an application responsibility to register the estream callback function with atexit
• gniibe mentioned this in Unknown Object (Maniphest Task).Mon, Oct 26, 3:32 AM

comment fixes were applied in: rG9797a67d3ae3: commond: Fix comments for gnupg_create_*_pipe.

Update the gnupg change, only for inbound pipe:

Inbound pipe is only used in the use of keyboxd, so, this will come with not-much surprise.