aws / s2n-tls

An implementation of the TLS/SSL protocols

Home Page:https://aws.github.io/s2n-tls/usage-guide/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

add support for linking multiple copies of s2n-tls in an application

jmayclin opened this issue · comments

Use Case:

s2n-tls need to make sure that features work across versions of s2n-tls. This is necessary because customer version upgrades are not atomic, so customers frequently run two different versions of s2n-tls in a single fleet. Or a customer might serialize a connection to disk, upgrade s2n-tls, and then need to deserialize that connection.

An easy way to write these tests would be to use cargo to depend on old versions

# bindings/rust/integration/Cargo.toml

# this is the current version of s2n-tls that is under development 
s2n-tls = { path = "../s2n-tls"}
old-s2n-tls = { package = "s2n-tls", version = "=0.2.0" }
// integration/src/lib.rs

#[test]
fn session_resumption_test {
    let old_session_ticket = handshake(
        old_s2n_tls::connection::Connection(), 
        old_s2n_tls::connection::Connection()
    );
    
    let new_server = s2n_tls::connection::Connection();
    let new_client = s2n_tls::connection::Connection();
    new_client.set_session(session_ticket)
    # assert that we can resume a connection when using an old session ticket
    handshake(new_server, new_client);
}

Problem 1: Cargo "link"

When trying to specify an additional s2n-tls dependency, cargo returns an error.

  [dependencies]
  s2n-tls = { path = "../s2n-tls" }
+ old-s2n-tls = { package = "s2n-tls", version = "=0.2.0" }
error: failed to select a version for `s2n-tls-sys`.
    ... required by package `s2n-tls v0.1.7 (/home/ec2-user/workspace/s2n-tls/bindings/rust/s2n-tls)`
    ... which satisfies path dependency `s2n-tls` (locked to 0.1.7) of package `bench v0.1.0 (/home/ec2-user/workspace/s2n-tls/bindings/rust/bench)`
versions that meet the requirements `=0.1.7` (locked to 0.1.7) are: 0.1.7

the package `s2n-tls-sys` links to the native library `s2n-tls`, but it conflicts with a previous package which links to `s2n-tls` as well:
package `s2n-tls-sys v0.2.0`
    ... which satisfies dependency `s2n-tls-sys = "=0.2.0"` of package `s2n-tls v0.2.0`
    ... which satisfies dependency `old-s2n-tls = "=0.2.0"` of package `bench v0.1.0 (/home/ec2-user/workspace/s2n-tls/bindings/rust/bench)`

Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "s2n-tls"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.

The relevant section is this

the package s2n-tls-sys links to the native library s2n-tls, but it conflicts with a previous package which links to s2n-tls as well: package s2n-tls-sys v0.2.0

Rust allows packages to declare the native dependencies that they link to by specifying the links key in the cargo.toml. s2n-tls-sys specifies links = "s2n-tls"

so to Cargo the build situation looks like the following

graph TD
    NEW_LIB[s2n-tls 0.2.3] -->|depends| NEW_SYS[s2n-tls-sys 0.2.3]
    OLD_LIB[s2n-tls 0.2.0] -->|depends| OLD_SYS[s2n-tls-sys 0.2.0]
    NEW_SYS[s2n-tls-sys 0.2.3] -->|links| ARTIFACT[libs2n.a]
    OLD_SYS[s2n-tls-sys 0.2.0] -->|links| ARTIFACT[libs2n.a]
Loading

However, this is actually unnecessarily restrictive for most customers, since there is a separate libs2n compiled by each s2n-tls-sys.

Solution 1:

Instead of specifying a single "links" key, we can specify a version-dependent “link” property.

links = “s2n-tls-VERSION”

Our s2n-tls-sys generation logic already includes support for replacing text tokens, so this should be relatively easy to do. Note that we'd also need to rename the actual artifact outputted by the compilation process.

graph TD
    NEW_LIB[s2n-tls 0.2.3] -->|depends| NEW_SYS[s2n-tls-sys 0.2.3]
    OLD_LIB[s2n-tls 0.2.0] -->|depends| OLD_SYS[s2n-tls-sys 0.2.0]
    NEW_SYS[s2n-tls-sys 0.2.3] -->|links| NEW_ARTIFACT[libs2n_0_2_3.a]
    OLD_SYS[s2n-tls-sys 0.2.0] -->|links| OLD_ARTIFACT[libs2n_0_2_0.a]
Loading

Problem 2: Duplicate Symbols

While the above solution will successfully let cargo build the project, it doesn’t actually solve the problem of duplicate symbols. In s2n-tls-sys, all of the bindings are declared using extern C linkage.

// bindings/rust/s2n-tls-sys/src/api.rs
extern "C" {
    #[doc = " Creates a new s2n_config object. This object can (and should) be associated with many connection\n objects.\n\n The returned config will be initialized with default system certificates in its trust store.\n\n The returned config should be freed with `s2n_config_free()` after it's no longer in use by any\n connection.\n\n @returns A new configuration object suitable for configuring connections and associating certs\n and keys."]
    pub fn s2n_config_new() -> *mut s2n_config;
}

But there are now two different definitions of s2n_config_new, one in libs2n_0_2_3.a and one in libs2n_0_2_0.a.

Solution 2: Symbol Prefixing

To get around this, s2n-tls can prefix it’s symbols, similar to how AWS-LC-RS prefixes it’s symbols. AWS-LC is able to do this using existing build functionality inherited from boringssl (link).

I'm not sure what exactly the solution would look like for s2n-tls, but some options would be

  1. autogenerated "prefix header" that would swap all of the public symbols names to a prefixed version?
#define s2n_config_new s2n_config_new_0_2_3
  1. objcopy shenanigans? This seems much messier than handling it through the compiler.

Whoops, should have searched a bit harder for that. Closing as duplicate.