~kennylevinsen/greetd-devel

greetd: Ensure initial session is only run once. v1 APPLIED

Antoine POPINEAU: 1
 Ensure initial session is only run once.

 4 files changed, 57 insertions(+), 9 deletions(-)
#544725 alpine.yml success
#544726 archlinux.yml success
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~kennylevinsen/greetd-devel/patches/23843/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH greetd] Ensure initial session is only run once. Export this patch

Security concerns were raised regarding the initial session being
executed whenever greetd was restarted (when signing out of one's DE,
when greetd or a greeter restarted or crashed, ...).

This creates a runfile (by default at /run/greetd.run) either when the
initial session is executed or when a greeter is started. Whenever this
file exists, the initial session is ignored (and the configured greeter
is always run).
---
This relates to the issue raised in
https://todo.sr.ht/~kennylevinsen/greetd/10 and on tuigreet's GitHub
repository.

 greetd/src/config/mod.rs | 28 +++++++++++++++++++++-------
 greetd/src/context.rs    | 21 ++++++++++++++++++++-
 greetd/src/server.rs     |  5 ++++-
 man/greetd-5.scd         | 12 ++++++++++++
 4 files changed, 57 insertions(+), 9 deletions(-)

diff --git a/greetd/src/config/mod.rs b/greetd/src/config/mod.rs
index 3dd5985..5f2d537 100644
--- a/greetd/src/config/mod.rs
+++ b/greetd/src/config/mod.rs
@@ -5,6 +5,8 @@ use getopts::Options;

use super::error::Error;

const RUNFILE: &str = "/run/greetd.run";

#[derive(Debug, Eq, PartialEq)]
pub enum VtSelection {
    Next,
@@ -38,12 +40,14 @@ pub struct ConfigTerminal {
#[derive(Debug, Eq, PartialEq)]
pub struct ConfigGeneral {
    pub source_profile: bool,
    pub runfile: String,
}

impl Default for ConfigGeneral {
    fn default() -> Self {
        ConfigGeneral {
            source_profile: true,
            runfile: RUNFILE.to_string(),
        }
    }
}
@@ -166,13 +170,21 @@ fn parse_new_config(config: &HashMap<&str, HashMap<&str, &str>>) -> Result<Confi
    }?;

    let general = match config.get("general") {
        Some(section) => ConfigGeneral {
            source_profile: section
                .get("source_profile")
                .unwrap_or(&"true")
                .parse()
                .map_err(|e| format!("could not parse source_profile: {}", e))?,
        },
        Some(section) => {
            let runfilestr = section.get("runfile").unwrap_or(&RUNFILE);
            let runfile = maybe_unquote(runfilestr)
                .map_err(|e| format!("unable to read general.runfile: {}", e))?;

            ConfigGeneral {
                source_profile: section
                    .get("source_profile")
                    .unwrap_or(&"true")
                    .parse()
                    .map_err(|e| format!("could not parse source_profile: {}", e))?,
                runfile,
            }
        }

        None => Default::default(),
    };

@@ -386,6 +398,7 @@ user = \"john\"
[terminal]\nvt = 1\n[default_session]\ncommand = \"agreety\"
[general]
source_profile = false
runfile = \"/path/to/greetd.state\"
",
        )
        .expect("config didn't parse");
@@ -401,6 +414,7 @@ source_profile = false
                },
                general: ConfigGeneral {
                    source_profile: false,
                    runfile: "/path/to/greetd.state".to_string(),
                },
                initial_session: None,
            }
diff --git a/greetd/src/context.rs b/greetd/src/context.rs
index 0d7fe97..cff0a44 100644
--- a/greetd/src/context.rs
+++ b/greetd/src/context.rs
@@ -1,4 +1,8 @@
use std::time::{Duration, Instant};
use std::{
    fs::File,
    path::Path,
    time::{Duration, Instant},
};

use nix::{
    sys::wait::{waitpid, WaitPidFlag, WaitStatus},
@@ -41,6 +45,7 @@ pub struct Context {
    pam_service: String,
    term_mode: TerminalMode,
    source_profile: bool,
    runfile: String,
}

impl Context {
@@ -51,6 +56,7 @@ impl Context {
        pam_service: String,
        term_mode: TerminalMode,
        source_profile: bool,
        runfile: String,
    ) -> Context {
        Context {
            inner: RwLock::new(ContextInner {
@@ -64,6 +70,7 @@ impl Context {
            pam_service,
            term_mode,
            source_profile,
            runfile,
        }
    }

@@ -131,6 +138,18 @@ impl Context {
        Ok(())
    }

    /// Check if this is the first time greetd starts since boot, or if it restarted for any reason
    pub fn is_first_run(&self) -> bool {
        !Path::new(&self.runfile).exists()
    }

    /// Create runfile used to check if greetd was already started since boot
    pub fn create_runfile(&self) {
        if let Err(err) = File::create(&self.runfile) {
            eprintln!("could not create runfile: {}", err);
        }
    }

    /// Directly start an initial session, bypassing the normal scheduling.
    pub async fn start_user_session(&self, user: &str, cmd: Vec<String>) -> Result<(), Error> {
        {
diff --git a/greetd/src/server.rs b/greetd/src/server.rs
index 9ad1a4f..0a1994b 100644
--- a/greetd/src/server.rs
+++ b/greetd/src/server.rs
@@ -222,9 +222,10 @@ pub async fn main(config: Config) -> Result<(), Error> {
        service.to_string(),
        term_mode.clone(),
        config.file.general.source_profile,
        config.file.general.runfile,
    ));

    if let Some(s) = config.file.initial_session {
    if let (Some(s), true) = (config.file.initial_session, ctx.is_first_run()) {
        if let Err(e) = ctx.start_user_session(&s.user, vec![s.command]).await {
            eprintln!("unable to start greeter: {}", e);
            reset_vt(&term_mode).map_err(|e| format!("unable to reset VT: {}", e))?;
@@ -238,6 +239,8 @@ pub async fn main(config: Config) -> Result<(), Error> {
        std::process::exit(1);
    }

    ctx.create_runfile();

    let mut alarm = signal(SignalKind::alarm()).expect("unable to listen for SIGALRM");
    let mut child = signal(SignalKind::child()).expect("unable to listen for SIGCHLD");
    let mut term = signal(SignalKind::terminate()).expect("unable to listen for SIGTERM");
diff --git a/man/greetd-5.scd b/man/greetd-5.scd
index 3cd20d4..18048ba 100644
--- a/man/greetd-5.scd
+++ b/man/greetd-5.scd
@@ -46,6 +46,13 @@ nor deserved its own section.
	Whether or not to source ~/.profile and /etc/profile if present when running
	commands. Defaults to true.

*runfile* = path-to-runfile
  Location of greetd's runfile that is created during the first run to prevent
  the initial session from being run again on session termination or on greetd
  restart.

  This file should be in a location that is cleared during a reboot.

## default_session

This section describes the default session, also referred to as the *greeter*.
@@ -66,6 +73,11 @@ This section describes the default session, also referred to as the *greeter*.
This optional section describes the initial session, commonly referred to as
"auto-login".

The initial session will only be executed during the first run of greetd since
boot in order to ensure signing out works properly and to prevent security
issues whenever greetd or the greeter exit. This is checked through the
presence of the runfile.

*command* = command-line
	The command-line to run to start the initial session, e.g. "sway". The
	initial session will be run when exactly once when greetd is initially
-- 
2.32.0
builds.sr.ht
greetd/patches: SUCCESS in 2m41s

[Ensure initial session is only run once.][0] from [Antoine POPINEAU][1]

[0]: https://lists.sr.ht/~kennylevinsen/greetd-devel/patches/23843
[1]: mailto:antoine@popineau.eu

✓ #544726 SUCCESS greetd/patches/archlinux.yml https://builds.sr.ht/~kennylevinsen/job/544726
✓ #544725 SUCCESS greetd/patches/alpine.yml    https://builds.sr.ht/~kennylevinsen/job/544725
Applied, thanks!