Skip to content

Commit

Permalink
Merge #7
Browse files Browse the repository at this point in the history
7: Support running interpreter scripts r=iliana,webern,tjkirch a=iliana

If the program starts with `#!` (a shebang or hash-bang), the kernel will (almost always; depends if `BINFMT_SCRIPT` is enabled) determine which interpreter to exec and pass the script along as the first argument. In this case, the argument will be `/proc/self/fd/{}`, which gets closed if MFD_CLOEXEC is set.

We now check for `#!` and only set MFD_CLOEXEC if it's not there.

cc @tjkirch, @webern

Co-authored-by: iliana destroyer of worlds <[email protected]>
  • Loading branch information
bors[bot] and iliana authored Jun 23, 2020
2 parents 73fdf1a + d14de48 commit 9cc5c27
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 4 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.0] - 2020-06-23
### Changed
- No longer set `MFD_CLOEXEC` if `#!` is detected at the beginning of a program

## [0.1.1] - 2020-03-15
### Changed
- Allow builds on Android platforms

## [0.1.0] - 2019-11-15
### Added
- Everything!

[Unreleased]: https://github.com/iliana/pentacle/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/iliana/pentacle/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/iliana/pentacle/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/iliana/pentacle/releases/tag/v0.1.0
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "pentacle"
version = "0.1.1"
version = "0.2.0"
authors = ["iliana destroyer of worlds <[email protected]>"]
edition = "2018"
exclude = [".gitignore"]
exclude = ["bors.toml", ".github", ".gitignore"]
description = "Executes programs as sealed anonymous files on Linux"
repository = "https://github.com/iliana/pentacle"
readme = "README.md"
Expand Down
23 changes: 21 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use libc::{F_SEAL_GROW, F_SEAL_SEAL, F_SEAL_SHRINK, F_SEAL_WRITE, MFD_ALLOW_SEAL
use std::ffi::CStr;
use std::fmt::{self, Debug};
use std::fs::File;
use std::io::{self, Read, Result};
use std::io::{self, Read, Result, Write};
use std::ops::{Deref, DerefMut};
use std::os::unix::io::AsRawFd;
use std::os::unix::process::CommandExt;
Expand Down Expand Up @@ -105,6 +105,9 @@ impl SealedCommand {
/// Constructs a new [`Command`] for launching the program data in `program` as a sealed
/// memory-backed file, with the same default configuration as [`Command::new`].
///
/// The memory-backed file will close on `execve(2)` **unless** the program starts with `#!`
/// (indicating that it is an interpreter script).
///
/// # Compatibility
///
/// This library is unable to set the program name (`argv[0]`), which will cause unexpected
Expand All @@ -115,9 +118,25 @@ impl SealedCommand {
/// An error is returned if `memfd_create(2)` fails, the `fcntl(2)` `F_ADD_SEALS` command
/// fails, or copying from `program` to the anonymous file fails.
pub fn new<R: Read>(program: &mut R) -> Result<Self> {
let mut memfd_flags = MFD_ALLOW_SEALING;

// If the program starts with `#!` (a shebang or hash-bang), the kernel will (almost
// always; depends if `BINFMT_SCRIPT` is enabled) determine which interpreter to exec and
// pass the script along as the first argument. In this case, the argument will be
// `/proc/self/fd/{}`, which gets closed if MFD_CLOEXEC is set. We check for `#!` and only
// set MFD_CLOEXEC if it's not there.
let mut buf = [0; 8192];
let n = program.read(&mut buf)?;
if !(n >= 2 && &buf[..2] == b"#!") {
memfd_flags |= MFD_CLOEXEC;
}

let memfd_name = unsafe { CStr::from_bytes_with_nul_unchecked(b"pentacle_sealed\0") };
let mut memfd = memfd_create(memfd_name, MFD_CLOEXEC | MFD_ALLOW_SEALING)?;
let mut memfd = memfd_create(memfd_name, memfd_flags)?;

memfd.write_all(&buf[..n])?;
io::copy(program, &mut memfd)?;

fcntl_add_seals(&memfd, MEMFD_SEALS)?;

Ok(Self {
Expand Down
10 changes: 10 additions & 0 deletions tests/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ fn so_nice_we_ran_it_twice() -> Result<()> {
}
Ok(())
}

// Test that we can execute a script with a shebang.
#[test]
fn shebang() {
let mut command = SealedCommand::new(&mut &b"#!/bin/sh\necho 'it works'\n"[..]).unwrap();
let output = command.output().unwrap();
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success());
assert_eq!(output.stdout, b"it works\n");
}

0 comments on commit 9cc5c27

Please sign in to comment.