Skip to content

Memory unsafety in nix::unistd::getgrouplist #1541

Closed
vitalyd/nix
#1
@vitalyd

Description

@vitalyd

If libc::getgrouplist returns -1, indicating the supplied groups buffer is too short to hold all the user's groups, the current code will double the buffer and try again. Unfortunately, the ngroups value it passes to libc::getgrouplist doesn't reflect the len of the buffer. After the 1st iteration in this scenario, libc will set ngroups to the # of groups it found, which can be a larger # than the doubling of the group's capacity. The 2nd iteration of the loop will now have a mismatch between group's capacity and the ngroups value it supplies. This will lead to libc writing into unallocated memory beyond the buffer's allocation, leading to a segfault, allocator corruption (e.g. free on destroying the Vec<Gid> complaining of invalid size passed in), or more generally, UB behavior.

The simplest fix is to set ngroups to groups.capacity() on each loop iteration, i.e.:

...
let mut groups = Vec::<Gid>::with_capacity(min(ngroups_max, 8) as usize);
...
loop {
        let mut ngroups = groups.capacity() as i32;
        let ret = unsafe {
            libc::getgrouplist(user.as_ptr(),
                               gid as getgrouplist_group_t,
                               groups.as_mut_ptr() as *mut getgrouplist_group_t,
                               &mut ngroups)
        };
       ...

Separately, since Linux sets ngroups to be the # of groups found (when returning -1), it probably makes sense to reserve that amount in groups rather than doing the doubling capacity dance. But that's a separate issue.

cc @geofft

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions