### Vulnerability Overview This vulnerability involves the incorrect resetting of a user's primary group GID (Group ID) to the user's UID (User ID) during login. This causes the GID set via the `authctl group set gid` command to be silently overwritten. As a result, the `getent passwd` and `getent group` commands return inconsistent results. ### Impact Scope - **Affected Component**: The user management module within the `authd` project. - **Specific Impact**: Upon user login, the user's primary group GID is erroneously reset to the user's UID, leading to inconsistencies in group information. ### Remediation - **Fix Details**: The fix ensures that the user's primary group GID is preserved during login, rather than unconditionally resetting it to the user's UID. - **Code Changes**: - Modified the `UpdateUser` function in `internal/users/manager.go` to ensure that if the user's private group does not exist during the first login, the default GID is set to UID. - For subsequent logins, existing group records are preserved to prevent the GID from being overwritten. - Added test cases to verify the corrected behavior. ### POC Code ```go // internal/users/manager.go // UpdateUser updates the user record. func (m *Manager) UpdateUser(u types.UserInfo) (err error) { // ... // User private Group GID is the same of the user UID. userPrivateGroup.GID = &u.UID // ... for _, g := range u.Groups { g := AuGroups[g] if g.Name == "" { return fmt.Errorf("empty group name for user %q, u.Name) } // ... } // ... if g.GID == nil { // The group does not exist in the database, so we generate a unique GID for it. newGroups = append(newGroups, g) continue } // The group does not exist in the database. if g == userPrivateGroup { // On first login the user private group doesn't exist yet, so we default to GID = UID. // Subsequent logins will find the existing group above and preserve any custom GID. g.GID = &u.UID } else { // Else, we add it to the list of new groups to create, since we need to generate a GID for it. newGroups = append(newGroups, g) continue } // ... } ``` ### Test Cases ```go // internal/users/manager_test.go func TestUpdateUser(t *testing.T) { // ... "User_private_group_GID_preserved_across_logins": { userWithPrimaryGroupGIDChanged, }, // ... } ``` ### Data Files ```yaml # testdata/db/user_with_primary_group_gid_changed.db.yaml users: - name: user1@example.com uid: 1111 gid: 60500 dir: /home/user1@example.com shell: /bin/bash groups: - name: user1@example.com gid: 60500 users: - user1@example.com users_to_groups: - uid: 1111 gid: 60500 schema_version: 2 ``` ```yaml # testdata/db/user_private_group_GID_preserved_across_logins.db.yaml users: - name: user1@example.com uid: 1111 gid: 60500 gecos: gecos for user1@example.com dir: /home/user1@example.com shell: /bin/bash groups: - name: user1@example.com gid: 60500 users: - user1@example.com users_to_groups: - uid: 1111 gid: 60500 schema_version: 2 ```