CVE-2026-6970
Description
authd prior to version 0.6.4 contains a logic error in primary group ID assignment that can lead to local privilege escalation. When a user's primary group ID (GID) differs from their UID, either because the account was created with authd prior to version 0.5.4 or because the primary group was manually changed via the authctl group set-gid command, and the user's identity provider record is updated, authd incorrectly resets the user's primary group ID to their UID upon next login. This causes newly created files and directories to be owned by the wrong group, causing denial of service issues, and potentially granting unintended access to other local users and allowing local privilege escalation.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/canonical/authdGo | >= 0.6.0, < 0.6.4 | 0.6.4 |
Affected products
1Patches
1154b428305cbfix: preserve user's primary group GID across logins (#1422)
5 files changed · +43 −9
authd-oidc-brokers/third_party/libhimmelblau+1 −1 modified@@ -1 +1 @@ -Subproject commit 03e4b55873a4adfbfc974441c9e4396ac8800c2c +Subproject commit 11bd85bd8bf2de0d645cbeead1292bcbc72b6f66
internal/users/manager.go+13 −8 modified@@ -231,13 +231,11 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } } - // User private Group GID is the same of the user UID. - userPrivateGroup.GID = &u.UID - var groupRows []db.GroupRow var localGroups []string var newGroups []types.GroupInfo - for _, g := range u.Groups { + for i := range u.Groups { + g := &u.Groups[i] if g.Name == "" { return fmt.Errorf("empty group name for user %q", u.Name) } @@ -256,7 +254,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } // Check if the group already exists in the database - oldGroup, err := m.findGroup(g) + oldGroup, err := m.findGroup(*g) if err != nil && !errors.Is(err, db.NoDataFoundError{}) { // Unexpected error return err @@ -267,9 +265,16 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } 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 + } } groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID))
internal/users/manager_test.go+1 −0 modified@@ -201,6 +201,7 @@ func TestUpdateUser(t *testing.T) { "GID_does_not_change_if_group_with_same_name_and_empty_UGID_exists": {groupsCase: "authd-group", dbFile: "group-with-empty-UGID"}, "Removing_last_user_from_a_group_keeps_the_group_record": {groupsCase: "no-groups", dbFile: "one_user_and_group"}, "Allow_login_with_existing_group_on_system": {groupsCase: "group-exists-on-system"}, + "User_private_group_GID_preserved_across_logins": {dbFile: "user_with_primary_group_gid_changed"}, "Error_if_user_has_no_username": {userCase: "nameless", wantErr: true, noOutput: true}, "Error_if_group_has_no_name": {groupsCase: "nameless-group", wantErr: true, noOutput: true},
internal/users/testdata/db/user_with_primary_group_gid_changed.db.yaml+13 −0 added@@ -0,0 +1,13 @@ +users: + - name: user1@example.com + uid: 1111 + gid: 60500 + dir: /home/user1@example.com + shell: /bin/bash +groups: + - name: user1@example.com + gid: 60500 + ugid: user1@example.com +users_to_groups: + - uid: 1111 + gid: 60500
internal/users/testdata/golden/TestUpdateUser/User_private_group_GID_preserved_across_logins+15 −0 added@@ -0,0 +1,15 @@ +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 + ugid: user1@example.com +users_to_groups: + - uid: 1111 + gid: 60500 +schema_version: 2
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.