VYPR
High severityNVD Advisory· Published Apr 27, 2026· Updated Apr 27, 2026

CVE-2026-6970

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.

PackageAffected versionsPatched versions
github.com/canonical/authdGo
>= 0.6.0, < 0.6.40.6.4

Affected products

1

Patches

1
154b428305cb

fix: preserve user's primary group GID across logins (#1422)

https://github.com/canonical/authdNoor Eldeen MansourApr 14, 2026via ghsa
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

4

News mentions

0

No linked articles in our index yet.