gpg randomly loses ownertrust for private keys
Open, NormalPublic

Assigned To
Authored By
m.eik
Wed, Jun 24, 10:22 AM

Description

tested with gnupg 2.5.20: from time to time, the ownertrust for my own private keys is being dropped, trust reducd from "ultimate"to "unknown".

the issue seems to have appeared after the update to 2.5.20 and wasn't present before. it doesn't affect all private keys at the same time, but strikes for individual private keys in a seemingly random pattern. they work again after restoring ownertrust, until it is being dropped again.

there's no common.conf on the system.

Event Timeline

if multiple private keys have lost ownertrust, it is sufficient to claim one key as your own in kleopatra to restore ownertrust also for all other keys.

I can't see any commits pertaining to gpg betweem 2.5.19 and .20 which could cause this. I would say, we need to bisect this but w/o a reliable reproducer this will take too long.

Did you compare ownertrust files exported after the problem occurs with files exported after the problem was "fixed"? A possible explanation would be that the trustdb (re-)calculation aborts in the middle and overwrites the previous correct result with a partial result. (I have no idea how the trustdb (re-)calculation works. Is it "throw everything away and start from scratch"?)

m.eik mentioned this in Unknown Object (Maniphest Task).Mon, Jun 29, 9:13 AM

There is a old ticket of T1675, and it is still valid (there are remaining race conditions for the access of trustdb.gpg).
The ticket has "low" priority, because concurrent gpg invocations are considered rare.

If Kleo invokes multiple gpg at once in some use cases, the task should have higher priority.

For an example, today, I found a race condition around update_validity in g10/trustdb.c.
(Since I observed my trustdb.gpg has multiple RECTYPE_VALID records for a single UID, I check the code path.)
(1) A process of gpg calls read_trust_record for PK, and it loops to locate an existing one. If not found,
(2) It calls tdbio_new_recnum to create a new entry.
(3) Another process of gpg may do same
(4) And here is no serialization, it may result two entries for a single UID.

Please run gpg --list-trustdb and show me the result when you think your trustdb is corrupted.

gniibe mentioned this in Unknown Object (Maniphest Task).Mon, Jul 6, 10:08 AM
gniibe triaged this task as Normal priority.EditedTue, Jul 7, 6:56 AM

In this ticket, let me focus on the symptom and try minimum symptomatic treatment.
(And continue by T1675, for the real fixes of concurrent access to trustdb.)

I think that it's hash table corruption (RECTYPE_TRUST records are still there).

The possible race is the following.
(1) Process A updates a trustdb RECTYPE_TRUST entry, it goes through: tdbio_write_record -> update_trusthashtbl -> upd_hashtable
(2) Process B does similarly, with another entry, it goes through: tdbio_write_record -> update_trusthashtbl -> upd_hashtable
(3) Suppose that for both entries, the slot are same : `msb (= key[level])
(4) Suppose that the accesses to the slot are mostly simultaneously done
(5) Then, one won, another lost
(6) It resulted that loser's entry has no trust, even though the RECTYPE_TRUST record itself is available in a file

I'm going to serialize this access so that the entry won't be lost.

EDIT: Now, I don't think these race conditions are related to this case

@m.eik
I investigated and found a possible lost of validity case, but I haven't found any possible lost of ownertrust case.

Reproducing lost of validity by expiration.

  • In my case, following command reproduce lost of validity by expiration
$ gpg --faked-system-time=20500101T000000 -k  # listing keys with future time
  • I can see the validity lost in trustdb, with v=0, by examining the trustdb
$ gpg --list-trustdb # example output of trustdb of mine
...
rec    32, trust B33F1FF9EE3F90C64C55083AA68055440A0417FA, ot=6, d=0, vl=33, mo=0, f=00
rec    33, valid 9F72E70445494E3AA57873869AEBDB41A514C172, v=0, next=0, f=0, m=0
  • It can be recovered with no --faked-system-time
$ gpg --check-trustdb # make sure trustdb is updated
...
$ gpg --list-trustdb # example output of trustdb of mine
...
rec    32, trust B33F1FF9EE3F90C64C55083AA68055440A0417FA, ot=6, d=0, vl=33, mo=0, f=00
rec    33, valid 9F72E70445494E3AA57873869AEBDB41A514C172, v=6, next=0, f=0, m=0

I wonder if your case is similar.

I found job->setOptions(QGpgME::ListAllKeysJob::DisableAutomaticTrustDatabaseCheck) in libkleo/src/models/keycache.cpp.
I think that default is automatic trust database check.

There are two kinds of data in trustdb.gpg (other than meta data like hash table and version):
(0) Those data can be directly manipulated by --import-ownertrust and --export-ownertrust

  • Dump can be show by --list-trustdb (which is a kind of debug command)

(1) trust (by --list-trustdb, RECTYPE_TRUST in terms of tdbio.c)

  • This data is only available in the file, each data is for a key fingerprint
  • It is created/updated:
    • key generation
    • use of --trusted-key <KEY> option
    • trust handling commands:--quick-set-ownertrust, and --edit-key <KEY> + trust
    • for those cases, it cannot be 0 (cannot be dropped)
  • It is dropped <-- dropping is only these cases
    • use of --trusted-key <KEY> option
    • importing revocation certificate,
    • deleting key

(2) valid (by --list-trustdb, RECTYPE_VALID in terms of tdbio.c)

  • This data is computed value with keys in keyrings + trust
    • it is computed and updated time to time
    • can be recovered by re-computation
    • each data is for a User-ID (hashed), and connected to a trust data

valid record can be updated with v=0 despite user's intention, like with wrong system time and auto-update (wrongly considered expiration of key). This can be recovered by re-computation with proper system time.

The bug is: dropping trust to ot=0 for some reason and mysterious recovery to ot=6.