Skip to content

mdo(1): Added extensive group setting options and --print-rule#3

Closed
thesynthax wants to merge 23 commits intoOlCe2:oc-thesynthaxfrom
thesynthax:mdo/groups
Closed

mdo(1): Added extensive group setting options and --print-rule#3
thesynthax wants to merge 23 commits intoOlCe2:oc-thesynthaxfrom
thesynthax:mdo/groups

Conversation

@thesynthax
Copy link

Added options to:

  1. set primary group (-g)
  2. override supplementary groups (-G)
  3. add, remove or reset supplementary groups (-s [+,-,@])
  4. override Real UID (-U), saved UID (-R), effective UID (-E), real GID (-P), saved GID (-Q)
  5. print the actual transition rules that will occur (--print-rule)

Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
…oups

Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
OlCe2 pushed a commit that referenced this pull request Jul 24, 2025
Multiple issues existed within the powerpc FP/VSX save/restore functionality,
leading to register corruption and loss of register contents in specific
scenarios involving high signal load and use of both floating point and VSX
instructions.

Issue #1

On little endian systems the PCB used the wrong location for the shadowed
FP register within the larger VSX register.  This appears to have been an
attempt to correct issue #2 without understanding how the vector load/store
instructions actually operate.

Issue #2

On little endian systems, the VSX state save/restore routines swapped 32-bit
words within the 64-bit aliased double word for the associated floating point
register.  This was due to the use of a word-oriented load/store vs. doubleword
oriented load/store.

Issue #3

The FPU was turned off in the PCB but not in hardware, leading to a potential
race condition if the same thread was scheduled immediately after sigreturn.

The triggering codebase for this is Go, which makes heavy use of signals and
and generates an unusual mix of floating point and VSX assembler.  As a result,
when combined with th powerpc lazy FPU restore, a condition was repeatedly hit
whereby the thread was interrupted in FP+VSX mode, then restored in FP only
mode, thus reliably triggering the issues above.

Also clean up the associated asm() style issue flagged by GitHub Actions.

Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>

MFC after:	1 week
Pull Request:	freebsd#1756
Copy link
Owner

@OlCe2 OlCe2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks good. Please see inline comments.

err(EXIT_FAILURE, "strdup failed");
char *tok = strtok(s, ",");
while (tok) {
if (strcmp(tok, "@") == 0) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if @ is accepted only as the first token. Else, there is confusion between whether @ should reset everything first even when not in first position, or should reset groups after the first directives, which anyway would be silly (and I don't see a use case for this feature with automatically produced command lines).

Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Copy link
Owner

@OlCe2 OlCe2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, had not validated this GitHub's review, so perhaps some my comments from yesterday were not visible.

Copy link
Owner

@OlCe2 OlCe2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a set of tests would be great.

As a separate, subsequent commit, it would be nice to add some way to say that the user should not be changed, but only groups should. E.g., some new -k option? Ideally, I would have preferred that this behavior is triggered simply by not specifying -u, but since all credentials-changing program actually interpret not specifying a user as a request to change to root, diverging from that would be too surprising to users.

Comment on lines 300 to 307
if (pw != NULL) {
const long max = sysconf(_SC_NGROUPS_MAX) + 2;
base = malloc(sizeof(*base) * max);
if (!base)
err(EXIT_FAILURE, "malloc failed");
base_count = max;
getgrouplist(pw->pw_name, pw->pw_gid, base, &base_count);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As expressed in the comment just above, this code should be moved up in another block. At this point, you should only be processing supp_add and supp_rem.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-s option is having problems when I do that. I'm not able to add a new group for some reason.

Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Copy link
Owner

@OlCe2 OlCe2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General style problems:

  • Always enclose return values following a return in parenthesis.
  • Use _ to separate words in a variable name.

Generally, in everything you do, be consistent.

The changes I'm requesting here are for readability but also to fix some remaining bugs, notably that -k with -u doesn't trigger an error, that groups to remove are not obeyed when -G is passed, and switching to root without -u is not handled correctly.

Comment on lines 225 to 226
if (keepuser && (ruid_str != NULL || svuid_str != NULL || euid_str != NULL))
errx(EXIT_FAILURE, "-k and --ruid/--svuid/--euid cannot be used together");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines do not need any ID processing. Move them up before if (!keepuser), and add username_provided to the disjunction (else, -k will simply be ignored with -u, without printing an error message).

Comment on lines 403 to 404
int ngroups = getgroups(0, NULL);
gid_t *groups = NULL;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All code with groups and ngroups here can be removed, as the current supplementary groups are irrelevant for a rule's "target" part. And, indeed, groups is not really used anywhere.

} else if (pw != NULL && !uidonly) {
gid = pw->pw_gid;
override_gid = true;
} else if (ruid_str != NULL || svuid_str != NULL || euid_str != NULL) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Setting GIDs with the effective GID is in fact not necessary if all 3 strings are present. That would save a call to getegid() in this case, but that's not really important.

struct setcred wcred = SETCRED_INITIALIZER;
u_int setcred_flags = 0;
bool uidonly = false;
bool keepuser = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool keepuser = false;
bool keep_user = false;

const char *group_mod_str = NULL;
struct setcred wcred = SETCRED_INITIALIZER;
u_int setcred_flags = 0;
bool uidonly = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool uidonly = false;
bool uid_only = false;

username);
pw = getpwuid(uid);
if (!keepuser) {
if (username_provided) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this if (username_provided). The code inside it must be executed unconditionally.

Copy link
Author

@thesynthax thesynthax Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this creates problems for explicit UID changes. I changed the check though to work allow the default use of mdo i.e. mdo without any options.

Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
@thesynthax thesynthax changed the base branch from main to oc-thesynthax August 30, 2025 07:25
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
@github-actions
Copy link

github-actions bot commented Sep 1, 2025

Thank you for taking the time to contribute to FreeBSD!
There is an issue that needs to be fixed:

  • Missing Signed-off-by lines080dd63

Please review CONTRIBUTING.md, then update and push your branch again.

Copy link
Owner

@OlCe2 OlCe2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Globally fine, there are a few bugs still.

I think we should slightly change the semantics of -i and -k, see inline comments.

Comment on lines +282 to 286
} else {
pw = getpwuid(geteuid());
if (pw == NULL)
err(EXIT_FAILURE, "invalid username '%s'", username);
err(EXIT_FAILURE, "cannot determine current user");
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If keeping the current user, we do not want to infer the groups from the DB, even if an entry exists. That feels too much surprising.

If some admin really wants to reload the groups from the DB, we'll let him enter the user name explicitly.

So, replace the content of the else block with (please indent as appropriate):

start_from_current_groups = true;

Also, please add in this same else block a comment explaining that, on keep_user the baseline for groups are the current ones.

"\n"
"Options:\n"
" -u <user> Target user (name or UID)\n"
" -i Only change UID, skip groups\n"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should change slightly semantics here:

Suggested change
" -i Only change UID, skip groups\n"
" -i Keep current groups, unless explicitly overridden\n"

This is still compatible with the initial mdo behavior, but a different extension than the one we have been doing so far. This new proposal has an important advantage: It allows more freedom to "admins" in that they can specify that mdo should leave groups alone unless explicitly told too (the previous behavior is to just leave groups alone entirely, and you would have to explicitly specify them all to their current values as the same starting point).

Basically, if the target user is in the password database, we just don't set the corresponding groups, and if it is not, we do not request -g (or the more fine-grained group options). But we let the admin still override groups manually if he wishes.

struct setcred wcred = SETCRED_INITIALIZER;
u_int setcred_flags = 0;
bool uidonly = false;
bool uid_only = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool uid_only = false;
bool start_from_current_groups = false;

In accordance with the change of semantics for -i.

(euid_str == NULL && ruid_str == NULL && svuid_str == NULL)) {
uid_t uid = parse_user_pwd(username, &pw);

if (pw == NULL && primary_group == NULL && !uid_only)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should rather be:

Suggested change
if (pw == NULL && primary_group == NULL && !uid_only)
if (!start_from_current_groups && pw == NULL &&
primary_group == NULL && (rgid_str == NULL || svgid_str == NULL || egid_str == NULL))

(please wrap the line to 80 columns and indent appropriately)

Please also move this if block out and just after the containing if (makes the general logic clearer, and changes the semantics in a way that makes some other check below unnecessary).

We don't need to treat the absence of explicitly specified supplementary groups as an error, if we effectively remove all supplementary groups in this case. If pw is not NULL, we readily have the group to use as a primary one, and we know we can call getgrouplist() to obtain the supplementary ones.

Please add a comment explaining the reason for this if (some elaboration of your own and perhaps a summary of the previous paragraph).

uid_t uid = parse_user_pwd(username, &pw);

if (pw == NULL && primary_group == NULL && !uid_only)
errx(EXIT_FAILURE, "must specify -g when using a numeric UID");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
errx(EXIT_FAILURE, "must specify -g when using a numeric UID");
errx(EXIT_FAILURE, "must specify primary groups or a user with an entry in the password database");

(please wrap the line as necessary; to be moved with its enclosing if as explained in my previous comment)

}
free(groups);
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}

Comment on lines 404 to 408
for (int i = 0; i < base_count; ++i) {
supp_groups_add = realloc_groups(supp_groups_add, add_count + 1);
supp_groups_add[add_count++] = groups[i];
}
free(groups);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, should have spotted this earlier, but this loop is in fact not necessary. Just set supp_groups_add to groups here.

wcred.sc_supp_groups = NULL;
wcred.sc_supp_groups_nb = 0;
setcred_flags |= SETCREDF_SUPP_GROUPS;
} else {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else {

The if in the else should be at the same level as the previous if.

wcred.sc_supp_groups_nb = 0;
setcred_flags |= SETCREDF_SUPP_GROUPS;
} else {
if (pw != NULL && !uid_only) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (pw != NULL && !uid_only) {
if (!start_from_current_groups && pw != NULL) {

}

static size_t
remove_groups_from_array(gid_t *array, size_t count, const gid_t *remove_list, size_t remove_count)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a priority, but if you have some time: Replace by qsort() + duplicate removal on remove_list followed by a merge-like algorithm, since array is sorted and without duplicates.

Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
Signed-off-by: Kushagra Srivastava <kushagra1403@gmail.com>
OlCe2 pushed a commit that referenced this pull request Sep 30, 2025
Multiple issues existed within the powerpc FP/VSX save/restore functionality,
leading to register corruption and loss of register contents in specific
scenarios involving high signal load and use of both floating point and VSX
instructions.

Issue #1

On little endian systems the PCB used the wrong location for the shadowed
FP register within the larger VSX register.  This appears to have been an
attempt to correct issue #2 without understanding how the vector load/store
instructions actually operate.

Issue #2

On little endian systems, the VSX state save/restore routines swapped 32-bit
words within the 64-bit aliased double word for the associated floating point
register.  This was due to the use of a word-oriented load/store vs. doubleword
oriented load/store.

Issue #3

The FPU was turned off in the PCB but not in hardware, leading to a potential
race condition if the same thread was scheduled immediately after sigreturn.

The triggering codebase for this is Go, which makes heavy use of signals and
and generates an unusual mix of floating point and VSX assembler.  As a result,
when combined with th powerpc lazy FPU restore, a condition was repeatedly hit
whereby the thread was interrupted in FP+VSX mode, then restored in FP only
mode, thus reliably triggering the issues above.

Also clean up the associated asm() style issue flagged by GitHub Actions.

Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>

MFC after:	1 week
Pull Request:	freebsd#1756

(cherry picked from commit 077e30e)
@OlCe2
Copy link
Owner

OlCe2 commented Nov 21, 2025

Code here was largely rewritten, for lots of reason (have -G and -s work together, fix array reallocations, fix bugs related to getpwnam() using a single static storage, generally have options work together, in particular overriding ones and also including change of semantics for -i and -k, use algorithms linear in the number of groups).

The final result was committed as 3ca1e69, and will be present in 15.0.

@OlCe2 OlCe2 closed this Nov 21, 2025
@OlCe2 OlCe2 added the merged Was merged label Nov 21, 2025
@OlCe2 OlCe2 self-assigned this Nov 21, 2025
@OlCe2
Copy link
Owner

OlCe2 commented Nov 21, 2025

Actually, the --print-rule part was omitted as it had problems and wasn't high priority. To be reworked later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged Was merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants