Skip to content

Commit

Permalink
Add a "link-guard" to avoid accidentally linking to a wrong dylib at …
Browse files Browse the repository at this point in the history
…runtime.

We want to prevent compiling something against one version
of a dynamic library and then, at runtime accidentally
using a different version of the dynamic library. With the
old symbol-naming scheme this could not happen because every
symbol had the SVH in it and you'd get an error by the
dynamic linker when using the wrong version of a dylib. With
the new naming scheme this isn't the case any more, so this
patch adds the "link-guard" to prevent this error case.

This is implemented as follows:

- In every crate that we compile, we emit a function called
  "__rustc_link_guard_<crate-name>_<crate-svh>"
- The body of this function contains calls to the
  "__rustc_link_guard" functions of all dependencies.
- An executable contains a call to it's own
  "__rustc_link_guard" function.

As a consequence the "__rustc_link_guard" function call graph
mirrors the crate graph and the dynamic linker will fail if a
wrong dylib is loaded somewhere because its
"__rustc_link_guard" function will contain a different SVH in
its name.
  • Loading branch information
michaelwoerister authored and nikomatsakis committed Mar 25, 2016
1 parent 82b5f1d commit 2475707
Show file tree
Hide file tree
Showing 14 changed files with 241 additions and 9 deletions.
7 changes: 7 additions & 0 deletions src/librustc/middle/cstore.rs
Expand Up @@ -204,7 +204,11 @@ pub trait CrateStore<'tcx> : Any {
fn is_explicitly_linked(&self, cnum: ast::CrateNum) -> bool;
fn is_allocator(&self, cnum: ast::CrateNum) -> bool;
fn crate_attrs(&self, cnum: ast::CrateNum) -> Vec<ast::Attribute>;
/// The name of the crate as it is referred to in source code of the current
/// crate.
fn crate_name(&self, cnum: ast::CrateNum) -> InternedString;
/// The name of the crate as it is stored in the crate's metadata.
fn original_crate_name(&self, cnum: ast::CrateNum) -> InternedString;
fn crate_hash(&self, cnum: ast::CrateNum) -> Svh;
fn crate_disambiguator(&self, cnum: ast::CrateNum) -> InternedString;
fn crate_struct_field_attrs(&self, cnum: ast::CrateNum)
Expand Down Expand Up @@ -385,6 +389,9 @@ impl<'tcx> CrateStore<'tcx> for DummyCrateStore {
fn crate_attrs(&self, cnum: ast::CrateNum) -> Vec<ast::Attribute>
{ unimplemented!() }
fn crate_name(&self, cnum: ast::CrateNum) -> InternedString { unimplemented!() }
fn original_crate_name(&self, cnum: ast::CrateNum) -> InternedString {
unimplemented!()
}
fn crate_hash(&self, cnum: ast::CrateNum) -> Svh { unimplemented!() }
fn crate_disambiguator(&self, cnum: ast::CrateNum) -> InternedString { unimplemented!() }
fn crate_struct_field_attrs(&self, cnum: ast::CrateNum)
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_metadata/creader.rs
Expand Up @@ -277,10 +277,10 @@ impl<'a> CrateReader<'a> {
}

fn verify_no_symbol_conflicts(&self,
crate_name: &str,
span: Span,
metadata: &MetadataBlob) {
let disambiguator = decoder::get_crate_disambiguator(metadata.as_slice());
let crate_name = decoder::get_crate_name(metadata.as_slice());

// Check for (potential) conflicts with the local crate
if self.local_crate_name == crate_name &&
Expand Down Expand Up @@ -318,7 +318,7 @@ impl<'a> CrateReader<'a> {
-> (ast::CrateNum, Rc<cstore::crate_metadata>,
cstore::CrateSource) {
self.verify_rustc_version(name, span, &lib.metadata);
self.verify_no_symbol_conflicts(name, span, &lib.metadata);
self.verify_no_symbol_conflicts(span, &lib.metadata);

// Claim this crate number and cache it
let cnum = self.next_crate_num;
Expand Down
5 changes: 5 additions & 0 deletions src/librustc_metadata/csearch.rs
Expand Up @@ -339,6 +339,11 @@ impl<'tcx> CrateStore<'tcx> for cstore::CStore {
token::intern_and_get_ident(&self.get_crate_data(cnum).name[..])
}

fn original_crate_name(&self, cnum: ast::CrateNum) -> token::InternedString
{
token::intern_and_get_ident(&self.get_crate_data(cnum).name())
}

fn crate_hash(&self, cnum: ast::CrateNum) -> Svh
{
let cdata = self.get_crate_data(cnum);
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_metadata/cstore.rs
Expand Up @@ -248,7 +248,7 @@ impl CStore {

impl crate_metadata {
pub fn data<'a>(&'a self) -> &'a [u8] { self.data.as_slice() }
pub fn name(&self) -> String { decoder::get_crate_name(self.data()) }
pub fn name(&self) -> &str { decoder::get_crate_name(self.data()) }
pub fn hash(&self) -> Svh { decoder::get_crate_hash(self.data()) }
pub fn disambiguator(&self) -> &str {
decoder::get_crate_disambiguator(self.data())
Expand Down
6 changes: 3 additions & 3 deletions src/librustc_metadata/decoder.rs
Expand Up @@ -1288,10 +1288,10 @@ pub fn get_crate_hash(data: &[u8]) -> Svh {
Svh::new(hashdoc.as_str_slice())
}

pub fn maybe_get_crate_name(data: &[u8]) -> Option<String> {
pub fn maybe_get_crate_name(data: &[u8]) -> Option<&str> {
let cratedoc = rbml::Doc::new(data);
reader::maybe_get_doc(cratedoc, tag_crate_crate_name).map(|doc| {
doc.as_str_slice().to_string()
doc.as_str_slice()
})
}

Expand All @@ -1308,7 +1308,7 @@ pub fn get_crate_triple(data: &[u8]) -> Option<String> {
triple_doc.map(|s| s.as_str().to_string())
}

pub fn get_crate_name(data: &[u8]) -> String {
pub fn get_crate_name(data: &[u8]) -> &str {
maybe_get_crate_name(data).expect("no crate name in crate")
}

Expand Down
21 changes: 21 additions & 0 deletions src/librustc_trans/back/linker.rs
Expand Up @@ -23,6 +23,7 @@ use session::config::CrateTypeDylib;
use session::config;
use syntax::ast;
use trans::CrateTranslation;
use trans::link_guard;

/// Linker abstraction used by back::link to build up the command to invoke a
/// linker.
Expand Down Expand Up @@ -359,6 +360,26 @@ impl<'a> Linker for MsvcLinker<'a> {
for symbol in symbols {
writeln!(f, " {}", symbol)?;
}

// Add link-guard symbols
{
// local crate
let symbol = link_guard::link_guard_name(&trans.link.crate_name[..],
&trans.link.crate_hash);
try!(writeln!(f, " {}", symbol));
}
// statically linked dependencies
for (i, format) in formats[&CrateTypeDylib].iter().enumerate() {
if *format == Linkage::Static {
let cnum = (i + 1) as ast::CrateNum;
let crate_name = cstore.original_crate_name(cnum);
let svh = cstore.crate_hash(cnum);

let symbol = link_guard::link_guard_name(&crate_name[..], &svh);
try!(writeln!(f, " {}", symbol));
}
}

Ok(())
})();
if let Err(e) = res {
Expand Down
24 changes: 24 additions & 0 deletions src/librustc_trans/trans/base.rs
Expand Up @@ -79,6 +79,7 @@ use trans::expr;
use trans::glue;
use trans::inline;
use trans::intrinsic;
use trans::link_guard;
use trans::machine;
use trans::machine::{llalign_of_min, llsize_of, llsize_of_real};
use trans::meth;
Expand Down Expand Up @@ -2382,6 +2383,7 @@ pub fn create_entry_wrapper(ccx: &CrateContext, sp: Span, main_llfn: ValueRef) {
unsafe {
llvm::LLVMPositionBuilderAtEnd(bld, llbb);

link_guard::insert_reference_to_link_guard(ccx, llbb);
debuginfo::gdb::insert_reference_to_gdb_debug_scripts_section_global(ccx);

let (start_fn, args) = if use_start_lang_item {
Expand Down Expand Up @@ -2758,6 +2760,8 @@ pub fn trans_crate<'tcx>(tcx: &TyCtxt<'tcx>,
collector::print_collection_results(&ccx);
}

emit_link_guard_if_necessary(&shared_ccx);

for ccx in shared_ccx.iter() {
if ccx.sess().opts.debuginfo != NoDebugInfo {
debuginfo::finalize(&ccx);
Expand Down Expand Up @@ -2818,6 +2822,8 @@ pub fn trans_crate<'tcx>(tcx: &TyCtxt<'tcx>,
if sess.entry_fn.borrow().is_some() {
reachable_symbols.push("main".to_string());
}
reachable_symbols.push(link_guard::link_guard_name(&link_meta.crate_name,
&link_meta.crate_hash));

// For the purposes of LTO, we add to the reachable set all of the upstream
// reachable extern fns. These functions are all part of the public ABI of
Expand Down Expand Up @@ -2861,6 +2867,24 @@ pub fn trans_crate<'tcx>(tcx: &TyCtxt<'tcx>,
}
}

fn emit_link_guard_if_necessary(shared_ccx: &SharedCrateContext) {
let link_meta = shared_ccx.link_meta();
let link_guard_name = link_guard::link_guard_name(&link_meta.crate_name,
&link_meta.crate_hash);
let link_guard_name = CString::new(link_guard_name).unwrap();

// Check if the link-guard has already been emitted in a codegen unit
let link_guard_already_emitted = shared_ccx.iter().any(|ccx| {
let link_guard = unsafe { llvm::LLVMGetNamedValue(ccx.llmod(),
link_guard_name.as_ptr()) };
!link_guard.is_null()
});

if !link_guard_already_emitted {
link_guard::get_or_insert_link_guard(&shared_ccx.get_ccx(0));
}
}

/// We visit all the items in the krate and translate them. We do
/// this in two walks. The first walk just finds module items. It then
/// walks the full contents of those module items and translates all
Expand Down
116 changes: 116 additions & 0 deletions src/librustc_trans/trans/link_guard.rs
@@ -0,0 +1,116 @@
// Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use back::svh::Svh;
use libc::c_uint;
use llvm;
use std::ffi::CString;
use std::ptr;
use trans::attributes;
use trans::builder;
use trans::CrateContext;
use trans::declare;
use trans::type_::Type;

const GUARD_PREFIX: &'static str = "__rustc_link_guard_";

pub fn link_guard_name(crate_name: &str, crate_svh: &Svh) -> String {

let mut guard_name = String::new();

guard_name.push_str(GUARD_PREFIX);
guard_name.push_str(crate_name);
guard_name.push_str("_");
guard_name.push_str(crate_svh.as_str());

guard_name
}

pub fn get_or_insert_link_guard<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>)
-> llvm::ValueRef {

let guard_name = link_guard_name(&ccx.tcx().crate_name[..],
&ccx.link_meta().crate_hash);

let guard_function = unsafe {
let guard_name_c_string = CString::new(&guard_name[..]).unwrap();
llvm::LLVMGetNamedValue(ccx.llmod(), guard_name_c_string.as_ptr())
};

if guard_function != ptr::null_mut() {
return guard_function;
}

let llfty = Type::func(&[], &Type::void(ccx));
let guard_function = declare::define_cfn(ccx,
&guard_name[..],
llfty,
ccx.tcx().mk_nil()).unwrap_or_else(|| {
ccx.sess().bug("Link guard already defined.");
});

attributes::emit_uwtable(guard_function, true);
attributes::unwind(guard_function, false);

let bld = ccx.raw_builder();
unsafe {
let llbb = llvm::LLVMAppendBasicBlockInContext(ccx.llcx(),
guard_function,
"link_guard_top\0".as_ptr() as *const _);
llvm::LLVMPositionBuilderAtEnd(bld, llbb);

for crate_num in ccx.sess().cstore.crates() {
if !ccx.sess().cstore.is_explicitly_linked(crate_num) {
continue;
}

let crate_name = ccx.sess().cstore.original_crate_name(crate_num);
let svh = ccx.sess().cstore.crate_hash(crate_num);

let dependency_guard_name = link_guard_name(&crate_name[..], &svh);

let decl = declare::declare_cfn(ccx,
&dependency_guard_name[..],
llfty,
ccx.tcx().mk_nil());
attributes::unwind(decl, false);

llvm::LLVMPositionBuilderAtEnd(bld, llbb);

let args: &[llvm::ValueRef] = &[];
llvm::LLVMRustBuildCall(bld,
decl,
args.as_ptr(),
args.len() as c_uint,
0 as *mut _,
builder::noname());
}

llvm::LLVMBuildRetVoid(bld);
}

guard_function
}

pub fn insert_reference_to_link_guard<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
llbb: llvm::BasicBlockRef) {
let guard_function = get_or_insert_link_guard(ccx);

unsafe {
llvm::LLVMPositionBuilderAtEnd(ccx.raw_builder(), llbb);
let args: &[llvm::ValueRef] = &[];
llvm::LLVMRustBuildCall(ccx.raw_builder(),
guard_function,
args.as_ptr(),
args.len() as c_uint,
0 as *mut _,
builder::noname());
}
}
1 change: 1 addition & 0 deletions src/librustc_trans/trans/mod.rs
Expand Up @@ -53,6 +53,7 @@ mod expr;
mod glue;
mod inline;
mod intrinsic;
pub mod link_guard;
mod machine;
mod _match;
mod meth;
Expand Down
13 changes: 13 additions & 0 deletions src/test/run-make/link-guard/Makefile
@@ -0,0 +1,13 @@
-include ../tools.mk

all:
-mkdir -p $(TMPDIR)/good
-mkdir -p $(TMPDIR)/bad
$(BARE_RUSTC) ./good/lib.rs -C prefer-dynamic --out-dir="$(TMPDIR)/good"
$(BARE_RUSTC) ./bad/lib.rs -C prefer-dynamic --out-dir="$(TMPDIR)/bad"
$(BARE_RUSTC) -L "$(TMPDIR)/good" -C prefer-dynamic -Crpath ./main.rs --out-dir="$(TMPDIR)"
# This should succeed because the correct library is in LD_LIBRARY_PATH
$(LD_LIB_PATH_ENVVAR)="$(TMPDIR)/good:$($(LD_LIB_PATH_ENVVAR))" $(TMPDIR)/main
# This should fail because the wrong library is in LD_LIBRARY_PATH
OUTPUT=`$(LD_LIB_PATH_ENVVAR)="$(TMPDIR)/bad:$($(LD_LIB_PATH_ENVVAR))" $(TMPDIR)/main || exit 0`
if ["$(OUTPUT)" == "bad"]; then exit 1; fi
16 changes: 16 additions & 0 deletions src/test/run-make/link-guard/bad/lib.rs
@@ -0,0 +1,16 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![crate_name="thelibrary"]
#![crate_type="dylib"]

pub fn some_library_function() {
println!("bad");
}
16 changes: 16 additions & 0 deletions src/test/run-make/link-guard/good/lib.rs
@@ -0,0 +1,16 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![crate_name="thelibrary"]
#![crate_type="dylib"]

pub fn some_library_function() {
println!("bad");
}
15 changes: 15 additions & 0 deletions src/test/run-make/link-guard/main.rs
@@ -0,0 +1,15 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate thelibrary;

fn main() {
thelibrary::some_library_function();
}
4 changes: 1 addition & 3 deletions src/test/run-make/relocation-model/Makefile
Expand Up @@ -7,8 +7,7 @@ all: others
$(RUSTC) -C relocation-model=default foo.rs
$(call RUN,foo)

$(RUSTC) -C relocation-model=default --crate-type=dylib foo.rs
$(RUSTC) -C relocation-model=dynamic-no-pic --crate-type=dylib foo.rs
$(RUSTC) -C relocation-model=dynamic-no-pic --crate-type=dylib foo.rs --emit=link,obj

ifdef IS_MSVC
# FIXME(#28026)
Expand All @@ -17,5 +16,4 @@ else
others:
$(RUSTC) -C relocation-model=static foo.rs
$(call RUN,foo)
$(RUSTC) -C relocation-model=static --crate-type=dylib foo.rs
endif

0 comments on commit 2475707

Please sign in to comment.