Ensure initial session is only run once.

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 commit is contained in:
Antoine POPINEAU
2021-07-14 22:33:28 +02:00
committed by Kenny Levinsen
parent 41c6b5f1d4
commit 5201469e39
4 changed files with 57 additions and 9 deletions

View File

@@ -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,
}

View File

@@ -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> {
{

View File

@@ -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");

View File

@@ -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