I just show that I had forgot to attach the patch here.


On 06/23/2015 06:30 PM, Tsantilas Christos wrote:

* Detect cases where the size file is corrupted or has a clearly wrong
value. Automatically rebuild the database in such cases.

* Teach ssl_crtd to keep running if it is unable to store the generated
certificate in the database. Return the generated certificate to Squid
and log an error message in such cases.

Background:

There are cases where ssl_crtd may corrupt its certificate database. The
known cases manifest themselves with an empty db index file.  When that
happens, ssl_crtd helpers quit, SSL bumping does not work any more, and
the certificate DB has to be deleted and re-initialized.

We do not know exactly what causes corruption in deployments, but one
known trigger that is easy to reproduce in a lab is the block size
change in the ssl_crtd configuration. That change has the following
side-effects:

1. When ssl_crtd removes certificates, it computes their size using a
different block size than the one used to store the certificates. This
is may result in negative database sizes.

2. Signed/unsigned conversion results in a huge number near LONG_MAX,
which is then written to the "size" file.

3. The ssl_crtd helper refuses to store new certificates because the
database size (as described by the "size" file) exceeds the configured
limit.

4. The ssl_crtd helper exits because it cannot store a new certificates
to the database. No helper response is sent to Squid in this case.

Most likely, there are other corruption triggers -- the database
management code is of an overall poor quality. This change resolves some
of the underlying problems in hope to address at least some of the
unknown triggers as well as the known one.

This is a Measurement Factory project.

Avoid SSL certificate db corruption with empty index.txt as a symptom.

* Detect cases where the size file is corrupted or has a clearly wrong
  value. Automatically rebuild the database in such cases.

* Teach ssl_crtd to keep running if it is unable to store the generated
  certificate in the database. Return the generated certificate to Squid
  and log an error message in such cases.

Background:

There are cases where ssl_crtd may corrupt its certificate database.
The known cases manifest themselves with an empty db index file.  When
that happens, ssl_crtd helpers quit, SSL bumping does not work any more,
and the certificate DB has to be deleted and re-initialized.

We do not know exactly what causes corruption in deployments, but one
known trigger that is easy to reproduce in a lab is the block size
change in the ssl_crtd configuration. That change has the following
side-effects:

1. When ssl_crtd removes certificates, it computes their size using a
   different block size than the one used to store the certificates.
   This is may result in negative database sizes.

2. Signed/unsigned conversion results in a huge number near LONG_MAX,
   which is then written to the "size" file.

3. The ssl_crtd helper refuses to store new certificates because the
   database size (as described by the "size" file) exceeds the
   configured limit.

4. The ssl_crtd helper exits because it cannot store a new certificates
   to the database. No helper response is sent to Squid in this case.

Most likely, there are other corruption triggers -- the database
management code is of an overall poor quality. This change resolves some
of the underlying problems in hope to address at least some of the
unknown triggers as well as the known one.

This is a Measurement Factory project.


=== modified file 'src/ssl/certificate_db.cc'
--- src/ssl/certificate_db.cc	2015-04-14 07:26:12 +0000
+++ src/ssl/certificate_db.cc	2015-06-12 17:15:03 +0000
@@ -303,52 +303,59 @@
         // TODO: check if the stored row is valid.
         return true;
     }
 
     {
         TidyPointer<char, tidyFree> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0));
         Ssl::X509_Pointer findCert;
         Ssl::EVP_PKEY_Pointer findPkey;
         if (pure_find(useName.empty() ? subject.get() : useName, findCert, findPkey)) {
             // Replace with database certificate
             cert.reset(findCert.release());
             pkey.reset(findPkey.release());
             return true;
         }
         // pure_find may fail because the entry is expired, or because the
         // certs file is corrupted. Remove any entry with given hostname
         deleteByHostname(useName.empty() ? subject.get() : useName);
     }
 
     // check db size while trying to minimize calls to size()
-    while (size() > max_db_size) {
-        if (deleteInvalidCertificate())
-            continue; // try to find another invalid certificate if needed
-
-        // there are no more invalid ones, but there must be valid certificates
-        do {
-            if (!deleteOldestCertificate()) {
-                save(); // Some entries may have been removed. Update the index file.
-                return false; // errors prevented us from freeing enough space
-            }
-        } while (size() > max_db_size);
-        break;
+    size_t dbSize = size();
+    if ((dbSize == 0 && hasRows()) ||
+        (dbSize > 0 && !hasRows()) ||
+        (dbSize >  10 * max_db_size)) {
+        // Invalid database size, rebuild
+        dbSize = rebuildSize();
+    }
+    while (dbSize > max_db_size && deleteInvalidCertificate()) {
+        dbSize = size(); // get the current database size
+        // and try to find another invalid certificate if needed
+    }
+    // there are no more invalid ones, but there must be valid certificates
+    while (dbSize > max_db_size){
+        if (!deleteOldestCertificate()) {
+            rebuildSize(); // No certificates in database.Update the size file.
+            save(); // Some entries may have been removed. Update the index file.
+            return false; // errors prevented us from freeing enough space
+        }
+        dbSize = size(); // get the current database size
     }
 
     row.setValue(cnlType, "V");
     ASN1_UTCTIME * tm = X509_get_notAfter(cert.get());
     row.setValue(cnlExp_date, std::string(reinterpret_cast<char *>(tm->data), tm->length).c_str());
     row.setValue(cnlFile, "unknown");
     if (!useName.empty())
         row.setValue(cnlName, useName.c_str());
     else {
         TidyPointer<char, tidyFree> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0));
         row.setValue(cnlName, subject.get());
     }
 
     if (!TXT_DB_insert(db.get(), row.getRow())) {
         // failed to add index (???) but we may have already modified
         // the database so save before exit
         save();
         return false;
     }
     rrow = row.getRow();
@@ -439,66 +446,69 @@
     readCertAndPrivateKeyFromFiles(cert, pkey, filename.c_str(), NULL);
     if (!cert || !pkey)
         return false;
     return true;
 }
 
 size_t Ssl::CertificateDb::size() {
     return readSize();
 }
 
 void Ssl::CertificateDb::addSize(std::string const & filename) {
     // readSize will rebuild 'size' file if missing or it is corrupted
     size_t dbSize = readSize();
     dbSize += getFileSize(filename);
     writeSize(dbSize);
 }
 
 void Ssl::CertificateDb::subSize(std::string const & filename) {
     // readSize will rebuild 'size' file if missing or it is corrupted
     size_t dbSize = readSize();
-    dbSize -= getFileSize(filename);
+    const size_t fileSize = getFileSize(filename);
+    dbSize = dbSize > fileSize ? dbSize - fileSize : 0;
     writeSize(dbSize);
 }
 
 size_t Ssl::CertificateDb::readSize() {
     std::ifstream ifstr(size_full.c_str());
     size_t db_size = 0;
     if (!ifstr || !(ifstr >> db_size))
         return rebuildSize();
     return db_size;
 }
 
 void Ssl::CertificateDb::writeSize(size_t db_size) {
     std::ofstream ofstr(size_full.c_str());
     if (!ofstr)
         throw std::runtime_error("cannot write \"" + size_full + "\" file");
     ofstr << db_size;
 }
 
 size_t Ssl::CertificateDb::getFileSize(std::string const & filename) {
     std::ifstream file(filename.c_str(), std::ios::binary);
     if (!file)
         return 0;
     file.seekg(0, std::ios_base::end);
-    size_t file_size = file.tellg();
-    return ((file_size + fs_block_size - 1) / fs_block_size) * fs_block_size;
+    const std::streampos file_size = file.tellg();
+    if (file_size < 0)
+        return 0;
+    return ((static_cast<size_t>(file_size) + fs_block_size - 1) / fs_block_size) * fs_block_size;
 }
 
 void Ssl::CertificateDb::load() {
     // Load db from file.
     Ssl::BIO_Pointer in(BIO_new(BIO_s_file()));
     if (!in || BIO_read_filename(in.get(), db_full.c_str()) <= 0)
         throw std::runtime_error("Uninitialized SSL certificate database directory: " + db_path + ". To initialize, run \"ssl_crtd -c -s " + db_path + "\".");
 
     bool corrupt = false;
     Ssl::TXT_DB_Pointer temp_db(TXT_DB_read(in.get(), cnlNumber));
     if (!temp_db)
         corrupt = true;
 
     // Create indexes in db.
     if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp)))
         corrupt = true;
 
     if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp)))
         corrupt = true;
 
@@ -544,73 +554,81 @@
 #else
         const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
 #endif
 #else
     for (int i = 0; i < sk_num(db.get()->data); ++i) {
         const char ** current_row = ((const char **)sk_value(db.get()->data, i));
 #endif
 
         if (!sslDateIsInTheFuture(current_row[cnlExp_date])) {
             deleteRow(current_row, i);
             removed_one = true;
             break;
         }
     }
 
     if (!removed_one)
         return false;
     return true;
 }
 
-bool Ssl::CertificateDb::deleteOldestCertificate() {
-    if (!db)
-        return false;
-
-#if SQUID_SSLTXTDB_PSTRINGDATA
-    if (sk_OPENSSL_PSTRING_num(db.get()->data) == 0)
-#else
-    if (sk_num(db.get()->data) == 0)
-#endif
+bool Ssl::CertificateDb::deleteOldestCertificate()
+{
+    if (!hasRows())
         return false;
 
 #if SQUID_SSLTXTDB_PSTRINGDATA
 #if SQUID_STACKOF_PSTRINGDATA_HACK
     const char **row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), 0));
 #else
     const char **row = (const char **)sk_OPENSSL_PSTRING_value(db.get()->data, 0);
 #endif
 #else
     const char **row = (const char **)sk_value(db.get()->data, 0);
 #endif
 
     deleteRow(row, 0);
 
     return true;
 }
 
 bool Ssl::CertificateDb::deleteByHostname(std::string const & host) {
     if (!db)
         return false;
 
 #if SQUID_SSLTXTDB_PSTRINGDATA
     for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); ++i) {
 #if SQUID_STACKOF_PSTRINGDATA_HACK
         const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), i));
 #else
         const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
 #endif
 #else
     for (int i = 0; i < sk_num(db.get()->data); ++i) {
         const char ** current_row = ((const char **)sk_value(db.get()->data, i));
 #endif
         if (host == current_row[cnlName]) {
             deleteRow(current_row, i);
             return true;
         }
     }
     return false;
 }
 
+bool Ssl::CertificateDb::hasRows() const
+{
+    if (!db)
+        return false;
+
+#if SQUID_SSLTXTDB_PSTRINGDATA
+    if (sk_OPENSSL_PSTRING_num(db.get()->data) == 0)
+#else
+    if (sk_num(db.get()->data) == 0)
+#endif
+        return false;
+    return true;
+}
+
 bool Ssl::CertificateDb::IsEnabledDiskStore() const {
     return enabled_disk_store;
 }
 

=== modified file 'src/ssl/certificate_db.h'
--- src/ssl/certificate_db.h	2015-01-13 09:13:49 +0000
+++ src/ssl/certificate_db.h	2015-06-12 16:32:50 +0000
@@ -110,40 +110,41 @@
     bool IsEnabledDiskStore() const; ///< Check enabled of dist store.
 private:
     void load(); ///< Load db from disk.
     void save(); ///< Save db to disk.
     size_t size(); ///< Get db size on disk in bytes.
     /// Increase db size by the given file size and update size_file
     void addSize(std::string const & filename);
     /// Decrease db size by the given file size and update size_file
     void subSize(std::string const & filename);
     size_t readSize(); ///< Read size from file size_file
     void writeSize(size_t db_size); ///< Write size to file size_file.
     size_t getFileSize(std::string const & filename); ///< get file size on disk.
     size_t rebuildSize(); ///< Rebuild size_file
     /// Only find certificate in current db and return it.
     bool pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey);
 
     void deleteRow(const char **row, int rowIndex); ///< Delete a row from TXT_DB
     bool deleteInvalidCertificate(); ///< Delete invalid certificate.
     bool deleteOldestCertificate(); ///< Delete oldest certificate.
     bool deleteByHostname(std::string const & host); ///< Delete using host name.
+    bool hasRows() const; ///< Whether the TXT_DB has stored items.
 
     /// Removes the first matching row from TXT_DB. Ignores failures.
     static void sq_TXT_DB_delete(TXT_DB *db, const char **row);
     /// Remove the row on position idx from TXT_DB. Ignores failures.
     static void sq_TXT_DB_delete_row(TXT_DB *db, int idx);
 
     /// Callback hash function for serials. Used to create TXT_DB index of serials.
     static unsigned long index_serial_hash(const char **a);
     /// Callback compare function for serials. Used to create TXT_DB index of serials.
     static int index_serial_cmp(const char **a, const char **b);
     /// Callback hash function for names. Used to create TXT_DB index of names..
     static unsigned long index_name_hash(const char **a);
     /// Callback compare function for  names. Used to create TXT_DB index of names..
     static int index_name_cmp(const char **a, const char **b);
 
     /// Definitions required by openSSL, to use the index_* functions defined above
     ///with TXT_DB_create_index.
 #if SQUID_USE_SSLLHASH_HACK
     static unsigned long index_serial_hash_LHASH_HASH(const void *a) {
         return index_serial_hash((const char **)a);

=== modified file 'src/ssl/ssl_crtd.cc'
--- src/ssl/ssl_crtd.cc	2015-01-13 09:13:49 +0000
+++ src/ssl/ssl_crtd.cc	2015-06-12 14:25:42 +0000
@@ -188,60 +188,78 @@
     std::cerr << help_string << std::endl;
 }
 
 /**
  \ingroup ssl_crtd
  * Proccess new request message.
  */
 static bool proccessNewRequest(Ssl::CrtdMessage & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size)
 {
     Ssl::CertificateProperties certProperties;
     std::string error;
     if (!request_message.parseRequest(certProperties, error))
         throw std::runtime_error("Error while parsing the crtd request: " + error);
 
     Ssl::CertificateDb db(db_path, max_db_size, fs_block_size);
 
     Ssl::X509_Pointer cert;
     Ssl::EVP_PKEY_Pointer pkey;
     std::string &cert_subject = certProperties.dbKey();
 
-    db.find(cert_subject, cert, pkey);
+    bool dbFailed = false;
+    try {
+        db.find(cert_subject, cert, pkey);
+    } catch (std::runtime_error &err) {
+        dbFailed = true;
+        error = err.what();
+    }
 
     if (cert.get()) {
         if (!Ssl::certificateMatchesProperties(cert.get(), certProperties)) {
             // The certificate changed (renewed or other reason).
             // Generete a new one with the updated fields.
             cert.reset(NULL);
             pkey.reset(NULL);
             db.purgeCert(cert_subject);
         }
     }
 
     if (!cert || !pkey) {
         if (!Ssl::generateSslCertificate(cert, pkey, certProperties))
             throw std::runtime_error("Cannot create ssl certificate or private key.");
 
-        if (!db.addCertAndPrivateKey(cert, pkey, cert_subject) && db.IsEnabledDiskStore())
-            throw std::runtime_error("Cannot add certificate to db.");
+        if (!dbFailed && db.IsEnabledDiskStore()) {
+            try {
+                if (!db.addCertAndPrivateKey(cert, pkey, cert_subject)) {
+                    dbFailed = true;
+                    error = "Cannot add certificate to db.";
+                }
+            } catch (const std::runtime_error &err) {
+                dbFailed = true;
+                error = err.what();
+            }
+        }
     }
 
+    if (dbFailed)
+        std::cerr << "ssl_crtd helper database '" << db_path  << "' failed: " << error << std::endl;
+
     std::string bufferToWrite;
     if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite))
         throw std::runtime_error("Cannot write ssl certificate or/and private key to memory.");
 
     Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY);
     response_message.setCode("OK");
     response_message.setBody(bufferToWrite);
 
     // Use the '\1' char as end-of-message character
     std::cout << response_message.compose() << '\1' << std::flush;
 
     return true;
 }
 
 /**
  \ingroup ssl_crtd
  * This is the external ssl_crtd process.
  */
 int main(int argc, char *argv[])
 {

_______________________________________________
squid-dev mailing list
squid-dev@lists.squid-cache.org
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to