ring/build.rs
David Benjamin 76e98c4351 Always end BN_mod_exp_mont_consttime with normal Montgomery reduction.
This partially fixes a bug where, on x86_64, BN_mod_exp_mont_consttime
would sometimes return m, the modulus, when it should have returned
zero. Thanks to Guido Vranken for reporting it. It is only a partial fix
because the same bug also exists in the "rsaz" codepath. That will be
fixed in the subsequent CL. (See the commented out test.)

The bug only affects zero outputs (with non-zero inputs), so we believe
it has no security impact on our cryptographic functions. BoringSSL
calls BN_mod_exp_mont_consttime in the following cases:

- RSA private key operations
- Primality testing, raising the witness to the odd part of p-1
- DSA keygen and key import, pub = g^priv (mod p)
- DSA signing, r = g^k (mod p)
- DH keygen, pub = g^priv (mod p)
- Diffie-Hellman, secret = peer^priv (mod p)

It is not possible in the RSA private key operation, provided p and q
are primes. If using CRT, we are working modulo a prime, so zero output
with non-zero input is impossible. If not using CRT, we work mod n.
While there are nilpotent values mod n, none of them hit zero by
exponentiating. (Both p and q would need to divide the input, which
means n divides the input.)

In primality testing, this can only be hit when the input was composite.
But as the rest of the loop cannot then hit 1, we'll correctly report it
as composite anyway.

DSA and DH work modulo a prime, where this case cannot happen.

Analysis:

This bug is the result of sloppiness with the looser bounds from "almost
Montgomery multiplication", described in
https://eprint.iacr.org/2011/239. Prior to upstream's
ec9cc70f72454b8d4a84247c86159613cee83b81, I believe x86_64-mont5.pl
implemented standard Montgomery reduction (the left half of figure 3 in
the paper).

Though it did not document this, ec9cc70f7245 changed it to implement
the "almost" variant (the right half of the figure.) The difference is
that, rather than subtracting if T >= m, it subtracts if T >= R. In
code, it is the difference between something like our bn_reduce_once,
vs. subtracting based only on T's carry bit. (Interestingly, the
.Lmul_enter branch of bn_mul_mont_gather5 seems to still implement
normal reduction, but the .Lmul4x_enter branch is an almost reduction.)

That means none of the intermediate values here are bounded by m. They
are only bounded by R. Accordingly, Figure 2 in the paper ends with
step 10: REDUCE h modulo m. BN_mod_exp_mont_consttime is missing this
step. The bn_from_montgomery call only implements step 9, AMM(h, 1).
(x86_64-mont5.pl's bn_from_montgomery only implements an almost
reduction.)

The impact depends on how unreduced AMM(h, 1) can be. Remark 1 of the
paper discusses this, but is ambiguous about the scope of its 2^(n-1) <
m < 2^n precondition. The m+1 bound appears to be unconditional:

Montgomery reduction ultimately adds some 0 <= Y < m*R to T, to get a
multiple of R, and then divides by R. The output, pre-subtraction, is
thus less than m + T/R. MM works because T < mR => T' < m + mR/R = 2m.
A single subtraction of m if T' >= m gives T'' < m. AMM works because
T < R^2 => T' < m + R^2/R = m + R. A single subtraction of m if T' >= R
gives T'' < R. See also Lemma 1, Section 3 and Section 4 of the paper,
though their formulation is more complicated to capture the word-by-word
algorithm. It's ultimately the same adjustment to T.

But in AMM(h, 1), T = h*1 = h < R, so AMM(h, 1) < m + R/R = m + 1. That
is, AMM(h, 1) <= m. So the only case when AMM(h, 1) isn't fully reduced
is if it outputs m. Thus, our limited impact. Indeed, Remark 1 mentions
step 10 isn't necessary because m is a prime and the inputs are
non-zero. But that doesn't apply here because BN_mod_exp_mont_consttime
may be called elsewhere.

Fix:

To fix this, we could add the missing step 10, but a full division would
not be constant-time. The analysis above says it could be a single
subtraction, bn_reduce_once, but then we could integrate it into
the subtraction already in plain Montgomery reduction, implemented by
uppercase BN_from_montgomery. h*1 = h < R <= m*R, so we are within
bounds.

Thus, we delete lowercase bn_from_montgomery altogether, and have the
mont5 path use the same BN_from_montgomery ending as the non-mont5 path.
This only impacts the final step of the whole exponentiation and has no
measurable perf impact.

In doing so, add comments describing these looser bounds.  This includes
one subtlety that BN_mod_exp_mont_consttime actually mixes bn_mul_mont
(MM) with bn_mul_mont_gather5/bn_power5 (AMM). But this is fine because
MM is AMM-compatible; when passed AMM's looser inputs, it will still
produce a correct looser output.

Ideally we'd drop the "almost" reduction and stick to the more
straightforward bounds. As this only impacts the final subtraction in
each reduction, I would be surprised if it actually had a real
performance impact. But this would involve deeper change to
x86_64-mont5.pl, so I haven't tried this yet.

I believe this is basically the same bug as
https://github.com/golang/go/issues/13907 from Go.

Change-Id: I06f879777bb2ef181e9da7632ec858582e2afa38
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/52825
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
2023-09-15 17:01:39 -07:00

1048 lines
32 KiB
Rust

// Copyright 2015-2016 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//! Build the non-Rust components.
// It seems like it would be a good idea to use `log!` for logging, but it
// isn't worth having the external dependencies (one for the `log` crate, and
// another for the concrete logging implementation). Instead we use `eprintln!`
// to log everything to stderr.
use std::{
fs::{self, DirEntry},
io::Write,
path::{Path, PathBuf},
process::Command,
};
const X86: &str = "x86";
const X86_64: &str = "x86_64";
const AARCH64: &str = "aarch64";
const ARM: &str = "arm";
#[rustfmt::skip]
const RING_SRCS: &[(&[&str], &str)] = &[
(&[], "crypto/curve25519/curve25519.c"),
(&[], "crypto/fipsmodule/aes/aes_nohw.c"),
(&[], "crypto/fipsmodule/bn/montgomery.c"),
(&[], "crypto/fipsmodule/bn/montgomery_inv.c"),
(&[], "crypto/fipsmodule/ec/ecp_nistz.c"),
(&[], "crypto/fipsmodule/ec/gfp_p256.c"),
(&[], "crypto/fipsmodule/ec/gfp_p384.c"),
(&[], "crypto/fipsmodule/ec/p256.c"),
(&[], "crypto/limbs/limbs.c"),
(&[], "crypto/mem.c"),
(&[], "crypto/poly1305/poly1305.c"),
(&[AARCH64, ARM, X86_64, X86], "crypto/crypto.c"),
(&[X86_64, X86], "crypto/cpu_intel.c"),
(&[X86], "crypto/fipsmodule/aes/asm/aesni-x86.pl"),
(&[X86], "crypto/fipsmodule/aes/asm/vpaes-x86.pl"),
(&[X86], "crypto/fipsmodule/bn/asm/x86-mont.pl"),
(&[X86], "crypto/chacha/asm/chacha-x86.pl"),
(&[X86], "crypto/fipsmodule/modes/asm/ghash-x86.pl"),
(&[X86_64], "crypto/chacha/asm/chacha-x86_64.pl"),
(&[X86_64], "crypto/fipsmodule/aes/asm/aesni-x86_64.pl"),
(&[X86_64], "crypto/fipsmodule/aes/asm/vpaes-x86_64.pl"),
(&[X86_64], "crypto/fipsmodule/bn/asm/x86_64-mont.pl"),
(&[X86_64], "crypto/fipsmodule/bn/asm/x86_64-mont5.pl"),
(&[X86_64], "crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl"),
(&[X86_64], "crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl"),
(&[X86_64], "crypto/fipsmodule/modes/asm/ghash-x86_64.pl"),
(&[X86_64], "crypto/poly1305/poly1305_vec.c"),
(&[X86_64], SHA512_X86_64),
(&[X86_64], "crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl"),
(&[AARCH64, X86_64], "crypto/fipsmodule/ec/p256-nistz.c"),
(&[AARCH64, ARM], "crypto/fipsmodule/aes/asm/aesv8-armx.pl"),
(&[AARCH64, ARM], "crypto/fipsmodule/modes/asm/ghashv8-armx.pl"),
(&[ARM], "crypto/fipsmodule/aes/asm/bsaes-armv7.pl"),
(&[ARM], "crypto/fipsmodule/aes/asm/vpaes-armv7.pl"),
(&[ARM], "crypto/fipsmodule/bn/asm/armv4-mont.pl"),
(&[ARM], "crypto/chacha/asm/chacha-armv4.pl"),
(&[ARM], "crypto/curve25519/asm/x25519-asm-arm.S"),
(&[ARM], "crypto/fipsmodule/modes/asm/ghash-armv4.pl"),
(&[ARM], "crypto/poly1305/poly1305_arm.c"),
(&[ARM], "crypto/poly1305/poly1305_arm_asm.S"),
(&[ARM], "crypto/fipsmodule/sha/asm/sha256-armv4.pl"),
(&[ARM], "crypto/fipsmodule/sha/asm/sha512-armv4.pl"),
(&[AARCH64], "crypto/chacha/asm/chacha-armv8.pl"),
(&[AARCH64], "crypto/cipher_extra/asm/chacha20_poly1305_armv8.pl"),
(&[AARCH64], "crypto/fipsmodule/aes/asm/vpaes-armv8.pl"),
(&[AARCH64], "crypto/fipsmodule/bn/asm/armv8-mont.pl"),
(&[AARCH64], "crypto/fipsmodule/ec/asm/p256-armv8-asm.pl"),
(&[AARCH64], "crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl"),
(&[AARCH64], SHA512_ARMV8),
];
const SHA256_X86_64: &str = "crypto/fipsmodule/sha/asm/sha256-x86_64.pl";
const SHA512_X86_64: &str = "crypto/fipsmodule/sha/asm/sha512-x86_64.pl";
const SHA256_ARMV8: &str = "crypto/fipsmodule/sha/asm/sha256-armv8.pl";
const SHA512_ARMV8: &str = "crypto/fipsmodule/sha/asm/sha512-armv8.pl";
const RING_TEST_SRCS: &[&str] = &[("crypto/constant_time_test.c")];
const PREGENERATED: &str = "pregenerated";
fn c_flags(compiler: &cc::Tool) -> &'static [&'static str] {
if !compiler.is_like_msvc() {
static NON_MSVC_FLAGS: &[&str] = &[
"-std=c1x", // GCC 4.6 requires "c1x" instead of "c11"
"-Wbad-function-cast",
"-Wnested-externs",
"-Wstrict-prototypes",
];
NON_MSVC_FLAGS
} else {
&[]
}
}
fn cpp_flags(compiler: &cc::Tool) -> &'static [&'static str] {
if !compiler.is_like_msvc() {
static NON_MSVC_FLAGS: &[&str] = &[
"-pedantic",
"-pedantic-errors",
"-Wall",
"-Wextra",
"-Wcast-align",
"-Wcast-qual",
"-Wconversion",
"-Wenum-compare",
"-Wfloat-equal",
"-Wformat=2",
"-Winline",
"-Winvalid-pch",
"-Wmissing-field-initializers",
"-Wmissing-include-dirs",
"-Wredundant-decls",
"-Wshadow",
"-Wsign-compare",
"-Wsign-conversion",
"-Wundef",
"-Wuninitialized",
"-Wwrite-strings",
"-fno-strict-aliasing",
"-fvisibility=hidden",
];
NON_MSVC_FLAGS
} else {
static MSVC_FLAGS: &[&str] = &[
"/GS", // Buffer security checks.
"/Gy", // Enable function-level linking.
"/EHsc", // C++ exceptions only, only in C++.
"/GR-", // Disable RTTI.
"/Zc:wchar_t",
"/Zc:forScope",
"/Zc:inline",
"/Zc:rvalueCast",
// Warnings.
"/sdl",
"/Wall",
"/wd4127", // C4127: conditional expression is constant
"/wd4464", // C4464: relative include path contains '..'
"/wd4514", // C4514: <name>: unreferenced inline function has be
"/wd4710", // C4710: function not inlined
"/wd4711", // C4711: function 'function' selected for inline expansion
"/wd4820", // C4820: <struct>: <n> bytes padding added after <name>
"/wd5045", /* C5045: Compiler will insert Spectre mitigation for memory load if
* /Qspectre switch specified */
];
MSVC_FLAGS
}
}
const LD_FLAGS: &[&str] = &[];
// None means "any OS" or "any target". The first match in sequence order is
// taken.
const ASM_TARGETS: &[AsmTarget] = &[
AsmTarget {
oss: LINUX_ABI,
arch: "aarch64",
perlasm_format: "linux64",
asm_extension: "S",
preassemble: false,
},
AsmTarget {
oss: LINUX_ABI,
arch: "arm",
perlasm_format: "linux32",
asm_extension: "S",
preassemble: false,
},
AsmTarget {
oss: LINUX_ABI,
arch: "x86",
perlasm_format: "elf",
asm_extension: "S",
preassemble: false,
},
AsmTarget {
oss: LINUX_ABI,
arch: "x86_64",
perlasm_format: "elf",
asm_extension: "S",
preassemble: false,
},
AsmTarget {
oss: MACOS_ABI,
arch: "aarch64",
perlasm_format: "ios64",
asm_extension: "S",
preassemble: false,
},
AsmTarget {
oss: MACOS_ABI,
arch: "x86_64",
perlasm_format: "macosx",
asm_extension: "S",
preassemble: false,
},
AsmTarget {
oss: &[WINDOWS],
arch: "x86",
perlasm_format: "win32n",
asm_extension: "asm",
preassemble: true,
},
AsmTarget {
oss: &[WINDOWS],
arch: "x86_64",
perlasm_format: "nasm",
asm_extension: "asm",
preassemble: true,
},
AsmTarget {
oss: &[WINDOWS],
arch: "aarch64",
perlasm_format: "win64",
asm_extension: "S",
preassemble: true,
},
];
struct AsmTarget {
/// Operating systems.
oss: &'static [&'static str],
/// Architectures.
arch: &'static str,
/// The PerlAsm format name.
perlasm_format: &'static str,
/// The filename extension for assembly files.
asm_extension: &'static str,
/// Whether pre-assembled object files should be included in the Cargo
/// package instead of the asm sources. This way, the user doesn't need
/// to install an assembler for the target. This is particularly important
/// for x86/x86_64 Windows since an assembler doesn't come with the C
/// compiler.
preassemble: bool,
}
/// Operating systems that have the same ABI as Linux on every architecture
/// mentioned in `ASM_TARGETS`.
const LINUX_ABI: &[&str] = &[
"android",
"dragonfly",
"freebsd",
"fuchsia",
"illumos",
"netbsd",
"openbsd",
"linux",
"redox",
"solaris",
];
/// Operating systems that have the same ABI as macOS on every architecture
/// mentioned in `ASM_TARGETS`.
const MACOS_ABI: &[&str] = &["ios", "macos"];
const WINDOWS: &str = "windows";
/// Read an environment variable and tell Cargo that we depend on it.
///
/// This needs to be used for any environment variable that isn't a standard
/// Cargo-supplied variable.
///
/// The name is static since we intend to only read a static set of environment
/// variables.
fn read_env_var(name: &'static str) -> Result<String, std::env::VarError> {
println!("cargo:rerun-if-env-changed={}", name);
std::env::var(name)
}
fn main() {
const RING_PREGENERATE_ASM: &str = "RING_PREGENERATE_ASM";
match read_env_var(RING_PREGENERATE_ASM).as_deref() {
Ok("1") => {
pregenerate_asm_main();
}
Err(std::env::VarError::NotPresent) => ring_build_rs_main(),
_ => {
panic!("${} has an invalid value", RING_PREGENERATE_ASM);
}
}
}
fn ring_build_rs_main() {
use std::env;
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = PathBuf::from(out_dir);
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let is_musl = {
let env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
env.starts_with("musl")
};
let is_git = std::fs::metadata(".git").is_ok();
// Published builds are always built in release mode.
let is_debug = is_git && env::var("DEBUG").unwrap() != "false";
// If `.git` exists then assume this is the "local hacking" case where
// we want to make it easy to build *ring* using `cargo build`/`cargo test`
// without a prerequisite `package` step, at the cost of needing additional
// tools like `Perl` and/or `nasm`.
//
// If `.git` doesn't exist then assume that this is a packaged build where
// we want to optimize for minimizing the build tools required: No Perl,
// no nasm, etc.
let use_pregenerated = !is_git;
// During local development, force warnings in non-Rust code to be treated
// as errors. Since warnings are highly compiler-dependent and compilers
// don't maintain backward compatibility w.r.t. which warnings they issue,
// don't do this for packaged builds.
let force_warnings_into_errors = is_git;
let target = Target {
arch,
os,
is_musl,
is_debug,
force_warnings_into_errors,
};
let pregenerated = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join(PREGENERATED);
build_c_code(
&target,
pregenerated,
&out_dir,
&ring_core_prefix(),
use_pregenerated,
);
emit_rerun_if_changed()
}
fn pregenerate_asm_main() {
println!("cargo:rustc-cfg=pregenerate_asm_only");
let pregenerated = PathBuf::from(PREGENERATED);
std::fs::create_dir(&pregenerated).unwrap();
let pregenerated_tmp = pregenerated.join("tmp");
std::fs::create_dir(&pregenerated_tmp).unwrap();
generate_prefix_symbols_asm_headers(&pregenerated_tmp, &ring_core_prefix()).unwrap();
for asm_target in ASM_TARGETS {
// For Windows, package pregenerated object files instead of
// pregenerated assembly language source files, so that the user
// doesn't need to install the assembler.
let asm_dir = if asm_target.preassemble {
&pregenerated_tmp
} else {
&pregenerated
};
let perlasm_src_dsts = perlasm_src_dsts(asm_dir, asm_target);
perlasm(&perlasm_src_dsts, asm_target);
if asm_target.preassemble {
// Preassembly is currently only done for Windows targets.
assert_eq!(&asm_target.oss, &[WINDOWS]);
let os = WINDOWS;
let srcs = asm_srcs(perlasm_src_dsts);
let target = Target {
arch: asm_target.arch.to_owned(),
os: os.to_owned(),
is_musl: false,
is_debug: false,
force_warnings_into_errors: true,
};
for src in srcs {
compile(&src, &target, &pregenerated_tmp, &pregenerated);
}
}
}
}
struct Target {
arch: String,
os: String,
/// Is the target one that uses the musl C standard library instead of the default?
is_musl: bool,
/// Is this a debug build? This affects whether assertions might be enabled
/// in the C code. For packaged builds, this should always be `false`.
is_debug: bool,
/// true: Force warnings to be treated as errors.
/// false: Use the default behavior (perhaps determined by `$CFLAGS`, etc.)
force_warnings_into_errors: bool,
}
fn build_c_code(
target: &Target,
pregenerated: PathBuf,
out_dir: &Path,
ring_core_prefix: &str,
use_pregenerated: bool,
) {
println!("cargo:rustc-env=RING_CORE_PREFIX={}", ring_core_prefix);
let asm_target = ASM_TARGETS.iter().find(|asm_target| {
asm_target.arch == target.arch && asm_target.oss.contains(&target.os.as_ref())
});
let asm_dir = if use_pregenerated {
&pregenerated
} else {
out_dir
};
generate_prefix_symbols_header(out_dir, "prefix_symbols.h", '#', None, ring_core_prefix)
.unwrap();
generate_prefix_symbols_asm_headers(out_dir, ring_core_prefix).unwrap();
let asm_srcs = if let Some(asm_target) = asm_target {
let perlasm_src_dsts = perlasm_src_dsts(asm_dir, asm_target);
if !use_pregenerated {
perlasm(&perlasm_src_dsts[..], asm_target);
}
let mut asm_srcs = asm_srcs(perlasm_src_dsts);
// For Windows we also pregenerate the object files for non-Git builds so
// the user doesn't need to install the assembler.
if use_pregenerated && target.os == WINDOWS {
asm_srcs = asm_srcs
.iter()
.map(|src| obj_path(&pregenerated, src.as_path()))
.collect::<Vec<_>>();
}
asm_srcs
} else {
Vec::new()
};
let core_srcs = sources_for_arch(&target.arch)
.into_iter()
.filter(|p| !is_perlasm(p))
.collect::<Vec<_>>();
let test_srcs = RING_TEST_SRCS.iter().map(PathBuf::from).collect::<Vec<_>>();
let libs = [
("", &core_srcs[..], &asm_srcs[..]),
("test", &test_srcs[..], &[]),
];
// XXX: Ideally, ring-test would only be built for `cargo test`, but Cargo
// can't do that yet.
libs.iter()
.for_each(|&(lib_name_suffix, srcs, additional_srcs)| {
let lib_name = String::from(ring_core_prefix) + lib_name_suffix;
build_library(target, out_dir, &lib_name, srcs, additional_srcs)
});
println!(
"cargo:rustc-link-search=native={}",
out_dir.to_str().expect("Invalid path")
);
}
fn build_library(
target: &Target,
out_dir: &Path,
lib_name: &str,
srcs: &[PathBuf],
additional_srcs: &[PathBuf],
) {
// Compile all the (dirty) source files into object files.
let objs = additional_srcs
.iter()
.chain(srcs.iter())
.map(|f| compile(f, target, out_dir, out_dir))
.collect::<Vec<_>>();
// Rebuild the library if necessary.
let lib_path = PathBuf::from(out_dir).join(format!("lib{}.a", lib_name));
let mut c = cc::Build::new();
for f in LD_FLAGS {
let _ = c.flag(f);
}
match target.os.as_str() {
"macos" => {
let _ = c.flag("-fPIC");
let _ = c.flag("-Wl,-dead_strip");
}
_ => {
let _ = c.flag("-Wl,--gc-sections");
}
}
for o in objs {
let _ = c.object(o);
}
// Handled below.
let _ = c.cargo_metadata(false);
c.compile(
lib_path
.file_name()
.and_then(|f| f.to_str())
.expect("No filename"),
);
// Link the library. This works even when the library doesn't need to be
// rebuilt.
println!("cargo:rustc-link-lib=static={}", lib_name);
}
fn compile(p: &Path, target: &Target, include_dir: &Path, out_dir: &Path) -> String {
let ext = p.extension().unwrap().to_str().unwrap();
if ext == "o" {
p.to_str().expect("Invalid path").into()
} else {
let out_file = obj_path(out_dir, p);
let cmd = if target.os != WINDOWS || ext != "asm" {
cc(p, ext, target, include_dir, &out_file)
} else {
nasm(p, &target.arch, include_dir, &out_file)
};
run_command(cmd);
out_file.to_str().expect("Invalid path").into()
}
}
fn obj_path(out_dir: &Path, src: &Path) -> PathBuf {
let mut out_path = out_dir.join(src.file_name().unwrap());
// To eliminate unnecessary conditional logic, use ".o" as the extension,
// even when the compiler (e.g. MSVC) would normally use something else
// (e.g. ".obj"). cc-rs seems to do the same.
assert!(out_path.set_extension("o"));
out_path
}
fn cc(file: &Path, ext: &str, target: &Target, include_dir: &Path, out_file: &Path) -> Command {
let mut c = cc::Build::new();
// FIXME: On Windows AArch64 we currently must use Clang to compile C code
if target.os == WINDOWS && target.arch == AARCH64 && !c.get_compiler().is_like_clang() {
let _ = c.compiler("clang");
}
let compiler = c.get_compiler();
let _ = c.include("include");
let _ = c.include(include_dir);
match ext {
"c" => {
for f in c_flags(&compiler) {
let _ = c.flag(f);
}
}
"S" => (),
e => panic!("Unsupported file extension: {:?}", e),
};
for f in cpp_flags(&compiler) {
let _ = c.flag(f);
}
if target.os != "none"
&& target.os != "redox"
&& target.os != "windows"
&& target.arch != "wasm32"
{
let _ = c.flag("-fstack-protector");
}
if target.os.as_str() == "macos" {
// ``-gfull`` is required for Darwin's |-dead_strip|.
let _ = c.flag("-gfull");
} else if !compiler.is_like_msvc() {
let _ = c.flag("-g3");
};
if !target.is_debug {
let _ = c.define("NDEBUG", None);
}
if compiler.is_like_msvc() {
if std::env::var("OPT_LEVEL").unwrap() == "0" {
let _ = c.flag("/Od"); // Disable optimization for debug builds.
// run-time checking: (s)tack frame, (u)ninitialized variables
let _ = c.flag("/RTCsu");
} else {
let _ = c.flag("/Ox"); // Enable full optimization.
}
}
// Allow cross-compiling without a target sysroot for these targets.
//
// poly1305_vec.c requires <emmintrin.h> which requires <stdlib.h>.
if (target.arch == "wasm32" && target.os == "unknown")
|| (target.os == "linux" && target.is_musl && target.arch != "x86_64")
{
if let Ok(compiler) = c.try_get_compiler() {
// TODO: Expand this to non-clang compilers in 0.17.0 if practical.
if compiler.is_like_clang() {
let _ = c.flag("-nostdlibinc");
let _ = c.define("RING_CORE_NOSTDLIBINC", "1");
}
}
}
if target.force_warnings_into_errors {
c.warnings_into_errors(true);
}
if target.is_musl {
// Some platforms enable _FORTIFY_SOURCE by default, but musl
// libc doesn't support it yet. See
// http://wiki.musl-libc.org/wiki/Future_Ideas#Fortify
// http://www.openwall.com/lists/musl/2015/02/04/3
// http://www.openwall.com/lists/musl/2015/06/17/1
let _ = c.flag("-U_FORTIFY_SOURCE");
}
let obj_opt = if compiler.is_like_msvc() { "/Fo" } else { "-o" };
let mut c = c.get_compiler().to_command();
let _ = c
.arg("-c")
.arg(format!(
"{}{}",
obj_opt,
out_file.to_str().expect("Invalid path")
))
.arg(file);
c
}
fn nasm(file: &Path, arch: &str, include_dir: &Path, out_file: &Path) -> Command {
let oformat = match arch {
"x86_64" => "win64",
"x86" => "win32",
_ => panic!("unsupported arch: {}", arch),
};
// Nasm requires that the path end in a path separator.
let mut include_dir = include_dir.as_os_str().to_os_string();
include_dir.push(std::ffi::OsString::from(String::from(
std::path::MAIN_SEPARATOR,
)));
let mut c = Command::new("./target/tools/windows/nasm/nasm");
let _ = c
.arg("-o")
.arg(out_file.to_str().expect("Invalid path"))
.arg("-f")
.arg(oformat)
.arg("-i")
.arg("include/")
.arg("-i")
.arg(include_dir)
.arg("-Xgnu")
.arg("-gcv8")
.arg(file);
c
}
fn run_command_with_args<S>(command_name: S, args: &[String])
where
S: AsRef<std::ffi::OsStr> + Copy,
{
let mut cmd = Command::new(command_name);
let _ = cmd.args(args);
run_command(cmd)
}
fn run_command(mut cmd: Command) {
eprintln!("running {:?}", cmd);
let status = cmd.status().unwrap_or_else(|e| {
panic!("failed to execute [{:?}]: {}", cmd, e);
});
if !status.success() {
panic!("execution failed");
}
}
fn sources_for_arch(arch: &str) -> Vec<PathBuf> {
RING_SRCS
.iter()
.filter(|&&(archs, _)| archs.is_empty() || archs.contains(&arch))
.map(|&(_, p)| PathBuf::from(p))
.collect::<Vec<_>>()
}
fn perlasm_src_dsts(out_dir: &Path, asm_target: &AsmTarget) -> Vec<(PathBuf, PathBuf)> {
let srcs = sources_for_arch(asm_target.arch);
let mut src_dsts = srcs
.iter()
.filter(|p| is_perlasm(p))
.map(|src| (src.clone(), asm_path(out_dir, src, asm_target)))
.collect::<Vec<_>>();
// Some PerlAsm source files need to be run multiple times with different
// output paths.
{
// Appease the borrow checker.
let mut maybe_synthesize = |concrete, synthesized| {
let concrete_path = PathBuf::from(concrete);
if srcs.contains(&concrete_path) {
let synthesized_path = PathBuf::from(synthesized);
src_dsts.push((
concrete_path,
asm_path(out_dir, &synthesized_path, asm_target),
))
}
};
maybe_synthesize(SHA512_X86_64, SHA256_X86_64);
maybe_synthesize(SHA512_ARMV8, SHA256_ARMV8);
}
src_dsts
}
fn asm_srcs(perlasm_src_dsts: Vec<(PathBuf, PathBuf)>) -> Vec<PathBuf> {
perlasm_src_dsts
.into_iter()
.map(|(_src, dst)| dst)
.collect::<Vec<_>>()
}
fn is_perlasm(path: &PathBuf) -> bool {
path.extension().unwrap().to_str().unwrap() == "pl"
}
fn asm_path(out_dir: &Path, src: &Path, asm_target: &AsmTarget) -> PathBuf {
let src_stem = src.file_stem().expect("source file without basename");
let dst_stem = src_stem.to_str().unwrap();
let dst_filename = format!(
"{}-{}.{}",
dst_stem, asm_target.perlasm_format, asm_target.asm_extension
);
out_dir.join(dst_filename)
}
fn perlasm(src_dst: &[(PathBuf, PathBuf)], asm_target: &AsmTarget) {
for (src, dst) in src_dst {
let mut args = vec![
src.to_string_lossy().into_owned(),
asm_target.perlasm_format.to_owned(),
];
if asm_target.arch == "x86" {
args.push("-fPIC".into());
args.push("-DOPENSSL_IA32_SSE2".into());
}
// Work around PerlAsm issue for ARM and AAarch64 targets by replacing
// back slashes with forward slashes.
let dst = dst
.to_str()
.expect("Could not convert path")
.replace('\\', "/");
args.push(dst);
run_command_with_args(&get_command("PERL_EXECUTABLE", "perl"), &args);
}
}
fn get_command(var: &'static str, default: &str) -> String {
read_env_var(var).unwrap_or_else(|_| default.into())
}
// TODO: We should emit `cargo:rerun-if-changed-env` for the various
// environment variables that affect the build.
fn emit_rerun_if_changed() {
for path in &["crypto", "include", "third_party/fiat"] {
walk_dir(&PathBuf::from(path), &|entry| {
let path = entry.path();
match path.extension().and_then(|ext| ext.to_str()) {
Some("c") | Some("S") | Some("h") | Some("inl") | Some("pl") | None => {
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
}
_ => {
// Ignore other types of files.
}
}
})
}
}
fn walk_dir(dir: &Path, cb: &impl Fn(&DirEntry)) {
if dir.is_dir() {
for entry in fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
walk_dir(&path, cb);
} else {
cb(&entry);
}
}
}
}
fn ring_core_prefix() -> String {
let links = std::env::var("CARGO_MANIFEST_LINKS").unwrap();
let computed = {
let name = std::env::var("CARGO_PKG_NAME").unwrap();
let version = std::env::var("CARGO_PKG_VERSION").unwrap();
name + "_core_" + &version.replace(&['-', '.'][..], "_")
};
assert_eq!(links, computed);
links + "_"
}
/// Creates the necessary header files for symbol renaming that are included by
/// assembly code.
///
/// For simplicity, both non-Nasm- and Nasm- style headers are always
/// generated, even though local non-packaged builds need only one of them.
fn generate_prefix_symbols_asm_headers(out_dir: &Path, prefix: &str) -> Result<(), std::io::Error> {
generate_prefix_symbols_header(
out_dir,
"prefix_symbols_asm.h",
'#',
Some("#if defined(__APPLE__)"),
prefix,
)?;
generate_prefix_symbols_header(
out_dir,
"prefix_symbols_nasm.inc",
'%',
Some("%ifidn __OUTPUT_FORMAT__,win32"),
prefix,
)?;
Ok(())
}
fn generate_prefix_symbols_header(
out_dir: &Path,
filename: &str,
pp: char,
prefix_condition: Option<&str>,
prefix: &str,
) -> Result<(), std::io::Error> {
let dir = out_dir.join("ring_core_generated");
std::fs::create_dir_all(&dir)?;
let path = dir.join(filename);
let mut file = std::fs::File::create(path)?;
let filename_ident = filename.replace('.', "_").to_uppercase();
writeln!(
file,
r#"
{pp}ifndef ring_core_generated_{filename_ident}
{pp}define ring_core_generated_{filename_ident}
"#,
pp = pp,
filename_ident = filename_ident
)?;
if let Some(prefix_condition) = prefix_condition {
writeln!(file, "{}", prefix_condition)?;
writeln!(file, "{}", prefix_all_symbols(pp, "_", prefix))?;
writeln!(file, "{pp}else", pp = pp)?;
};
writeln!(file, "{}", prefix_all_symbols(pp, "", prefix))?;
if prefix_condition.is_some() {
writeln!(file, "{pp}endif", pp = pp)?
}
writeln!(file, "{pp}endif", pp = pp)?;
Ok(())
}
fn prefix_all_symbols(pp: char, prefix_prefix: &str, prefix: &str) -> String {
// Rename some nistz256 assembly functions to match the names of their
// polyfills.
static SYMBOLS_TO_RENAME: &[(&str, &str)] = &[
("ecp_nistz256_point_double", "p256_point_double"),
("ecp_nistz256_point_add", "p256_point_add"),
("ecp_nistz256_point_add_affine", "p256_point_add_affine"),
("ecp_nistz256_ord_mul_mont", "p256_scalar_mul_mont"),
("ecp_nistz256_ord_sqr_mont", "p256_scalar_sqr_rep_mont"),
("ecp_nistz256_mul_mont", "p256_mul_mont"),
("ecp_nistz256_sqr_mont", "p256_sqr_mont"),
];
static SYMBOLS_TO_PREFIX: &[&str] = &[
"CRYPTO_poly1305_finish",
"CRYPTO_poly1305_finish_neon",
"CRYPTO_poly1305_init",
"CRYPTO_poly1305_init_neon",
"CRYPTO_poly1305_update",
"CRYPTO_poly1305_update_neon",
"ChaCha20_ctr32",
"LIMBS_add_mod",
"LIMBS_are_even",
"LIMBS_are_zero",
"LIMBS_equal",
"LIMBS_equal_limb",
"LIMBS_less_than",
"LIMBS_less_than_limb",
"LIMBS_reduce_once",
"LIMBS_select_512_32",
"LIMBS_shl_mod",
"LIMBS_sub_mod",
"LIMBS_window5_split_window",
"LIMBS_window5_unsplit_window",
"LIMB_shr",
"OPENSSL_armcap_P",
"OPENSSL_cpuid_setup",
"OPENSSL_ia32cap_P",
"OPENSSL_memcmp",
"aes_hw_ctr32_encrypt_blocks",
"aes_hw_encrypt",
"aes_hw_set_encrypt_key",
"aes_nohw_ctr32_encrypt_blocks",
"aes_nohw_encrypt",
"aes_nohw_set_encrypt_key",
"aesni_gcm_decrypt",
"aesni_gcm_encrypt",
"bn_from_montgomery_in_place",
"bn_gather5",
"bn_mul_mont",
"bn_mul_mont_gather5",
"bn_neg_inv_mod_r_u64",
"bn_power5",
"bn_scatter5",
"bn_sqr8x_internal",
"bn_sqrx8x_internal",
"bsaes_ctr32_encrypt_blocks",
"bssl_constant_time_test_main",
"chacha20_poly1305_open",
"chacha20_poly1305_seal",
"gcm_ghash_avx",
"gcm_ghash_clmul",
"gcm_ghash_neon",
"gcm_gmult_clmul",
"gcm_gmult_neon",
"gcm_init_avx",
"gcm_init_clmul",
"gcm_init_neon",
"limbs_mul_add_limb",
"little_endian_bytes_from_scalar",
"ecp_nistz256_neg",
"ecp_nistz256_select_w5",
"ecp_nistz256_select_w7",
"nistz384_point_add",
"nistz384_point_double",
"nistz384_point_mul",
"p256_mul_mont",
"p256_point_add",
"p256_point_add_affine",
"p256_point_double",
"p256_point_mul",
"p256_point_mul_base",
"p256_scalar_mul_mont",
"p256_scalar_sqr_rep_mont",
"p256_sqr_mont",
"p384_elem_div_by_2",
"p384_elem_mul_mont",
"p384_elem_neg",
"p384_elem_sub",
"p384_scalar_mul_mont",
"openssl_poly1305_neon2_addmulmod",
"openssl_poly1305_neon2_blocks",
"sha256_block_data_order",
"sha512_block_data_order",
"vpaes_ctr32_encrypt_blocks",
"vpaes_encrypt",
"vpaes_encrypt_key_to_bsaes",
"vpaes_set_encrypt_key",
"x25519_NEON",
"x25519_fe_invert",
"x25519_fe_isnegative",
"x25519_fe_mul_ttt",
"x25519_fe_neg",
"x25519_fe_tobytes",
"x25519_ge_double_scalarmult_vartime",
"x25519_ge_frombytes_vartime",
"x25519_ge_scalarmult_base",
"x25519_public_from_private_generic_masked",
"x25519_sc_mask",
"x25519_sc_muladd",
"x25519_sc_reduce",
"x25519_scalar_mult_generic_masked",
];
let mut out = String::new();
for (old, new) in SYMBOLS_TO_RENAME {
let line = format!(
"{pp}define {prefix_prefix}{old} {prefix_prefix}{new}\n",
pp = pp,
prefix_prefix = prefix_prefix,
old = old,
new = new
);
out += &line;
}
for symbol in SYMBOLS_TO_PREFIX {
let line = format!(
"{pp}define {prefix_prefix}{symbol} {prefix_prefix}{prefix}{symbol}\n",
pp = pp,
prefix_prefix = prefix_prefix,
prefix = prefix,
symbol = symbol
);
out += &line;
}
out
}