Fix memory leak with X509V3_ADD_DELETE.
It seems no one has ever exercised this mode to notice it doesn't work.
Add a test for this, which catches the memory leak. See also
https://github.com/openssl/openssl/issues/18677
Change-Id: I8890febc71decd553a040bdda8739f68ed616c39
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53285
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 425d745..50121c9 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -4388,3 +4388,163 @@
40:86
)");
}
+
+TEST(X509Test, AddExt) {
+ bssl::UniquePtr<X509> x509(X509_new());
+ ASSERT_TRUE(x509);
+
+ struct Extension {
+ int nid;
+ bool critical;
+ std::vector<uint8_t> data;
+ };
+ auto expect_extensions = [&](const std::vector<Extension> &exts) {
+ ASSERT_EQ(static_cast<size_t>(X509_get_ext_count(x509.get())), exts.size());
+ for (size_t i = 0; i < exts.size(); i++) {
+ SCOPED_TRACE(i);
+ X509_EXTENSION *ext = X509_get_ext(x509.get(), static_cast<int>(i));
+ EXPECT_EQ(OBJ_obj2nid(X509_EXTENSION_get_object(ext)), exts[i].nid);
+ EXPECT_EQ(X509_EXTENSION_get_critical(ext), exts[i].critical ? 1 : 0);
+ ASN1_OCTET_STRING *data = X509_EXTENSION_get_data(ext);
+ EXPECT_EQ(Bytes(ASN1_STRING_get0_data(data), ASN1_STRING_length(data)),
+ Bytes(exts[i].data));
+ }
+ };
+
+ // Make a few sample extensions.
+
+ // SEQUENCE {}
+ std::vector<uint8_t> basic1_der = {0x30, 0x00};
+ const uint8_t *inp = basic1_der.data();
+ bssl::UniquePtr<BASIC_CONSTRAINTS> basic1_obj(
+ d2i_BASIC_CONSTRAINTS(nullptr, &inp, basic1_der.size()));
+ EXPECT_EQ(inp, basic1_der.data() + basic1_der.size());
+
+ // SEQUENCE { BOOLEAN { TRUE } }
+ std::vector<uint8_t> basic2_der = {0x30, 0x03, 0x01, 0x01, 0xff};
+ inp = basic2_der.data();
+ bssl::UniquePtr<BASIC_CONSTRAINTS> basic2_obj(
+ d2i_BASIC_CONSTRAINTS(nullptr, &inp, basic2_der.size()));
+ EXPECT_EQ(inp, basic2_der.data() + basic2_der.size());
+
+ // OCTET_STRING {}
+ std::vector<uint8_t> skid1_der = {0x04, 0x00};
+ inp = skid1_der.data();
+ bssl::UniquePtr<ASN1_OCTET_STRING> skid1_obj(
+ d2i_ASN1_OCTET_STRING(nullptr, &inp, skid1_der.size()));
+ EXPECT_EQ(inp, skid1_der.data() + skid1_der.size());
+
+ // OCTET_STRING { "a" }
+ std::vector<uint8_t> skid2_der = {0x04, 0x01, 0x61};
+ inp = skid2_der.data();
+ bssl::UniquePtr<ASN1_OCTET_STRING> skid2_obj(
+ d2i_ASN1_OCTET_STRING(nullptr, &inp, skid2_der.size()));
+ EXPECT_EQ(inp, skid2_der.data() + skid2_der.size());
+
+ // Initially, the extension list is empty.
+ expect_extensions({});
+
+ // Adding extensions works with the default settings.
+ EXPECT_EQ(
+ 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(),
+ /*crit=*/1, X509V3_ADD_DEFAULT));
+ expect_extensions({{NID_basic_constraints, true, basic1_der}});
+ EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_subject_key_identifier,
+ skid1_obj.get(),
+ /*crit=*/0, X509V3_ADD_DEFAULT));
+ expect_extensions({{NID_basic_constraints, true, basic1_der},
+ {NID_subject_key_identifier, false, skid1_der}});
+
+ // By default, we cannot add duplicates.
+ EXPECT_EQ(
+ 0, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(),
+ /*crit=*/0, X509V3_ADD_DEFAULT));
+ expect_extensions({{NID_basic_constraints, true, basic1_der},
+ {NID_subject_key_identifier, false, skid1_der}});
+
+ // |X509V3_ADD_KEEP_EXISTING| silently keeps the existing extension if already
+ // present.
+ EXPECT_EQ(
+ 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(),
+ /*crit=*/0, X509V3_ADD_KEEP_EXISTING));
+ expect_extensions({{NID_basic_constraints, true, basic1_der},
+ {NID_subject_key_identifier, false, skid1_der}});
+
+ // |X509V3_ADD_REPLACE| replaces it.
+ EXPECT_EQ(
+ 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(),
+ /*crit=*/0, X509V3_ADD_REPLACE));
+ expect_extensions({{NID_basic_constraints, false, basic2_der},
+ {NID_subject_key_identifier, false, skid1_der}});
+
+ // |X509V3_ADD_REPLACE_EXISTING| also replaces matches.
+ EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_subject_key_identifier,
+ skid2_obj.get(),
+ /*crit=*/1, X509V3_ADD_REPLACE_EXISTING));
+ expect_extensions({{NID_basic_constraints, false, basic2_der},
+ {NID_subject_key_identifier, true, skid2_der}});
+
+ // |X509V3_ADD_DELETE| ignores the value and deletes the extension.
+ EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0,
+ X509V3_ADD_DELETE));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der}});
+
+ // Not finding an extension to delete is an error.
+ EXPECT_EQ(0, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0,
+ X509V3_ADD_DELETE));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der}});
+
+ // |X509V3_ADD_REPLACE_EXISTING| fails if it cannot find a match.
+ EXPECT_EQ(
+ 0, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(),
+ /*crit=*/1, X509V3_ADD_REPLACE_EXISTING));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der}});
+
+ // |X509V3_ADD_REPLACE| adds a new extension if not preseent.
+ EXPECT_EQ(
+ 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(),
+ /*crit=*/1, X509V3_ADD_REPLACE));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der},
+ {NID_basic_constraints, true, basic1_der}});
+
+ // Delete the extension again.
+ EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0,
+ X509V3_ADD_DELETE));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der}});
+
+ // |X509V3_ADD_KEEP_EXISTING| adds a new extension if not preseent.
+ EXPECT_EQ(
+ 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(),
+ /*crit=*/1, X509V3_ADD_KEEP_EXISTING));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der},
+ {NID_basic_constraints, true, basic1_der}});
+
+ // Delete the extension again.
+ EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0,
+ X509V3_ADD_DELETE));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der}});
+
+ // |X509V3_ADD_APPEND| adds a new extension if not present.
+ EXPECT_EQ(
+ 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(),
+ /*crit=*/1, X509V3_ADD_APPEND));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der},
+ {NID_basic_constraints, true, basic1_der}});
+
+ // |X509V3_ADD_APPEND| keeps adding duplicates (invalid) even if present.
+ EXPECT_EQ(
+ 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(),
+ /*crit=*/0, X509V3_ADD_APPEND));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der},
+ {NID_basic_constraints, true, basic1_der},
+ {NID_basic_constraints, false, basic2_der}});
+
+ // |X509V3_ADD_DELETE| only deletes one extension at a time.
+ EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0,
+ X509V3_ADD_DELETE));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der},
+ {NID_basic_constraints, false, basic2_der}});
+ EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0,
+ X509V3_ADD_DELETE));
+ expect_extensions({{NID_subject_key_identifier, true, skid2_der}});
+}
diff --git a/crypto/x509v3/v3_lib.c b/crypto/x509v3/v3_lib.c
index 4400b8e..51b0f37 100644
--- a/crypto/x509v3/v3_lib.c
+++ b/crypto/x509v3/v3_lib.c
@@ -316,9 +316,11 @@
}
// If delete, just delete it
if (ext_op == X509V3_ADD_DELETE) {
- if (!sk_X509_EXTENSION_delete(*x, extidx)) {
+ X509_EXTENSION *prev_ext = sk_X509_EXTENSION_delete(*x, extidx);
+ if (prev_ext == NULL) {
return -1;
}
+ X509_EXTENSION_free(prev_ext);
return 1;
}
} else {