domainset(9): Split domainset validation logic into a separate function

This change splits the validation and 'struct domainset'-filling logic
from kern_cpuset_setdomain into a separate function - domainset_populate.
This function's main use is to validate user-provided domainset(9)
policies and populate a struct domainset before handing it off
to domainset_create. No functional change intended.

Differential Revision:	https://reviews.freebsd.org/D46608
Reviewed by:	markj
This commit is contained in:
Bojan Novković 2024-09-08 18:10:53 +02:00
parent aae23170c8
commit cf571e0850
3 changed files with 88 additions and 50 deletions

View file

@ -22,7 +22,7 @@
.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd April 14, 2021
.Dd June 24, 2025
.Dt DOMAINSET 9
.Os
.Sh NAME
@ -54,6 +54,8 @@ struct domainset {
.Ft struct domainset *
.Fn domainset_create "const struct domainset *key"
.Ft int
.Fn domainset_populate "struct domainset *domain" "domainset_t *mask" "int policy" "size_t mask_size"
.Ft int
.Fn sysctl_handle_domainset "SYSCTL_HANDLER_ARGS"
.Sh DESCRIPTION
The
@ -137,6 +139,7 @@ These policies should be used in preference to
to avoid blocking indefinitely on a
.Dv M_WAITOK
request.
.Pp
The
.Fn domainset_create
function takes a partially filled in domainset as a key and returns a
@ -148,6 +151,17 @@ is an immutable type that is shared among all matching keys and must
not be modified after return.
.Pp
The
.Fn domainset_populate
function fills a
.Vt domainset
struct using a domain mask and policy.
It is used for validating and
translating a domain mask and policy into a
.Vt domainset
struct when creating a custom domainset using
.Vt domainset_create .
.Pp
The
.Fn sysctl_handle_domainset
function is provided as a convenience for modifying or viewing domainsets
that are not accessible via

View file

@ -530,7 +530,7 @@ _domainset_create(struct domainset *domain, struct domainlist *freelist)
* remove them and update the domainset accordingly. If only empty
* domains are present, we must return failure.
*/
static bool
bool
domainset_empty_vm(struct domainset *domain)
{
domainset_t empty;
@ -2408,6 +2408,59 @@ sys_cpuset_setdomain(struct thread *td, struct cpuset_setdomain_args *uap)
uap->id, uap->domainsetsize, uap->mask, uap->policy, &copy_set));
}
int
domainset_populate(struct domainset *domain, const domainset_t *mask, int policy,
size_t mask_size)
{
if (policy <= DOMAINSET_POLICY_INVALID ||
policy > DOMAINSET_POLICY_MAX) {
return (EINVAL);
}
/*
* Verify that no high bits are set.
*/
if (mask_size > sizeof(domainset_t)) {
const char *end;
const char *cp;
end = cp = (const char *)&mask->__bits;
end += mask_size;
cp += sizeof(domainset_t);
while (cp != end) {
if (*cp++ != 0) {
return (EINVAL);
}
}
}
if (DOMAINSET_EMPTY(mask)) {
return (EDEADLK);
}
DOMAINSET_COPY(mask, &domain->ds_mask);
domain->ds_policy = policy;
/*
* Sanitize the provided mask.
*/
if (!DOMAINSET_SUBSET(&all_domains, &domain->ds_mask)) {
return (EINVAL);
}
/* Translate preferred policy into a mask and fallback. */
if (policy == DOMAINSET_POLICY_PREFER) {
/* Only support a single preferred domain. */
if (DOMAINSET_COUNT(&domain->ds_mask) != 1) {
return (EINVAL);
}
domain->ds_prefer = DOMAINSET_FFS(&domain->ds_mask) - 1;
/* This will be constrained by domainset_shadow(). */
DOMAINSET_COPY(&all_domains, &domain->ds_mask);
}
return (0);
}
int
kern_cpuset_setdomain(struct thread *td, cpulevel_t level, cpuwhich_t which,
id_t id, size_t domainsetsize, const domainset_t *maskp, int policy,
@ -2421,62 +2474,20 @@ kern_cpuset_setdomain(struct thread *td, cpulevel_t level, cpuwhich_t which,
domainset_t *mask;
int error;
if (domainsetsize < sizeof(domainset_t) ||
domainsetsize > DOMAINSET_MAXSIZE / NBBY)
return (ERANGE);
if (policy <= DOMAINSET_POLICY_INVALID ||
policy > DOMAINSET_POLICY_MAX)
return (EINVAL);
error = cpuset_check_capabilities(td, level, which, id);
if (error != 0)
return (error);
if (domainsetsize < sizeof(domainset_t) ||
domainsetsize > DOMAINSET_MAXSIZE / NBBY)
return (ERANGE);
memset(&domain, 0, sizeof(domain));
mask = malloc(domainsetsize, M_TEMP, M_WAITOK | M_ZERO);
error = cb->cpuset_copyin(maskp, mask, domainsetsize);
if (error)
goto out;
/*
* Verify that no high bits are set.
*/
if (domainsetsize > sizeof(domainset_t)) {
char *end;
char *cp;
end = cp = (char *)&mask->__bits;
end += domainsetsize;
cp += sizeof(domainset_t);
while (cp != end)
if (*cp++ != 0) {
error = EINVAL;
goto out;
}
}
if (DOMAINSET_EMPTY(mask)) {
error = EDEADLK;
error = domainset_populate(&domain, mask, policy, domainsetsize);
if (error)
goto out;
}
DOMAINSET_COPY(mask, &domain.ds_mask);
domain.ds_policy = policy;
/*
* Sanitize the provided mask.
*/
if (!DOMAINSET_SUBSET(&all_domains, &domain.ds_mask)) {
error = EINVAL;
goto out;
}
/* Translate preferred policy into a mask and fallback. */
if (policy == DOMAINSET_POLICY_PREFER) {
/* Only support a single preferred domain. */
if (DOMAINSET_COUNT(&domain.ds_mask) != 1) {
error = EINVAL;
goto out;
}
domain.ds_prefer = DOMAINSET_FFS(&domain.ds_mask) - 1;
/* This will be constrained by domainset_shadow(). */
DOMAINSET_COPY(&all_domains, &domain.ds_mask);
}
/*
* When given an impossible policy, fall back to interleaving
@ -2484,7 +2495,6 @@ kern_cpuset_setdomain(struct thread *td, cpulevel_t level, cpuwhich_t which,
*/
if (domainset_empty_vm(&domain))
domainset_copy(domainset2, &domain);
switch (level) {
case CPU_LEVEL_ROOT:
case CPU_LEVEL_CPUSET:

View file

@ -113,6 +113,20 @@ void domainset_zero(void);
* returned value will not match the key pointer.
*/
struct domainset *domainset_create(const struct domainset *);
/*
* Remove empty domains from a given domainset.
* Returns 'false' if the domainset consists entirely of empty domains.
*/
bool domainset_empty_vm(struct domainset *domain);
/*
* Validate and populate a domainset structure according to the specified
* policy and mask.
*/
int domainset_populate(struct domainset *domain, const domainset_t *mask, int policy,
size_t mask_size);
#ifdef _SYS_SYSCTL_H_
int sysctl_handle_domainset(SYSCTL_HANDLER_ARGS);
#endif