Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add misc. x509 un/lock and set1 functions #1449

Merged
merged 12 commits into from
Mar 1, 2024
15 changes: 13 additions & 2 deletions .github/workflows/integrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
- name: Run integration build
run: |
./tests/ci/integration/run_socat_integration.sh
python:
python-main:
runs-on: ubuntu-latest
steps:
- name: Install OS Dependencies
Expand All @@ -101,7 +101,18 @@ jobs:
- uses: actions/checkout@v3
- name: Build AWS-LC, build python, run tests
run: |
./tests/ci/integration/run_python_integration.sh
./tests/ci/integration/run_python_integration.sh main
python-releases:
runs-on: ubuntu-latest
steps:
- name: Install OS Dependencies
run: |
sudo apt-get update
sudo apt-get -y --no-install-recommends install cmake gcc ninja-build golang make
- uses: actions/checkout@v3
- name: Build AWS-LC, build python, run tests
run: |
./tests/ci/integration/run_python_integration.sh 3.10 3.11 3.12
bind9:
runs-on: ubuntu-latest
steps:
Expand Down
38 changes: 38 additions & 0 deletions crypto/x509/x509_lu.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ X509_STORE *X509_STORE_new(void) {
return NULL;
}

int X509_STORE_lock(X509_STORE *v) {
if (v == NULL) {
return 0;
}
CRYPTO_MUTEX_lock_write(&v->objs_lock);
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}

int X509_STORE_unlock(X509_STORE *v) {
if (v == NULL) {
return 0;
}
CRYPTO_MUTEX_unlock_write(&v->objs_lock);
return 1;
}

int X509_STORE_up_ref(X509_STORE *store) {
CRYPTO_refcount_inc(&store->references);
return 1;
Expand Down Expand Up @@ -405,6 +421,28 @@ X509_CRL *X509_OBJECT_get0_X509_CRL(const X509_OBJECT *a) {
return a->data.crl;
}

int X509_OBJECT_set1_X509(X509_OBJECT *a, X509 *obj) {
if (a == NULL || !X509_up_ref(obj)) {
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved
return 0;
}

X509_OBJECT_free_contents(a);
a->type = X509_LU_X509;
a->data.x509 = obj;
return 1;
}

int X509_OBJECT_set1_X509_CRL(X509_OBJECT *a, X509_CRL *obj) {
if (a == NULL || !X509_CRL_up_ref(obj)) {
return 0;
}

X509_OBJECT_free_contents(a);
a->type = X509_LU_CRL;
a->data.crl = obj;
return 1;
}

static int x509_object_idx_cnt(STACK_OF(X509_OBJECT) *h, int type,
X509_NAME *name, int *pnmatch) {
X509_OBJECT stmp;
Expand Down
61 changes: 57 additions & 4 deletions crypto/x509/x509_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <openssl/nid.h>
#include <openssl/pem.h>
#include <openssl/pool.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

Expand Down Expand Up @@ -1945,6 +1946,26 @@ TEST(X509Test, TestCRL) {
ASSERT_EQ(nullptr, X509_OBJECT_get0_X509_CRL(&invalidCRL));
}

TEST(X509Test, TestX509GettersSetters) {
bssl::UniquePtr<X509_OBJECT> obj(X509_OBJECT_new());
bssl::UniquePtr<X509> x509(CertFromPEM(kCRLTestRoot));
bssl::UniquePtr<X509_CRL> crl(CRLFromPEM(kBasicCRL));

ASSERT_TRUE(obj);
ASSERT_TRUE(x509);
ASSERT_TRUE(crl);

EXPECT_EQ(0, X509_OBJECT_get0_X509(obj.get()));
EXPECT_EQ(0, X509_OBJECT_get0_X509_CRL(obj.get()));
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_EQ(0, X509_OBJECT_set1_X509(nullptr, x509.get()));
EXPECT_EQ(0, X509_OBJECT_set1_X509_CRL(nullptr, crl.get()));

EXPECT_EQ(1, X509_OBJECT_set1_X509(obj.get(), x509.get()));
EXPECT_EQ(x509.get(), X509_OBJECT_get0_X509(obj.get()));
EXPECT_EQ(1, X509_OBJECT_set1_X509_CRL(obj.get(), crl.get()));
EXPECT_EQ(crl.get(), X509_OBJECT_get0_X509_CRL(obj.get()));
}

TEST(X509Test, ManyNamesAndConstraints) {
bssl::UniquePtr<X509> many_constraints(CertFromPEM(
GetTestData("crypto/x509/test/many_constraints.pem").c_str()));
Expand Down Expand Up @@ -5022,12 +5043,44 @@ TEST(X509Test, AddDuplicates) {
ASSERT_TRUE(a);
ASSERT_TRUE(b);

// To begin, add the certs to the store. Subsequent adds will be duplicative.
EXPECT_TRUE(X509_STORE_add_cert(store.get(), a.get()));
EXPECT_TRUE(X509_STORE_add_cert(store.get(), b.get()));
EXPECT_TRUE(X509_STORE_add_cert(store.get(), a.get()));
EXPECT_TRUE(X509_STORE_add_cert(store.get(), b.get()));
EXPECT_TRUE(X509_STORE_add_cert(store.get(), a.get()));
EXPECT_TRUE(X509_STORE_add_cert(store.get(), b.get()));

// Half the threads add duplicate certs, the other half take a lock and
// look them up to exercise un/locking functions.
const size_t kNumThreads = 10;
std::vector<std::thread> threads;
for (size_t i = 0; i < kNumThreads/2; i++) {
threads.emplace_back([&] {
// Sleep with some jitter to offset thread execution
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the jitter? Wouldn't we want them to all start at the same time? If there was enough time during the sleep for other threads to finish would that defeat the point of this test?

Copy link
Contributor Author

@WillChilds-Klein WillChilds-Klein Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my worry with jitter-less execution is that the adders/getters might be fast enough as to take/release locks while the subsequent threads are spawning, with the threads not getting a chance to contend. maybe this isn't a viable concern; i don't know what the overhead for spawning a thread is like... maybe this is overkill and thread spawn overhead is small enough that we can be confident that contention would occur without any sleeps?

the way it's currently written is that half the threads sleep for some random jitter before adding a cert (which takes then releases a lock internally) while the other half of the threads take a lock immediately, then sleep, then do a lookup and release the lock. my hope is that with the getters all taking locks immediately then sleeping (while holding the lock), it would cause contention with the adders who sleep for a bit before attempting to take a lock.

we can ensure that the two sets of threads always entirely overlap by making the getter lock-holders always sleep for longer than the adders.

uint8_t sleep_buf[1];
ASSERT_TRUE(RAND_bytes(sleep_buf, sizeof(sleep_buf)));
std::this_thread::sleep_for(std::chrono::microseconds(1 + (sleep_buf[0] % 5)));
EXPECT_TRUE(X509_STORE_add_cert(store.get(), a.get()));
EXPECT_TRUE(X509_STORE_add_cert(store.get(), b.get()));
});
threads.emplace_back([&] {
uint8_t sleep_buf[1];
ASSERT_TRUE(RAND_bytes(sleep_buf, sizeof(sleep_buf)));
ASSERT_TRUE(X509_STORE_lock(store.get()));
// Sleep after taking the lock to cause contention. Sleep longer than the
// adder half of threads to ensure we hold the lock while they contend
// for it. |X509_OBJECT_retrieve_by_subject| is called because it doesn't
// take a lock on the store, thus avoiding deadlock.
std::this_thread::sleep_for(std::chrono::microseconds(11 + (sleep_buf[0] % 5)));
EXPECT_TRUE(X509_OBJECT_retrieve_by_subject(
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved
store->objs, X509_LU_X509, X509_get_subject_name(a.get())
));
EXPECT_TRUE(X509_OBJECT_retrieve_by_subject(
store->objs, X509_LU_X509, X509_get_subject_name(b.get())
));
ASSERT_TRUE(X509_STORE_unlock(store.get()));
});
}
for (auto &thread : threads) {
thread.join();
}

EXPECT_EQ(sk_X509_OBJECT_num(X509_STORE_get0_objects(store.get())), 2u);
}
Expand Down
3 changes: 3 additions & 0 deletions crypto/x509/x509cset.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ int X509_CRL_sort(X509_CRL *c) {
}

int X509_CRL_up_ref(X509_CRL *crl) {
if (crl == NULL) {
return 0;
}
CRYPTO_refcount_inc(&crl->references);
return 1;
}
Expand Down
3 changes: 3 additions & 0 deletions crypto/x509/x_x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ X509 *X509_parse_from_buffer(CRYPTO_BUFFER *buf) {
}

int X509_up_ref(X509 *x) {
if (x == NULL) {
return 0;
}
CRYPTO_refcount_inc(&x->references);
return 1;
}
Expand Down
20 changes: 20 additions & 0 deletions include/openssl/x509.h
Original file line number Diff line number Diff line change
Expand Up @@ -2802,7 +2802,26 @@ OPENSSL_EXPORT int X509_OBJECT_get_type(const X509_OBJECT *a);
OPENSSL_EXPORT X509 *X509_OBJECT_get0_X509(const X509_OBJECT *a);
// X509_OBJECT_get0_X509_CRL returns the |X509_CRL| associated with |a|
OPENSSL_EXPORT X509_CRL *X509_OBJECT_get0_X509_CRL(const X509_OBJECT *a);

// X509_OBJECT_set1_X509 sets |obj| on |a| and uprefs |obj|. As with other set1
// methods, |a| does not take ownership of |obj|; the caller is responsible for
// managing freeing |obj| when appropriate.
OPENSSL_EXPORT int X509_OBJECT_set1_X509(X509_OBJECT *a, X509 *obj);

// X509_OBJECT_set1_X509_CRL sets CRL |obj| on |a| and uprefs |obj|. As with
// other set1 methods, |a| does not take ownership of |obj|; the caller is
// responsible for managing freeing |obj| when appropriate.
OPENSSL_EXPORT int X509_OBJECT_set1_X509_CRL(X509_OBJECT *a, X509_CRL *obj);

OPENSSL_EXPORT X509_STORE *X509_STORE_new(void);
// X509_STORE_lock takes a write lock on |v|. return 1 on success, 0 on failure.
//
// Avoid operations on the X509_STORE or a X509_STORE_CTX containing it while
// it is locked; many |X509_STORE_*| functions take this lock internally which
// will cause a deadlock when called on a locked store.
OPENSSL_EXPORT int X509_STORE_lock(X509_STORE *v);
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved
// X509_STORE_unlock releases a lock on |v|. return 1 on success, 0 on failure
OPENSSL_EXPORT int X509_STORE_unlock(X509_STORE *v);
OPENSSL_EXPORT int X509_STORE_up_ref(X509_STORE *store);
OPENSSL_EXPORT void X509_STORE_free(X509_STORE *v);

Expand Down Expand Up @@ -3124,6 +3143,7 @@ BORINGSSL_MAKE_UP_REF(X509_CRL, X509_CRL_up_ref)
BORINGSSL_MAKE_DELETER(X509_EXTENSION, X509_EXTENSION_free)
BORINGSSL_MAKE_DELETER(X509_INFO, X509_INFO_free)
BORINGSSL_MAKE_DELETER(X509_LOOKUP, X509_LOOKUP_free)
BORINGSSL_MAKE_DELETER(X509_OBJECT, X509_OBJECT_free)
BORINGSSL_MAKE_DELETER(X509_NAME, X509_NAME_free)
BORINGSSL_MAKE_DELETER(X509_NAME_ENTRY, X509_NAME_ENTRY_free)
BORINGSSL_MAKE_DELETER(X509_PKEY, X509_PKEY_free)
Expand Down
12 changes: 10 additions & 2 deletions tests/ci/integration/run_python_integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ function python_patch() {
local branch=${1}
local src_dir="${PYTHON_SRC_FOLDER}/${branch}"
local patch_dir="${PYTHON_PATCH_FOLDER}/${branch}"
if [[ ! $(find -L ${patch_dir} -type f -name '*.patch') ]]; then
echo "No patch for ${branch}!"
exit 1
fi
git clone https://github.com/python/cpython.git ${src_dir} \
--depth 1 \
--branch ${branch}
Expand All @@ -106,6 +110,11 @@ function python_patch() {
done
}

if [[ "$#" -eq "0" ]]; then
echo "No python branches provided for testing"
exit 1
fi

mkdir -p ${SCRATCH_FOLDER}
rm -rf ${SCRATCH_FOLDER}/*
cd ${SCRATCH_FOLDER}
Expand All @@ -126,9 +135,8 @@ pushd ${PYTHON_SRC_FOLDER}
which sysctl && ( sysctl -w net.ipv6.conf.all.disable_ipv6=0 || /bin/true )
echo 0 >/proc/sys/net/ipv6/conf/all/disable_ipv6 || /bin/true

# NOTE: cpython keeps a unique branch per version, add version branches here
# NOTE: As we add more versions to support, we may want to parallelize here
for branch in 3.10 3.11 3.12 main; do
for branch in "$@"; do
python_patch ${branch}
python_build ${branch}
python_run_tests ${branch}
Expand Down
Loading