Commit 667bd7d15c for openssl.org

commit 667bd7d15c86e523fe586b212c4c235c08ee0f14
Author: Igor Ustinov <igus@openssl.foundation>
Date:   Tue Oct 20 14:27:17 2026 +0200

    CMS_verify() stops at the first failure without CMS_VERIFY_PARTIAL

    When CMS_VERIFY_PARTIAL was added, every "goto err" on a failed
    certificate, signed attributes, CAdES or content check was replaced by
    "continue", so that the remaining SignerInfos are still checked.
    Without the flag one failure already makes the whole call fail, yet
    CMS_verify() went on checking all the other signers and reading the
    content, which only wastes time.

    Goto err on the first failure unless CMS_VERIFY_PARTIAL is set again.

    Fixes: 5af9dbf12832 "Implement partial verification for CMS with multiple signatures"
    Reported-by: Anthropic's OSS Scanner, ANT-2026-D12JMUYJ

    Assisted-by: Claude:claude-opus-5-5
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Milan Broz <mbroz@openssl.org>
    Merge-date: Wed Oct 21 15:36:24 2026
    Merged-from: https://github.com/openssl/openssl/pull/33283

diff --git a/crypto/cms/cms_smime.c b/crypto/cms/cms_smime.c
index 9315c35d1c..6ed16afd82 100644
--- a/crypto/cms/cms_smime.c
+++ b/crypto/cms/cms_smime.c
@@ -353,6 +353,7 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
     int i, n = 0, scount = 0, ret = 0;
     BIO *cmsbio = NULL, *tmpin = NULL, *tmpout = NULL;
     int cadesVerify = (flags & CMS_CADES) != 0;
+    int partial = (flags & CMS_VERIFY_PARTIAL) != 0;
     const CMS_CTX *ctx = ossl_cms_get0_cmsctx(cms);

     if (dcont == NULL && !check_content(cms)) {
@@ -439,6 +440,8 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
                     si_chains ? &si_chains[i] : NULL,
                     ctx)) {
                 si->verify_result = 0;
+                if (!partial)
+                    goto err;
                 continue;
             }
             si->cert_verified = 1;
@@ -458,6 +461,8 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
             }
             if (CMS_SignerInfo_verify(si) <= 0) {
                 si->verify_result = 0;
+                if (!partial)
+                    goto err;
                 continue;
             }
             if (cadesVerify) {
@@ -465,6 +470,8 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,

                 if (ossl_cms_check_signing_certs(si, si_chain) <= 0) {
                     si->verify_result = 0;
+                    if (!partial)
+                        goto err;
                     continue;
                 }
             }
@@ -538,6 +545,8 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
             if (CMS_SignerInfo_verify_ex(si, cmsbio, tmpin) <= 0) {
                 ERR_raise(ERR_LIB_CMS, CMS_R_CONTENT_VERIFY_ERROR);
                 si->verify_result = 0;
+                if (!partial)
+                    goto err;
                 continue;
             }
             si->content_verified = 1;
@@ -549,7 +558,7 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
         if (sk_CMS_SignerInfo_value(sinfos, i)->verify_result)
             n++;
     }
-    if ((flags & CMS_VERIFY_PARTIAL) != 0)
+    if (partial)
         ret = n > 0; /* One success is enough */
     else
         ret = n == scount; /* All must be successful */
diff --git a/test/cmsapitest.c b/test/cmsapitest.c
index 35ffa79ca0..eff7884142 100644
--- a/test/cmsapitest.c
+++ b/test/cmsapitest.c
@@ -741,7 +741,8 @@ end:
  * With two independently trusted signers and CMS_VERIFY_PARTIAL, one
  * signer's signature failure must not affect the *other* signer's per-field
  * results -- including once that other signer also fails and the overall
- * call fails too.
+ * call fails too.  Without CMS_VERIFY_PARTIAL, verification must stop at
+ * the first failure.
  */
 static int test_CMS_verify_result_partial_independence(void)
 {
@@ -791,6 +792,24 @@ static int test_CMS_verify_result_partial_independence(void)
         goto end;
     ((uint8_t *)ASN1_STRING_get0_data(sig))[0] ^= 0xff;

+    ERR_clear_error();
+    if (!TEST_int_eq(CMS_verify(cms, NULL, store, NULL, out, CMS_BINARY), 0))
+        goto end;
+
+    /*
+     * Without CMS_VERIFY_PARTIAL, verification stops at signer 1's failure:
+     * signer 2's signed attributes are not checked and the content is not
+     * read.
+     */
+    if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_RESULT), 0)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_CERT), 1)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_ATTR), 0)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_RESULT), 0)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_CERT), 1)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_ATTR), 0)
+        || !TEST_size_t_eq(BIO_ctrl_pending(out), 0))
+        goto end;
+
     ERR_clear_error();
     if (!TEST_int_eq(CMS_verify(cms, NULL, store, NULL, out,
                          CMS_BINARY | CMS_VERIFY_PARTIAL),
@@ -804,8 +823,11 @@ static int test_CMS_verify_result_partial_independence(void)
      */
     if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_RESULT), 0)
         || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_CERT), 1)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_ATTR), 0)
         || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_RESULT), 1)
-        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_CERT), 1))
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_CERT), 1)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_ATTR), 1)
+        || !TEST_size_t_gt(BIO_ctrl_pending(out), 0))
         goto end;

     /* now also break signer 2's signature: the overall call must now fail */
@@ -828,8 +850,10 @@ static int test_CMS_verify_result_partial_independence(void)
      */
     if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_RESULT), 0)
         || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_CERT), 1)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_ATTR), 0)
         || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_RESULT), 0)
-        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_CERT), 1))
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_CERT), 1)
+        || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_ATTR), 0))
         goto end;

     ret = 1;