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

PoC: Make it possible to write code that interacts with OpenSSL in Rust #6695

Closed
wants to merge 25 commits into from

Conversation

messense
Copy link

@messense messense commented Dec 12, 2021

Implements #6634

TODO:

  • Bump MSRV to 1.48.0 for the openssl crate.
  • Fix tests on PyPy
  • Deal with Py_LIMITED_API

if line.starts_with("cargo:") {
println!("{}", line);
} else if line.starts_with("include:") {
include = line.replace("include:", "");
Copy link
Author

Choose a reason for hiding this comment

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

I'm thinking the PYO3_PYTHON above and this include from sysconfig should be obtained from pyo3-build-config crate.

Copy link
Member

Choose a reason for hiding this comment

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

This sounds good to me!

src/_cffi_src/build_openssl.py Outdated Show resolved Hide resolved
src/_cffi_src/build_openssl.py Outdated Show resolved Hide resolved
src/rust/.cargo/config.toml Outdated Show resolved Hide resolved
src/rust/build.rs Outdated Show resolved Hide resolved
src/rust/src/lib.rs Show resolved Hide resolved
@messense
Copy link
Author

running: "cc" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "-m64" "-Werror=implicit-function-declaration" "-I/home/runner/work/cryptography/cryptography/osslcache/openssl-1.1.0l-1c9f97a0fce5ec7feed4e4c4944a9d9cca01bcfc/include" "-I" "" "-I" "/usr/include" "-o" "/home/runner/work/cryptography/cryptography/src/rust/target/release/build/cryptography-rust-c7d21142bca98d61/out/_openssl.o" "-c" "/home/runner/work/cryptography/cryptography/src/rust/target/release/build/cryptography-rust-c7d21142bca98d61/out/_openssl.c"
cargo:warning=cc: error: /home/runner/work/cryptography/cryptography/src/rust/target/release/build/cryptography-rust-c7d21142bca98d61/out/_openssl.c: No such file or directory
cargo:warning=cc: fatal error: no input files

I'm having trouble understand why this fails with tox. Does tox have a cli option to ask it not to delete everything after failure?

@alex
Copy link
Member

alex commented Dec 12, 2021

Hmm, I don't think tox should be deleting anything, all the files should still be in .tox/<env name>/. Is it possibly some other file getting deleted?

@messense
Copy link
Author

Here is the same error on my local machine, looks like tox put files a in temp dir.

running: "cc" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "-arch" "arm64" "-I" "" "-I" "/opt/homebrew/opt/[email protected]/include" "-Wall" "-Wextra" "-o" "/private/var/folders/0r/4fqyt_zs2tj4zvglsb7f4zjw0000gn/T/pip-req-build-9o4tjl27/src/rust/target/release/build/cryptography-rust-c81c254f7cb1dd51/out/_openssl.o" "-c" "/private/var/folders/0r/4fqyt_zs2tj4zvglsb7f4zjw0000gn/T/pip-req-build-9o4tjl27/src/rust/target/release/build/cryptography-rust-c81c254f7cb1dd51/out/_openssl.c"
cargo:warning=clang: error: no such file or directory: '/private/var/folders/0r/4fqyt_zs2tj4zvglsb7f4zjw0000gn/T/pip-req-build-9o4tjl27/src/rust/target/release/build/cryptography-rust-c81c254f7cb1dd51/out/_openssl.c'
cargo:warning=clang: error: no input files

@alex
Copy link
Member

alex commented Dec 12, 2021

Oh no :-/ this isn't tox itself, it's pip's pep517 support. I think you want --no-clean on pip, but I'm not sure how to pass it down :-/

@messense
Copy link
Author

I also tried python -m build and it works for me. Not sure why tox doesn't like it.

@alex
Copy link
Member

alex commented Dec 12, 2021

I'm very confused by what's happening with tox, if I put 1 / 0 in build_openssl.py it still doesn't seem to fail.

@alex
Copy link
Member

alex commented Dec 12, 2021

Ok, this might be because we need println!("cargo:rerun-if-changed=../_cffi_src/build_openssl.py");. Still poking though.

@alex
Copy link
Member

alex commented Dec 12, 2021

Ok, this fixes it, it's because of PYTHONPATH:

diff --git a/src/rust/build.rs b/src/rust/build.rs
index 60d28131..0dfc2534 100644
--- a/src/rust/build.rs
+++ b/src/rust/build.rs
@@ -6,12 +6,24 @@ fn main() {
     let out_dir = env::var("OUT_DIR").unwrap();
     // FIXME: maybe pyo3-build-config should provide a way to do this?
     let python = env::var("PYO3_PYTHON").unwrap_or("python3".to_string());
+    println!("cargo:rerun-if-changed=../_cffi_src/");
+    let python_path = match env::var("PYTHONPATH") {
+        Ok(mut val) => {
+            val.push_str(":../");
+            val
+        },
+        Err(_) => {
+            "../".to_string()
+        }
+    };
     let output = Command::new(&python)
-        .env("PYTHONPATH", "../")
+        .env("PYTHONPATH", python_path)
         .env("OUT_DIR", &out_dir)
         .arg("../_cffi_src/build_openssl.py")
         .output()
         .expect("failed to execute build_openssl.py");
+    assert!(output.status.success());
+
     let stdout = String::from_utf8(output.stdout).unwrap();
     let mut include = String::new();
     for line in stdout.lines() {

@alex
Copy link
Member

alex commented Dec 13, 2021

Right now we specify our OpenSSL with CFLAGS/LDFLAGS, I guess we'll need to update to use openssl-sys's env vars. See .github/workflows/ci.yml.

@messense
Copy link
Author

Some of the test matrix passed, hooray!

Debugging unfamiliar CI failure is time consuming, so please feel free to push to my branch to experiment.

@alex
Copy link
Member

alex commented Dec 13, 2021 via email

@alex
Copy link
Member

alex commented Dec 13, 2021 via email

@messense
Copy link
Author

thread 'main' panicked at 'OpenSSL library directory does not exist: /home/runner/work/cryptography/cryptography/osslcache/openssl-3.0.0-1c9f97a0fce5ec7feed4e4c4944a9d9cca01bcfc/lib', /home/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.72/build/main.rs:68:9

Where can I download this openssl-3.0.0 cached build?

@alex
Copy link
Member

alex commented Dec 13, 2021

We don't put them anywhere, they're just built ephemerally in CI.

@messense
Copy link
Author

messense commented Dec 13, 2021

Looks like openssl-sys is expecting libs in lib/ not lib64 when using the OPENSSL_DIR env var.

/home/runner/work/cryptography/cryptography/osslcache/openssl-3.0.0-1c9f97a0fce5ec7feed4e4c4944a9d9cca01bcfc
├── bin
│   ├── c_rehash
│   └── openssl
├── include
│   └── openssl
├── lib64
│   ├── engines-3
│   ├── libcrypto.a
│   ├── libcrypto.so -> libcrypto.so.3
│   ├── libcrypto.so.3
│   ├── libssl.a
│   ├── libssl.so -> libssl.so.3
│   ├── libssl.so.3
│   ├── ossl-modules
│   └── pkgconfig
└── ssl
    ├── certs
    ├── ct_log_list.cnf
    ├── ct_log_list.cnf.dist
    ├── misc
    ├── openssl.cnf
    ├── openssl.cnf.dist
    └── private

@alex
Copy link
Member

alex commented Dec 13, 2021

How fun, putting it in lib64 is new in OpenSSL 3.0.

@reaperhulk
Copy link
Member

Yes OpenSSL switched to using lib64 by default in 3.0.0-beta2 (#6192).

@messense
Copy link
Author

Opened sfackler/rust-openssl#1581

@messense messense force-pushed the rust-openssl branch 4 times, most recently from 0447f78 to 11dcf48 Compare December 13, 2021 06:40
@messense
Copy link
Author

There are two errors I don't quite understand:

  1. undefined symbol: PyInit__openssl on PyPy
  2. Symbol not found: ___isPlatformVersionAtLeast on macOS Python 3.10

@messense
Copy link
Author

Symbol not found: ___isPlatformVersionAtLeast

Could this be that the pre-compiled OpenSSL is built on macOS 11 but we're using it on macOS 10.15?

@messense
Copy link
Author

undefined symbol: PyInit__openssl on PyPy

This is interesting.

root@5b52dbd4d5c2:/io# readelf -s /io/src/cryptography/hazmat/bindings/_rust.pypy38-pp73-aarch64-linux-gnu.so | grep PyInit
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND PyInit__openssl
   148: 0000000000115290    32 FUNC    GLOBAL DEFAULT   11 PyInit__rust
 52405: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND PyInit__openssl
 52520: 0000000000115290    32 FUNC    GLOBAL DEFAULT   11 PyInit__rust

V.S. the _openssl.*.so on main branch for PyPy

root@5b52dbd4d5c2:/io# readelf -s src/cryptography/hazmat/bindings/_openssl.pypy38-pp73-aarch64-linux-gnu.so | grep PyInit
root@5b52dbd4d5c2:/io# readelf -s src/cryptography/hazmat/bindings/_openssl.pypy38-pp73-aarch64-linux-gnu.so | grep _cffi_pypyinit
   709: 0000000000025fa0    32 FUNC    GLOBAL DEFAULT   11 _cffi_pypyinit__openssl
  1407: 0000000000025fa0    32 FUNC    GLOBAL DEFAULT   11 _cffi_pypyinit__openssl

The only useful information I can find is pypa/auditwheel#93

Looking at the generated _openssl.c

#ifdef PYPY_VERSION
PyMODINIT_FUNC
_cffi_pypyinit__openssl(const void *p[])
{
    p[0] = (const void *)0x2601;
    p[1] = &_cffi_type_context;
#if PY_MAJOR_VERSION >= 3
    return NULL;
#endif
}
#  ifdef _MSC_VER
     PyMODINIT_FUNC
#  if PY_MAJOR_VERSION >= 3
     PyInit__openssl(void) { return NULL; }
#  else
     init_openssl(void) { }
#  endif
#  endif
#elif PY_MAJOR_VERSION >= 3
PyMODINIT_FUNC
PyInit__openssl(void)
{
  return _cffi_init("_openssl", 0x2601, &_cffi_type_context);
}
#else
PyMODINIT_FUNC
init_openssl(void)
{
  _cffi_init("_openssl", 0x2601, &_cffi_type_context);
}
#endif

It seems that we need to define PYPY_VERSION for it when building on PyPy. PyInit__openssl(void) { return NULL; } is also interesting.

// Enable abi3 mode if we're not using PyPy.
if python_impl != "PyPy" {
// cp36
// build.define("Py_LIMITED_API", "0x030600f0");
Copy link
Author

Choose a reason for hiding this comment

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

Unable to get cffi embedding to compile with Py_LIMITED_API.

Copy link
Member

Choose a reason for hiding this comment

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

Oof :-/ Maybe we need to do the embedding way on PyPy and the "normal" way on CPython? PyPy already doesn't support Py_LIMITED_API.

// let ptr = PyInit__openssl();
// pyo3::types::PyModule::from_owned_ptr(py, ptr)
make_cryptography_openssl_module();
pyo3::types::PyModule::import(py, "_openssl")?
Copy link
Author

Choose a reason for hiding this comment

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

This also makes import _openssl work after importing cryptography, could this be a problem for end users?

Copy link
Member

Choose a reason for hiding this comment

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

Probably not. Maybe we should name it _cryptography_openssl or something to ensure no one accidentally uses it?

@messense
Copy link
Author

messense commented Dec 28, 2021

It's not building on Windows, I'm not sure why and I don't have a Windows machine, help appreciated!

Edit: Tried to include Winsock2.h before windows.h, not working.

#ifndef _MSC_VER
   /* --- Assuming a GCC not infinitely old --- */
# define cffi_compare_and_swap(l,o,n)  __sync_bool_compare_and_swap(l,o,n)
# define cffi_write_barrier()          __sync_synchronize()
# if !defined(__amd64__) && !defined(__x86_64__) &&   \
     !defined(__i386__) && !defined(__i386)
#   define cffi_read_barrier()         __sync_synchronize()
# else
#   define cffi_read_barrier()         (void)0
# endif
#else
   /* --- Windows threads version --- */
# include <Windows.h>
# define cffi_compare_and_swap(l,o,n) \
                               (InterlockedCompareExchangePointer(l,n,o) == (o))
# define cffi_write_barrier()       InterlockedCompareExchange(&_cffi_dummy,0,0)
# define cffi_read_barrier()           (void)0
static volatile LONG _cffi_dummy;
#endif

Looks like cffi embedding includes Windows.h before we define WIN32_LEAN_AND_MEAN.

@messense
Copy link
Author

messense commented Dec 28, 2021

Unfortunately looks like PyPy tests are hanging with the cffi embedding approach.

Debugged it locally (pypy3.8 setup.py develop then import) and got the following error message

>>>> from cryptography.hazmat.bindings import _rust
Fatal RPython error: a thread is trying to wait for the GIL, but the GIL was not initialized
(For PyPy, see https://bitbucket.org/pypy/pypy/issues/2274)
Aborted

https://foss.heptapod.net/pypy/pypy/-/issues/2274

@alex
Copy link
Member

alex commented Jan 2, 2022

Looks like that issue is already closed, I'd suggest either opening a new one or including a comment on the cffi thread we have open. (Sorry I was slow to look at this!)

c_file = os.path.join(out_dir, module_name + source_extension)
ffi.embedding_api(
"""
extern "Python" void _this_is_not_used(void);
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a comment explaining what this is for?

@@ -73,6 +73,12 @@ def build_ffi(
verify_source += '\n#define CRYPTOGRAPHY_PACKAGE_VERSION "{}"'.format(
about["__version__"]
)
verify_source += r"""

int make_cryptography_openssl_module(void) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's name this Cryptography_make_openssl_module(), which is the prefix we generally use.

Comment on lines +17 to +18
ffi = _openssl.ffi # type: ignore
lib = _openssl.lib # type: ignore
Copy link
Member

Choose a reason for hiding this comment

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

What actually uses these? Theoretically everything should go through a Binding instance.

// Enable abi3 mode if we're not using PyPy.
if python_impl != "PyPy" {
// cp36
// build.define("Py_LIMITED_API", "0x030600f0");
Copy link
Member

Choose a reason for hiding this comment

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

Oof :-/ Maybe we need to do the embedding way on PyPy and the "normal" way on CPython? PyPy already doesn't support Py_LIMITED_API.

// let ptr = PyInit__openssl();
// pyo3::types::PyModule::from_owned_ptr(py, ptr)
make_cryptography_openssl_module();
pyo3::types::PyModule::import(py, "_openssl")?
Copy link
Member

Choose a reason for hiding this comment

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

Probably not. Maybe we should name it _cryptography_openssl or something to ensure no one accidentally uses it?

@messense
Copy link
Author

Sorry I'm busy recently and unable to make progress on this. I'll come back to this maybe after Chinese lunar new year. In the meantime, feel free to take over.

@alex
Copy link
Member

alex commented Jan 20, 2022 via email

@alex
Copy link
Member

alex commented Apr 30, 2022

I'm attempting to take over this PR in #7164. Thanks for all your work on this!

Still stuck on the PyPy hangs!

@alex alex closed this Apr 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants