mirror of
https://github.com/nushell/nushell.git
synced 2025-05-28 22:07:40 +02:00
fix(nu-command): support ACL, SELinux, e.g. in cd have_permission check (#15360)
fixes #8095
# Description
This approach is a bit straightforward, call access() check with the
flag `X_OK`.
Zsh[^1], Fish perform this check by the same approach.
[^1]:
435cb1b748/Src/exec.c (L6406)
It could also avoid manual xattrs check on other *nix platforms.
BTW, the execution bit for directories in *nix world means permission to
access it's content,
while the read bit means to list it's content. [^0]
[^0]: https://superuser.com/a/169418
# User-Facing Changes
Users could face less permission check bugs in their `cd` usage.
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
This commit is contained in:
parent
eaf522b41f
commit
07be33c119
@ -864,7 +864,7 @@ fn do_auto_cd(
|
||||
path.to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
if let PermissionResult::PermissionDenied(_) = have_permission(path.clone()) {
|
||||
if let PermissionResult::PermissionDenied = have_permission(path.clone()) {
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::Io(IoError::new_with_additional_context(
|
||||
|
@ -132,7 +132,7 @@ impl Command for Cd {
|
||||
stack.set_cwd(path)?;
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
PermissionResult::PermissionDenied(_) => {
|
||||
PermissionResult::PermissionDenied => {
|
||||
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,23 @@
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::{access, AccessFlags};
|
||||
#[cfg(any(windows, unix))]
|
||||
use std::path::Path;
|
||||
#[cfg(unix)]
|
||||
use {
|
||||
nix::{
|
||||
sys::stat::{mode_t, Mode},
|
||||
unistd::{Gid, Uid},
|
||||
},
|
||||
std::os::unix::fs::MetadataExt,
|
||||
};
|
||||
|
||||
// The result of checking whether we have permission to cd to a directory
|
||||
#[derive(Debug)]
|
||||
pub enum PermissionResult<'a> {
|
||||
pub enum PermissionResult {
|
||||
PermissionOk,
|
||||
PermissionDenied(&'a str),
|
||||
PermissionDenied,
|
||||
}
|
||||
|
||||
// TODO: Maybe we should use file_attributes() from https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
|
||||
// More on that here: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||
#[cfg(windows)]
|
||||
pub fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
|
||||
pub fn have_permission(dir: impl AsRef<Path>) -> PermissionResult {
|
||||
match dir.as_ref().read_dir() {
|
||||
Err(e) => {
|
||||
if matches!(e.kind(), std::io::ErrorKind::PermissionDenied) {
|
||||
PermissionResult::PermissionDenied("Folder is unable to be read")
|
||||
PermissionResult::PermissionDenied
|
||||
} else {
|
||||
PermissionResult::PermissionOk
|
||||
}
|
||||
@ -33,73 +27,15 @@ pub fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
|
||||
match dir.as_ref().metadata() {
|
||||
Ok(metadata) => {
|
||||
let mode = Mode::from_bits_truncate(metadata.mode() as mode_t);
|
||||
let current_user_uid = users::get_current_uid();
|
||||
if current_user_uid.is_root() {
|
||||
return PermissionResult::PermissionOk;
|
||||
}
|
||||
let current_user_gid = users::get_current_gid();
|
||||
let owner_user = Uid::from_raw(metadata.uid());
|
||||
let owner_group = Gid::from_raw(metadata.gid());
|
||||
match (
|
||||
current_user_uid == owner_user,
|
||||
current_user_gid == owner_group,
|
||||
) {
|
||||
(true, _) => {
|
||||
if mode.contains(Mode::S_IXUSR) {
|
||||
PermissionResult::PermissionOk
|
||||
} else {
|
||||
PermissionResult::PermissionDenied(
|
||||
"You are the owner but do not have execute permission",
|
||||
)
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
if mode.contains(Mode::S_IXGRP) {
|
||||
PermissionResult::PermissionOk
|
||||
} else {
|
||||
PermissionResult::PermissionDenied(
|
||||
"You are in the group but do not have execute permission",
|
||||
)
|
||||
}
|
||||
}
|
||||
(false, false) => {
|
||||
if mode.contains(Mode::S_IXOTH)
|
||||
|| (mode.contains(Mode::S_IXGRP)
|
||||
&& any_group(current_user_gid, owner_group))
|
||||
{
|
||||
PermissionResult::PermissionOk
|
||||
} else {
|
||||
PermissionResult::PermissionDenied(
|
||||
"You are neither the owner, in the group, nor the super user and do not have permission",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => PermissionResult::PermissionDenied("Could not retrieve file metadata"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "android"))]
|
||||
fn any_group(_current_user_gid: Gid, owner_group: Gid) -> bool {
|
||||
users::current_user_groups()
|
||||
.unwrap_or_default()
|
||||
.contains(&owner_group)
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(any(target_os = "linux", target_os = "freebsd", target_os = "android"))
|
||||
))]
|
||||
fn any_group(current_user_gid: Gid, owner_group: Gid) -> bool {
|
||||
users::get_current_username()
|
||||
.and_then(|name| users::get_user_groups(&name, current_user_gid))
|
||||
.unwrap_or_default()
|
||||
.contains(&owner_group)
|
||||
/// Check that the process' user id has permissions to execute or
|
||||
/// in the case of a directory traverse the particular directory
|
||||
pub fn have_permission(dir: impl AsRef<Path>) -> PermissionResult {
|
||||
// We check permissions for real user id, but that's fine, because in
|
||||
// proper installations of nushell, effective UID (EUID) rarely differs
|
||||
// from real UID (RUID). We strongly advise against setting the setuid bit
|
||||
// on the nushell executable or shebang scripts starts with `#!/usr/bin/env nu` e.g.
|
||||
// Most Unix systems ignore setuid on shebang by default anyway.
|
||||
access(dir.as_ref(), AccessFlags::X_OK).into()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@ -209,3 +145,12 @@ pub mod users {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> From<Result<T, E>> for PermissionResult {
|
||||
fn from(value: Result<T, E>) -> Self {
|
||||
match value {
|
||||
Ok(_) => Self::PermissionOk,
|
||||
Err(_) => Self::PermissionDenied,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user