-
Notifications
You must be signed in to change notification settings - Fork 171
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
store and storecontext: shareable when frozen #807
base: master
Are you sure you want to change the base?
Conversation
b4b3141
to
dcdd71f
Compare
An cert = OpenSSL::X509::Certificate.new
store = OpenSSL::X509::Store.new
store.add_cert(cert) # increments the reference counter on X509 wrapped by `cert`, using X509_up_ref()
store.freeze
cert.serial = 1234 # indirectly modifies `store` I'm not sure what is the best solution for this.
Regarding I wonder if we can keep it unshareable, as I can't think of any use case of a frozen We should still add these |
IMO the least intrusive and less overhead way of doing so is what you propose in 3. The store could then keep added certificates in an internal array, and a call to Bear in mind that the "happy path" this PR is targeting is freeze-and-share the default cert store, which uses For store context, I agree with you, the yielded ones don't need to be shared, as they're only used within a block, and there's very little chance a ractor is initiated there to use it. I think that reasoning matches my patch, right? |
FYI: a After rethinking the approach 3, it wouldn't work because someone can add the same certificate repeatedly. We don't want the internal array to grow indefinitely in that case.
While looking at the man pages, I stumbled upon a function called
It is unsafe to be shared even if we have frozen checks. While it's technically possible to introduce a mutex to make it safe, I don't think it's worth it. My suggestion is to drop |
dcdd71f
to
6432852
Compare
I'm a bit out of my ruby internals depth here. Not sure how to access referenced objects. Do you have an example I can learn from?
Wouldn't a simple "return if present in array" suffice? Or is it supposed to allow multiple calls to Added a POC in the last commit.
Indeed, I forgot I enabled it. reverted. |
Yes. Duplicates are checked by the contents and the details aren't documented. Even if we can replicate the logic and maintain a list ourselves, it seems inefficient and fragile against future changes in OpenSSL. |
If I understand correctly, If we have a complete list of dependencies, we can just store it in an instance variable like 80ee037 did and that's all. However, it doesn't seem possible. I don't have a good example to show, but we'll need to extend this current openssl/ext/openssl/ossl_x509store.c Lines 115 to 123 in ebadd0f
|
I was thinking of doing something like this with in x509_cert.c, when initializing an object
X509_get_ex_data(x509, ossl_x509_ex_obj_idx, (void *)self);
// Another question is what to do if `x509` already has a linked Ruby object
// Is that possible in the first place, or not?
in the dfree function
X509_set_ex_data(x509, ossl_x509_ex_obj_idx, NULL);
in Init_x509cert()
ossl_x509_ex_obj_idx = X509_get_ex_new_index(0, (void *)"ossl_x509_ex_obj_idx", 0, 0, 0);
diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c
index 670519febc4a..43a51ad4f114 100644
--- a/ext/openssl/ossl_x509store.c
+++ b/ext/openssl/ossl_x509store.c
@@ -120,6 +132,32 @@ ossl_x509store_mark(void *ptr)
// However we do need to ensure GC compaction won't move it, hence why
// we call rb_gc_mark here.
rb_gc_mark((VALUE)X509_STORE_get_ex_data(store, store_ex_verify_cb_idx));
+
+ STACK_OF(X509_OBJECT) *objects = X509_STORE_get1_objects(store);
+ for (int i = 0; i < sk_X509_OBJECT_num(objects); i++) {
+ X509_OBJECT *xobj = sk_X509_OBJECT_value(objects, i);
+ switch (X509_OBJECT_get_type(xobj)) {
+ case X509_LU_X509: {
+ X509 *x509 = X509_OBJECT_get0_X509(xobj);
+ VALUE obj = (VALUE)X509_get_ex_data(x509, ossl_x509_ex_obj_idx);
+ if (obj != 0)
+ rb_gc_mark(obj);
+ break;
+ }
+ case X509_LU_CRL: {
+ X509_CRL *crl = X509_OBJECT_get0_X509_CRL(xobj);
+ // Wait... X509_CRL doesn't even have an ex_data. Any way around?
+ VALUE obj = (VALUE)X509_CRL_get_ex_data(crl, ossl_x509crl_ex_obj_idx);
+ if (obj != 0)
+ rb_gc_mark(obj);
+ break;
+ }
+ default:
+ // TODO: future OpenSSL versions may add more types
+ break;
+ }
+ }
+ sk_X509_OBJECT_pop_free(objects, X509_OBJECT_free);
}
static void |
…ng state change functions this makes them mostly useless, considering how they're used standalone
e4b656e
to
440618c
Compare
Why not? I've just extended the commit to include CRLs and the
I see what you mean now, that'd be awesome if it worked. FWIW I was trying to avoid explicitly using While my suggested approach imposes some overhead, I'd say it's not much (if it works, of course). FWIW we're talking about two extra objects (array), and some operational overhead on calling |
The issue is with keeping the list of references up to date. As I mentioned in an earlier comment, it's not documented how
Isn't a AFAIK, before the |
440618c
to
9bcc630
Compare
ok, I see what you mean. I made a small tweak to accomodate that requirement: always call This overhead would probably be mitigated if your workaround using
I think that's something that only the ruby core team can answer, but the way I perceived it, operations like Moreover, the main goal of the MR is to make |
It makes no difference if you check before or after the Actually, this is a new behavior that was changed in OpenSSL 1.1.0i (a patch release!). The old behavior was undocumented and the change was therefore allowed even without a changelog entry: openssl/openssl@f96d3c1. This is a good example why we shouldn't depend on OpenSSL's undocumented behaviors. There is no guarantee that
Note that the approach I suggested with Honestly, I don't know what we can do. Would it be acceptable that
I looked at these issues and while I didn't really get the reason for rejection, changing A
FWIW, |
Isn't that what the last iteration of the last commit does? 🤔 As in, in the case of certs, unless frozen, it always calls
Indeed, that's less important for that use-case. but one has to deal with class-level expectations nontheless. |
This is the problematic part: if(!RTEST(rb_funcall(certificates, rb_intern("include?"), 1, arg)))
rb_ary_push(certificates, arg); This makes the assumption that the certificate
I wanted to suggest a safe option where freezing a store with an unshareable certificates/CRL should not make it shareable. There should be a smarter way, but one way I can think of is to inject a forever unshareable object into it when we can't confidently say the This is completely untested:
--- a/ext/openssl/ossl_x509store.c
+++ b/ext/openssl/ossl_x509store.c
@@ -429,6 +429,22 @@ ossl_x509store_set_default_paths(VALUE self)
return Qnil;
}
+static void
+ossl_make_ractor_unshareable(VALUE obj)
+{
+ static const rb_data_type_t ossl_ractor_unshareable_type = {
+ "OpenSSL/RACTOR_UNSHAREABLE_DUMMY_OBJECT",
+ {
+ 0,
+ },
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
+ };
+
+ if (!RTEST(rb_attr_get(obj, rb_intern("ractor_unshareable_poison"))))
+ rb_ivar_set(obj, rb_intern("ractor_unshareable_poison"),
+ TypedData_Wrap_Struct(0, &ossl_ractor_unshareable_type, NULL));
+}
+
/*
* call-seq:
* store.add_cert(cert) -> self
@@ -448,6 +464,9 @@ ossl_x509store_add_cert(VALUE self, VALUE arg)
if (X509_STORE_add_cert(store, cert) != 1)
ossl_raise(eX509StoreError, "X509_STORE_add_cert");
+ if (!rb_ractor_shareable_p(arg))
+ ossl_make_ractor_unshareable(self);
+
return self;
}
|
Ahhh, I finally see what you mean:
If I understood the suggestion there, you'd set a hidden ivar with an unshareable object which would then make it impossible to share further. I could also think of this alternative: why don't we just unset the shareable when frozen flag when adding cert/crl? Smth like: # on add cert to unfrozen store
RTYPEDDATA(self)-> flags ^= RUBY_TYPED_FROZEN_SHAREABLE; Not sure if there's performance impact here. It's also dependent on the internal shape of the rbdata struct. Will it fail under compaction? Let me know what you think. |
I think the cost for setting an instance variable would be negligible. Instances variables should be handled during compaction automatically. But as a hack, I didn't actually test if this works. In any case, we must take care of |
version of #803 scoped to store and store context classes.
It adds frozen checks in all methods which perform state changes on internal state, which includes
X509_STORE_CTX
andX509_STORE
state.This makes them useless standalone, due to how
OpenSSL:::X509::Store#verify
is implemented, where when it's called, the context verification call info gets passed into store ivars. I believe this could be reimplemented in a less intrusive way, where this info could be part of the return data of the method. This would be a breaking change though.This change still has value considering the main goal of making
DEFAULT_CERT_STORE
shareable due to its usage inOpenSSL::SSL::SSLContext
, and how the internalX509_STORE
is passed internally toSSL_CTX
viaSSL_CTX_set_cert_store
, so the ruby object doesn't get exposed.