Page MenuHomeFreeBSD

kern: adopt the cr_gid macro for cr_groups[0] more widely
ClosedPublic

Authored by kevans on Jul 3 2025, 4:57 PM.
Tags
None
Referenced Files
Unknown Object (File)
Tue, Aug 5, 3:11 AM
Unknown Object (File)
Sat, Aug 2, 7:33 AM
Unknown Object (File)
Fri, Aug 1, 10:21 PM
Unknown Object (File)
Fri, Aug 1, 9:44 PM
Unknown Object (File)
Mon, Jul 28, 5:42 PM
Unknown Object (File)
Sat, Jul 26, 10:37 PM
Unknown Object (File)
Thu, Jul 24, 3:01 PM
Unknown Object (File)
Thu, Jul 24, 12:31 PM

Details

Summary

A future change may split cr_gid out of cr_groups[0] so that there's a
cleaner separation between the supplemental groups and the effective
group. Do the mechanical conversion where we can, and drop some
comments where we need further work because some assumptions about
cr_gid == cr_groups[0] have been made.

This should not be a functional change.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 65195
Build 62078: arc lint + arc unit

Event Timeline

kevans requested review of this revision.Jul 3 2025, 4:57 PM

Some context for this change from IRC: we're in a small minority of systems today that continues to conflate the egid and supplemental groups as we do, which makes some uses of setgroups() a landmine if they're assuming FreeBSD behavior of also setting the egid. It would be good to start investigating heading in the direction of the rest of the world and separating cr_gid out to avoid this one weird difference with security implications for software ported to other systems.

After having written some inline comments, it now appears to me that we may not want to substitute cr_groups[0] with cr_gid for objects of type struct xucred in the kernel, but on the contrary ensure that references to the egid in struct xucred are always made through cr_groups[0]. Why is that, you may ask? Because of the fact that, currently, field names are shared between struct ucred and struct xucred, and I suspect we may want to switch struct ucred (internal) independently of struct xucred (exported, and used, e.g., for LOCAL_PEERCRED), most probably doing struct ucred before struct xucred. In this case, we will want to preserve the fact that cr_uid is defined to cr_groups[0] for userland, but obviously that should not apply in the kernel when egid is stored in a real cr_gid field in struct ucred, hence the need for an explicit cr_groups[0] inside the kernel. That said, it is possible (and probably a better idea) to convert references to cr_groups[0] on struct xucred into a function call taking a struct ucred (e.g., xucred_gid()) and that returns a pointer to the right slot.

I would generally replace the XXXKE comments by code that supports both the current state and the cr_gid being split state (see inline comments for suggestions on how to do this), but that could be done as a separate step.

A lot more places need changes at the moment of the split than those commented here. In particular (probably not exhaustive list):

  • groups_is_supplementary()
  • setcred() & co.
  • Helpers such as groups_check_normalized(), groups_check_positive_len(), groups_normalize(), crsetgroups(), etc.

If keeping the XXXKE comments, please explicitly say in the commit message that they do not form an exhaustive list.

Depending on you plans, I could perhaps take on some of the burden of the actual split.

sys/fs/nfs/nfs_commonport.c
383–384

If you're OK, I'd try harder to be bullet proof here, even if that results in some ugly comparison.

Better yet, as this case occurs a number of places below, factor out the test part into a temporary helper function that goes into sys/ucred.h, called e.g. cred_is_egid_in_groups(). That way, the uglyness is a bit hidden, and it becomes easier to spot which code can be removed after the cr_gid/cr_groups[0] split.

Lots of such inline comments follow. If you feel that's too much for this review, it's fine.

sys/fs/nfsclient/nfs_clrpcops.c
6929–6930

Same here.

sys/rpc/authunix_prot.c
96

Keep cr_groups[0] or use xucred_gid(), see general comment.

98–104

xucred_sgroups_nb() would just return the number of supplementary groups.

But not sure if it's worth bothering for the struct xucred part (I think it is for struct ucred occurences).

109

No functional change obviously... but this is simpler and stays correct in both cases (before and after the split).

110

I'd just assign a new local variable groups with the result of a new function xucred_sgroups() (outside of the loop) and use it here.

118–124

With xucred_is_egid_in_groups() as the cred_is_egid_in_groups() defined in an earlier comment but for struct xucred.

sys/rpc/svc_auth_unix.c
86

Same here: Keep cr_groups[0] or use xucred_gid(), see general comment.

92–93

Generally, same strategy as for xdr_authunix_parms() above. (Or keep the comment.)

sys/ufs/ufs/ufs_vnops.c
2081–2085
2083
2087

No need to set ucred_group. Before or after the split, the only thing that matters is cr_gid. ucred_group is just a temporary storage that is not used after the split.

2840–2848

Same comment and suggestions as in ufs_mkdir() above.

One note: We could actually just change the contract of setgroups() without actually splitting cr_gid on struct ucred, if that is pressing and we lack time for the full split. I really think we should get this into 15.0 rather than 16.0.

After having written some inline comments, it now appears to me that we may not want to substitute cr_groups[0] with cr_gid for objects of type struct xucred in the kernel, but on the contrary ensure that references to the egid in struct xucred are always made through cr_groups[0]. Why is that, you may ask? Because of the fact that, currently, field names are shared between struct ucred and struct xucred, and I suspect we may want to switch struct ucred (internal) independently of struct xucred (exported, and used, e.g., for LOCAL_PEERCRED), most probably doing struct ucred before struct xucred. In this case, we will want to preserve the fact that cr_uid is defined to cr_groups[0] for userland, but obviously that should not apply in the kernel when egid is stored in a real cr_gid field in struct ucred, hence the need for an explicit cr_groups[0] inside the kernel. That said, it is possible (and probably a better idea) to convert references to cr_groups[0] on struct xucred into a function call taking a struct ucred (e.g., xucred_gid()) and that returns a pointer to the right slot.

My follow-up change that I'm toying with locally that actually splits cr_gid out solves this instead with some union trickery in struct xucred:

short   cr_ngroups;             /* number of groups (incl. cr_gid). */  
union {                                                                 
        /*                                                              
         * Special little hack to avoid needing a cr_gid macro, which   
         * would cause problems if one were to use it with struct ucred 
         * which also has a cr_groups member.                           
         */                                                             
        struct {                                                        
                gid_t   cr_gid;         /* effective group id */        
                gid_t   cr_sgroups[XU_NGROUPS - 1];                     
        };                                                              
                                                                        
        gid_t   cr_groups[XU_NGROUPS];  /* groups */                    
};

I would generally replace the XXXKE comments by code that supports both the current state and the cr_gid being split state (see inline comments for suggestions on how to do this), but that could be done as a separate step.

A lot more places need changes at the moment of the split than those commented here. In particular (probably not exhaustive list):

  • groups_is_supplementary()
  • setcred() & co.
  • Helpers such as groups_check_normalized(), groups_check_positive_len(), groups_normalize(), crsetgroups(), etc.

If keeping the XXXKE comments, please explicitly say in the commit message that they do not form an exhaustive list.

Depending on you plans, I could perhaps take on some of the burden of the actual split.

Right, this diff was intended to do all of the mechanical conversions. You can see a prototype of the split that I've started preparing here: https://people.freebsd.org/~kevans/cr_gid.diff

This also tries to handle the xucred problem mentioned above by simply punting on it entirely; we get a cr_gid member that's aliased to the first member of cr_groups. It avoids changing setgroups()/getgroups() for the initial split, continuing to use the first member as the egid and writing out the egid as the first member for the time being so that we can audit userland usage separately.

My follow-up change that I'm toying with locally that actually splits cr_gid out solves this instead with some union trickery in struct xucred:

short   cr_ngroups;             /* number of groups (incl. cr_gid). */  
union {                                                                 
        /*                                                              
         * Special little hack to avoid needing a cr_gid macro, which   
         * would cause problems if one were to use it with struct ucred 
         * which also has a cr_groups member.                           
         */                                                             
        struct {                                                        
                gid_t   cr_gid;         /* effective group id */        
                gid_t   cr_sgroups[XU_NGROUPS - 1];                     
        };                                                              
                                                                        
        gid_t   cr_groups[XU_NGROUPS];  /* groups */                    
};

Great! I had this same idea yesterday evening a while after having left comments here. It's better like that.

Right, this diff was intended to do all of the mechanical conversions. You can see a prototype of the split that I've started preparing here: https://people.freebsd.org/~kevans/cr_gid.diff

I'll have a look, but a bit later. Just wanted to make sure you had a broader picture of the impacts.

I had considered doing all this after fixing some security problem where NFS would sometimes not fill cr_groups[] at all (so, the egid would not be initialized, and would end up being 0...), but more pressing stuff came in. Thanks for doing it!

The fusefs part LGTM. I didn't read the rest.

This revision is now accepted and ready to land.Jul 4 2025, 7:21 PM

Did you considered writing a coccinelle script to do the transformation? We even have the place for it, tools/coccinelle.

Whatever you plan, do you agree that the ABI properties of struct xucred must be intact?

In D51153#1167820, @kib wrote:

Did you considered writing a coccinelle script to do the transformation? We even have the place for it, tools/coccinelle.

I hadn't, mostly because it wasn't as bad as I expected and I wanted to take a look at each to make sure I documented assumptions about cr_gid in the surrounding area that would need fixed.

Whatever you plan, do you agree that the ABI properties of struct xucred must be intact?

Indeed. I did a little bit of thinking when I thought I'd need to bump the version (until I came up with a union-based solution that does what I want), but the existing users make this a lot more painful than it needs to be. Life would be a lot better if the version field was populated before userland asks kernel to populate one and we could take that as a request for an older version, but that ship has clearly sailed.

My follow-up change that I'm toying with locally that actually splits cr_gid out solves this instead with some union trickery in struct xucred:

short   cr_ngroups;             /* number of groups (incl. cr_gid). */  
union {                                                                 
        /*                                                              
         * Special little hack to avoid needing a cr_gid macro, which   
         * would cause problems if one were to use it with struct ucred 
         * which also has a cr_groups member.                           
         */                                                             
        struct {                                                        
                gid_t   cr_gid;         /* effective group id */        
                gid_t   cr_sgroups[XU_NGROUPS - 1];                     
        };                                                              
                                                                        
        gid_t   cr_groups[XU_NGROUPS];  /* groups */                    
};

Great! I had this same idea yesterday evening a while after having left comments here. It's better like that.

Right, this diff was intended to do all of the mechanical conversions. You can see a prototype of the split that I've started preparing here: https://people.freebsd.org/~kevans/cr_gid.diff

Have you had a chance to look further at that patch? I'd like to land this one, then post an updated version of that diff for review as the next step (which immediately resolves all of the comments in this one)

Have you had a chance to look further at that patch? I'd like to land this one, then post an updated version of that diff for review as the next step (which immediately resolves all of the comments in this one)

Hi, no, was AFK the previous ~10 days. Will look at it soonish (tomorrow).

I've looked at your second patch, it indeed fixes the comments here. That said, it also needs fine-tuning, so eager to see the next review. Thanks.

This revision was automatically updated to reflect the committed changes.
kevans marked an inline comment as done.