Skip to content
Snippets Groups Projects
Verified Commit 580b5ba0 authored by mirabilos's avatar mirabilos Committed by Nik | Klampfradler
Browse files

[NSS] Implement re-entry-safe getpwnam/getpwuid


Fixed some inconsistencies in error handling on the way.

Signed-off-by: Nik | Klampfradler's avatarDominik George <dominik.george@teckids.org>
parent b5bfa128
No related branches found
No related tags found
No related merge requests found
/* Copyright 2021 Dominik George <dominik.george@teckids.org> /* Copyright 2021 Dominik George <dominik.george@teckids.org>
* Copyright 2021 mirabilos <thorsten.glaser@teckids.org>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -15,10 +16,11 @@ ...@@ -15,10 +16,11 @@
use crate::BASE_NAME; use crate::BASE_NAME;
use crate::unix::getpwnam_safe;
use std::collections::HashMap; use std::collections::HashMap;
use libc::{geteuid, seteuid, getpwnam, uid_t}; use libc::{geteuid, seteuid, uid_t};
use std::ffi::{CStr, CString};
use oauth2::basic::BasicTokenResponse; use oauth2::basic::BasicTokenResponse;
...@@ -26,7 +28,7 @@ use std::env; ...@@ -26,7 +28,7 @@ use std::env;
use std::fs; use std::fs;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
use xdg::{BaseDirectories, BaseDirectoriesError}; use xdg::BaseDirectories;
use serde::Serialize; use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
...@@ -53,48 +55,40 @@ impl Cache { ...@@ -53,48 +55,40 @@ impl Cache {
} }
} }
fn drop_privileges(&self, username: &String) -> Result<uid_t, &str> { fn drop_privileges(&self, username: &String) -> Result<uid_t, io::Error> {
let current_euid; let current_euid;
unsafe { unsafe {
current_euid = geteuid(); current_euid = geteuid();
}; };
let nam = match CString::new(username.as_str()) { let pw = match getpwnam_safe(username.to_string()) {
Ok(nam) => nam, Ok(p) => p,
Err(_) => return Err("Invalid username in lookup") Err(e) => {
}; error!("Failed to lookup user {}: {}", username, e);
let pw; return Err(e);
unsafe { }
pw = getpwnam(nam.as_ptr());
};
if pw.is_null() {
error!("Failed to lookup user {}", username);
return Err("Failed to lookup user");
}
let target_euid;
unsafe {
target_euid = (*pw).pw_uid;
}; };
debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
debug!("name {} passwd {} gecos {} dir {} shell {}", pw.pw_name, pw.pw_passwd, pw.pw_gecos, pw.pw_dir, pw.pw_shell);
let target_euid = pw.pw_uid;
if target_euid == current_euid { if target_euid == current_euid {
debug!("No need to drop privileges, already running as {}", username); debug!("No need to drop privileges, already running as {}", username);
return Ok(self.original_euid); return Ok(current_euid);
} else if self.original_euid == 0 {
let res;
unsafe {
res = seteuid(target_euid);
};
if res == 0 {
debug!("Successfully dropped privileges to {}", username);
return Ok(target_euid);
} else {
error!("Could not drop privileges to {}", username);
return Err("Failed to drop privileges");
}
} }
error!("Not running as root or target user, cannot drop privileges"); let res;
return Err("Dropping privileges not supported"); unsafe {
res = seteuid(target_euid);
};
if res == 0 {
debug!("Successfully dropped privileges to {}", username);
return Ok(target_euid);
} else {
let e = io::Error::last_os_error();
error!("Could not drop privileges to {}", username);
return Err(e);
}
} }
fn restore_privileges(&self) { fn restore_privileges(&self) {
...@@ -105,30 +99,39 @@ impl Cache { ...@@ -105,30 +99,39 @@ impl Cache {
if current_euid != self.original_euid { if current_euid != self.original_euid {
debug!("Restoring privileges"); debug!("Restoring privileges");
let res;
unsafe { unsafe {
seteuid(self.original_euid); res = seteuid(self.original_euid);
}; };
if res != 0 {
panic!("Could not restore privileges to {}", self.original_euid);
}
} else { } else {
debug!("No need to restore privileges, already running as original user"); debug!("No need to restore privileges, already running as original user");
} }
} }
fn get_user_xdg_base_directories(&self, username: &String) -> Result<BaseDirectories, BaseDirectoriesError> { fn get_user_xdg_base_directories(&self, username: &String) -> Result<BaseDirectories, io::Error> {
let saved_home = env::var_os("HOME"); let saved_home = env::var_os("HOME");
let nam = match CString::new(username.as_str()) { let pw = match getpwnam_safe(username.to_string()) {
Ok(nam) => nam, Ok(p) => p,
Err(_) => CString::new("nobody").ok().unwrap() Err(e) => {
}; error!("Failed to lookup user {}: {}", username, e);
let user_home; return Err(e);
unsafe { }
user_home = CStr::from_ptr((*getpwnam(nam.as_ptr())).pw_dir);
}; };
debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
debug!("name {} passwd {} gecos {} dir {} shell {}", pw.pw_name, pw.pw_passwd, pw.pw_gecos, pw.pw_dir, pw.pw_shell);
let user_home = pw.pw_dir;
env::set_var("HOME", user_home.to_str().unwrap()); env::set_var("HOME", &user_home);
debug!("Home directory for {} is {}", username, user_home.to_str().unwrap()); debug!("Home directory for {} is {}", username, user_home);
let base_dirs = BaseDirectories::with_prefix(BASE_NAME)?; let base_dirs = match BaseDirectories::with_prefix(BASE_NAME) {
Ok(b) => b,
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e))
};
if saved_home != None { if saved_home != None {
env::set_var("HOME", saved_home.unwrap()); env::set_var("HOME", saved_home.unwrap());
} else { } else {
......
...@@ -2,6 +2,7 @@ const BASE_NAME: &str = "nss_pam_oidc"; ...@@ -2,6 +2,7 @@ const BASE_NAME: &str = "nss_pam_oidc";
// Modules and macro imports for our own code // Modules and macro imports for our own code
#[macro_use] extern crate log; #[macro_use] extern crate log;
mod unix;
mod cache; mod cache;
mod logging; mod logging;
mod config; mod config;
......
...@@ -26,8 +26,9 @@ use crate::logging::setup_log; ...@@ -26,8 +26,9 @@ use crate::logging::setup_log;
use crate::oauth::{get_access_token_client, get_data_jq}; use crate::oauth::{get_access_token_client, get_data_jq};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use libc::{getpwuid, geteuid};
use std::ffi::CStr; use std::ffi::CStr;
use libc::geteuid;
use crate::unix::getpwuid_safe;
use libnss::interop::Response; use libnss::interop::Response;
use libnss::passwd::{PasswdHooks, Passwd}; use libnss::passwd::{PasswdHooks, Passwd};
...@@ -61,21 +62,17 @@ fn nss_hook_prepare() -> (Cache, Config) { ...@@ -61,21 +62,17 @@ fn nss_hook_prepare() -> (Cache, Config) {
fn get_current_user() -> String { fn get_current_user() -> String {
let euid; let euid;
let pw;
unsafe { unsafe {
euid = geteuid(); euid = geteuid();
pw = getpwuid(euid);
}; };
if pw.is_null() { match getpwuid_safe(euid) {
error!("Failed to look up user name for UID {}", euid); Ok(p) => p.pw_name,
return "nobody".to_string(); Err(e) => {
error!("Failed to lookup username for UID {}: {}", euid, e);
/* FIXME: return something (empty string?) to make the caller bail out early */
"nobody".to_string()
}
} }
let euser;
unsafe {
euser = CStr::from_ptr((*pw).pw_name);
};
euser.to_str().ok().unwrap().to_string()
} }
struct OidcPasswd; struct OidcPasswd;
......
/* Copyright 2021 Dominik George <dominik.george@teckids.org>
* Copyright 2021 mirabilos <thorsten.glaser@teckids.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use libc::{ERANGE, getpwnam_r, getpwuid_r, passwd, c_void, uid_t, gid_t, size_t, malloc, free};
use std::ffi::{CStr, CString};
use std::io;
use std::mem::uninitialized;
use std::ptr::null_mut;
pub struct Passwd {
pub pw_name: String,
pub pw_passwd: String,
pub pw_uid: uid_t,
pub pw_gid: gid_t,
pub pw_gecos: String,
pub pw_dir: String,
pub pw_shell: String
}
const MAX_BUFLEN: size_t = 1024 * 1024;
fn getpwxx_fillpw(c_passwd: passwd) -> Passwd {
unsafe {
Passwd {
pw_name: CStr::from_ptr(c_passwd.pw_name).to_string_lossy().into_owned(),
pw_passwd: CStr::from_ptr(c_passwd.pw_passwd).to_string_lossy().into_owned(),
pw_uid: c_passwd.pw_uid,
pw_gid: c_passwd.pw_gid,
pw_gecos: CStr::from_ptr(c_passwd.pw_gecos).to_string_lossy().into_owned(),
pw_dir: CStr::from_ptr(c_passwd.pw_dir).to_string_lossy().into_owned(),
pw_shell: CStr::from_ptr(c_passwd.pw_shell).to_string_lossy().into_owned(),
}
}
}
pub fn getpwnam_safe(name: String) -> Result<Passwd, io::Error> {
let res: Passwd;
unsafe {
let mut c_passwd: passwd = uninitialized();
let mut c_passwd_ptr: *mut passwd = null_mut();
let mut buf: *mut i8 = uninitialized();
let mut buflen: size_t = 1024;
let nam = match CString::new(name.as_str()) {
Ok(nam) => nam,
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e))
};
while c_passwd_ptr.is_null() {
buf = malloc(buflen) as *mut i8;
if buf.is_null() {
return Err(io::Error::last_os_error());
}
let error = getpwnam_r(nam.as_ptr(), &mut c_passwd, buf, buflen, &mut c_passwd_ptr);
if c_passwd_ptr.is_null() && (error != ERANGE || buflen >= MAX_BUFLEN) {
free(buf as *mut c_void);
return Err(io::Error::from_raw_os_error(error));
}
buflen += 1024;
}
res = getpwxx_fillpw(c_passwd);
free(buf as *mut c_void);
}
return Ok(res);
}
pub fn getpwuid_safe(uid: uid_t) -> Result<Passwd, io::Error> {
let res: Passwd;
unsafe {
let mut c_passwd: passwd = uninitialized();
let mut c_passwd_ptr: *mut passwd = null_mut();
let mut buf: *mut i8 = uninitialized();
let mut buflen: size_t = 1024;
while c_passwd_ptr.is_null() {
buf = malloc(buflen) as *mut i8;
if buf.is_null() {
return Err(io::Error::last_os_error());
}
let error = getpwuid_r(uid, &mut c_passwd, buf, buflen, &mut c_passwd_ptr);
if c_passwd_ptr.is_null() && (error != ERANGE || buflen >= MAX_BUFLEN) {
free(buf as *mut c_void);
return Err(io::Error::from_raw_os_error(error));
}
buflen += 1024;
}
res = getpwxx_fillpw(c_passwd);
free(buf as *mut c_void);
}
return Ok(res);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment