Index: Mk/Scripts/qa.sh =================================================================== --- Mk/Scripts/qa.sh +++ Mk/Scripts/qa.sh @@ -942,10 +942,21 @@ return 0 } +cargo_audit() +{ + if [ -z "${USESCARGO}" ] || [ -n "${CARGO_DISABLE_AUDIT}" ]; then + return 0 + fi + + "${LOCALBASE}/bin/cargo-audit" audit --offline \ + --db "${CARGO_ADVISORY_DB}" \ + --file "${CARGO_CARGOLOCK}" +} + checks="shebang symlinks paths stripped desktopfileutils sharedmimeinfo" checks="$checks suidfiles libtool libperl prefixvar baselibs terminfo" checks="$checks proxydeps sonames perlcore no_arch gemdeps gemfiledeps flavors" -checks="$checks license" +checks="$checks license cargo_audit" ret=0 cd ${STAGEDIR} || exit 1 Index: Mk/Uses/cargo.mk =================================================================== --- Mk/Uses/cargo.mk +++ Mk/Uses/cargo.mk @@ -36,6 +36,9 @@ # Save crates inside ${DISTDIR}/rust/crates by default. CARGO_DIST_SUBDIR?= rust/crates +# Location of the RustSec advisory database. +CARGO_ADVISORY_DB?= ${LOCALBASE}/share/rustsec-advisory-db + # Generate list of DISTFILES. .for _crate in ${CARGO_CRATES} MASTER_SITES+= ${MASTER_SITES_CRATESIO}/${_crate:C/^(.*)-[0-9].*/\1/}/${_crate:C/^.*-([0-9].*)/\1/}/download?dummy=/:cargo_${_crate:S/-//g:S/.//g} @@ -44,10 +47,21 @@ # Build dependencies. +.if !defined(DEVELOPER) && !defined(DEVELOPER_MODE) && !defined(POUDRIERE_CARGO_AUDIT_WORKAROUND) +# TODO: Poudriere does not define DEVELOPER{,_MODE} when gathering +# dependencies, so one has to define POUDRIERE_CARGO_AUDIT_WORKAROUND +# in make.conf right now if this is supposed to work. +CARGO_DISABLE_AUDIT= yes +.endif + CARGO_BUILDDEP?= yes .if ${CARGO_BUILDDEP:tl} == "yes" -BUILD_DEPENDS+= rust>=1.29.1:lang/rust +BUILD_DEPENDS+= rust>=1.29.1:lang/rust +.if !defined(CARGO_DISABLE_AUDIT) +BUILD_DEPENDS+= cargo-audit>=0.5.2_1:security/cargo-audit \ + rustsec-advisory-db>=g20180901:security/rustsec-advisory-db .endif +.endif # Location of cargo binary (default to lang/rust's Cargo binary) CARGO_CARGO_BIN?= ${LOCALBASE}/bin/cargo @@ -64,14 +78,20 @@ # - RUSTFLAGS: custom flags to pass to all compiler invocations that Cargo performs # # XXX LDFLAGS => -C link-arg=$1 (via RUSTFLAGS) +CARGO_HOME= ${WRKDIR}/.cargo-home CARGO_ENV+= \ - CARGO_HOME=${WRKDIR}/cargo-home \ + CARGO_HOME=${CARGO_HOME} \ CARGO_BUILD_JOBS=${MAKE_JOBS_NUMBER} \ CARGO_TARGET_DIR=${CARGO_TARGET_DIR} \ RUSTC=${LOCALBASE}/bin/rustc \ RUSTDOC=${LOCALBASE}/bin/rustdoc \ RUSTFLAGS="${RUSTFLAGS}" +QA_ENV+= CARGO_ADVISORY_DB=${CARGO_ADVISORY_DB} \ + CARGO_CARGO_BIN=${CARGO_CARGO_BIN} \ + CARGO_CARGOLOCK=${CARGO_CARGOLOCK} \ + CARGO_DISABLE_AUDIT=${CARGO_DISABLE_AUDIT:Dyes} + # Adjust -C target-cpu if -march/-mcpu is set by bsd.cpu.mk .if ${ARCH} == amd64 || ${ARCH} == i386 RUSTFLAGS+= ${CFLAGS:M-march=*:S/-march=/-C target-cpu=/} @@ -213,6 +233,7 @@ # configure hook. Place a config file for overriding crates-io index # by local source directory. do-configure: + @${MKDIR} ${CARGO_HOME} @${MKDIR} ${WRKDIR}/.cargo @${ECHO_CMD} "[source.cargo]" > ${WRKDIR}/.cargo/config @${ECHO_CMD} "directory = '${CARGO_VENDOR_DIR}'" >> ${WRKDIR}/.cargo/config @@ -242,6 +263,7 @@ do-install: @${CARGO_CARGO_RUN} install \ --root "${STAGEDIR}${PREFIX}" \ + --force \ --verbose \ ${CARGO_INSTALL_ARGS} @${RM} -- "${STAGEDIR}${PREFIX}/.crates.toml" @@ -258,6 +280,29 @@ # # Helper targets for port maintainers # + +# This is a convenience target that allows quickly checking for crate +# vulnerabilities using security/cargo-audit without having to do a +# full extract-patch-build cycle. It uses CARGO_CRATES and creates a +# dummy Cargo.lock file that is parsable by cargo-audit. +# +# In developer mode qa.sh checks for crate vulnerabilities after the +# build completes with the actual Cargo.lock that has been used (or +# that has been generated during the build). It might be different +# than the dummy Cargo.lock file generated in the `cargo-audit` target +# which is based only on whatever the porter has set in CARGO_CRATES. +cargo-audit: build-depends + @${MKDIR} ${WRKDIR} + @${ECHO_CMD} -n > ${WRKDIR}/.cargo-audit.Cargo.lock +.for _crate in ${CARGO_CRATES} + @${PRINTF} "[[package]]\nname = \"%s\"\nversion = \"%s\"\n\n" \ + ${_crate:C/^(.*)-[0-9].*/\1/} \ + ${_crate:C/^.*-([0-9].*)/\1/} \ + >> ${WRKDIR}/.cargo-audit.Cargo.lock +.endfor + @${LOCALBASE}/bin/cargo-audit audit --offline \ + --db ${CARGO_ADVISORY_DB} \ + --file ${WRKDIR}/.cargo-audit.Cargo.lock # cargo-crates will output the crates list from Cargo.lock. If there # is no Cargo.lock for some reason, try and generate it first. Index: Mk/bsd.port.mk =================================================================== --- Mk/bsd.port.mk +++ Mk/bsd.port.mk @@ -1644,6 +1644,9 @@ .if !empty(USES:Mssl) QA_ENV+= USESSSL=yes .endif +.if !empty(USES:Mcargo) +QA_ENV+= USESCARGO=yes +.endif .if !empty(USES:Mdesktop-file-utils) QA_ENV+= USESDESKTOPFILEUTILS=yes .endif Index: security/cargo-audit/Makefile =================================================================== --- security/cargo-audit/Makefile +++ security/cargo-audit/Makefile @@ -3,6 +3,7 @@ PORTNAME= cargo-audit DISTVERSIONPREFIX= v DISTVERSION= 0.5.2 +PORTREVISION= 1 CATEGORIES= security MAINTAINER= tobik@FreeBSD.org @@ -73,6 +74,7 @@ winapi-0.3.6 \ winapi-i686-pc-windows-gnu-0.4.0 \ winapi-x86_64-pc-windows-gnu-0.4.0 +CARGO_DISABLE_AUDIT= yes # cargo-audit cannot depend on itself PLIST_FILES= bin/cargo-audit Index: security/cargo-audit/files/patch-cargo-crates_rustsec-0.9.1_src_repository_commit.rs =================================================================== --- /dev/null +++ security/cargo-audit/files/patch-cargo-crates_rustsec-0.9.1_src_repository_commit.rs @@ -0,0 +1,76 @@ +--- cargo-crates/rustsec-0.9.1/src/repository/commit.rs.orig 2018-10-07 07:50:34 UTC ++++ cargo-crates/rustsec-0.9.1/src/repository/commit.rs +@@ -33,19 +33,29 @@ pub struct Commit { + + impl Commit { + /// Get information about HEAD +- pub(crate) fn from_repo_head(repo: &Repository) -> Result { +- let head = repo.repo.head()?; ++ pub(crate) fn from_repo_head(repo_: &Repository) -> Result { ++ let repo = if let Some(ref x) = repo_.repo { ++ x ++ } else { ++ Err(err!( ++ ErrorKind::Repo, ++ "not a git repository: {}", ++ repo_.path.display() ++ ))? ++ }; ++ ++ let head = repo.head()?; + + let oid = head.target().ok_or_else(|| { + err!( + ErrorKind::Repo, + "no ref target for: {}", +- repo.path.display() ++ repo_.path.display() + ) + })?; + + let commit_id = oid.to_string(); +- let commit_object = repo.repo.find_object(oid, Some(git2::ObjectType::Commit))?; ++ let commit_object = repo.find_object(oid, Some(git2::ObjectType::Commit))?; + let commit = commit_object.as_commit().unwrap(); + let author = commit.author().to_string(); + +@@ -54,9 +64,9 @@ impl Commit { + .ok_or_else(|| err!(ErrorKind::Repo, "no commit summary for {}", commit_id))? + .to_owned(); + +- let (signature, signed_data) = match repo.repo.extract_signature(&oid, None) { +- Ok((sig, data)) => (Some(Signature::new(&*sig)?), Some(Vec::from(&*data))), +- _ => (None, None), ++ let (signature, signed_data) = match repo.extract_signature(&oid, None) { ++ Ok((sig, data)) => (Some(Signature::new(&*sig)?), Some(Vec::from(&*data))), ++ _ => (None, None), + }; + + #[cfg(feature = "chrono")] +@@ -83,16 +93,16 @@ impl Commit { + + /// Reset the repository's state to match this commit + #[cfg(feature = "chrono")] +- pub(crate) fn reset(&self, repo: &Repository) -> Result<(), Error> { +- let commit_object = repo.repo.find_object( +- git2::Oid::from_str(&self.commit_id).unwrap(), +- Some(git2::ObjectType::Commit), +- )?; +- +- // Reset the state of the repository to the latest commit +- repo.repo +- .reset(&commit_object, git2::ResetType::Hard, None)?; +- ++ pub(crate) fn reset(&self, repo_: &Repository) -> Result<(), Error> { ++ if let Some(ref repo) = repo_.repo { ++ let commit_object = repo.find_object( ++ git2::Oid::from_str(&self.commit_id).unwrap(), ++ Some(git2::ObjectType::Commit), ++ )?; ++ ++ // Reset the state of the repository to the latest commit ++ repo.reset(&commit_object, git2::ResetType::Hard, None)?; ++ } + Ok(()) + } + Index: security/cargo-audit/files/patch-cargo-crates_rustsec-0.9.1_src_repository_mod.rs =================================================================== --- /dev/null +++ security/cargo-audit/files/patch-cargo-crates_rustsec-0.9.1_src_repository_mod.rs @@ -0,0 +1,36 @@ +--- cargo-crates/rustsec-0.9.1/src/repository/mod.rs.orig 2018-10-07 07:47:47 UTC ++++ cargo-crates/rustsec-0.9.1/src/repository/mod.rs +@@ -42,7 +42,7 @@ pub struct Repository { + path: PathBuf, + + /// Repository object +- repo: git2::Repository, ++ repo: Option, + } + + impl Repository { +@@ -152,12 +152,18 @@ impl Repository { + /// Open a repository at the given path + pub fn open>(into_path: P) -> Result { + let path = into_path.into(); +- let repo = git2::Repository::open(&path)?; +- +- // Ensure the repo is in a clean state +- match repo.state() { +- git2::RepositoryState::Clean => Ok(Repository { path, repo }), +- state => fail!(ErrorKind::Repo, "bad repository state: {:?}", state), ++ match git2::Repository::open(&path) { ++ Ok(repo) => { ++ // Ensure the repo is in a clean state ++ match repo.state() { ++ git2::RepositoryState::Clean => ++ Ok(Repository { path: path, repo: Some(repo) }), ++ state => ++ fail!(ErrorKind::Repo, "bad repository state: {:?}", state), ++ } ++ }, ++ Err(_) => ++ Ok(Repository { path: path, repo: None }), + } + } + Index: security/cargo-audit/files/patch-src_main.rs =================================================================== --- /dev/null +++ security/cargo-audit/files/patch-src_main.rs @@ -0,0 +1,68 @@ +--- src/main.rs.orig 2018-07-29 21:14:21 UTC ++++ src/main.rs +@@ -59,6 +59,14 @@ struct AuditOpts { + )] + db: Option, + ++ /// Fetch/update the advisory database and exit ++ #[options( ++ no_short, ++ long = "fetch-only", ++ help = "fetch the advisory database and exit" ++ )] ++ fetch_only: bool, ++ + /// Path to the advisory database git repository + #[options( + short = "f", +@@ -67,6 +75,14 @@ struct AuditOpts { + )] + file: Option, + ++ /// Do not allow network connections ++ #[options( ++ no_short, ++ long = "offline", ++ help = "do not allow network connections" ++ )] ++ offline: bool, ++ + /// Allow stale advisory databases that haven't been recently updated + #[options(no_short, long = "stale", help = "allow stale database")] + stale: bool, +@@ -108,7 +124,9 @@ impl Default for AuditOpts { + AuditOpts { + color: "auto".into(), + db: None, ++ fetch_only: false, + file: None, ++ offline: false, + stale: false, + target_arch: None, + target_os: None, +@@ -170,13 +188,19 @@ fn load_advisory_db(opts: &AuditOpts) -> AdvisoryDatab + .map(PathBuf::from) + .unwrap_or_else(Repository::default_path); + +- status_ok!("Fetching", "advisory database from `{}`", opts.url); ++ let advisory_db_repo = if opts.offline { ++ Repository::open(&advisory_repo_path) ++ } else { ++ status_ok!("Fetching", "advisory database from `{}`", opts.url); ++ Repository::fetch(&opts.url, &advisory_repo_path, !opts.stale) ++ }.unwrap_or_else(|e| { ++ status_error!("couldn't fetch advisory database: {}", e); ++ exit(1); ++ }); + +- let advisory_db_repo = Repository::fetch(&opts.url, &advisory_repo_path, !opts.stale) +- .unwrap_or_else(|e| { +- status_error!("couldn't fetch advisory database: {}", e); +- exit(1); +- }); ++ if opts.fetch_only { ++ exit(0); ++ } + + let advisory_db = AdvisoryDatabase::from_repository(&advisory_db_repo).unwrap_or_else(|e| { + status_error!("error loading advisory database: {}", e); Index: security/rustsec-advisory-db/Makefile =================================================================== --- /dev/null +++ security/rustsec-advisory-db/Makefile @@ -0,0 +1,41 @@ +# $FreeBSD$ + +PORTNAME= advisory-db +DISTVERSION= g20180901 +CATEGORIES= security +PKGNAMEPREFIX= rustsec- + +MAINTAINER= tobik@FreeBSD.org +COMMENT= Security advisory database for Rust crates + +LICENSE= PD +LICENSE_FILE= ${WRKSRC}/LICENSE.txt + +TEST_DEPENDS= cargo-audit>=0.5.2_1:security/cargo-audit + +USE_GITHUB= yes +GH_ACCOUNT= RustSec +GH_TAGNAME= b2125b68a5db85f7cfaa577a8600460d3fd4fa52 + +NO_ARCH= yes +NO_BUILD= yes + +DATADIR= ${PREFIX}/share/${PKGBASE} + +do-install: + @${MKDIR} ${STAGEDIR}${DATADIR} + @cd ${WRKSRC} && ${COPYTREE_SHARE} crates ${STAGEDIR}${DATADIR} + +# Dumb smoke test to check if the database can be loaded from a +# directory and if cargo-audit prints out some vulnerabilities. +do-test: + @${LOCALBASE}/bin/cargo-audit audit \ + --offline \ + --file ${FILESDIR}/Cargo.lock.test \ + --db ${STAGEDIR}${DATADIR} | ${AWK} '\ + BEGIN { status = 1 } \ + { print } \ + /^error: [0-9]+ vulnerabilities found/ { status = 0 } \ + END { exit(status); }' + +.include Index: security/rustsec-advisory-db/distinfo =================================================================== --- /dev/null +++ security/rustsec-advisory-db/distinfo @@ -0,0 +1,3 @@ +TIMESTAMP = 1538884375 +SHA256 (RustSec-advisory-db-g20180901-b2125b68a5db85f7cfaa577a8600460d3fd4fa52_GH0.tar.gz) = c80af8361c09253794fdb7a30881a34aabe9dc798406f03bf0dbc22372253e49 +SIZE (RustSec-advisory-db-g20180901-b2125b68a5db85f7cfaa577a8600460d3fd4fa52_GH0.tar.gz) = 9491 Index: security/rustsec-advisory-db/files/Cargo.lock.test =================================================================== --- /dev/null +++ security/rustsec-advisory-db/files/Cargo.lock.test @@ -0,0 +1,8 @@ +[[package]] +name = "tar" +version = "0.4.15" + +[[package]] +name = "smallvec" +version = "0.6.2" + Index: security/rustsec-advisory-db/pkg-descr =================================================================== --- /dev/null +++ security/rustsec-advisory-db/pkg-descr @@ -0,0 +1,4 @@ +Security advisory database for Rust crates published through +https://crates.io. + +WWW: https://rustsec.org/ Index: security/rustsec-advisory-db/pkg-plist =================================================================== --- /dev/null +++ security/rustsec-advisory-db/pkg-plist @@ -0,0 +1,11 @@ +%%DATADIR%%/crates/base64/RUSTSEC-2017-0004.toml +%%DATADIR%%/crates/claxon/RUSTSEC-2018-0004.toml +%%DATADIR%%/crates/cookie/RUSTSEC-2017-0005.toml +%%DATADIR%%/crates/hyper/RUSTSEC-2016-0002.toml +%%DATADIR%%/crates/hyper/RUSTSEC-2017-0002.toml +%%DATADIR%%/crates/openssl/RUSTSEC-2016-0001.toml +%%DATADIR%%/crates/security-framework/RUSTSEC-2017-0003.toml +%%DATADIR%%/crates/smallvec/RUSTSEC-2018-0003.toml +%%DATADIR%%/crates/sodiumoxide/RUSTSEC-2017-0001.toml +%%DATADIR%%/crates/tar/RUSTSEC-2018-0002.toml +%%DATADIR%%/crates/untrusted/RUSTSEC-2018-0001.toml