diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 901cc052f..78413277c 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -6502,7 +6502,8 @@ static int selinux_key_permission(key_ref_t
key_ref,
{
struct key *key;
struct key_security_struct *ksec;
- unsigned oldstyle_perm;
+ unsigned int key_perm = 0, switch_perm = 0;
+ int x = KEY_NEED_ALL, bit = 1;
u32 sid;
/* if no specific permissions are requested, we skip the
@@ -6511,18 +6512,67 @@ static int selinux_key_permission(key_ref_t
key_ref,
if (perm == 0)
return 0;
- oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ |
KEY_NEED_WRITE |
- KEY_NEED_SEARCH | KEY_NEED_LINK);
- if (perm & KEY_NEED_SETSEC)
- oldstyle_perm |= OLD_KEY_NEED_SETATTR;
- if (perm & KEY_NEED_INVAL)
- oldstyle_perm |= KEY_NEED_SEARCH;
- if (perm & KEY_NEED_REVOKE && !(perm & OLD_KEY_NEED_SETATTR))
- oldstyle_perm |= KEY_NEED_WRITE;
- if (perm & KEY_NEED_JOIN)
- oldstyle_perm |= KEY_NEED_SEARCH;
- if (perm & KEY_NEED_CLEAR)
- oldstyle_perm |= KEY_NEED_WRITE;
+ /*
+ * selinux_key_permission() is called with only one permission
set.
+ * However this will handle multiple bits set.
+ */
+ while (x) {
+ switch_perm = bit & perm;
+ switch (switch_perm) {
+ case KEY_NEED_VIEW:
+ key_perm |= KEY__VIEW;
+ break;
+ case KEY_NEED_READ:
+ key_perm |= KEY__READ;
+ break;
+ case KEY_NEED_WRITE:
+ key_perm |= KEY__WRITE;
+ break;
+ case KEY_NEED_SEARCH:
+ key_perm |= KEY__SEARCH;
+ break;
+ case KEY_NEED_LINK:
+ key_perm |= KEY__LINK;
+ break;
+ case KEY_NEED_SETSEC: /* Keep this as "setattr" in
policy */
+ key_perm |= KEY__SETATTR;
+ break;
+ case KEY_NEED_INVAL:
+ key_perm |= KEY__INVAL;
+ break;
+ case KEY_NEED_REVOKE:
+ key_perm |= KEY__REVOKE;
+ break;
+ case KEY_NEED_JOIN:
+ key_perm |= KEY__JOIN;
+ break;
+ case KEY_NEED_CLEAR:
+ key_perm |= KEY__CLEAR;
+ break;
+ }
+ bit <<= 1;
+ x >>= 1;
+ }
+
+ /* If old policy, then reset new perms to orig. */
+ if (!selinux_policycap_key_perms()) {
+ if (perm & KEY_NEED_INVAL) {
+ key_perm &= ~KEY__INVAL;
+ key_perm |= KEY__SEARCH;
+ }
+ if (perm & KEY_NEED_REVOKE && !(perm &
OLD_KEY_NEED_SETATTR)) {
+ key_perm &= ~KEY__REVOKE;
+ key_perm |= KEY__WRITE;
+ }
+ if (perm & KEY_NEED_JOIN) {
+ key_perm &= ~KEY__JOIN;
+ key_perm |= KEY__SEARCH;
+ }
+ if (perm & KEY_NEED_CLEAR) {
+ key_perm &= ~KEY__CLEAR;
+ key_perm |= KEY__WRITE;
+ }
+ }
sid = cred_sid(cred);
@@ -6530,7 +6580,7 @@ static int selinux_key_permission(key_ref_t
key_ref,
ksec = key->security;
return avc_has_perm(&selinux_state,
- sid, ksec->sid, SECCLASS_KEY,
oldstyle_perm, NULL);
+ sid, ksec->sid, SECCLASS_KEY, key_perm,
NULL);
}
static int selinux_key_getsecurity(struct key *key, char **_buffer)
@@ -6555,7 +6605,7 @@ static int selinux_watch_key(struct key *key)
u32 sid = current_sid();
return avc_has_perm(&selinux_state,
- sid, ksec->sid, SECCLASS_KEY,
KEY_NEED_VIEW, NULL);
+ sid, ksec->sid, SECCLASS_KEY, KEY__VIEW,
NULL);
}
#endif
#endif
diff --git a/security/selinux/include/classmap.h
b/security/selinux/include/classmap.h
index 201f7e588..a51ab9bd9 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -158,7 +158,7 @@ struct security_class_mapping secclass_map[] = {
{ "send", "recv", "relabelto", "forward_in", "forward_out",
NULL } },
{ "key",
{ "view", "read", "write", "search", "link", "setattr",
"create",
- NULL } },
+ "inval", "revoke", "join", "clear", NULL } },
{ "dccp_socket",
{ COMMON_SOCK_PERMS,
"node_bind", "name_connect", NULL } },
diff --git a/security/selinux/include/security.h
b/security/selinux/include/security.h
index 111121281..a248eef75 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -78,6 +78,7 @@ enum {
POLICYDB_CAPABILITY_ALWAYSNETWORK,
POLICYDB_CAPABILITY_CGROUPSECLABEL,
POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
+ POLICYDB_CAPABILITY_KEYPERMS,
__POLICYDB_CAPABILITY_MAX
};
#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
@@ -177,6 +178,13 @@ static inline bool
selinux_policycap_nnp_nosuid_transition(void)
return state-
policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION];
}
+static inline bool selinux_policycap_key_perms(void)
+{
+ struct selinux_state *state = &selinux_state;
+
+ return state->policycap[POLICYDB_CAPABILITY_KEYPERMS];
+}
+
int security_mls_enabled(struct selinux_state *state);
int security_load_policy(struct selinux_state *state,
void *data, size_t len);
diff --git a/security/selinux/ss/services.c
b/security/selinux/ss/services.c
index d61563a36..eb3949fc8 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -73,7 +73,8 @@ const char
*selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
"extended_socket_class",
"always_check_network",
"cgroup_seclabel",
- "nnp_nosuid_transition"
+ "nnp_nosuid_transition",
+ "key_perms"
};
static struct selinux_ss selinux_ss;