From c8e222616c7e98305bdc861db3ccac520bc29921 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 28 May 2020 10:39:40 -0400 Subject: selinux: allow reading labels before policy is loaded This patch does for `getxattr` what commit 3e3e24b42043 ("selinux: allow labeling before policy is loaded") did for `setxattr`; it allows querying the current SELinux label on disk before the policy is loaded. One of the motivations described in that commit message also drives this patch: for Fedora CoreOS (and eventually RHEL CoreOS), we want to be able to move the root filesystem for example, from xfs to ext4 on RAID, on first boot, at initrd time.[1] Because such an operation works at the filesystem level, we need to be able to read the SELinux labels first from the original root, and apply them to the files of the new root. The previous commit enabled the second part of this process; this commit enables the first part. [1] https://github.com/coreos/fedora-coreos-tracker/issues/94 Acked-by: Stephen Smalley Signed-off-by: Jonathan Lebon Signed-off-by: Paul Moore --- security/selinux/hooks.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index efa6108b1ce9..ca901025802a 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3332,7 +3332,12 @@ static int selinux_inode_getsecurity(struct inode *inode, const char *name, void char *context = NULL; struct inode_security_struct *isec; - if (strcmp(name, XATTR_SELINUX_SUFFIX)) + /* + * If we're not initialized yet, then we can't validate contexts, so + * just let vfs_getxattr fall back to using the on-disk xattr. + */ + if (!selinux_initialized(&selinux_state) || + strcmp(name, XATTR_SELINUX_SUFFIX)) return -EOPNOTSUPP; /* -- cgit v1.2.3 From 7383c0f94d3c902d5dd68792331e9a1d4a5ad6cb Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Wed, 17 Jun 2020 15:23:09 -0400 Subject: selinux: log error messages on required process class / permissions In general SELinux no longer treats undefined object classes or permissions in the policy as a fatal error, instead handling them in accordance with handle_unknown. However, the process class and process transition and dyntransition permissions are still required to be defined due to dependencies on these definitions for default labeling behaviors, role and range transitions in older policy versions that lack an explicit class field, and role allow checking. Log error messages in these cases since otherwise the policy load will fail silently with no indication to the user as to the underlying cause. While here, fix the checking for process transition / dyntransition so that omitting either permission is handled as an error; both are needed in order to ensure that role allow checking is consistently applied. Reported-by: bauen1 Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 98f343005d6b..6f8115224852 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -2376,7 +2376,7 @@ int policydb_read(struct policydb *p, void *fp) struct role_trans_datum *rtd = NULL; int i, j, rc; __le32 buf[4]; - u32 len, nprim, nel; + u32 len, nprim, nel, perm; char *policydb_str; struct policydb_compat_info *info; @@ -2519,8 +2519,10 @@ int policydb_read(struct policydb *p, void *fp) rc = -EINVAL; p->process_class = string_to_security_class(p, "process"); - if (!p->process_class) + if (!p->process_class) { + pr_err("SELinux: process class is required, not defined in policy\n"); goto bad; + } rc = avtab_read(&p->te_avtab, fp, p); if (rc) @@ -2618,10 +2620,18 @@ int policydb_read(struct policydb *p, void *fp) goto bad; rc = -EINVAL; - p->process_trans_perms = string_to_av_perm(p, p->process_class, "transition"); - p->process_trans_perms |= string_to_av_perm(p, p->process_class, "dyntransition"); - if (!p->process_trans_perms) + perm = string_to_av_perm(p, p->process_class, "transition"); + if (!perm) { + pr_err("SELinux: process transition permission is required, not defined in policy\n"); + goto bad; + } + p->process_trans_perms = perm; + perm = string_to_av_perm(p, p->process_class, "dyntransition"); + if (!perm) { + pr_err("SELinux: process dyntransition permission is required, not defined in policy\n"); goto bad; + } + p->process_trans_perms |= perm; rc = ocontext_read(p, info, fp); if (rc) -- cgit v1.2.3 From 65d96351b135c2a694814520a6031f3721020ba9 Mon Sep 17 00:00:00 2001 From: Ethan Edwards Date: Wed, 24 Jun 2020 12:12:58 -0400 Subject: selinux: fixed a checkpatch warning with the sizeof macro `sizeof buf` changed to `sizeof(buf)` Signed-off-by: Ethan Edwards [PM: rewrote the subject line] Signed-off-by: Paul Moore --- security/selinux/ss/conditional.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index da94a1b4bfda..25850c9ea6fa 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -212,7 +212,7 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) if (!booldatum) return -ENOMEM; - rc = next_entry(buf, fp, sizeof buf); + rc = next_entry(buf, fp, sizeof(buf)); if (rc) goto err; @@ -421,7 +421,7 @@ int cond_read_list(struct policydb *p, void *fp) u32 i, len; int rc; - rc = next_entry(buf, fp, sizeof buf); + rc = next_entry(buf, fp, sizeof(buf)); if (rc) return rc; -- cgit v1.2.3 From 2c3d8dfecedd7f5e24e09730a992b59b1c6f956e Mon Sep 17 00:00:00 2001 From: lihao Date: Tue, 7 Jul 2020 12:00:04 +0800 Subject: selinux: Fix spelling mistakes in the comments Fix spelling mistakes in the comments quering==>querying Signed-off-by: lihao Signed-off-by: Paul Moore --- security/selinux/netif.c | 2 +- security/selinux/netnode.c | 2 +- security/selinux/netport.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'security') diff --git a/security/selinux/netif.c b/security/selinux/netif.c index 15b8c1bcd7d0..86813b46fad5 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -124,7 +124,7 @@ static void sel_netif_destroy(struct sel_netif *netif) * @sid: interface SID * * Description: - * This function determines the SID of a network interface by quering the + * This function determines the SID of a network interface by querying the * security policy. The result is added to the network interface table to * speedup future queries. Returns zero on success, negative values on * failure. diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index dff587d1e164..461fb548453a 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -181,7 +181,7 @@ static void sel_netnode_insert(struct sel_netnode *node) * @sid: node SID * * Description: - * This function determines the SID of a network address by quering the + * This function determines the SID of a network address by querying the * security policy. The result is added to the network address table to * speedup future queries. Returns zero on success, negative values on * failure. diff --git a/security/selinux/netport.c b/security/selinux/netport.c index de727f7489b7..d340f4dcdf5f 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -130,7 +130,7 @@ static void sel_netport_insert(struct sel_netport *port) * @sid: port SID * * Description: - * This function determines the SID of a network port by quering the security + * This function determines the SID of a network port by querying the security * policy. The result is added to the network port table to speedup future * queries. Returns zero on success, negative values on failure. * -- cgit v1.2.3 From 237389e3015e0f4ceac7cf00c70a59746150561d Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Wed, 8 Jul 2020 13:24:45 +0200 Subject: selinux: specialize symtab insert and search functions This encapsulates symtab a little better and will help with further refactoring later. Signed-off-by: Ondrej Mosnacek Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/conditional.c | 4 +-- security/selinux/ss/conditional.h | 2 +- security/selinux/ss/mls.c | 21 ++++++++-------- security/selinux/ss/policydb.c | 52 +++++++++++++++++++-------------------- security/selinux/ss/services.c | 34 ++++++++++++------------- security/selinux/ss/symtab.c | 9 +++++++ security/selinux/ss/symtab.h | 3 +++ 7 files changed, 69 insertions(+), 56 deletions(-) (limited to 'security') diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index 25850c9ea6fa..e68dd2e4cac1 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -200,7 +200,7 @@ static int bool_isvalid(struct cond_bool_datum *b) return 1; } -int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) +int cond_read_bool(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct cond_bool_datum *booldatum; @@ -235,7 +235,7 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto err; key[len] = '\0'; - rc = hashtab_insert(h, key, booldatum); + rc = symtab_insert(s, key, booldatum); if (rc) goto err; diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h index 90c9c964f5f5..79e7e03db859 100644 --- a/security/selinux/ss/conditional.h +++ b/security/selinux/ss/conditional.h @@ -69,7 +69,7 @@ int cond_destroy_bool(void *key, void *datum, void *p); int cond_index_bool(void *key, void *datum, void *datap); -int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp); +int cond_read_bool(struct policydb *p, struct symtab *s, void *fp); int cond_read_list(struct policydb *p, void *fp); int cond_write_bool(void *key, void *datum, void *ptr); int cond_write_list(struct policydb *p, void *fp); diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index cd8734f25b39..5be241b6b190 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -165,8 +165,8 @@ int mls_level_isvalid(struct policydb *p, struct mls_level *l) if (!l->sens || l->sens > p->p_levels.nprim) return 0; - levdatum = hashtab_search(&p->p_levels.table, - sym_name(p, SYM_LEVELS, l->sens - 1)); + levdatum = symtab_search(&p->p_levels, + sym_name(p, SYM_LEVELS, l->sens - 1)); if (!levdatum) return 0; @@ -293,7 +293,7 @@ int mls_context_to_sid(struct policydb *pol, *(next_cat++) = '\0'; /* Parse sensitivity. */ - levdatum = hashtab_search(&pol->p_levels.table, sensitivity); + levdatum = symtab_search(&pol->p_levels, sensitivity); if (!levdatum) return -EINVAL; context->range.level[l].sens = levdatum->level->sens; @@ -312,7 +312,7 @@ int mls_context_to_sid(struct policydb *pol, *rngptr++ = '\0'; } - catdatum = hashtab_search(&pol->p_cats.table, cur_cat); + catdatum = symtab_search(&pol->p_cats, cur_cat); if (!catdatum) return -EINVAL; @@ -325,7 +325,7 @@ int mls_context_to_sid(struct policydb *pol, if (rngptr == NULL) continue; - rngdatum = hashtab_search(&pol->p_cats.table, rngptr); + rngdatum = symtab_search(&pol->p_cats, rngptr); if (!rngdatum) return -EINVAL; @@ -458,9 +458,10 @@ int mls_convert_context(struct policydb *oldp, return 0; for (l = 0; l < 2; l++) { - levdatum = hashtab_search(&newp->p_levels.table, - sym_name(oldp, SYM_LEVELS, - oldc->range.level[l].sens - 1)); + char *name = sym_name(oldp, SYM_LEVELS, + oldc->range.level[l].sens - 1); + + levdatum = symtab_search(&newp->p_levels, name); if (!levdatum) return -EINVAL; @@ -470,8 +471,8 @@ int mls_convert_context(struct policydb *oldp, node, i) { int rc; - catdatum = hashtab_search(&newp->p_cats.table, - sym_name(oldp, SYM_CATS, i)); + catdatum = symtab_search(&newp->p_cats, + sym_name(oldp, SYM_CATS, i)); if (!catdatum) return -EINVAL; rc = ebitmap_set_bit(&newc->range.level[l].cat, diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 6f8115224852..02b722c5c189 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -400,7 +400,7 @@ static int roles_init(struct policydb *p) if (!key) goto out; - rc = hashtab_insert(&p->p_roles.table, key, role); + rc = symtab_insert(&p->p_roles, key, role); if (rc) goto out; @@ -1065,7 +1065,7 @@ static int str_read(char **strp, gfp_t flags, void *fp, u32 len) return 0; } -static int perm_read(struct policydb *p, struct hashtab *h, void *fp) +static int perm_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct perm_datum *perdatum; @@ -1088,7 +1088,7 @@ static int perm_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, perdatum); + rc = symtab_insert(s, key, perdatum); if (rc) goto bad; @@ -1098,7 +1098,7 @@ bad: return rc; } -static int common_read(struct policydb *p, struct hashtab *h, void *fp) +static int common_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct common_datum *comdatum; @@ -1128,12 +1128,12 @@ static int common_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; for (i = 0; i < nel; i++) { - rc = perm_read(p, &comdatum->permissions.table, fp); + rc = perm_read(p, &comdatum->permissions, fp); if (rc) goto bad; } - rc = hashtab_insert(h, key, comdatum); + rc = symtab_insert(s, key, comdatum); if (rc) goto bad; return 0; @@ -1262,7 +1262,7 @@ static int read_cons_helper(struct policydb *p, return 0; } -static int class_read(struct policydb *p, struct hashtab *h, void *fp) +static int class_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct class_datum *cladatum; @@ -1300,8 +1300,8 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; rc = -EINVAL; - cladatum->comdatum = hashtab_search(&p->p_commons.table, - cladatum->comkey); + cladatum->comdatum = symtab_search(&p->p_commons, + cladatum->comkey); if (!cladatum->comdatum) { pr_err("SELinux: unknown common %s\n", cladatum->comkey); @@ -1309,7 +1309,7 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) } } for (i = 0; i < nel; i++) { - rc = perm_read(p, &cladatum->permissions.table, fp); + rc = perm_read(p, &cladatum->permissions, fp); if (rc) goto bad; } @@ -1347,7 +1347,7 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) cladatum->default_type = le32_to_cpu(buf[0]); } - rc = hashtab_insert(h, key, cladatum); + rc = symtab_insert(s, key, cladatum); if (rc) goto bad; @@ -1357,7 +1357,7 @@ bad: return rc; } -static int role_read(struct policydb *p, struct hashtab *h, void *fp) +static int role_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct role_datum *role; @@ -1404,7 +1404,7 @@ static int role_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; } - rc = hashtab_insert(h, key, role); + rc = symtab_insert(s, key, role); if (rc) goto bad; return 0; @@ -1413,7 +1413,7 @@ bad: return rc; } -static int type_read(struct policydb *p, struct hashtab *h, void *fp) +static int type_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct type_datum *typdatum; @@ -1451,7 +1451,7 @@ static int type_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, typdatum); + rc = symtab_insert(s, key, typdatum); if (rc) goto bad; return 0; @@ -1487,7 +1487,7 @@ static int mls_read_level(struct mls_level *lp, void *fp) return 0; } -static int user_read(struct policydb *p, struct hashtab *h, void *fp) +static int user_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct user_datum *usrdatum; @@ -1528,7 +1528,7 @@ static int user_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; } - rc = hashtab_insert(h, key, usrdatum); + rc = symtab_insert(s, key, usrdatum); if (rc) goto bad; return 0; @@ -1537,7 +1537,7 @@ bad: return rc; } -static int sens_read(struct policydb *p, struct hashtab *h, void *fp) +static int sens_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct level_datum *levdatum; @@ -1569,7 +1569,7 @@ static int sens_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, levdatum); + rc = symtab_insert(s, key, levdatum); if (rc) goto bad; return 0; @@ -1578,7 +1578,7 @@ bad: return rc; } -static int cat_read(struct policydb *p, struct hashtab *h, void *fp) +static int cat_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct cat_datum *catdatum; @@ -1602,7 +1602,7 @@ static int cat_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, catdatum); + rc = symtab_insert(s, key, catdatum); if (rc) goto bad; return 0; @@ -1611,7 +1611,7 @@ bad: return rc; } -static int (*read_f[SYM_NUM]) (struct policydb *p, struct hashtab *h, void *fp) = +static int (*read_f[SYM_NUM]) (struct policydb *p, struct symtab *s, void *fp) = { common_read, class_read, @@ -1751,7 +1751,7 @@ u16 string_to_security_class(struct policydb *p, const char *name) { struct class_datum *cladatum; - cladatum = hashtab_search(&p->p_classes.table, name); + cladatum = symtab_search(&p->p_classes, name); if (!cladatum) return 0; @@ -1770,9 +1770,9 @@ u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name) cladatum = p->class_val_to_struct[tclass-1]; comdatum = cladatum->comdatum; if (comdatum) - perdatum = hashtab_search(&comdatum->permissions.table, name); + perdatum = symtab_search(&comdatum->permissions, name); if (!perdatum) - perdatum = hashtab_search(&cladatum->permissions.table, name); + perdatum = symtab_search(&cladatum->permissions, name); if (!perdatum) return 0; @@ -2509,7 +2509,7 @@ int policydb_read(struct policydb *p, void *fp) } for (j = 0; j < nel; j++) { - rc = read_f[i](p, &p->symtab[i].table, fp); + rc = read_f[i](p, &p->symtab[i], fp); if (rc) goto bad; } diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 313919bd42f8..1d12bb9ff3dd 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -1441,7 +1441,7 @@ static int string_to_context_struct(struct policydb *pol, *p++ = 0; - usrdatum = hashtab_search(&pol->p_users.table, scontextp); + usrdatum = symtab_search(&pol->p_users, scontextp); if (!usrdatum) goto out; @@ -1457,7 +1457,7 @@ static int string_to_context_struct(struct policydb *pol, *p++ = 0; - role = hashtab_search(&pol->p_roles.table, scontextp); + role = symtab_search(&pol->p_roles, scontextp); if (!role) goto out; ctx->role = role->value; @@ -1469,7 +1469,7 @@ static int string_to_context_struct(struct policydb *pol, oldc = *p; *p++ = 0; - typdatum = hashtab_search(&pol->p_types.table, scontextp); + typdatum = symtab_search(&pol->p_types, scontextp); if (!typdatum || typdatum->attribute) goto out; @@ -2024,26 +2024,26 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) /* Convert the user. */ rc = -EINVAL; - usrdatum = hashtab_search(&args->newp->p_users.table, - sym_name(args->oldp, - SYM_USERS, oldc->user - 1)); + usrdatum = symtab_search(&args->newp->p_users, + sym_name(args->oldp, + SYM_USERS, oldc->user - 1)); if (!usrdatum) goto bad; newc->user = usrdatum->value; /* Convert the role. */ rc = -EINVAL; - role = hashtab_search(&args->newp->p_roles.table, - sym_name(args->oldp, SYM_ROLES, oldc->role - 1)); + role = symtab_search(&args->newp->p_roles, + sym_name(args->oldp, SYM_ROLES, oldc->role - 1)); if (!role) goto bad; newc->role = role->value; /* Convert the type. */ rc = -EINVAL; - typdatum = hashtab_search(&args->newp->p_types.table, - sym_name(args->oldp, - SYM_TYPES, oldc->type - 1)); + typdatum = symtab_search(&args->newp->p_types, + sym_name(args->oldp, + SYM_TYPES, oldc->type - 1)); if (!typdatum) goto bad; newc->type = typdatum->value; @@ -2623,7 +2623,7 @@ int security_get_user_sids(struct selinux_state *state, goto out_unlock; rc = -EINVAL; - user = hashtab_search(&policydb->p_users.table, username); + user = symtab_search(&policydb->p_users, username); if (!user) goto out_unlock; @@ -2975,7 +2975,7 @@ static int security_preserve_bools(struct selinux_state *state, if (rc) goto out; for (i = 0; i < nbools; i++) { - booldatum = hashtab_search(&policydb->p_bools.table, bnames[i]); + booldatum = symtab_search(&policydb->p_bools, bnames[i]); if (booldatum) booldatum->state = bvalues[i]; } @@ -3226,7 +3226,7 @@ int security_get_permissions(struct selinux_state *state, read_lock(&state->ss->policy_rwlock); rc = -EINVAL; - match = hashtab_search(&policydb->p_classes.table, class); + match = symtab_search(&policydb->p_classes, class); if (!match) { pr_err("SELinux: %s: unrecognized class %s\n", __func__, class); @@ -3365,7 +3365,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) case AUDIT_SUBJ_USER: case AUDIT_OBJ_USER: rc = -EINVAL; - userdatum = hashtab_search(&policydb->p_users.table, rulestr); + userdatum = symtab_search(&policydb->p_users, rulestr); if (!userdatum) goto out; tmprule->au_ctxt.user = userdatum->value; @@ -3373,7 +3373,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) case AUDIT_SUBJ_ROLE: case AUDIT_OBJ_ROLE: rc = -EINVAL; - roledatum = hashtab_search(&policydb->p_roles.table, rulestr); + roledatum = symtab_search(&policydb->p_roles, rulestr); if (!roledatum) goto out; tmprule->au_ctxt.role = roledatum->value; @@ -3381,7 +3381,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) case AUDIT_SUBJ_TYPE: case AUDIT_OBJ_TYPE: rc = -EINVAL; - typedatum = hashtab_search(&policydb->p_types.table, rulestr); + typedatum = symtab_search(&policydb->p_types, rulestr); if (!typedatum) goto out; tmprule->au_ctxt.type = typedatum->value; diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c index 92d7a948070e..48f523ef93aa 100644 --- a/security/selinux/ss/symtab.c +++ b/security/selinux/ss/symtab.c @@ -39,3 +39,12 @@ int symtab_init(struct symtab *s, unsigned int size) return hashtab_init(&s->table, symhash, symcmp, size); } +int symtab_insert(struct symtab *s, char *name, void *datum) +{ + return hashtab_insert(&s->table, name, datum); +} + +void *symtab_search(struct symtab *s, const char *name) +{ + return hashtab_search(&s->table, name); +} diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h index f145301b9d9f..f2614138d0cd 100644 --- a/security/selinux/ss/symtab.h +++ b/security/selinux/ss/symtab.h @@ -19,6 +19,9 @@ struct symtab { int symtab_init(struct symtab *s, unsigned int size); +int symtab_insert(struct symtab *s, char *name, void *datum); +void *symtab_search(struct symtab *s, const char *name); + #endif /* _SS_SYMTAB_H_ */ -- cgit v1.2.3 From 24def7bb92c19337cee26d506f87dc4eeeba7a19 Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Thu, 9 Jul 2020 21:19:51 +0200 Subject: selinux: prepare for inlining of hashtab functions Refactor searching and inserting into hashtabs to pave the way for converting hashtab_search() and hashtab_insert() to inline functions in the next patch. This will avoid indirect calls and allow the compiler to better optimize individual callers, leading to a significant performance improvement. In order to avoid the indirect calls, the key hashing and comparison callbacks need to be extracted from the hashtab struct and passed directly to hashtab_search()/_insert() by the callers so that the callback address is always known at compile time. The kernel's rhashtable library () does the same thing. This of course makes the hashtab functions slightly easier to misuse by passing a wrong callback set, but unfortunately there is no better way to implement a hash table that is both generic and efficient in C. This patch tries to somewhat mitigate this by only calling the hashtab functions in the same file where the corresponding callbacks are defined (wrapping them into more specialized functions as needed). Note that this patch doesn't bring any benefit without also moving the definitions of hashtab_search() and -_insert() to the header file, which is done in a follow-up patch for easier review of the hashtab.c changes in this patch. Signed-off-by: Ondrej Mosnacek Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/hashtab.c | 44 ++++++++++++------------ security/selinux/ss/hashtab.h | 22 ++++++------ security/selinux/ss/mls.c | 2 +- security/selinux/ss/policydb.c | 76 ++++++++++++++++++++++++++++++------------ security/selinux/ss/policydb.h | 9 +++++ security/selinux/ss/services.c | 4 +-- security/selinux/ss/symtab.c | 16 +++++---- 7 files changed, 110 insertions(+), 63 deletions(-) (limited to 'security') diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c index 5ee868116d70..8126b909a757 100644 --- a/security/selinux/ss/hashtab.c +++ b/security/selinux/ss/hashtab.c @@ -29,16 +29,10 @@ static u32 hashtab_compute_size(u32 nel) return nel == 0 ? 0 : roundup_pow_of_two(nel); } -int hashtab_init(struct hashtab *h, - u32 (*hash_value)(struct hashtab *h, const void *key), - int (*keycmp)(struct hashtab *h, const void *key1, - const void *key2), - u32 nel_hint) +int hashtab_init(struct hashtab *h, u32 nel_hint) { h->size = hashtab_compute_size(nel_hint); h->nel = 0; - h->hash_value = hash_value; - h->keycmp = keycmp; if (!h->size) return 0; @@ -46,7 +40,8 @@ int hashtab_init(struct hashtab *h, return h->htable ? 0 : -ENOMEM; } -int hashtab_insert(struct hashtab *h, void *key, void *datum) +int hashtab_insert(struct hashtab *h, void *key, void *datum, + struct hashtab_key_params key_params) { u32 hvalue; struct hashtab_node *prev, *cur, *newnode; @@ -56,17 +51,20 @@ int hashtab_insert(struct hashtab *h, void *key, void *datum) if (!h->size || h->nel == HASHTAB_MAX_NODES) return -EINVAL; - hvalue = h->hash_value(h, key); + hvalue = key_params.hash(key) & (h->size - 1); prev = NULL; cur = h->htable[hvalue]; - while (cur && h->keycmp(h, key, cur->key) > 0) { + while (cur) { + int cmp = key_params.cmp(key, cur->key); + + if (cmp == 0) + return -EEXIST; + if (cmp < 0) + break; prev = cur; cur = cur->next; } - if (cur && (h->keycmp(h, key, cur->key) == 0)) - return -EEXIST; - newnode = kmem_cache_zalloc(hashtab_node_cachep, GFP_KERNEL); if (!newnode) return -ENOMEM; @@ -84,7 +82,8 @@ int hashtab_insert(struct hashtab *h, void *key, void *datum) return 0; } -void *hashtab_search(struct hashtab *h, const void *key) +void *hashtab_search(struct hashtab *h, const void *key, + struct hashtab_key_params key_params) { u32 hvalue; struct hashtab_node *cur; @@ -92,15 +91,18 @@ void *hashtab_search(struct hashtab *h, const void *key) if (!h->size) return NULL; - hvalue = h->hash_value(h, key); + hvalue = key_params.hash(key) & (h->size - 1); cur = h->htable[hvalue]; - while (cur && h->keycmp(h, key, cur->key) > 0) - cur = cur->next; + while (cur) { + int cmp = key_params.cmp(key, cur->key); - if (!cur || (h->keycmp(h, key, cur->key) != 0)) - return NULL; - - return cur->datum; + if (cmp == 0) + return cur->datum; + if (cmp < 0) + break; + cur = cur->next; + } + return NULL; } void hashtab_destroy(struct hashtab *h) diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h index 31c11511fe10..4885234257d4 100644 --- a/security/selinux/ss/hashtab.h +++ b/security/selinux/ss/hashtab.h @@ -13,6 +13,12 @@ #define HASHTAB_MAX_NODES 0xffffffff +struct hashtab_key_params { + u32 (*hash)(const void *key); /* hash function */ + int (*cmp)(const void *key1, const void *key2); + /* key comparison function */ +}; + struct hashtab_node { void *key; void *datum; @@ -23,10 +29,6 @@ struct hashtab { struct hashtab_node **htable; /* hash table */ u32 size; /* number of slots in hash table */ u32 nel; /* number of elements in hash table */ - u32 (*hash_value)(struct hashtab *h, const void *key); - /* hash function */ - int (*keycmp)(struct hashtab *h, const void *key1, const void *key2); - /* key comparison function */ }; struct hashtab_info { @@ -39,11 +41,7 @@ struct hashtab_info { * * Returns -ENOMEM if insufficient space is available or 0 otherwise. */ -int hashtab_init(struct hashtab *h, - u32 (*hash_value)(struct hashtab *h, const void *key), - int (*keycmp)(struct hashtab *h, const void *key1, - const void *key2), - u32 nel_hint); +int hashtab_init(struct hashtab *h, u32 nel_hint); /* * Inserts the specified (key, datum) pair into the specified hash table. @@ -53,7 +51,8 @@ int hashtab_init(struct hashtab *h, * -EINVAL for general errors or 0 otherwise. */ -int hashtab_insert(struct hashtab *h, void *k, void *d); +int hashtab_insert(struct hashtab *h, void *k, void *d, + struct hashtab_key_params key_params); /* * Searches for the entry with the specified key in the hash table. @@ -61,7 +60,8 @@ int hashtab_insert(struct hashtab *h, void *k, void *d); * Returns NULL if no entry has the specified key or * the datum of the entry otherwise. */ -void *hashtab_search(struct hashtab *h, const void *k); +void *hashtab_search(struct hashtab *h, const void *k, + struct hashtab_key_params key_params); /* * Destroys the specified hash table. diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index 5be241b6b190..408d306895f8 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -507,7 +507,7 @@ int mls_compute_sid(struct policydb *p, rtr.source_type = scontext->type; rtr.target_type = tcontext->type; rtr.target_class = tclass; - r = hashtab_search(&p->range_tr, &rtr); + r = policydb_rangetr_search(p, &rtr); if (r) return mls_range_set(newcontext, r); diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 02b722c5c189..9fccf417006b 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -411,7 +411,7 @@ out: return rc; } -static u32 filenametr_hash(struct hashtab *h, const void *k) +static u32 filenametr_hash(const void *k) { const struct filename_trans_key *ft = k; unsigned long hash; @@ -423,10 +423,10 @@ static u32 filenametr_hash(struct hashtab *h, const void *k) byte_num = 0; while ((focus = ft->name[byte_num++])) hash = partial_name_hash(focus, hash); - return hash & (h->size - 1); + return hash; } -static int filenametr_cmp(struct hashtab *h, const void *k1, const void *k2) +static int filenametr_cmp(const void *k1, const void *k2) { const struct filename_trans_key *ft1 = k1; const struct filename_trans_key *ft2 = k2; @@ -444,15 +444,26 @@ static int filenametr_cmp(struct hashtab *h, const void *k1, const void *k2) } -static u32 rangetr_hash(struct hashtab *h, const void *k) +static const struct hashtab_key_params filenametr_key_params = { + .hash = filenametr_hash, + .cmp = filenametr_cmp, +}; + +struct filename_trans_datum *policydb_filenametr_search( + struct policydb *p, struct filename_trans_key *key) +{ + return hashtab_search(&p->filename_trans, key, filenametr_key_params); +} + +static u32 rangetr_hash(const void *k) { const struct range_trans *key = k; - return (key->source_type + (key->target_type << 3) + - (key->target_class << 5)) & (h->size - 1); + return key->source_type + (key->target_type << 3) + + (key->target_class << 5); } -static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) +static int rangetr_cmp(const void *k1, const void *k2) { const struct range_trans *key1 = k1, *key2 = k2; int v; @@ -470,15 +481,25 @@ static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) return v; } -static u32 role_trans_hash(struct hashtab *h, const void *k) +static const struct hashtab_key_params rangetr_key_params = { + .hash = rangetr_hash, + .cmp = rangetr_cmp, +}; + +struct mls_range *policydb_rangetr_search(struct policydb *p, + struct range_trans *key) +{ + return hashtab_search(&p->range_tr, key, rangetr_key_params); +} + +static u32 role_trans_hash(const void *k) { const struct role_trans_key *key = k; - return (key->role + (key->type << 3) + (key->tclass << 5)) & - (h->size - 1); + return key->role + (key->type << 3) + (key->tclass << 5); } -static int role_trans_cmp(struct hashtab *h, const void *k1, const void *k2) +static int role_trans_cmp(const void *k1, const void *k2) { const struct role_trans_key *key1 = k1, *key2 = k2; int v; @@ -494,6 +515,17 @@ static int role_trans_cmp(struct hashtab *h, const void *k1, const void *k2) return key1->tclass - key2->tclass; } +static const struct hashtab_key_params roletr_key_params = { + .hash = role_trans_hash, + .cmp = role_trans_cmp, +}; + +struct role_trans_datum *policydb_roletr_search(struct policydb *p, + struct role_trans_key *key) +{ + return hashtab_search(&p->role_tr, key, roletr_key_params); +} + /* * Initialize a policy database structure. */ @@ -1796,7 +1828,7 @@ static int range_read(struct policydb *p, void *fp) nel = le32_to_cpu(buf[0]); - rc = hashtab_init(&p->range_tr, rangetr_hash, rangetr_cmp, nel); + rc = hashtab_init(&p->range_tr, nel); if (rc) return rc; @@ -1841,7 +1873,7 @@ static int range_read(struct policydb *p, void *fp) goto out; } - rc = hashtab_insert(&p->range_tr, rt, r); + rc = hashtab_insert(&p->range_tr, rt, r, rangetr_key_params); if (rc) goto out; @@ -1888,7 +1920,7 @@ static int filename_trans_read_helper_compat(struct policydb *p, void *fp) otype = le32_to_cpu(buf[3]); last = NULL; - datum = hashtab_search(&p->filename_trans, &key); + datum = policydb_filenametr_search(p, &key); while (datum) { if (unlikely(ebitmap_get_bit(&datum->stypes, stype - 1))) { /* conflicting/duplicate rules are ignored */ @@ -1918,7 +1950,8 @@ static int filename_trans_read_helper_compat(struct policydb *p, void *fp) if (!ft) goto out; - rc = hashtab_insert(&p->filename_trans, ft, datum); + rc = hashtab_insert(&p->filename_trans, ft, datum, + filenametr_key_params); if (rc) goto out; name = NULL; @@ -2006,7 +2039,8 @@ static int filename_trans_read_helper(struct policydb *p, void *fp) ft->tclass = tclass; ft->name = name; - rc = hashtab_insert(&p->filename_trans, ft, first); + rc = hashtab_insert(&p->filename_trans, ft, first, + filenametr_key_params); if (rc == -EEXIST) pr_err("SELinux: Duplicate filename transition key\n"); if (rc) @@ -2044,8 +2078,7 @@ static int filename_trans_read(struct policydb *p, void *fp) if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) { p->compat_filename_trans_count = nel; - rc = hashtab_init(&p->filename_trans, filenametr_hash, - filenametr_cmp, (1 << 11)); + rc = hashtab_init(&p->filename_trans, (1 << 11)); if (rc) return rc; @@ -2055,8 +2088,7 @@ static int filename_trans_read(struct policydb *p, void *fp) return rc; } } else { - rc = hashtab_init(&p->filename_trans, filenametr_hash, - filenametr_cmp, nel); + rc = hashtab_init(&p->filename_trans, nel); if (rc) return rc; @@ -2539,7 +2571,7 @@ int policydb_read(struct policydb *p, void *fp) goto bad; nel = le32_to_cpu(buf[0]); - rc = hashtab_init(&p->role_tr, role_trans_hash, role_trans_cmp, nel); + rc = hashtab_init(&p->role_tr, nel); if (rc) goto bad; for (i = 0; i < nel; i++) { @@ -2576,7 +2608,7 @@ int policydb_read(struct policydb *p, void *fp) !policydb_role_isvalid(p, rtd->new_role)) goto bad; - rc = hashtab_insert(&p->role_tr, rtk, rtd); + rc = hashtab_insert(&p->role_tr, rtk, rtd, roletr_key_params); if (rc) goto bad; diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index 9591c9587cb6..c24d4e1063ea 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -324,6 +324,15 @@ extern int policydb_role_isvalid(struct policydb *p, unsigned int role); extern int policydb_read(struct policydb *p, void *fp); extern int policydb_write(struct policydb *p, void *fp); +extern struct filename_trans_datum *policydb_filenametr_search( + struct policydb *p, struct filename_trans_key *key); + +extern struct mls_range *policydb_rangetr_search( + struct policydb *p, struct range_trans *key); + +extern struct role_trans_datum *policydb_roletr_search( + struct policydb *p, struct role_trans_key *key); + #define POLICYDB_CONFIG_MLS 1 /* the config flags related to unknown classes/perms are bits 2 and 3 */ diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 1d12bb9ff3dd..9e76a80db6e1 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -1671,7 +1671,7 @@ static void filename_compute_type(struct policydb *policydb, ft.tclass = tclass; ft.name = objname; - datum = hashtab_search(&policydb->filename_trans, &ft); + datum = policydb_filenametr_search(policydb, &ft); while (datum) { if (ebitmap_get_bit(&datum->stypes, stype - 1)) { newcontext->type = datum->otype; @@ -1834,7 +1834,7 @@ static int security_compute_sid(struct selinux_state *state, .tclass = tclass, }; - rtd = hashtab_search(&policydb->role_tr, &rtk); + rtd = policydb_roletr_search(policydb, &rtk); if (rtd) newcontext.role = rtd->new_role; } diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c index 48f523ef93aa..c42a6648a07d 100644 --- a/security/selinux/ss/symtab.c +++ b/security/selinux/ss/symtab.c @@ -9,7 +9,7 @@ #include #include "symtab.h" -static unsigned int symhash(struct hashtab *h, const void *key) +static unsigned int symhash(const void *key) { const char *p, *keyp; unsigned int size; @@ -20,10 +20,10 @@ static unsigned int symhash(struct hashtab *h, const void *key) size = strlen(keyp); for (p = keyp; (p - keyp) < size; p++) val = (val << 4 | (val >> (8*sizeof(unsigned int)-4))) ^ (*p); - return val & (h->size - 1); + return val; } -static int symcmp(struct hashtab *h, const void *key1, const void *key2) +static int symcmp(const void *key1, const void *key2) { const char *keyp1, *keyp2; @@ -32,19 +32,23 @@ static int symcmp(struct hashtab *h, const void *key1, const void *key2) return strcmp(keyp1, keyp2); } +static const struct hashtab_key_params symtab_key_params = { + .hash = symhash, + .cmp = symcmp, +}; int symtab_init(struct symtab *s, unsigned int size) { s->nprim = 0; - return hashtab_init(&s->table, symhash, symcmp, size); + return hashtab_init(&s->table, size); } int symtab_insert(struct symtab *s, char *name, void *datum) { - return hashtab_insert(&s->table, name, datum); + return hashtab_insert(&s->table, name, datum, symtab_key_params); } void *symtab_search(struct symtab *s, const char *name) { - return hashtab_search(&s->table, name); + return hashtab_search(&s->table, name, symtab_key_params); } -- cgit v1.2.3 From 54b27f9287a7b3dfc85549f01fc9d292c92c68b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Thu, 9 Jul 2020 21:19:52 +0200 Subject: selinux: complete the inlining of hashtab functions Move (most of) the definitions of hashtab_search() and hashtab_insert() to the header file. In combination with the previous patch, this avoids calling the callbacks indirectly by function pointers and allows for better optimization, leading to a drastic performance improvement of these operations. With this patch, I measured a speed up in the following areas (measured on x86_64 F32 VM with 4 CPUs): 1. Policy load (`load_policy`) - takes ~150 ms instead of ~230 ms. 2. `chcon -R unconfined_u:object_r:user_tmp_t:s0:c381,c519 /tmp/linux-src` where /tmp/linux-src is an extracted linux-5.7 source tarball - takes ~522 ms instead of ~576 ms. This is because of many symtab_search() calls in string_to_context_struct() when there are many categories specified in the context. 3. `stress-ng --msg 1 --msg-ops 10000000` - takes 12.41 s instead of 13.95 s (consumes 18.6 s of kernel CPU time instead of 21.6 s). This is thanks to security_transition_sid() being ~43% faster after this patch. Signed-off-by: Ondrej Mosnacek Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/hashtab.c | 59 ++++------------------------------------ security/selinux/ss/hashtab.h | 63 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 59 deletions(-) (limited to 'security') diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c index 8126b909a757..d9287bb4bfeb 100644 --- a/security/selinux/ss/hashtab.c +++ b/security/selinux/ss/hashtab.c @@ -7,7 +7,6 @@ #include #include #include -#include #include "hashtab.h" static struct kmem_cache *hashtab_node_cachep; @@ -40,71 +39,23 @@ int hashtab_init(struct hashtab *h, u32 nel_hint) return h->htable ? 0 : -ENOMEM; } -int hashtab_insert(struct hashtab *h, void *key, void *datum, - struct hashtab_key_params key_params) +int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, + void *key, void *datum) { - u32 hvalue; - struct hashtab_node *prev, *cur, *newnode; - - cond_resched(); - - if (!h->size || h->nel == HASHTAB_MAX_NODES) - return -EINVAL; - - hvalue = key_params.hash(key) & (h->size - 1); - prev = NULL; - cur = h->htable[hvalue]; - while (cur) { - int cmp = key_params.cmp(key, cur->key); - - if (cmp == 0) - return -EEXIST; - if (cmp < 0) - break; - prev = cur; - cur = cur->next; - } + struct hashtab_node *newnode; newnode = kmem_cache_zalloc(hashtab_node_cachep, GFP_KERNEL); if (!newnode) return -ENOMEM; newnode->key = key; newnode->datum = datum; - if (prev) { - newnode->next = prev->next; - prev->next = newnode; - } else { - newnode->next = h->htable[hvalue]; - h->htable[hvalue] = newnode; - } + newnode->next = *dst; + *dst = newnode; h->nel++; return 0; } -void *hashtab_search(struct hashtab *h, const void *key, - struct hashtab_key_params key_params) -{ - u32 hvalue; - struct hashtab_node *cur; - - if (!h->size) - return NULL; - - hvalue = key_params.hash(key) & (h->size - 1); - cur = h->htable[hvalue]; - while (cur) { - int cmp = key_params.cmp(key, cur->key); - - if (cmp == 0) - return cur->datum; - if (cmp < 0) - break; - cur = cur->next; - } - return NULL; -} - void hashtab_destroy(struct hashtab *h) { u32 i; diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h index 4885234257d4..3c952f0f01f9 100644 --- a/security/selinux/ss/hashtab.h +++ b/security/selinux/ss/hashtab.h @@ -11,7 +11,11 @@ #ifndef _SS_HASHTAB_H_ #define _SS_HASHTAB_H_ -#define HASHTAB_MAX_NODES 0xffffffff +#include +#include +#include + +#define HASHTAB_MAX_NODES U32_MAX struct hashtab_key_params { u32 (*hash)(const void *key); /* hash function */ @@ -43,6 +47,9 @@ struct hashtab_info { */ int hashtab_init(struct hashtab *h, u32 nel_hint); +int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, + void *key, void *datum); + /* * Inserts the specified (key, datum) pair into the specified hash table. * @@ -51,8 +58,34 @@ int hashtab_init(struct hashtab *h, u32 nel_hint); * -EINVAL for general errors or 0 otherwise. */ -int hashtab_insert(struct hashtab *h, void *k, void *d, - struct hashtab_key_params key_params); +static inline int hashtab_insert(struct hashtab *h, void *key, void *datum, + struct hashtab_key_params key_params) +{ + u32 hvalue; + struct hashtab_node *prev, *cur; + + cond_resched(); + + if (!h->size || h->nel == HASHTAB_MAX_NODES) + return -EINVAL; + + hvalue = key_params.hash(key) & (h->size - 1); + prev = NULL; + cur = h->htable[hvalue]; + while (cur) { + int cmp = key_params.cmp(key, cur->key); + + if (cmp == 0) + return -EEXIST; + if (cmp < 0) + break; + prev = cur; + cur = cur->next; + } + + return __hashtab_insert(h, prev ? &prev->next : &h->htable[hvalue], + key, datum); +} /* * Searches for the entry with the specified key in the hash table. @@ -60,8 +93,28 @@ int hashtab_insert(struct hashtab *h, void *k, void *d, * Returns NULL if no entry has the specified key or * the datum of the entry otherwise. */ -void *hashtab_search(struct hashtab *h, const void *k, - struct hashtab_key_params key_params); +static inline void *hashtab_search(struct hashtab *h, const void *key, + struct hashtab_key_params key_params) +{ + u32 hvalue; + struct hashtab_node *cur; + + if (!h->size) + return NULL; + + hvalue = key_params.hash(key) & (h->size - 1); + cur = h->htable[hvalue]; + while (cur) { + int cmp = key_params.cmp(key, cur->key); + + if (cmp == 0) + return cur->datum; + if (cmp < 0) + break; + cur = cur->next; + } + return NULL; +} /* * Destroys the specified hash table. -- cgit v1.2.3