Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use X11 protocol directly via libxcb, instead of xhost command #163

Merged
merged 11 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/ego.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 34 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ log = { version = "0.4.20", features = ["std"] }
shell-words = "1.1.0"
nix = { version = "0.29.0", default-features = false, features = ["user"] }
anstyle = "1.0.4"
xcb = "1.4.0"

[features]
default = []
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ego (a.k.a Alter Ego)

**Ego** is a tool to run Linux desktop applications under a different local user. Currently
integrates with Wayland, Xorg, PulseAudio and xdg-desktop-portal. You may think of it as `xhost`
for Wayland and PulseAudio. This is done using filesystem ACLs and `xhost` command.
for Wayland and PulseAudio. This is done using filesystem ACLs and X11 host access control.

Disclaimer: **DO NOT RUN UNTRUSTED PROGRAMS VIA EGO.** However, using ego is more secure than
running applications directly under your primary user.
Expand All @@ -32,7 +32,7 @@ Ego aims to come with sane defaults and be easy to set up.
**Requirements:**
* [Rust & cargo](https://www.rust-lang.org/tools/install)
* `libacl.so` library (Debian/Ubuntu: libacl1-dev; Fedora: libacl-devel; Arch: acl)
* `xhost` binary (Debian/Ubuntu: x11-xserver-utils; Fedora: xorg-xhost; Arch: xorg-xhost)
* `libX11.so` library (Debian/Ubuntu: libx11; Fedora: libX11; Arch: libx11) # FIXME - devel?
intgr marked this conversation as resolved.
Show resolved Hide resolved

**Recommended:** (Not needed when using `--sudo` mode, but some desktop functionality may not work).
* `machinectl` command (Debian/Ubuntu/Fedora: systemd-container; Arch: systemd)
Expand Down Expand Up @@ -81,6 +81,9 @@ For sudo, add the following to `/etc/sudoers` (replace `<myname>` with your own
Changelog
---------

##### Unreleased
* Use libX11 directly instead of executing `xhost` command (#89)
intgr marked this conversation as resolved.
Show resolved Hide resolved

##### 1.1.7 (2023-06-26)
* Distro packaging: added tmpfiles.d conf to create missing ego user home directory (#134, fixed issue #131)
* Ego now detects and warns when target user's home directory does not exist or has wrong ownership (#139)
Expand Down
8 changes: 8 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct Args {
pub command: Vec<String>,
pub log_level: Level,
pub method: Option<Method>,
pub old_xhost: bool,
}

pub fn build_cli() -> Command {
Expand Down Expand Up @@ -47,6 +48,12 @@ pub fn build_cli() -> Command {
.help("Use 'machinectl' but skip xdg-desktop-portal setup"),
)
.group(ArgGroup::new("method").args(["sudo", "machinectl", "machinectl-bare"]))
.arg(
Arg::new("old-xhost")
.long("old-xhost")
.action(ArgAction::SetTrue)
.help("Execute 'xhost' command instead of connecting to X11 directly"),
)
.arg(
Arg::new("command")
.help("Command name and arguments to run (default: user shell)")
Expand Down Expand Up @@ -79,6 +86,7 @@ pub fn parse_args<T: Into<OsString> + Clone>(args: impl IntoIterator<Item = T>)
2 => Level::Debug,
_ => Level::Trace,
},
old_xhost: matches.get_flag("old-xhost"),
method: if matches.get_flag("machinectl") {
Some(Method::Machinectl)
} else if matches.get_flag("machinectl-bare") {
Expand Down
15 changes: 11 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern crate simple_error;
use crate::cli::{parse_args, Method};
use crate::errors::{print_error, AnyErr, ErrorWithHint};
use crate::util::{exec_command, have_command, run_command, sd_booted};
use crate::x11::x11_add_acl;
use log::{debug, info, log, warn, Level};
use nix::libc::uid_t;
use nix::unistd::{Uid, User};
Expand All @@ -28,6 +29,7 @@ mod logging;
#[cfg(test)]
mod tests;
mod util;
mod x11;

#[derive(Clone)]
struct EgoContext {
Expand Down Expand Up @@ -60,7 +62,7 @@ fn main_inner() -> Result<(), AnyErr> {
Err(msg) => bail!("Error preparing Wayland: {msg}"),
Ok(ret) => vars.extend(ret),
}
match prepare_x11(&ctx) {
match prepare_x11(&ctx, args.old_xhost) {
Err(msg) => bail!("Error preparing X11: {msg}"),
Ok(ret) => vars.extend(ret),
}
Expand Down Expand Up @@ -235,15 +237,20 @@ fn prepare_wayland(ctx: &EgoContext) -> Result<Vec<String>, AnyErr> {

/// Detect `DISPLAY` and run `xhost` to grant permissions.
/// Return environment vars for `DISPLAY`
fn prepare_x11(ctx: &EgoContext) -> Result<Vec<String>, AnyErr> {
fn prepare_x11(ctx: &EgoContext, old_xhost: bool) -> Result<Vec<String>, AnyErr> {
let display = getenv_optional("DISPLAY")?;
if display.is_none() {
debug!("X11: DISPLAY not set, skipping");
return Ok(vec![]);
}

let grant = format!("+si:localuser:{}", ctx.target_user);
run_command("xhost", &[grant])?;
if old_xhost {
warn!("--old-xhost is deprecated. If there are issues with the new method, please report a bug.");
let grant = format!("+si:localuser:{}", ctx.target_user);
run_command("xhost", &[grant])?;
} else {
x11_add_acl("localuser", ctx.target_user.as_str())?;
}
// TODO should also test /tmp/.X11-unix/X0 permissions?

Ok(vec![format!("DISPLAY={}", display.unwrap())])
Expand Down
1 change: 1 addition & 0 deletions src/snapshots/ego.help
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Options:
--sudo Use 'sudo' to change user
--machinectl Use 'machinectl' to change user (default, if available)
--machinectl-bare Use 'machinectl' but skip xdg-desktop-portal setup
--old-xhost Execute 'xhost' command instead of connecting to X11 directly
-v, --verbose... Verbose output. Use multiple times for more output.
-h, --help Print help
-V, --version Print version
16 changes: 14 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use snapbox::{file, Data};

use crate::cli::{build_cli, parse_args, Method};
use crate::util::have_command;
use crate::x11::x11_add_acl;
use crate::{check_user_homedir, get_wayland_socket, EgoContext};

/// `vec![]` constructor that converts arguments to String
Expand All @@ -27,11 +28,11 @@ fn snapshot() -> &'static Assert {
fn assert_log_snapshot(expected_path: &Data) {
testing_logger::validate(|logs| {
let output = logs.iter().fold(String::new(), |mut a, b| {
write!(a, "{}: {}\n", b.level.as_str(), b.body).unwrap();
writeln!(a, "{}: {}", b.level.as_str(), b.body).unwrap();
a
});
snapshot().eq(output, expected_path);
})
});
}

fn render_completion(generator: impl Generator) -> Data {
Expand Down Expand Up @@ -107,6 +108,17 @@ fn wayland_socket() {
);
}

#[test]
fn test_x11_error() {
env::remove_var("DISPLAY");

let err = x11_add_acl("test", "test").unwrap_err();
assert_eq!(
err.to_string(),
"Connection closed, error during parsing display string"
);
}

#[test]
fn test_cli() {
build_cli().debug_assert();
Expand Down
23 changes: 23 additions & 0 deletions src/x11.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use log::debug;
use xcb::x::{ChangeHosts, Family, HostMode};
use xcb::Connection;

use crate::errors::AnyErr;

#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::ptr_as_ptr)]
pub fn x11_add_acl(type_tag: &str, value: &str) -> Result<(), AnyErr> {
let (conn, _screen_num) = Connection::connect(None)?;

debug!("X11: Adding XHost entry SI:{type_tag}:{value}");

let result = conn.send_and_check_request(&ChangeHosts {
mode: HostMode::Insert,
family: Family::ServerInterpreted,
address: format!("{type_tag}::\x00{value}").as_bytes(),
});
map_err_with!(result, "Error adding XHost entry")?;

Ok(())
}
2 changes: 1 addition & 1 deletion varia/ego-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ _ego() {

case "${cmd}" in
ego)
opts="-u -v -h -V --user --sudo --machinectl --machinectl-bare --verbose --help --version [command]..."
opts="-u -v -h -V --user --sudo --machinectl --machinectl-bare --old-xhost --verbose --help --version [command]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down
1 change: 1 addition & 0 deletions varia/ego-completion.fish
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ complete -c ego -s u -l user -d 'Specify a username (default: ego)' -r -f -a "(_
complete -c ego -l sudo -d 'Use \'sudo\' to change user'
complete -c ego -l machinectl -d 'Use \'machinectl\' to change user (default, if available)'
complete -c ego -l machinectl-bare -d 'Use \'machinectl\' but skip xdg-desktop-portal setup'
complete -c ego -l old-xhost -d 'Execute \'xhost\' command instead of connecting to X11 directly'
complete -c ego -s v -l verbose -d 'Verbose output. Use multiple times for more output.'
complete -c ego -s h -l help -d 'Print help'
complete -c ego -s V -l version -d 'Print version'
1 change: 1 addition & 0 deletions varia/ego-completion.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ _ego() {
'--sudo[Use '\''sudo'\'' to change user]' \
'--machinectl[Use '\''machinectl'\'' to change user (default, if available)]' \
'--machinectl-bare[Use '\''machinectl'\'' but skip xdg-desktop-portal setup]' \
'--old-xhost[Execute '\''xhost'\'' command instead of connecting to X11 directly]' \
'*-v[Verbose output. Use multiple times for more output.]' \
'*--verbose[Verbose output. Use multiple times for more output.]' \
'-h[Print help]' \
Expand Down