1 // Copyright 2015-2016 Brian Smith.
2 //
3 // Permission to use, copy, modify, and/or distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10 // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 
15 //! Build the non-Rust components.
16 
17 // It seems like it would be a good idea to use `log!` for logging, but it
18 // isn't worth having the external dependencies (one for the `log` crate, and
19 // another for the concrete logging implementation). Instead we use `eprintln!`
20 // to log everything to stderr.
21 
22 use std::{
23     ffi::{OsStr, OsString},
24     fs::{self, DirEntry},
25     io::Write,
26     path::{Path, PathBuf},
27     process::Command,
28 };
29 
30 const X86: &str = "x86";
31 const X86_64: &str = "x86_64";
32 const AARCH64: &str = "aarch64";
33 const ARM: &str = "arm";
34 
35 #[rustfmt::skip]
36 const RING_SRCS: &[(&[&str], &str)] = &[
37     (&[], "crypto/curve25519/curve25519.c"),
38     (&[], "crypto/fipsmodule/aes/aes_nohw.c"),
39     (&[], "crypto/fipsmodule/bn/montgomery.c"),
40     (&[], "crypto/fipsmodule/bn/montgomery_inv.c"),
41     (&[], "crypto/fipsmodule/ec/ecp_nistz.c"),
42     (&[], "crypto/fipsmodule/ec/gfp_p256.c"),
43     (&[], "crypto/fipsmodule/ec/gfp_p384.c"),
44     (&[], "crypto/fipsmodule/ec/p256.c"),
45     (&[], "crypto/limbs/limbs.c"),
46     (&[], "crypto/mem.c"),
47     (&[], "crypto/poly1305/poly1305.c"),
48 
49     (&[AARCH64, ARM, X86_64, X86], "crypto/crypto.c"),
50 
51     (&[X86_64, X86], "crypto/cpu_intel.c"),
52 
53     (&[X86], "crypto/fipsmodule/aes/asm/aesni-x86.pl"),
54     (&[X86], "crypto/fipsmodule/aes/asm/vpaes-x86.pl"),
55     (&[X86], "crypto/fipsmodule/bn/asm/x86-mont.pl"),
56     (&[X86], "crypto/chacha/asm/chacha-x86.pl"),
57     (&[X86], "crypto/fipsmodule/modes/asm/ghash-x86.pl"),
58 
59     (&[X86_64], "crypto/chacha/asm/chacha-x86_64.pl"),
60     (&[X86_64], "crypto/curve25519/curve25519_64_adx.c"),
61     (&[X86_64], "crypto/fipsmodule/aes/asm/aesni-x86_64.pl"),
62     (&[X86_64], "crypto/fipsmodule/aes/asm/vpaes-x86_64.pl"),
63     (&[X86_64], "crypto/fipsmodule/bn/asm/x86_64-mont.pl"),
64     (&[X86_64], "crypto/fipsmodule/bn/asm/x86_64-mont5.pl"),
65     (&[X86_64], "crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl"),
66     (&[X86_64], "crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl"),
67     (&[X86_64], "crypto/fipsmodule/modes/asm/ghash-x86_64.pl"),
68     (&[X86_64], "crypto/poly1305/poly1305_vec.c"),
69     (&[X86_64], SHA512_X86_64),
70     (&[X86_64], "crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl"),
71     (&[X86_64], "third_party/fiat/asm/fiat_curve25519_adx_mul.S"),
72     (&[X86_64], "third_party/fiat/asm/fiat_curve25519_adx_square.S"),
73 
74     (&[AARCH64, X86_64], "crypto/fipsmodule/ec/p256-nistz.c"),
75 
76     (&[AARCH64, ARM], "crypto/fipsmodule/aes/asm/aesv8-armx.pl"),
77     (&[AARCH64, ARM], "crypto/fipsmodule/modes/asm/ghashv8-armx.pl"),
78 
79     (&[ARM], "crypto/fipsmodule/aes/asm/bsaes-armv7.pl"),
80     (&[ARM], "crypto/fipsmodule/aes/asm/vpaes-armv7.pl"),
81     (&[ARM], "crypto/fipsmodule/bn/asm/armv4-mont.pl"),
82     (&[ARM], "crypto/chacha/asm/chacha-armv4.pl"),
83     (&[ARM], "crypto/curve25519/asm/x25519-asm-arm.S"),
84     (&[ARM], "crypto/fipsmodule/modes/asm/ghash-armv4.pl"),
85     (&[ARM], "crypto/poly1305/poly1305_arm.c"),
86     (&[ARM], "crypto/poly1305/poly1305_arm_asm.S"),
87     (&[ARM], "crypto/fipsmodule/sha/asm/sha256-armv4.pl"),
88     (&[ARM], "crypto/fipsmodule/sha/asm/sha512-armv4.pl"),
89 
90     (&[AARCH64], "crypto/chacha/asm/chacha-armv8.pl"),
91     (&[AARCH64], "crypto/cipher_extra/asm/chacha20_poly1305_armv8.pl"),
92     (&[AARCH64], "crypto/fipsmodule/aes/asm/vpaes-armv8.pl"),
93     (&[AARCH64], "crypto/fipsmodule/bn/asm/armv8-mont.pl"),
94     (&[AARCH64], "crypto/fipsmodule/ec/asm/p256-armv8-asm.pl"),
95     (&[AARCH64], "crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl"),
96     (&[AARCH64], SHA512_ARMV8),
97 ];
98 
99 const SHA256_X86_64: &str = "crypto/fipsmodule/sha/asm/sha256-x86_64.pl";
100 const SHA512_X86_64: &str = "crypto/fipsmodule/sha/asm/sha512-x86_64.pl";
101 
102 const SHA256_ARMV8: &str = "crypto/fipsmodule/sha/asm/sha256-armv8.pl";
103 const SHA512_ARMV8: &str = "crypto/fipsmodule/sha/asm/sha512-armv8.pl";
104 
105 const RING_TEST_SRCS: &[&str] = &[("crypto/constant_time_test.c")];
106 
107 const PREGENERATED: &str = "pregenerated";
108 
cpp_flags(compiler: &cc::Tool) -> &'static [&'static str]109 fn cpp_flags(compiler: &cc::Tool) -> &'static [&'static str] {
110     if !compiler.is_like_msvc() {
111         static NON_MSVC_FLAGS: &[&str] = &[
112             "-std=c1x", // GCC 4.6 requires "c1x" instead of "c11"
113             "-pedantic",
114             "-pedantic-errors",
115             "-Wall",
116             "-Wextra",
117             "-Wbad-function-cast",
118             "-Wcast-align",
119             "-Wcast-qual",
120             "-Wconversion",
121             "-Wenum-compare",
122             "-Wfloat-equal",
123             "-Wformat=2",
124             "-Winline",
125             "-Winvalid-pch",
126             "-Wmissing-field-initializers",
127             "-Wmissing-include-dirs",
128             "-Wnested-externs",
129             "-Wredundant-decls",
130             "-Wshadow",
131             "-Wsign-compare",
132             "-Wsign-conversion",
133             "-Wstrict-prototypes",
134             "-Wundef",
135             "-Wuninitialized",
136             "-Wwrite-strings",
137             "-fno-strict-aliasing",
138             "-fvisibility=hidden",
139         ];
140         NON_MSVC_FLAGS
141     } else {
142         static MSVC_FLAGS: &[&str] = &[
143             "/GS",   // Buffer security checks.
144             "/Gy",   // Enable function-level linking.
145             "/EHsc", // C++ exceptions only, only in C++.
146             "/GR-",  // Disable RTTI.
147             "/Zc:wchar_t",
148             "/Zc:forScope",
149             "/Zc:inline",
150             "/Zc:rvalueCast",
151             // Warnings.
152             "/sdl",
153             "/Wall",
154             "/wd4127", // C4127: conditional expression is constant
155             "/wd4464", // C4464: relative include path contains '..'
156             "/wd4514", // C4514: <name>: unreferenced inline function has be
157             "/wd4710", // C4710: function not inlined
158             "/wd4711", // C4711: function 'function' selected for inline expansion
159             "/wd4820", // C4820: <struct>: <n> bytes padding added after <name>
160             "/wd5045", /* C5045: Compiler will insert Spectre mitigation for memory load if
161                         * /Qspectre switch specified */
162         ];
163         MSVC_FLAGS
164     }
165 }
166 
167 // None means "any OS" or "any target". The first match in sequence order is
168 // taken.
169 const ASM_TARGETS: &[AsmTarget] = &[
170     AsmTarget {
171         oss: LINUX_ABI,
172         arch: "aarch64",
173         perlasm_format: "linux64",
174         asm_extension: "S",
175         preassemble: false,
176     },
177     AsmTarget {
178         oss: LINUX_ABI,
179         arch: "arm",
180         perlasm_format: "linux32",
181         asm_extension: "S",
182         preassemble: false,
183     },
184     AsmTarget {
185         oss: LINUX_ABI,
186         arch: "x86",
187         perlasm_format: "elf",
188         asm_extension: "S",
189         preassemble: false,
190     },
191     AsmTarget {
192         oss: LINUX_ABI,
193         arch: "x86_64",
194         perlasm_format: "elf",
195         asm_extension: "S",
196         preassemble: false,
197     },
198     AsmTarget {
199         oss: MACOS_ABI,
200         arch: "aarch64",
201         perlasm_format: "ios64",
202         asm_extension: "S",
203         preassemble: false,
204     },
205     AsmTarget {
206         oss: MACOS_ABI,
207         arch: "x86_64",
208         perlasm_format: "macosx",
209         asm_extension: "S",
210         preassemble: false,
211     },
212     AsmTarget {
213         oss: &[WINDOWS],
214         arch: "x86",
215         perlasm_format: "win32n",
216         asm_extension: "asm",
217         preassemble: true,
218     },
219     AsmTarget {
220         oss: &[WINDOWS],
221         arch: "x86_64",
222         perlasm_format: "nasm",
223         asm_extension: "asm",
224         preassemble: true,
225     },
226     AsmTarget {
227         oss: &[WINDOWS],
228         arch: "aarch64",
229         perlasm_format: "win64",
230         asm_extension: "S",
231         preassemble: false,
232     },
233 ];
234 
235 struct AsmTarget {
236     /// Operating systems.
237     oss: &'static [&'static str],
238 
239     /// Architectures.
240     arch: &'static str,
241 
242     /// The PerlAsm format name.
243     perlasm_format: &'static str,
244 
245     /// The filename extension for assembly files.
246     asm_extension: &'static str,
247 
248     /// Whether pre-assembled object files should be included in the Cargo
249     /// package instead of the asm sources. This way, the user doesn't need
250     /// to install an assembler for the target. This is particularly important
251     /// for x86/x86_64 Windows since an assembler doesn't come with the C
252     /// compiler.
253     preassemble: bool,
254 }
255 
256 /// Operating systems that have the same ABI as Linux on every architecture
257 /// mentioned in `ASM_TARGETS`.
258 const LINUX_ABI: &[&str] = &[
259     "android",
260     "dragonfly",
261     "freebsd",
262     "fuchsia",
263     "haiku",
264     "illumos",
265     "netbsd",
266     "openbsd",
267     "linux",
268     "redox",
269     "solaris",
270 ];
271 
272 /// Operating systems that have the same ABI as macOS on every architecture
273 /// mentioned in `ASM_TARGETS`.
274 const MACOS_ABI: &[&str] = &["ios", "macos", "tvos"];
275 
276 const WINDOWS: &str = "windows";
277 
278 /// Read an environment variable and tell Cargo that we depend on it.
279 ///
280 /// This needs to be used for any environment variable that isn't a standard
281 /// Cargo-supplied variable.
282 ///
283 /// The name is static since we intend to only read a static set of environment
284 /// variables.
read_env_var(name: &'static str) -> Option<OsString>285 fn read_env_var(name: &'static str) -> Option<OsString> {
286     println!("cargo:rerun-if-env-changed={}", name);
287     std::env::var_os(name)
288 }
289 
main()290 fn main() {
291     const RING_PREGENERATE_ASM: &str = "RING_PREGENERATE_ASM";
292     match read_env_var(RING_PREGENERATE_ASM).as_deref() {
293         Some(s) if s == "1" => {
294             pregenerate_asm_main();
295         }
296         None => ring_build_rs_main(),
297         _ => {
298             panic!("${} has an invalid value", RING_PREGENERATE_ASM);
299         }
300     }
301 }
302 
ring_build_rs_main()303 fn ring_build_rs_main() {
304     use std::env;
305 
306     let out_dir = env::var_os("OUT_DIR").unwrap();
307     let out_dir = PathBuf::from(out_dir);
308 
309     let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
310     let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
311     let is_musl = {
312         let env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
313         env.starts_with("musl")
314     };
315 
316     let is_git = std::fs::metadata(".git").is_ok();
317 
318     // Published builds are always built in release mode.
319     let is_debug = is_git && env::var("DEBUG").unwrap() != "false";
320 
321     // If `.git` exists then assume this is the "local hacking" case where
322     // we want to make it easy to build *ring* using `cargo build`/`cargo test`
323     // without a prerequisite `package` step, at the cost of needing additional
324     // tools like `Perl` and/or `nasm`.
325     //
326     // If `.git` doesn't exist then assume that this is a packaged build where
327     // we want to optimize for minimizing the build tools required: No Perl,
328     // no nasm, etc.
329     let use_pregenerated = !is_git;
330 
331     // During local development, force warnings in non-Rust code to be treated
332     // as errors. Since warnings are highly compiler-dependent and compilers
333     // don't maintain backward compatibility w.r.t. which warnings they issue,
334     // don't do this for packaged builds.
335     let force_warnings_into_errors = is_git;
336 
337     let target = Target {
338         arch,
339         os,
340         is_musl,
341         is_debug,
342         force_warnings_into_errors,
343     };
344     let pregenerated = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join(PREGENERATED);
345 
346     build_c_code(
347         &target,
348         pregenerated,
349         &out_dir,
350         &ring_core_prefix(),
351         use_pregenerated,
352     );
353     emit_rerun_if_changed()
354 }
355 
pregenerate_asm_main()356 fn pregenerate_asm_main() {
357     println!("cargo:rustc-cfg=pregenerate_asm_only");
358 
359     let pregenerated = PathBuf::from(PREGENERATED);
360     std::fs::create_dir(&pregenerated).unwrap();
361     let pregenerated_tmp = pregenerated.join("tmp");
362     std::fs::create_dir(&pregenerated_tmp).unwrap();
363 
364     generate_prefix_symbols_asm_headers(&pregenerated_tmp, &ring_core_prefix()).unwrap();
365 
366     for asm_target in ASM_TARGETS {
367         // For Windows, package pregenerated object files instead of
368         // pregenerated assembly language source files, so that the user
369         // doesn't need to install the assembler.
370         let asm_dir = if asm_target.preassemble {
371             &pregenerated_tmp
372         } else {
373             &pregenerated
374         };
375 
376         let perlasm_src_dsts = perlasm_src_dsts(asm_dir, asm_target);
377         perlasm(&perlasm_src_dsts, asm_target);
378 
379         if asm_target.preassemble {
380             // Preassembly is currently only done for Windows targets.
381             assert_eq!(&asm_target.oss, &[WINDOWS]);
382             let os = WINDOWS;
383 
384             let srcs = asm_srcs(perlasm_src_dsts);
385 
386             let target = Target {
387                 arch: asm_target.arch.to_owned(),
388                 os: os.to_owned(),
389                 is_musl: false,
390                 is_debug: false,
391                 force_warnings_into_errors: true,
392             };
393 
394             let b = new_build(&target, &pregenerated_tmp);
395             for src in srcs {
396                 compile(&b, &src, &target, &pregenerated_tmp, &pregenerated);
397             }
398         }
399     }
400 }
401 
402 struct Target {
403     arch: String,
404     os: String,
405 
406     /// Is the target one that uses the musl C standard library instead of the default?
407     is_musl: bool,
408 
409     /// Is this a debug build? This affects whether assertions might be enabled
410     /// in the C code. For packaged builds, this should always be `false`.
411     is_debug: bool,
412 
413     /// true: Force warnings to be treated as errors.
414     /// false: Use the default behavior (perhaps determined by `$CFLAGS`, etc.)
415     force_warnings_into_errors: bool,
416 }
417 
build_c_code( target: &Target, pregenerated: PathBuf, out_dir: &Path, ring_core_prefix: &str, use_pregenerated: bool, )418 fn build_c_code(
419     target: &Target,
420     pregenerated: PathBuf,
421     out_dir: &Path,
422     ring_core_prefix: &str,
423     use_pregenerated: bool,
424 ) {
425     println!("cargo:rustc-env=RING_CORE_PREFIX={}", ring_core_prefix);
426 
427     let asm_target = ASM_TARGETS.iter().find(|asm_target| {
428         asm_target.arch == target.arch && asm_target.oss.contains(&target.os.as_ref())
429     });
430 
431     let asm_dir = if use_pregenerated {
432         &pregenerated
433     } else {
434         out_dir
435     };
436 
437     generate_prefix_symbols_header(out_dir, "prefix_symbols.h", '#', None, ring_core_prefix)
438         .unwrap();
439 
440     generate_prefix_symbols_asm_headers(out_dir, ring_core_prefix).unwrap();
441 
442     let asm_srcs = if let Some(asm_target) = asm_target {
443         let perlasm_src_dsts = perlasm_src_dsts(asm_dir, asm_target);
444 
445         if !use_pregenerated {
446             perlasm(&perlasm_src_dsts[..], asm_target);
447         }
448 
449         let mut asm_srcs = asm_srcs(perlasm_src_dsts);
450 
451         // For Windows we also pregenerate the object files for non-Git builds so
452         // the user doesn't need to install the assembler.
453         if use_pregenerated && target.os == WINDOWS && asm_target.preassemble {
454             asm_srcs = asm_srcs
455                 .iter()
456                 .map(|src| obj_path(&pregenerated, src.as_path()))
457                 .collect::<Vec<_>>();
458         }
459 
460         asm_srcs
461     } else {
462         Vec::new()
463     };
464 
465     let core_srcs = sources_for_arch(&target.arch)
466         .into_iter()
467         .filter(|p| !is_perlasm(p))
468         .filter(|p| {
469             if let Some(extension) = p.extension() {
470                 // We don't (and can't) use any .S on Windows since MSVC and NASM can't assemble
471                 // them.
472                 if extension == "S"
473                     && (target.arch == X86_64 || target.arch == X86)
474                     && target.os == WINDOWS
475                 {
476                     return false;
477                 }
478             }
479             true
480         })
481         .collect::<Vec<_>>();
482 
483     let test_srcs = RING_TEST_SRCS.iter().map(PathBuf::from).collect::<Vec<_>>();
484 
485     let libs = [
486         ("", &core_srcs[..], &asm_srcs[..]),
487         ("test", &test_srcs[..], &[]),
488     ];
489 
490     // XXX: Ideally, ring-test would only be built for `cargo test`, but Cargo
491     // can't do that yet.
492     libs.iter()
493         .for_each(|&(lib_name_suffix, srcs, additional_srcs)| {
494             let lib_name = String::from(ring_core_prefix) + lib_name_suffix;
495             build_library(target, out_dir, &lib_name, srcs, additional_srcs)
496         });
497 
498     println!(
499         "cargo:rustc-link-search=native={}",
500         out_dir.to_str().expect("Invalid path")
501     );
502 }
503 
new_build(target: &Target, include_dir: &Path) -> cc::Build504 fn new_build(target: &Target, include_dir: &Path) -> cc::Build {
505     let mut b = cc::Build::new();
506     configure_cc(&mut b, target, include_dir);
507     b
508 }
509 
build_library( target: &Target, out_dir: &Path, lib_name: &str, srcs: &[PathBuf], additional_srcs: &[PathBuf], )510 fn build_library(
511     target: &Target,
512     out_dir: &Path,
513     lib_name: &str,
514     srcs: &[PathBuf],
515     additional_srcs: &[PathBuf],
516 ) {
517     let mut c = new_build(target, out_dir);
518 
519     // Compile all the (dirty) source files into object files.
520     let objs = additional_srcs
521         .iter()
522         .chain(srcs.iter())
523         .map(|f| compile(&c, f, target, out_dir, out_dir))
524         .collect::<Vec<_>>();
525 
526     // Rebuild the library if necessary.
527     let lib_path = PathBuf::from(out_dir).join(format!("lib{}.a", lib_name));
528 
529     for o in objs {
530         let _ = c.object(o);
531     }
532 
533     // Handled below.
534     let _ = c.cargo_metadata(false);
535 
536     c.compile(
537         lib_path
538             .file_name()
539             .and_then(|f| f.to_str())
540             .expect("No filename"),
541     );
542 
543     // Link the library. This works even when the library doesn't need to be
544     // rebuilt.
545     println!("cargo:rustc-link-lib=static={}", lib_name);
546 }
547 
compile( b: &cc::Build, p: &Path, target: &Target, include_dir: &Path, out_dir: &Path, ) -> PathBuf548 fn compile(
549     b: &cc::Build,
550     p: &Path,
551     target: &Target,
552     include_dir: &Path,
553     out_dir: &Path,
554 ) -> PathBuf {
555     let ext = p.extension().unwrap().to_str().unwrap();
556     if ext == "o" {
557         p.into()
558     } else {
559         let out_file = obj_path(out_dir, p);
560         let cmd = if target.os != WINDOWS || ext != "asm" {
561             cc(b, p, ext, &out_file)
562         } else {
563             nasm(p, &target.arch, include_dir, &out_file)
564         };
565 
566         run_command(cmd);
567         out_file
568     }
569 }
570 
obj_path(out_dir: &Path, src: &Path) -> PathBuf571 fn obj_path(out_dir: &Path, src: &Path) -> PathBuf {
572     let mut out_path = out_dir.join(src.file_name().unwrap());
573     // To eliminate unnecessary conditional logic, use ".o" as the extension,
574     // even when the compiler (e.g. MSVC) would normally use something else
575     // (e.g. ".obj"). cc-rs seems to do the same.
576     assert!(out_path.set_extension("o"));
577     out_path
578 }
579 
configure_cc(c: &mut cc::Build, target: &Target, include_dir: &Path)580 fn configure_cc(c: &mut cc::Build, target: &Target, include_dir: &Path) {
581     // FIXME: On Windows AArch64 we currently must use Clang to compile C code
582     if target.os == WINDOWS && target.arch == AARCH64 && !c.get_compiler().is_like_clang() {
583         let _ = c.compiler("clang");
584     }
585 
586     let compiler = c.get_compiler();
587 
588     let _ = c.include("include");
589     let _ = c.include(include_dir);
590     for f in cpp_flags(&compiler) {
591         let _ = c.flag(f);
592     }
593     if target.os != "none"
594         && target.os != "redox"
595         && target.os != "windows"
596         && target.arch != "wasm32"
597     {
598         let _ = c.flag("-fstack-protector");
599     }
600 
601     if target.os.as_str() == "macos" {
602         // ``-gfull`` is required for Darwin's |-dead_strip|.
603         let _ = c.flag("-gfull");
604     } else if !compiler.is_like_msvc() {
605         let _ = c.flag("-g3");
606     };
607 
608     if !target.is_debug {
609         let _ = c.define("NDEBUG", None);
610     }
611 
612     if compiler.is_like_msvc() {
613         if std::env::var("OPT_LEVEL").unwrap() == "0" {
614             let _ = c.flag("/Od"); // Disable optimization for debug builds.
615                                    // run-time checking: (s)tack frame, (u)ninitialized variables
616             let _ = c.flag("/RTCsu");
617         } else {
618             let _ = c.flag("/Ox"); // Enable full optimization.
619         }
620     }
621 
622     // Allow cross-compiling without a target sysroot for these targets.
623     //
624     // poly1305_vec.c requires <emmintrin.h> which requires <stdlib.h>.
625     if (target.arch == "wasm32" && target.os == "unknown")
626         || (target.os == "linux" && target.is_musl && target.arch != "x86_64")
627     {
628         if let Ok(compiler) = c.try_get_compiler() {
629             // TODO: Expand this to non-clang compilers in 0.17.0 if practical.
630             if compiler.is_like_clang() {
631                 let _ = c.flag("-nostdlibinc");
632                 let _ = c.define("RING_CORE_NOSTDLIBINC", "1");
633             }
634         }
635     }
636 
637     if target.force_warnings_into_errors {
638         c.warnings_into_errors(true);
639     }
640     if target.is_musl {
641         // Some platforms enable _FORTIFY_SOURCE by default, but musl
642         // libc doesn't support it yet. See
643         // http://wiki.musl-libc.org/wiki/Future_Ideas#Fortify
644         // http://www.openwall.com/lists/musl/2015/02/04/3
645         // http://www.openwall.com/lists/musl/2015/06/17/1
646         let _ = c.flag("-U_FORTIFY_SOURCE");
647     }
648 }
649 
cc(b: &cc::Build, file: &Path, ext: &str, out_file: &Path) -> Command650 fn cc(b: &cc::Build, file: &Path, ext: &str, out_file: &Path) -> Command {
651     match ext {
652         "c" | "S" => (),
653         e => panic!("Unsupported file extension: {:?}", e),
654     };
655 
656     let cc = b.get_compiler();
657     let obj_opt = if cc.is_like_msvc() { "/Fo" } else { "-o" };
658     let mut arg = OsString::from(obj_opt);
659     arg.push(out_file);
660 
661     let mut c = cc.to_command();
662     let _ = c.arg("-c").arg(arg).arg(file);
663     c
664 }
665 
nasm(file: &Path, arch: &str, include_dir: &Path, out_file: &Path) -> Command666 fn nasm(file: &Path, arch: &str, include_dir: &Path, out_file: &Path) -> Command {
667     let oformat = match arch {
668         "x86_64" => "win64",
669         "x86" => "win32",
670         _ => panic!("unsupported arch: {}", arch),
671     };
672 
673     // Nasm requires that the path end in a path separator.
674     let mut include_dir = include_dir.as_os_str().to_os_string();
675     include_dir.push(std::ffi::OsString::from(String::from(
676         std::path::MAIN_SEPARATOR,
677     )));
678 
679     let mut c = Command::new("./target/tools/windows/nasm/nasm");
680     let _ = c
681         .arg("-o")
682         .arg(out_file.to_str().expect("Invalid path"))
683         .arg("-f")
684         .arg(oformat)
685         .arg("-i")
686         .arg("include/")
687         .arg("-i")
688         .arg(include_dir)
689         .arg("-Xgnu")
690         .arg("-gcv8")
691         .arg(file);
692     c
693 }
694 
run_command_with_args(command_name: &OsStr, args: &[String])695 fn run_command_with_args(command_name: &OsStr, args: &[String]) {
696     let mut cmd = Command::new(command_name);
697     let _ = cmd.args(args);
698     run_command(cmd)
699 }
700 
run_command(mut cmd: Command)701 fn run_command(mut cmd: Command) {
702     eprintln!("running {:?}", cmd);
703     let status = cmd.status().unwrap_or_else(|e| {
704         panic!("failed to execute [{:?}]: {}", cmd, e);
705     });
706     if !status.success() {
707         panic!("execution failed");
708     }
709 }
710 
sources_for_arch(arch: &str) -> Vec<PathBuf>711 fn sources_for_arch(arch: &str) -> Vec<PathBuf> {
712     RING_SRCS
713         .iter()
714         .filter(|&&(archs, _)| archs.is_empty() || archs.contains(&arch))
715         .map(|&(_, p)| PathBuf::from(p))
716         .collect::<Vec<_>>()
717 }
718 
perlasm_src_dsts(out_dir: &Path, asm_target: &AsmTarget) -> Vec<(PathBuf, PathBuf)>719 fn perlasm_src_dsts(out_dir: &Path, asm_target: &AsmTarget) -> Vec<(PathBuf, PathBuf)> {
720     let srcs = sources_for_arch(asm_target.arch);
721     let mut src_dsts = srcs
722         .iter()
723         .filter(|p| is_perlasm(p))
724         .map(|src| (src.clone(), asm_path(out_dir, src, asm_target)))
725         .collect::<Vec<_>>();
726 
727     // Some PerlAsm source files need to be run multiple times with different
728     // output paths.
729     {
730         // Appease the borrow checker.
731         let mut maybe_synthesize = |concrete, synthesized| {
732             let concrete_path = PathBuf::from(concrete);
733             if srcs.contains(&concrete_path) {
734                 let synthesized_path = PathBuf::from(synthesized);
735                 src_dsts.push((
736                     concrete_path,
737                     asm_path(out_dir, &synthesized_path, asm_target),
738                 ))
739             }
740         };
741         maybe_synthesize(SHA512_X86_64, SHA256_X86_64);
742         maybe_synthesize(SHA512_ARMV8, SHA256_ARMV8);
743     }
744 
745     src_dsts
746 }
747 
asm_srcs(perlasm_src_dsts: Vec<(PathBuf, PathBuf)>) -> Vec<PathBuf>748 fn asm_srcs(perlasm_src_dsts: Vec<(PathBuf, PathBuf)>) -> Vec<PathBuf> {
749     perlasm_src_dsts
750         .into_iter()
751         .map(|(_src, dst)| dst)
752         .collect::<Vec<_>>()
753 }
754 
is_perlasm(path: &PathBuf) -> bool755 fn is_perlasm(path: &PathBuf) -> bool {
756     path.extension().unwrap().to_str().unwrap() == "pl"
757 }
758 
asm_path(out_dir: &Path, src: &Path, asm_target: &AsmTarget) -> PathBuf759 fn asm_path(out_dir: &Path, src: &Path, asm_target: &AsmTarget) -> PathBuf {
760     let src_stem = src.file_stem().expect("source file without basename");
761 
762     let dst_stem = src_stem.to_str().unwrap();
763     let dst_filename = format!(
764         "{}-{}.{}",
765         dst_stem, asm_target.perlasm_format, asm_target.asm_extension
766     );
767     out_dir.join(dst_filename)
768 }
769 
perlasm(src_dst: &[(PathBuf, PathBuf)], asm_target: &AsmTarget)770 fn perlasm(src_dst: &[(PathBuf, PathBuf)], asm_target: &AsmTarget) {
771     for (src, dst) in src_dst {
772         let mut args = vec![
773             src.to_string_lossy().into_owned(),
774             asm_target.perlasm_format.to_owned(),
775         ];
776         if asm_target.arch == "x86" {
777             args.push("-fPIC".into());
778             args.push("-DOPENSSL_IA32_SSE2".into());
779         }
780         // Work around PerlAsm issue for ARM and AAarch64 targets by replacing
781         // back slashes with forward slashes.
782         let dst = dst
783             .to_str()
784             .expect("Could not convert path")
785             .replace('\\', "/");
786         args.push(dst);
787         run_command_with_args(&get_command("PERL_EXECUTABLE", "perl"), &args);
788     }
789 }
790 
get_command(var: &'static str, default: &str) -> OsString791 fn get_command(var: &'static str, default: &str) -> OsString {
792     read_env_var(var).unwrap_or_else(|| default.into())
793 }
794 
795 // TODO: We should emit `cargo:rerun-if-changed-env` for the various
796 // environment variables that affect the build.
emit_rerun_if_changed()797 fn emit_rerun_if_changed() {
798     for path in &["crypto", "include", "third_party/fiat"] {
799         walk_dir(&PathBuf::from(path), &|entry| {
800             let path = entry.path();
801             match path.extension().and_then(|ext| ext.to_str()) {
802                 Some("c") | Some("S") | Some("h") | Some("inl") | Some("pl") | None => {
803                     println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
804                 }
805                 _ => {
806                     // Ignore other types of files.
807                 }
808             }
809         })
810     }
811 }
812 
walk_dir(dir: &Path, cb: &impl Fn(&DirEntry))813 fn walk_dir(dir: &Path, cb: &impl Fn(&DirEntry)) {
814     if dir.is_dir() {
815         for entry in fs::read_dir(dir).unwrap() {
816             let entry = entry.unwrap();
817             let path = entry.path();
818             if path.is_dir() {
819                 walk_dir(&path, cb);
820             } else {
821                 cb(&entry);
822             }
823         }
824     }
825 }
826 
ring_core_prefix() -> String827 fn ring_core_prefix() -> String {
828     let links = std::env::var("CARGO_MANIFEST_LINKS").unwrap();
829 
830     let computed = {
831         let name = std::env::var("CARGO_PKG_NAME").unwrap();
832         let version = std::env::var("CARGO_PKG_VERSION").unwrap();
833         name + "_core_" + &version.replace(&['-', '.'][..], "_")
834     };
835 
836     assert_eq!(links, computed);
837 
838     links + "_"
839 }
840 
841 /// Creates the necessary header files for symbol renaming that are included by
842 /// assembly code.
843 ///
844 /// For simplicity, both non-Nasm- and Nasm- style headers are always
845 /// generated, even though local non-packaged builds need only one of them.
generate_prefix_symbols_asm_headers(out_dir: &Path, prefix: &str) -> Result<(), std::io::Error>846 fn generate_prefix_symbols_asm_headers(out_dir: &Path, prefix: &str) -> Result<(), std::io::Error> {
847     generate_prefix_symbols_header(
848         out_dir,
849         "prefix_symbols_asm.h",
850         '#',
851         Some("#if defined(__APPLE__)"),
852         prefix,
853     )?;
854 
855     generate_prefix_symbols_header(
856         out_dir,
857         "prefix_symbols_nasm.inc",
858         '%',
859         Some("%ifidn __OUTPUT_FORMAT__,win32"),
860         prefix,
861     )?;
862 
863     Ok(())
864 }
865 
generate_prefix_symbols_header( out_dir: &Path, filename: &str, pp: char, prefix_condition: Option<&str>, prefix: &str, ) -> Result<(), std::io::Error>866 fn generate_prefix_symbols_header(
867     out_dir: &Path,
868     filename: &str,
869     pp: char,
870     prefix_condition: Option<&str>,
871     prefix: &str,
872 ) -> Result<(), std::io::Error> {
873     let dir = out_dir.join("ring_core_generated");
874     std::fs::create_dir_all(&dir)?;
875 
876     let path = dir.join(filename);
877     let mut file = std::fs::File::create(path)?;
878 
879     let filename_ident = filename.replace('.', "_").to_uppercase();
880     writeln!(
881         file,
882         r#"
883 {pp}ifndef ring_core_generated_{filename_ident}
884 {pp}define ring_core_generated_{filename_ident}
885 "#,
886         pp = pp,
887         filename_ident = filename_ident
888     )?;
889 
890     if let Some(prefix_condition) = prefix_condition {
891         writeln!(file, "{}", prefix_condition)?;
892         writeln!(file, "{}", prefix_all_symbols(pp, "_", prefix))?;
893         writeln!(file, "{pp}else", pp = pp)?;
894     };
895     writeln!(file, "{}", prefix_all_symbols(pp, "", prefix))?;
896     if prefix_condition.is_some() {
897         writeln!(file, "{pp}endif", pp = pp)?
898     }
899 
900     writeln!(file, "{pp}endif", pp = pp)?;
901 
902     Ok(())
903 }
904 
prefix_all_symbols(pp: char, prefix_prefix: &str, prefix: &str) -> String905 fn prefix_all_symbols(pp: char, prefix_prefix: &str, prefix: &str) -> String {
906     // Rename some nistz256 assembly functions to match the names of their
907     // polyfills.
908     static SYMBOLS_TO_RENAME: &[(&str, &str)] = &[
909         ("ecp_nistz256_point_double", "p256_point_double"),
910         ("ecp_nistz256_point_add", "p256_point_add"),
911         ("ecp_nistz256_point_add_affine", "p256_point_add_affine"),
912         ("ecp_nistz256_ord_mul_mont", "p256_scalar_mul_mont"),
913         ("ecp_nistz256_ord_sqr_mont", "p256_scalar_sqr_rep_mont"),
914         ("ecp_nistz256_mul_mont", "p256_mul_mont"),
915         ("ecp_nistz256_sqr_mont", "p256_sqr_mont"),
916     ];
917 
918     static SYMBOLS_TO_PREFIX: &[&str] = &[
919         "CRYPTO_poly1305_finish",
920         "CRYPTO_poly1305_finish_neon",
921         "CRYPTO_poly1305_init",
922         "CRYPTO_poly1305_init_neon",
923         "CRYPTO_poly1305_update",
924         "CRYPTO_poly1305_update_neon",
925         "ChaCha20_ctr32",
926         "LIMBS_add_mod",
927         "LIMBS_are_even",
928         "LIMBS_are_zero",
929         "LIMBS_equal",
930         "LIMBS_equal_limb",
931         "LIMBS_less_than",
932         "LIMBS_less_than_limb",
933         "LIMBS_reduce_once",
934         "LIMBS_select_512_32",
935         "LIMBS_shl_mod",
936         "LIMBS_sub_mod",
937         "LIMBS_window5_split_window",
938         "LIMBS_window5_unsplit_window",
939         "LIMB_shr",
940         "OPENSSL_armcap_P",
941         "OPENSSL_cpuid_setup",
942         "OPENSSL_ia32cap_P",
943         "OPENSSL_memcmp",
944         "aes_hw_ctr32_encrypt_blocks",
945         "aes_hw_encrypt",
946         "aes_hw_set_encrypt_key",
947         "aes_nohw_ctr32_encrypt_blocks",
948         "aes_nohw_encrypt",
949         "aes_nohw_set_encrypt_key",
950         "aesni_gcm_decrypt",
951         "aesni_gcm_encrypt",
952         "bn_from_montgomery_in_place",
953         "bn_gather5",
954         "bn_mul_mont",
955         "bn_mul_mont_gather5",
956         "bn_neg_inv_mod_r_u64",
957         "bn_power5",
958         "bn_scatter5",
959         "bn_sqr8x_internal",
960         "bn_sqrx8x_internal",
961         "bsaes_ctr32_encrypt_blocks",
962         "bssl_constant_time_test_conditional_memcpy",
963         "bssl_constant_time_test_conditional_memxor",
964         "bssl_constant_time_test_main",
965         "chacha20_poly1305_open",
966         "chacha20_poly1305_seal",
967         "fiat_curve25519_adx_mul",
968         "fiat_curve25519_adx_square",
969         "gcm_ghash_avx",
970         "gcm_ghash_clmul",
971         "gcm_ghash_neon",
972         "gcm_gmult_clmul",
973         "gcm_gmult_neon",
974         "gcm_init_avx",
975         "gcm_init_clmul",
976         "gcm_init_neon",
977         "k25519Precomp",
978         "limbs_mul_add_limb",
979         "little_endian_bytes_from_scalar",
980         "ecp_nistz256_neg",
981         "ecp_nistz256_select_w5",
982         "ecp_nistz256_select_w7",
983         "nistz384_point_add",
984         "nistz384_point_double",
985         "nistz384_point_mul",
986         "p256_mul_mont",
987         "p256_point_add",
988         "p256_point_add_affine",
989         "p256_point_double",
990         "p256_point_mul",
991         "p256_point_mul_base",
992         "p256_scalar_mul_mont",
993         "p256_scalar_sqr_rep_mont",
994         "p256_sqr_mont",
995         "p384_elem_div_by_2",
996         "p384_elem_mul_mont",
997         "p384_elem_neg",
998         "p384_elem_sub",
999         "p384_scalar_mul_mont",
1000         "openssl_poly1305_neon2_addmulmod",
1001         "openssl_poly1305_neon2_blocks",
1002         "sha256_block_data_order",
1003         "sha512_block_data_order",
1004         "vpaes_ctr32_encrypt_blocks",
1005         "vpaes_encrypt",
1006         "vpaes_encrypt_key_to_bsaes",
1007         "vpaes_set_encrypt_key",
1008         "x25519_NEON",
1009         "x25519_fe_invert",
1010         "x25519_fe_isnegative",
1011         "x25519_fe_mul_ttt",
1012         "x25519_fe_neg",
1013         "x25519_fe_tobytes",
1014         "x25519_ge_double_scalarmult_vartime",
1015         "x25519_ge_frombytes_vartime",
1016         "x25519_ge_scalarmult_base",
1017         "x25519_ge_scalarmult_base_adx",
1018         "x25519_public_from_private_generic_masked",
1019         "x25519_sc_mask",
1020         "x25519_sc_muladd",
1021         "x25519_sc_reduce",
1022         "x25519_scalar_mult_adx",
1023         "x25519_scalar_mult_generic_masked",
1024     ];
1025 
1026     let mut out = String::new();
1027 
1028     for (old, new) in SYMBOLS_TO_RENAME {
1029         let line = format!(
1030             "{pp}define {prefix_prefix}{old} {prefix_prefix}{new}\n",
1031             pp = pp,
1032             prefix_prefix = prefix_prefix,
1033             old = old,
1034             new = new
1035         );
1036         out += &line;
1037     }
1038 
1039     for symbol in SYMBOLS_TO_PREFIX {
1040         let line = format!(
1041             "{pp}define {prefix_prefix}{symbol} {prefix_prefix}{prefix}{symbol}\n",
1042             pp = pp,
1043             prefix_prefix = prefix_prefix,
1044             prefix = prefix,
1045             symbol = symbol
1046         );
1047         out += &line;
1048     }
1049 
1050     out
1051 }
1052