Skip to content

Deterministic subid calculation#1571

Open
jcpunk wants to merge 3 commits intoshadow-maint:masterfrom
jcpunk:deterministic-subid
Open

Deterministic subid calculation#1571
jcpunk wants to merge 3 commits intoshadow-maint:masterfrom
jcpunk:deterministic-subid

Conversation

@jcpunk
Copy link

@jcpunk jcpunk commented Mar 9, 2026

This patch solves a long standing problem at my site.

We have two main environments: LDAP and "not LDAP".

Our LDAP server doesn't have a schema for subid, so that is local to each system. Users are added to LDAP by a different group and we aren't usually told when a new user appears.

Our "not LDAP" systems are expected to have their UID match whatever is in LDAP, but beyond that we are free to customize. This is typically a lab environment where folks have groups specific to their access within that lab cluster. So here we explicitly want to mangage the subids.

More often then I'd like, users want us to setup an sync job where their home area from the LDAP is synced down to a sub area on the test cluster. So that makes keeping the subids in sync a bit more of an adventure.

Then it gets worse. My site has currently around 63,000 users. Which means if I give every user 65,536 subids to play with, in a few years I'll be out of space since uid_t is u_int_32. Thankfully, my test labs don't the full user list. They generally have about 100 or so users.

Before the merge of 1ed06fe I figured this was a 100% my problem thing since usermod didn't have a "recommend" mode. But now it does.

This patch makes it automatic for me to keep all my "16bit" user subids in perfect sync by just setting up a job to run usermod -S as needed.

I'm going to need a subid solution for user 75537 (since UID_MIN==1000) eventually. Since the only place where I'd consider enabling wrap mode is in my heavily curated lab computers, seeding an imperfect solution. I can't shake the feeling that I'm going to need this one day. When LDAP "rolls over" I think we may just drop user namespaces on those hosts...

I'll certainly need help with tests.

@jcpunk jcpunk force-pushed the deterministic-subid branch 3 times, most recently from d725670 to b4d445d Compare March 10, 2026 16:59
@jcpunk
Copy link
Author

jcpunk commented Mar 10, 2026

I think I've reworked this into a more linear patch set. Hopefully I've gotten the overflow checks clearly documented.

@jcpunk jcpunk force-pushed the deterministic-subid branch from b4d445d to 3353237 Compare March 10, 2026 17:02
Comment on lines +177 to +190
/*
* UNSAFE_SUB_UID_DETERMINISTIC_WRAP MODE
*
* Promote to uintmax_t before multiplying to avoid truncation on
* 32-bit platforms where unsigned long is 32 bits. The modulo
* folds the result back into [0, space) before adding min.
*/
uintmax_t logical_offset = (uintmax_t)uid_offset * (uintmax_t)count;

*range_start = (id_t)(sub_uid_min + (unsigned long)(logical_offset % space));
*range_count = count;
return 0;
}

Copy link
Collaborator

@alejandro-colomar alejandro-colomar Mar 10, 2026

Choose a reason for hiding this comment

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

Why would we want an unsafe mode?

Copy link
Author

Choose a reason for hiding this comment

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

At my site, once a UNIX UID is assigned, that is your ID forever. It is never reused or reassigned. It is expected to be identical across all systems.

In the next 5 years I expect to have more assigned UIDs than will allow me to give each user 65535 subids.

Eventually I'm going to hit a point where my newest users can't get subids because a bunch of subids are assigned to folks who've left the laboratory.

In my systems with the full user list, I'm going to probably just disable user namespaces since there is no safe way for me to give everyone their own big subid space. I don't really like that plan, but I don't have a better one.

My systems with a limited user list are more interesting... On hosts dedicated to experimental development, I'm willing to take more risks. These hosts typically have less than 100 users.

The great thing about strong determinism in subids is that, so long as all the ids fit between SUBID_MIN and SUBID_MAX, I have perfect consistency across the whole site. Once I run out of space things get rough.

On these hosts as the sysadmin responsible for safety and security of the system, I can see that my UIDs are not contiguous. At times I've got a 1,000+ "unallocated" UIDs on a given host because there are no users in that range who have access to the system. I'm willing to "risk" some overlap when I can see that I've zero interactive UIDs under 2000.

"Unsafe mode" is my best compromise. I get the determinism I want so subids are predicable, and, when I run out of space, I at least have an option I can consider for how to approach the problem.

With a UNIX UID being uint_32, and the traditional subid allocation being "each user gets their own uint_16", I'm going to hit that wall and I don't know what else to do.

@jcpunk jcpunk force-pushed the deterministic-subid branch from 3353237 to 1549cdc Compare March 11, 2026 15:13
Comment on lines +70 to +78
int find_new_sub_gids (id_t *range_start, unsigned long *range_count)
{
if (!range_start || !range_count) {
errno = EINVAL;
return -1;
}

return find_new_sub_gids_linear (range_start, range_count);
}
Copy link
Collaborator

@alejandro-colomar alejandro-colomar Mar 11, 2026

Choose a reason for hiding this comment

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

EINVAL here seems an addition, and seems to be unnecessary, right? I'm not fundamentally against it, but in this commit, it seems superfluous.

While fixing that, you may want to follow the coding style for new code:

int
find_new_sub_gids(id_t *range_start, unsigned long *range_count)
{
	if (range_start == NULL || range_count == NULL)
		return -1;

	return find_new_sub_gids_linear(range_start, range_count);
}

Copy link
Author

Choose a reason for hiding this comment

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

I think I've sorted this

jcpunk added 3 commits March 11, 2026 13:07
Signed-off-by: Pat Riehecky <riehecky@fnal.gov>
They are not active at this commit, but they are documented.

Signed-off-by: Pat Riehecky <riehecky@fnal.gov>
This adds four new options to /etc/login.defs:
* SUB_UID_DETERMINISTIC
* UNSAFE_SUB_UID_DETERMINISTIC_WRAP
* SUB_GID_DETERMINISTIC
* UNSAFE_SUB_GID_DETERMINISTIC_WRAP

In a lab where users are created ad hoc subids might drift
from one host to the other. If there is a shared home area,
this drift can create some frustration.

The manpages provide documentation on how these can be used.

With deterministic mode three overflow guards are required:
1. uid_offset * count
   Overflows for large UIDs or large counts.

2. sub_gid_min + product
   Overflows when the offset pushes start past range.

3. start_id + (count-1)
   Overflows when start_id is near max even after guards 1
   and 2 pass.  Required on platforms where id_t is larger
   than the sub-GID value space.

Omitting any one leaves a range-escape vector on some possible
arch/config combinations.

Signed-off-by: Pat Riehecky <riehecky@fnal.gov>
@jcpunk jcpunk force-pushed the deterministic-subid branch from 1549cdc to 282ffa6 Compare March 11, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants