From 8ed3270c8119a41e42624b75247ac10c133fba58 Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Mon, 14 Jun 2021 18:34:55 -0400 Subject: [PATCH 1/2] bridge_core: add a basic security infrastructure After adding shell-escape I started feeling like we should have a bit more of a systematic approach to ensuring that API users do their due diligence with regards to security. Here, we minorly break the crate behavior to default to disabling all known-insecure features (currently, only shell-escape), but give a new mechanism to override that. The setup is centralized such that whenever you try to create a security policy, it can be overridden in a more conservative direction by setting the environment variable `TECTONIC_UNTRUSTED_MODE`. --- crates/bridge_core/README.md | 2 +- crates/bridge_core/src/lib.rs | 144 +++++++++++++++++++++++++++++++--- 2 files changed, 133 insertions(+), 13 deletions(-) diff --git a/crates/bridge_core/README.md b/crates/bridge_core/README.md index 84ae7a8a77..06dbc3f3c1 100644 --- a/crates/bridge_core/README.md +++ b/crates/bridge_core/README.md @@ -28,6 +28,6 @@ use tectonic_bridge_core; ## Cargo features -This crate does not currently provides any [Cargo features][features]. +This crate does not currently provide any [Cargo features][features]. [features]: https://doc.rust-lang.org/cargo/reference/features.html diff --git a/crates/bridge_core/src/lib.rs b/crates/bridge_core/src/lib.rs index 00baf3edc5..79679c49f7 100644 --- a/crates/bridge_core/src/lib.rs +++ b/crates/bridge_core/src/lib.rs @@ -216,14 +216,32 @@ impl std::error::Error for EngineAbortedError {} pub struct CoreBridgeLauncher<'a> { hooks: &'a mut dyn DriverHooks, status: &'a mut dyn StatusBackend, + security: SecuritySettings, } impl<'a> CoreBridgeLauncher<'a> { /// Set up a new context for launching bridged FFI code. + /// + /// This function uses the default security stance, which disallows all + /// known-insecure engine features. Use [`Self::new_with_security`] to + /// provide your own security settings that can attempt to allow the use of + /// such features. pub fn new(hooks: &'a mut dyn DriverHooks, status: &'a mut dyn StatusBackend) -> Self { - CoreBridgeLauncher { hooks, status } + Self::new_with_security(hooks, status, SecuritySettings::default()) } + /// Set up a new context for launching bridged FFI code. + pub fn new_with_security( + hooks: &'a mut dyn DriverHooks, + status: &'a mut dyn StatusBackend, + security: SecuritySettings, + ) -> Self { + CoreBridgeLauncher { + hooks, + status, + security, + } + } /// Invoke a function to launch a bridged FFI engine with a global mutex /// held. /// @@ -242,7 +260,7 @@ impl<'a> CoreBridgeLauncher<'a> { F: FnOnce(&mut CoreBridgeState<'_>) -> Result, { let _guard = ENGINE_LOCK.lock().unwrap(); - let mut state = CoreBridgeState::new(self.hooks, self.status); + let mut state = CoreBridgeState::new(self.security.clone(), self.hooks, self.status); let result = callback(&mut state); if let Err(ref e) = result { @@ -262,6 +280,9 @@ impl<'a> CoreBridgeLauncher<'a> { /// these state structures into the C/C++ layer. It is essential that lifetimes /// be properly managed across the Rust/C boundary. pub struct CoreBridgeState<'a> { + /// The security settings for this invocation + security: SecuritySettings, + /// The driver hooks associated with this engine invocation. hooks: &'a mut dyn DriverHooks, @@ -286,10 +307,12 @@ pub struct CoreBridgeState<'a> { impl<'a> CoreBridgeState<'a> { fn new( + security: SecuritySettings, hooks: &'a mut dyn DriverHooks, status: &'a mut dyn StatusBackend, ) -> CoreBridgeState<'a> { CoreBridgeState { + security, hooks, status, output_handles: Vec::new(), @@ -636,22 +659,119 @@ impl<'a> CoreBridgeState<'a> { } fn shell_escape(&mut self, command: &str) -> bool { - match self.hooks.sysrq_shell_escape(command, self.status) { - Ok(_) => false, + if self.security.allow_shell_escape() { + match self.hooks.sysrq_shell_escape(command, self.status) { + Ok(_) => false, - Err(e) => { - tt_error!( - self.status, - "failed to execute the shell-escape command \"{}\": {}", - command, - e - ); - true + Err(e) => { + tt_error!( + self.status, + "failed to execute the shell-escape command \"{}\": {}", + command, + e + ); + true + } } + } else { + tt_error!( + self.status, + "forbidden to execute shell-escape command \"{}\"", + command + ); + true } } } +/// A type for storing settings about potentially insecure engine features. +/// +/// This type encapsulates configuration about which potentially insecure engine +/// features are enabled. Methods that configure or instantiate engines require +/// values of this type, and values of this type can only be created through +/// centralized methods that respect standard environment variables, ensuring +/// that there is some level of uniform control over the activation of any +/// known-insecure features. +/// +/// The purpose of this framework is to manage the use of engine features that +/// are known to create security risks with *untrusted* input, but that trusted +/// users may wish to use due to the extra functionalities they bring. (This is +/// why these are settings and not simply security flaws!) The primary example +/// of this is the TeX engine’s shell-escape feature. +/// +/// Of course, this framework is only as good as our understanding of Tectonic’s +/// security profile. Future versions might disable or restrict different pieces +/// of functionality as new risks are discovered. +#[derive(Clone, Debug)] +pub struct SecuritySettings { + /// While we might eventually gain finer-grained enable/disable settings, + /// there should always be a hard "disable everything known to be risky" + /// option that supersedes everything else. + disable_insecures: bool, +} + +/// Different high-level security stances that can be adopted when creating +/// [`SecuritySettings`]. +#[derive(Clone, Debug)] +pub enum SecurityStance { + /// Ensure that all known-insecure features are disabled. + /// + /// Use this stance if you are processing untrusted input. + DisableInsecures, + + /// Request to allow the use of known-insecure features. + /// + /// Use this stance if you are processing trusted input *and* there is some + /// user-level request to use such features. The request to allow insecure + /// features might be overridden if the environment variable + /// `TECTONIC_UNTRUSTED_MODE` is set. + MaybeAllowInsecures, +} + +impl Default for SecurityStance { + fn default() -> Self { + // Obvi, the default is secure!!! + SecurityStance::DisableInsecures + } +} + +impl SecuritySettings { + /// Create a new security configuration. + /// + /// The *stance* argument specifies the high-level security stance. If your + /// program will be run by a trusted user, they should be able to control + /// the setting through a command-line argument or something comparable. + /// Even if there is a request to enable known-insecure features, however, + /// such a request might be overridden by other mechanisms. In particular, + /// if the environment variable `TECTONIC_UNTRUSTED_MODE` is set to any + /// value, insecure features will always be disabled regardless of the + /// user-level setting. Other mechanisms for disable known-insecure features + /// may be added in the future. + pub fn new(stance: SecurityStance) -> Self { + let disable_insecures = if std::env::var_os("TECTONIC_UNTRUSTED_MODE").is_some() { + true + } else { + match stance { + SecurityStance::DisableInsecures => true, + SecurityStance::MaybeAllowInsecures => false, + } + }; + + SecuritySettings { disable_insecures } + } + + /// Query whether the shell-escape TeX engine feature is allowed to be used. + pub fn allow_shell_escape(&self) -> bool { + !self.disable_insecures + } +} + +impl Default for SecuritySettings { + fn default() -> Self { + SecuritySettings::new(SecurityStance::default()) + } +} + // The entry points. /// Issue a warning. From ce4f000b9d71ddecc83c0c3086c4522cd4ab0937 Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Mon, 14 Jun 2021 18:38:17 -0400 Subject: [PATCH 2/2] tectonic: use the new security API Building off of the new core bridge API, we can provide a bit of a more refined approach to security controls. `tectonic -X compile` and `tectonic -X build` now take an `--untrusted` option that ensures that `-Z shell-escape`, and any future insecure features, cannot be enabled. As with the core bridge work, this changes the ProcessingSessionBuilder to be in untrusted mode by default, and adds a new API to configure in a more-trusted mode if that's what you want to do. --- Cargo.toml | 2 +- docs/src/v2cli/build.md | 13 +++++++ docs/src/v2cli/compile.md | 25 +++++++++++++- src/bin/tectonic/compile.rs | 23 ++++++++++--- src/bin/tectonic/v2cli.rs | 21 ++++++++++-- src/docmodel.rs | 31 ++++++----------- src/driver.rs | 68 +++++++++++++++---------------------- tests/executable.rs | 62 ++++++++++++++++++++++++++++----- 8 files changed, 166 insertions(+), 79 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 102d6ea513..bbf1519630 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,7 +125,7 @@ x86_64-unknown-linux-gnu = { install = ["fontconfig","freetype","harfbuzz[icu,gr x86_64-pc-windows-msvc = { triplet = "x64-windows-static", install = ["fontconfig","freetype","harfbuzz[icu,graphite2]"] } [package.metadata.internal_dep_versions] -tectonic_bridge_core = "4e16bf963700aae59772a6fb223981ceaa9b5f57" +tectonic_bridge_core = "thiscommit:2021-06-14:3sp2O1O" tectonic_bridge_flate = "thiscommit:2021-01-01:eer4ahL4" tectonic_bridge_graphite2 = "2c1ffcd702a662c003bd3d7d0ca4d169784cb6ad" tectonic_bridge_harfbuzz = "2c1ffcd702a662c003bd3d7d0ca4d169784cb6ad" diff --git a/docs/src/v2cli/build.md b/docs/src/v2cli/build.md index 74305d7d4c..ab5330a592 100644 --- a/docs/src/v2cli/build.md +++ b/docs/src/v2cli/build.md @@ -17,6 +17,7 @@ tectonic -X build [--only-cached] [--print] [--open] + [--untrusted] ``` #### Remarks @@ -52,3 +53,15 @@ identical to, the contents of the log file. By default, this output is only printed if the engine encounteres a fatal error. The `--open` option will open the built document using the system handler. + +Use the `--untrusted` option if building untrusted content. This is not the +default because in most cases you *will* trust the document that you’re +building, probably because you have created it yourself, and it would be very +annoying to have to pass `--trusted` every time you build a document that uses +shell-escape. See the security discussion in the documentation of the +[compile](./compile.md) command for details. In actual usage, it would obviously +be easy to forget to use this option; in cases where untrusted inputs are a +genuine concern, we recommend setting the environment variable +`TECTONIC_UNTRUSTED_MODE` to a non-empty value. This has the same effect as the +`--untrusted` option. Note, however, that a hostile shell user can trivially +clear this variable. \ No newline at end of file diff --git a/docs/src/v2cli/compile.md b/docs/src/v2cli/compile.md index 9378097e2d..5985e1a738 100644 --- a/docs/src/v2cli/compile.md +++ b/docs/src/v2cli/compile.md @@ -36,6 +36,7 @@ tectonic -X compile # full form [--print] [-p] [--reruns COUNT] [-r COUNT] [--synctex] + [--untrusted] [--web-bundle URL] [-w] [-Z UNSTABLE-OPTION] TEXPATH @@ -63,6 +64,27 @@ This will compile the file and create `myfile.pdf` if nothing went wrong. You can use an input filename of `-` to have Tectonic process standard input. (In this case, the output file will be named `texput.pdf`.) +##### Security + +By default, the document is compiled in a “trusted” mode. This means that the +calling user can request to enable certain engine features that could raise +security concerns if used with untrusted input: the classic example of this +being TeX's “shell-escape” functionality. These features are *not* enabled by +default, but they can be enabled on the command line; in the case of +shell-escape, this is done with `-Z shell-escape`. + +If the command-line argument `--untrusted` is provided, these features cannot be +enabled, regardless of other settings such as `-Z shell-escape`. So if you are +going to process untrusted input in a command-line script, as long as you make +sure that `--untrusted` is provided, the known-dangerous features will be +disabled. + +Furthermore, if the environment variable `TECTONIC_UNTRUSTED_MODE` is set to a +non-empty value, Tectonic will behave as if `--untrusted` were specified, +regardless of the actual command-line arguments. Setting this variable can +provide a modest extra layer of protection if the Tectonic engine is being run +outside of its CLI form. Keep in mind that untrusted shell scripts and the like +can trivially defeat this by explicitly clearing the environment variable. #### Options @@ -87,6 +109,7 @@ The following are the available flags. | `-p` | `--print` | Print the engine's chatter during processing | | `-r` | `--reruns ` | Rerun the TeX engine exactly this many times after the first | | | `--synctex` | Generate SyncTeX data | +| | `--untrusted` | Input is untrusted: disable all known-insecure features | | `-V` | `--version` | Prints version information | | `-w` | `--web-bundle ` | Use this URL find resource files instead of the default | | `-Z` | `-Z ` | Activate experimental “unstable” options | @@ -102,5 +125,5 @@ the set of unstable options is subject to change at any time. | `-Z continue-on-errors` | Keep compiling even when severe errors occur | | `-Z min-crossrefs=` | Equivalent to bibtex's `-min-crossrefs` flag. Default vaue: 2 | | `-Z paper-size=` | Change the initial paper size. Default: `letter` | -| `-Z shell-escape` | Enable `\write18` | +| `-Z shell-escape` | Enable `\write18` (unless `--untrusted` has been specified) | diff --git a/src/bin/tectonic/compile.rs b/src/bin/tectonic/compile.rs index 8830eaef3d..069914f1c3 100644 --- a/src/bin/tectonic/compile.rs +++ b/src/bin/tectonic/compile.rs @@ -1,18 +1,18 @@ -// Copyright 2016-2020 the Tectonic Project +// Copyright 2016-2021 the Tectonic Project // Licensed under the MIT License. //! Standalone compilation of TeX documents. This implements the "classic" / //! "V1" / "rustc-like" Tectonic command-line interface, as well as the //! `compile` subcommand of the "V2" / "cargo-like" interface. -use structopt::StructOpt; - use std::{ env, path::{Path, PathBuf}, str::FromStr, time, }; +use structopt::StructOpt; +use tectonic_bridge_core::{SecuritySettings, SecurityStance}; use tectonic::{ config::PersistentConfig, @@ -87,6 +87,10 @@ pub struct CompileOptions { #[structopt(name = "outdir", short, long, parse(from_os_str))] outdir: Option, + /// Input is untrusted -- disable all known-insecure features + #[structopt(long)] + untrusted: bool, + /// Unstable options. Pass -Zhelp to show a list // TODO we can't pass -Zhelp without also passing #[structopt(name = "option", short = "Z", number_of_values = 1)] @@ -97,7 +101,18 @@ impl CompileOptions { pub fn execute(self, config: PersistentConfig, status: &mut dyn StatusBackend) -> Result { let unstable = UnstableOptions::from_unstable_args(self.unstable.into_iter()); - let mut sess_builder = ProcessingSessionBuilder::default(); + // Default to allowing insecure since it would be super duper annoying + // to have to pass `--trusted` every time to build a personal document + // that uses shell-escape! This default can be overridden by setting the + // environment variable TECTONIC_UNTRUSTED_MODE to a nonempty value. + let stance = if self.untrusted { + SecurityStance::DisableInsecures + } else { + SecurityStance::MaybeAllowInsecures + }; + + let mut sess_builder = + ProcessingSessionBuilder::new_with_security(SecuritySettings::new(stance)); let format_path = self.format; sess_builder .unstables(unstable) diff --git a/src/bin/tectonic/v2cli.rs b/src/bin/tectonic/v2cli.rs index 96f389b7b9..2a52397246 100644 --- a/src/bin/tectonic/v2cli.rs +++ b/src/bin/tectonic/v2cli.rs @@ -15,6 +15,7 @@ use tectonic::{ status::{termcolor::TermcolorStatusBackend, ChatterLevel, StatusBackend}, tt_error, tt_note, }; +use tectonic_bridge_core::{SecuritySettings, SecurityStance}; use tectonic_bundles::Bundle; use tectonic_docmodel::workspace::{Workspace, WorkspaceCreator}; use tectonic_errors::Error as NewError; @@ -178,6 +179,10 @@ impl Commands { /// `build`: Build a document #[derive(Debug, PartialEq, StructOpt)] pub struct BuildCommand { + /// Document is untrusted -- disable all known-insecure features + #[structopt(long)] + untrusted: bool, + /// Use only resource files cached locally #[structopt(short = "C", long)] only_cached: bool, @@ -206,8 +211,18 @@ impl BuildCommand { let ws = Workspace::open_from_environment()?; let doc = ws.first_document(); - // XXX NO WAY TO DISABLE INSECURE FEATURES - let mut setup_options = DocumentSetupOptions::new(false); + // Default to allowing insecure since it would be super duper annoying + // to have to pass `--trusted` every time to build a personal document + // that uses shell-escape! This default can be overridden by setting the + // environment variable TECTONIC_UNTRUSTED_MODE to a nonempty value. + let stance = if self.untrusted { + SecurityStance::DisableInsecures + } else { + SecurityStance::MaybeAllowInsecures + }; + + let mut setup_options = + DocumentSetupOptions::new_with_security(SecuritySettings::new(stance)); setup_options.only_cached(self.only_cached); for output_name in doc.output_names() { @@ -283,7 +298,7 @@ fn get_a_bundle( match Workspace::open_from_environment() { Ok(ws) => { let doc = ws.first_document(); - let mut options = DocumentSetupOptions::new(true); + let mut options: DocumentSetupOptions = Default::default(); options.only_cached(only_cached); doc.bundle(&options, status) } diff --git a/src/docmodel.rs b/src/docmodel.rs index f36dadbc56..0a23b3a214 100644 --- a/src/docmodel.rs +++ b/src/docmodel.rs @@ -12,6 +12,7 @@ use std::{ fs, io, path::{Path, PathBuf}, }; +use tectonic_bridge_core::SecuritySettings; use tectonic_bundles::{ cache::Cache, dir::DirBundle, itar::IndexedTarBackend, zip::ZipBundle, Bundle, }; @@ -31,31 +32,23 @@ use crate::{ }; /// Options for setting up [`Document`] instances with the driver -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct DocumentSetupOptions { /// Disable requests to the network, if the document’s bundle happens to be /// network-based. only_cached: bool, - /// Disable all known-insecure engine features. - /// - /// This setting should be true if any untrusted input will be handled. - /// However, it is not always activated because sometimes users want the - /// functionality provided by known-insecure features (such as - /// shell-escape). - disable_insecure: bool, + /// Security settings for engine features. + security: SecuritySettings, } impl DocumentSetupOptions { - /// Create a new set of document setup options. - /// - /// This function primarily exists to *force* you to consider whether you - /// ought to disable known-insecure features. As usual, they should be - /// disabled if there is any untrusted input that will be handled. - pub fn new(disable_insecure: bool) -> Self { + /// Create a new set of document setup options with custom security + /// settings. + pub fn new_with_security(security: SecuritySettings) -> Self { DocumentSetupOptions { only_cached: false, - disable_insecure, + security, } } @@ -157,12 +150,8 @@ impl DocumentExt for Document { writeln!(input_buffer, "\\input{{{}}}", profile.postamble_file)?; } - let mut sess_builder = ProcessingSessionBuilder::default(); - - // Do this before anything else!!!! - if setup_options.disable_insecure { - sess_builder.disable_insecure(); - } + let mut sess_builder = + ProcessingSessionBuilder::new_with_security(setup_options.security.clone()); sess_builder .output_format(output_format) diff --git a/src/driver.rs b/src/driver.rs index c7db3d2d91..5da8d127b6 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -26,7 +26,7 @@ use std::{ str::FromStr, time::SystemTime, }; -use tectonic_bridge_core::{CoreBridgeLauncher, DriverHooks, SystemRequestError}; +use tectonic_bridge_core::{CoreBridgeLauncher, DriverHooks, SecuritySettings, SystemRequestError}; use tectonic_bundles::Bundle; use tectonic_io_base::{ digest::DigestData, @@ -638,13 +638,14 @@ impl Default for ShellEscapeMode { /// A builder-style interface for creating a [`ProcessingSession`]. /// -/// This uses standard builder patterns. See especially -/// [`Self::disable_insecure`], which prevents any known-insecure features from -/// being activated in the session. It should always be the first method you -/// call if you are going to process input that is not totally trusted. +/// This uses standard builder patterns. The `Default` implementation defaults +/// to restrictive security settings that disable all known-insecure features +/// that could be abused by untrusted inputs. Use +/// [`ProcessingSessionBuilder::new_with_security()`] in order to have the +/// option to enable potentially-insecure features such as shell-escape. #[derive(Default)] pub struct ProcessingSessionBuilder { - disable_insecures: bool, + security: SecuritySettings, primary_input: PrimaryInputMode, tex_input_name: Option, output_dest: OutputDestination, @@ -667,33 +668,12 @@ pub struct ProcessingSessionBuilder { } impl ProcessingSessionBuilder { - /// Disable any known insecure settings. - /// - /// Some session options, like [`Self::shell_escape_with_temp_dir`], are - /// known to create security risks and should not be used with untrusted - /// input. This function disables any such settings. The intended usage is - /// that you can create a session builder, activate this feature, and then - /// hand the session builder off to other initializers confident in the - /// knowledge that they will be prevented from activating any insecure - /// settings. Therefore this operation is idempotent and irreversible. - /// - /// When you know that you are handling trusted input, on the other hand, - /// some of these known-insecure capabilities provide functionality that - /// users empirically want. This is why this setting isn't permanently - /// enabled. - /// - /// Of course, this approach is only as good as our understanding of - /// Tectonic’s security profile. Future versions might disable or restrict - /// different pieces of functionality as new risks are discovered. - pub fn disable_insecure(&mut self) -> &mut Self { - self.disable_insecures = true; - self - } - - /// A very dumb helper to minimize the chances of boolean logic mistakes. - #[inline(always)] - fn allow_insecures(&self) -> bool { - !self.disable_insecures + /// Create a new builder with customized security settings. + pub fn new_with_security(security: SecuritySettings) -> Self { + ProcessingSessionBuilder { + security, + ..Default::default() + } } /// Sets the path to the primary input file. @@ -861,7 +841,7 @@ impl ProcessingSessionBuilder { /// disable shell-escape unless the [`UnstableOptions`] say otherwise, /// in which case a driver-managed temporary directory will be used. pub fn shell_escape_with_work_dir>(&mut self, path: P) -> &mut Self { - if self.allow_insecures() { + if self.security.allow_shell_escape() { self.shell_escape_mode = ShellEscapeMode::ExternallyManagedDir(path.as_ref().to_owned()); } @@ -873,7 +853,7 @@ impl ProcessingSessionBuilder { /// unless the [`UnstableOptions`] say otherwise, in which case a /// driver-managed temporary directory will be used. pub fn shell_escape_with_temp_dir(&mut self) -> &mut Self { - if self.allow_insecures() { + if self.security.allow_shell_escape() { self.shell_escape_mode = ShellEscapeMode::TempDir; } self @@ -987,7 +967,7 @@ impl ProcessingSessionBuilder { let mut pdf_path = aux_path.clone(); pdf_path.set_extension("pdf"); - let shell_escape_mode = if self.disable_insecures { + let shell_escape_mode = if !self.security.allow_shell_escape() { ShellEscapeMode::Disabled } else { match self.shell_escape_mode { @@ -1004,6 +984,7 @@ impl ProcessingSessionBuilder { }; Ok(ProcessingSession { + security: self.security, bs, pass: self.pass, primary_input_path, @@ -1036,6 +1017,9 @@ enum RerunReason { /// processing a file. It understands, for example, the need to re-run the TeX /// engine if the `.aux` file changed. pub struct ProcessingSession { + // Security settings. + security: SecuritySettings, + /// The subset of the session state that's can be mutated while the C/C++ /// engines are running. Importantly, this includes the full I/O stack. bs: BridgeState, @@ -1528,7 +1512,8 @@ impl ProcessingSession { let result = { self.bs .enter_format_mode(&format!("tectonic-format-{}.tex", stem)); - let mut launcher = CoreBridgeLauncher::new(&mut self.bs, status); + let mut launcher = + CoreBridgeLauncher::new_with_security(&mut self.bs, status, self.security.clone()); let r = TexEngine::default() .halt_on_error_mode(true) .initex_mode(true) @@ -1590,7 +1575,8 @@ impl ProcessingSession { status.note_highlighted("Running ", "TeX", " ..."); } - let mut launcher = CoreBridgeLauncher::new(&mut self.bs, status); + let mut launcher = + CoreBridgeLauncher::new_with_security(&mut self.bs, status, self.security.clone()); TexEngine::default() .halt_on_error_mode(true) @@ -1623,7 +1609,8 @@ impl ProcessingSession { fn bibtex_pass(&mut self, status: &mut dyn StatusBackend) -> Result { let result = { status.note_highlighted("Running ", "BibTeX", " ..."); - let mut launcher = CoreBridgeLauncher::new(&mut self.bs, status); + let mut launcher = + CoreBridgeLauncher::new_with_security(&mut self.bs, status, self.security.clone()); let mut engine = BibtexEngine::new(); engine.process(&mut launcher, &self.tex_aux_path, &self.unstables) }; @@ -1655,7 +1642,8 @@ impl ProcessingSession { { status.note_highlighted("Running ", "xdvipdfmx", " ..."); - let mut launcher = CoreBridgeLauncher::new(&mut self.bs, status); + let mut launcher = + CoreBridgeLauncher::new_with_security(&mut self.bs, status, self.security.clone()); let mut engine = XdvipdfmxEngine::default(); engine.build_date(self.build_date); diff --git a/tests/executable.rs b/tests/executable.rs index 062d8e2049..339b17c522 100644 --- a/tests/executable.rs +++ b/tests/executable.rs @@ -567,6 +567,16 @@ fn v2_new_build_multiple_outputs() { success_or_panic(output); } +const SHELL_ESCAPE_TEST_DOC: &str = r#"\immediate\write18{mkdir shellwork} +\immediate\write18{echo 123 >shellwork/persist} +\ifnum123=\input{shellwork/persist} +a +\else +\ohnotheshellescapedidntwork +\fi +\bye +"#; + /// Test that shell escape actually runs the commands #[test] fn shell_escape() { @@ -576,15 +586,49 @@ fn shell_escape() { let output = run_tectonic_with_stdin( tempdir.path(), &[&fmt_arg, "-", "-Zshell-escape"], - r#"\immediate\write18{mkdir shellwork} - \immediate\write18{echo 123 >shellwork/persist} - \ifnum123=\input{shellwork/persist} - a - \else - \ohnotheshellescapedidntwork - \fi - \bye - "#, + SHELL_ESCAPE_TEST_DOC, ); success_or_panic(output); } + +/// Test that shell-escape can be killed by command-line-option +#[test] +fn shell_escape_cli_override() { + let fmt_arg = get_plain_format_arg(); + let tempdir = setup_and_copy_files(&[]); + + let output = run_tectonic_with_stdin( + tempdir.path(), + &[&fmt_arg, "--untrusted", "-", "-Zshell-escape"], + SHELL_ESCAPE_TEST_DOC, + ); + error_or_panic(output); +} + +/// Test that shell-escape can be killed by environment variable +#[test] +fn shell_escape_env_override() { + let fmt_arg = get_plain_format_arg(); + let tempdir = setup_and_copy_files(&[]); + + // Note that we intentionally set the variable to 0 below -- it takes it + // effect if it has ANY value, not just a "truthy" one. + + let mut command = prep_tectonic(tempdir.path(), &[&fmt_arg, "-", "-Zshell-escape"]); + command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .env("TECTONIC_UNTRUSTED_MODE", "0"); + + println!("running {:?}", command); + let mut child = command.spawn().expect("tectonic failed to start"); + write!(child.stdin.as_mut().unwrap(), "{}", SHELL_ESCAPE_TEST_DOC) + .expect("failed to send data to tectonic subprocess"); + + let output = child + .wait_with_output() + .expect("failed to wait on tectonic subprocess"); + + error_or_panic(output); +}