Page MenuHome GnuPG

gpgme_op_verify from libgpgme hang without returning anything when verifying corrupted file signature
Open, NormalPublic

Description

While trying to verify corrupted file signature, I used this code :

gpgme_ctx_t ctx;
gpgme_error_t err;
gpgme_data_t input_signed = NULL;
gpgme_data_t output_data = NULL;
gpgme_verify_result_t vresult;
ssize_t chars_read;

error = renson::error::kNoError;

try
{
  // set the locale
  setlocale(LC_ALL, "");
  // check the GPG version
  gpgme_check_version(NULL);
  gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));

  // verify if OpenPGP is supported
  err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
  if (err)
  {
    LOG_ERROR("Error during gpgme_engine_check_version: " + std::string(gpgme_strerror(err)));
    throw renson::error::kBadConfiguration;
  }

  // create the context for the GPGME operations
  err = gpgme_new(&ctx);
  if (err)
  {
    LOG_ERROR("Error during gpgme_new: " + std::string(gpgme_strerror(err)));
    throw renson::error::kInvalidOperation;
  }

  // check if the input path is valid
  if (!exists(input_file_path_))
  {
    LOG_ERROR("Given input file does not exist: " + input_file_path_.string());
    throw renson::error::kWrongInputParam;
  }

  if (!is_regular_file(input_file_path_))
  {
    LOG_ERROR("Given input path is a directory, not a file: " + input_file_path_.string());
    throw renson::error::kWrongInputParam;
  }

  if (!exists(output_path.parent_path()))
  {
    LOG_ERROR("Output directory does not exist for: " + output_path.parent_path().string());
    throw renson::error::kWrongInputParam;
  }

  // open signed input file
  err = gpgme_data_new_from_file(&input_signed, input_file_path_.string().c_str(), 1);
  if (err)
  {
    LOG_ERROR("Error during gpgme_data_new_from_file on " + input_file_path_.string() + ": " + std::string(gpgme_strerror(err)));
    throw renson::error::kWrongInputParam;
  }

  // create output data
  err = gpgme_data_new(&output_data);
  if (err)
  {
    LOG_ERROR("Error during gpgme_data_new: " + std::string(gpgme_strerror(err)));
    throw renson::error::kInvalidOperation;
  }

  // call the actual verify call: "gpg --verify xxx.gpg"
  err = gpgme_op_verify(ctx, input_signed, NULL, output_data);
  if (err)
  {
    LOG_ERROR("Error during gpgme_op_verify: " + std::string(gpgme_strerror(err)));
    throw renson::error::kFileSignatureError;
  }

  // interpret the result from gpg
  vresult = gpgme_op_verify_result(ctx);
  if (gpgme_err_code(vresult->signatures->status) != GPG_ERR_NO_ERROR)
  {
    LOG_ERROR("unexpected signature status: " + std::string(gpgme_strerror(vresult->signatures->status)));
    throw renson::error::kFileSignatureError;
  }
  else if (vresult->signatures->next)
  {
    LOG_ERROR("unexpected number of signatures");
    throw renson::error::kFileSignatureError;
  }
  else if (vresult->signatures->summary == GPGME_SIGSUM_RED)
  {
    LOG_ERROR("unexpected signature summary " + std::to_string(vresult->signatures->summary));
    throw renson::error::kFileSignatureError;
  }
  else if (vresult->signatures->wrong_key_usage)
  {
    LOG_ERROR("unexpected wrong key usage");
    throw renson::error::kFileSignatureError;
  }
  else if (gpgme_err_code(vresult->signatures->validity_reason) != GPG_ERR_NO_ERROR)
  {
    LOG_ERROR("unexpected validity reason: " + std::string(gpgme_strerror(vresult->signatures->validity_reason)));
    throw renson::error::kFileSignatureError;
  }

  // Open the output file
  std::ofstream output_fs = std::ofstream(output_path.string(), std::ofstream::binary);

  if (!output_fs.good())
  {
    LOG_ERROR("Error opening file: " + output_path.string());
    throw renson::error::kFileWriteError;
  }

  // Rewind the "output_data" data object
  err = gpgme_data_seek(output_data, 0, SEEK_SET);
  if (err)
  {
    LOG_ERROR("Error during gpgme_data_seek: " + std::string(gpgme_strerror(err)));
    throw renson::error::kInvalidOperation;
  }

  // Read the contents of output_data and place it into "contents"
  const int buffer_size = 256 * 1024;
  char contents[buffer_size];
  while ((chars_read = gpgme_data_read(output_data, contents, buffer_size)) > 0)
  {
    // Write the contents of contents to "output_fs"
    output_fs.write(contents, chars_read);

    if (!output_fs.good())
    {
      LOG_ERROR("Error writing file: " + output_path.string());
      throw renson::error::kFileWriteError;
    }
  }
}
catch (renson::error::Error& e)
{
  error = e;
}

// cleanup
if (input_signed)
  gpgme_data_release(input_signed);

if (output_data)
  gpgme_data_release(output_data);

// when no exception was thrown, return true, otherwise false
return error == renson::error::kNoError;

}

I works fine with a regular gpg file, however, passing a corrupted file, it got stuck at gpgme_op_verify and never return anything.
however using gpgme command works fine and returns that file is corrupted.
Any help ?

Details

Version
libgpgme 1.17.1 (tested also on 1.22.0)

Event Timeline

Karam changed Version from 1.17.1 (tested also on 1.22.0 to libgpgme 1.17.1 (tested also on 1.22.0).

Does the run-verify example (in gpgme/tests) hang when verifying a corrupted file?

werner triaged this task as Normal priority.Feb 7 2024, 9:22 AM
werner added a subscriber: werner.

Oh well, it does not use the c++ binding .

BTW, you may pass a NULL ptr to gpgme_data_release which is a no-op then of course. This rule is true for almost all release function in the GnuPG field.

@werner I 'm not passing nullptr to gpgme_data_release.
@ikloecker Honestly I didn't test it.
Is there anything wrong with code ? have anyone encountered such behavior ?
I was trying adding a timeout as a workaround for gpgme_op_verify to avoid hanging but it depends on the file size and how much it will take to verify it signature...

@Karam, please test as suggested by @ikloecker.

(The NULL ptr remark was just a general coding advice for the benefit of search engines et al.)

We provide the examples for a reason. Actually, two reasons: To test our changes ourselves. And to provide working examples for others. If your code doesn't work then you'll have to figure out where the example and your code differ. If the example doesn't work then we'll have a look.

By the way, this is a bug tracker and not a support forum for developers. I'm not sure whether the gnupg-users mailing list or the gnupg-devel mailing list is the better forum to ask for help using gpgme.

Hello,
So after testing on gpgme-1.17.1, with run-verify under tests as you mentioned, with corrupted file it hangs forever.
Now we can say it's a bug in gpgme_op_verify.

This is normal file :

./run-verify --openpgp /home/karam/Downloads/OS_flux_2024-02-16_02-09-34_99.0.3519.zip.gpg
Original file name .: [none]
MIME flag ..........: no
Signature ...: 0

status ....: No public key
summary ...: key-missing
fingerprint: 1B5913EE6FA0240863B59D972112543A3E222A46
created ...: 1708045801
expires ...: 0
validity ..: unknown
val.reason : No public key
pubkey algo: 1 (RSA)
digest algo: 10 (SHA512)
pka address: [none]
pka trust .: n/a
other flags:

and this is a corrupted file :
./run-verify --openpgp /home/karam/Downloads/OS_flux_2023-07-28_02-53-13_99.0.1650.zip.gpg

It doesn't return any kind of error.

Can you make this corrupted file available to us?

You can also run with GPGME_DEBUG=8 to get lots of debug output from gpgme.

Uploaded the corrupted file.

In T6977#183049, @Karam wrote:

Uploaded the corrupted file.

Where?

gpg --list-packets shows this:

gpg: Fatal: zlib inflate problem: invalid stored block lengths
# off=0 ctb=a3 tag=8 hlen=1 plen=0 indeterminate
:compressed packet: algo=1
# off=2 ctb=90 tag=4 hlen=2 plen=13
:onepass_sig packet: keyid 2112543A3E222A46

Or when trying to verify the file:

[GNUPG:] PLAINTEXT 62 1690505613 OS_flux_2023-07-28_02-53-13_99.0.1650.zip
[GNUPG:] PLAINTEXT_LENGTH 31664703
gpg: Fatal: zlib inflate problem: invalid stored block lengths

You signed ZIP archive and also compressed it using gpg (did you use --unwrap). Some where the file got corrupted in the outer ZLIB compression layer. What is missing is a status line to convey that error to gpgme. I need to see whey this not the case. However, gpgme should not hang. I can confirm that gpgme/tests/run-verify hangs if --openpgp is not used.