diff mbox series

[RFC] genfscon wildcard support for faster sysfs labeling

Message ID CAH9xa6dmxzcooYYya5kH=KwfhhKUJSq9LYVKiwxj1sxsDB3h-w@mail.gmail.com (mailing list archive)
State Superseded
Delegated to: Paul Moore
Headers show
Series [RFC] genfscon wildcard support for faster sysfs labeling | expand

Commit Message

Takaya Saeki Dec. 5, 2024, 8:53 a.m. UTC
Hello SELinux developers,

I propose wildcard match support in genfscon to improve sysfs labeling
performance, and I would like to hear your opinions on it.

Currently, labeling numerous dynamic sysfs entries (e.g.,
`/sys/devices/pci0000:00/0000:00:03.1/<...>/0000:04:00.1/wakeup`)
requires either listing all specific PCI paths in genfscon entries,
which are hard to maintain, or using slow regex rules in file_contexts
with restorecon. On our test device, `restorecon -R /sys` takes 2.7
seconds with regular expression rules.

Wildcard matching offers a good balance here. The patch below allows us
to avoid the slowdowns of regex while keeping genfscon maintainable for
diverse hardware.

This requires defining precedence when multiple wildcards match. I
suggest prioritizing longer matches, which is the existing behavior. For
example, given the rules `/sys/devices/*/wakeup` and
`/sys/devices/*/wakeup/*/uevent` (note `*` also matches `/`), the path
`/devices/LNXSYSTM:00/PNP0C14:01/wakeup/wakeup57/uevent` would match
both, but the second rule would be applied. Users can control the
precedence by writing concrete parent directories.

There are also cases where multiple rules of the same length match
against a path. To remove this ambiguity, we can document the current
behavior that the first matching rule in the genfscon file takes
precedence. Users should not rely on this rule but should specify paths
that are concrete enough, though.

I'd appreciate your feedback.

Signed-off-by: Takaya Saeki <takayas@chromium.org>
---
 security/selinux/include/policycap.h       |  1 +
 security/selinux/include/policycap_names.h |  1 +
 security/selinux/include/security.h        |  6 +++
 security/selinux/ss/policydb.c             | 56 ++++++++++++++++++----
 security/selinux/ss/services.c             | 13 +++--
 5 files changed, 66 insertions(+), 11 deletions(-)

  if (!c)

Comments

Christian Göttsche Dec. 5, 2024, 9:53 a.m. UTC | #1
Dec 5, 2024 09:53:33 Takaya Saeki <takayas@chromium.org>:

> Hello SELinux developers,
>
> I propose wildcard match support in genfscon to improve sysfs labeling
> performance, and I would like to hear your opinions on it.
>
> Currently, labeling numerous dynamic sysfs entries (e.g.,
> `/sys/devices/pci0000:00/0000:00:03.1/<...>/0000:04:00.1/wakeup`)
> requires either listing all specific PCI paths in genfscon entries,
> which are hard to maintain, or using slow regex rules in file_contexts
> with restorecon. On our test device, `restorecon -R /sys` takes 2.7
> seconds with regular expression rules.

Out of curiosity: can you give libselinux 3.8-rc1 a try, which might/should improve the runtime?

> Wildcard matching offers a good balance here. The patch below allows us
> to avoid the slowdowns of regex while keeping genfscon maintainable for
> diverse hardware.
>
> This requires defining precedence when multiple wildcards match. I
> suggest prioritizing longer matches, which is the existing behavior. For
> example, given the rules `/sys/devices/*/wakeup` and
> `/sys/devices/*/wakeup/*/uevent` (note `*` also matches `/`), the path
> `/devices/LNXSYSTM:00/PNP0C14:01/wakeup/wakeup57/uevent` would match
> both, but the second rule would be applied. Users can control the
> precedence by writing concrete parent directories.
>
> There are also cases where multiple rules of the same length match
> against a path. To remove this ambiguity, we can document the current
> behavior that the first matching rule in the genfscon file takes
> precedence. Users should not rely on this rule but should specify paths
> that are concrete enough, though.
>
> I'd appreciate your feedback.
>
> Signed-off-by: Takaya Saeki <takayas@chromium.org>
> ---
> security/selinux/include/policycap.h       |  1 +
> security/selinux/include/policycap_names.h |  1 +
> security/selinux/include/security.h        |  6 +++
> security/selinux/ss/policydb.c             | 56 ++++++++++++++++++----
> security/selinux/ss/services.c             | 13 +++--
> 5 files changed, 66 insertions(+), 11 deletions(-)
>
> diff --git a/security/selinux/include/policycap.h
> b/security/selinux/include/policycap.h
> index 079679fe7254..2b4014a826f0 100644
> --- a/security/selinux/include/policycap.h
> +++ b/security/selinux/include/policycap.h
> @@ -15,6 +15,7 @@ enum {
>   POLICYDB_CAP_IOCTL_SKIP_CLOEXEC,
>   POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT,
>   POLICYDB_CAP_NETLINK_XPERM,
> + POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
>   __POLICYDB_CAP_MAX
> };
> #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
> diff --git a/security/selinux/include/policycap_names.h
> b/security/selinux/include/policycap_names.h
> index e080827408c4..17e5c51f3cf4 100644
> --- a/security/selinux/include/policycap_names.h
> +++ b/security/selinux/include/policycap_names.h
> @@ -18,6 +18,7 @@ const char *const
> selinux_policycap_names[__POLICYDB_CAP_MAX] = {
>   "ioctl_skip_cloexec",
>   "userspace_initial_context",
>   "netlink_xperm",
> + "genfs_wildcard",
> };
> /* clang-format on */
>
> diff --git a/security/selinux/include/security.h
> b/security/selinux/include/security.h
> index c7f2731abd03..ccd80fb037d5 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -201,6 +201,12 @@ static inline bool selinux_policycap_netlink_xperm(void)
>   selinux_state.policycap[POLICYDB_CAP_NETLINK_XPERM]);
> }
>
> +static inline bool selinux_policycap_genfs_seclabel_wildcard(void)
> +{
> + return READ_ONCE(
> + selinux_state.policycap[POLICYDB_CAP_GENFS_SECLABEL_WILDCARD]);
> +}
> +
> struct selinux_policy_convert_data;
>
> struct selinux_load_state {
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 383f3ae82a73..959e98fae3d5 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -1090,29 +1090,59 @@ static int context_read_and_validate(struct
> context *c, struct policydb *p,
>   * binary representation file.
>   */
>
> -static int str_read(char **strp, gfp_t flags, void *fp, u32 len)
> +static int entry_read(char **bufp, gfp_t flags, void *fp, u32 entry_len,
> +       u32 buf_len)
> {
>   int rc;
> - char *str;
> + char *buf;
>
> - if ((len == 0) || (len == (u32)-1))
> + if ((buf_len == 0) || (buf_len == (u32)-1) || (buf_len < entry_len))
>   return -EINVAL;
>
> - str = kmalloc(len + 1, flags | __GFP_NOWARN);
> - if (!str)
> + buf = kmalloc(buf_len, flags | __GFP_NOWARN);
> + if (!buf)
>   return -ENOMEM;
>
> - rc = next_entry(str, fp, len);
> + rc = next_entry(buf, fp, entry_len);
>   if (rc) {
> - kfree(str);
> + kfree(buf);
>   return rc;
>   }
>
> + *bufp = buf;
> + return 0;
> +}
> +
> +static int str_read(char **strp, gfp_t flags, void *fp, u32 len)
> +{
> + int rc;
> + char *str;
> +
> + rc = entry_read(&str, flags, fp, len, len + 1);
> + if (rc)
> + return rc;
> +
>   str[len] = '\0';
>   *strp = str;
>   return 0;
> }
>
> +static int str_read_with_padding(char **strp, gfp_t flags, void *fp, u32 len,
> + char padding)
> +{
> + int rc;
> + char *str;
> +
> + rc = entry_read(&str, flags, fp, len, len + 2);
> + if (rc)
> + return rc;
> +
> + str[len] = padding;
> + str[len + 1] = '\0';
> + *strp = str;
> + return 0;
> +}
> +
> static int perm_read(struct policydb *p, struct symtab *s, void *fp)
> {
>   char *key = NULL;
> @@ -2193,7 +2223,17 @@ static int genfs_read(struct policydb *p, void *fp)
>   if (!newc)
>   goto out;
>
> - rc = str_read(&newc->u.name, GFP_KERNEL, fp, len);
> + if (ebitmap_get_bit(
> +     &p->policycaps,
> +     POLICYDB_CAP_GENFS_SECLABEL_WILDCARD))
> + /* Append a wildcard '*' to make it a wildcard pattern */
> + rc = str_read_with_padding(&newc->u.name,
> +    GFP_KERNEL, fp, len,
> +    '*');
> + else
> + /* Prefix pattern */
> + rc = str_read(&newc->u.name, GFP_KERNEL, fp,
> +       len);
>   if (rc)
>   goto out;
>
> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 971c45d576ba..da4d22220fe8 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -48,6 +48,7 @@
> #include <linux/audit.h>
> #include <linux/vmalloc.h>
> #include <linux/lsm_hooks.h>
> +#include <linux/parser.h>
> #include <net/netlabel.h>
>
> #include "flask.h"
> @@ -2861,9 +2862,15 @@ static inline int __security_genfs_sid(struct
> selinux_policy *policy,
>
>   for (c = genfs->head; c; c = c->next) {
>   size_t len = strlen(c->u.name);
> - if ((!c->v.sclass || sclass == c->v.sclass) &&
> -     (strncmp(c->u.name, path, len) == 0))
> - break;
> + if (selinux_policycap_genfs_seclabel_wildcard()) {
> + if ((!c->v.sclass || sclass == c->v.sclass) &&
> +     (match_wildcard(c->u.name, path)))
> + break;
> + } else {
> + if ((!c->v.sclass || sclass == c->v.sclass) &&
> +     (strncmp(c->u.name, path, len)))
> + break;
> + }
>   }
>
>   if (!c)
> --
> 2.47.0.338.g60cca15819-goog
Takaya Saeki Dec. 6, 2024, 1:12 p.m. UTC | #2
> Out of curiosity: can you give libselinux 3.8-rc1 a try, which might/should
> improve the runtime?

Yes, we are excited to see the latest rework on the file_label structure.
However, we have a few hundreds of non-trivial regular expression rules instead
of literal rules. So, the latest rework is still not enough for us. By the way,
I found a bug in the latest libselinux which breaks our existing rules. I'll
share it in another thread.

In addition, it's not enough even if restorecon is improved from 2.7 seconds to
a few hundred milliseconds, which is the time of `restorecon -R /sys` in a
clean Debian with the latest libselinux. On Android, restorecon runs for `/sys`
when a device wakes up. Spending a few hundred milliseconds CPU time every time
hurts the battery life a lot. Thus, we want to eliminate this overhead entirely
by genfscon. Actually, we have another PoC to further improve the restorecon
performance, but for the reason above we want to improve genfscon instead.
Takaya Saeki Dec. 10, 2024, 10:16 a.m. UTC | #3
Hi Christian,

I'm sorry for being late, but I reported the incompatible behavior I mentioned
in
https://lore.kernel.org/selinux/CAH9xa6eFO6BNeGko90bsq8CuDba9eO+qdDoF+7zfyAUHEDpH9g@mail.gmail.com/T/#u

As for my RFC of this thread, I'll turn this into a decent patch instead of a
RFC, and send it to reviewers for further discussion.

Thanks,
Takaya
diff mbox series

Patch

diff --git a/security/selinux/include/policycap.h
b/security/selinux/include/policycap.h
index 079679fe7254..2b4014a826f0 100644
--- a/security/selinux/include/policycap.h
+++ b/security/selinux/include/policycap.h
@@ -15,6 +15,7 @@  enum {
  POLICYDB_CAP_IOCTL_SKIP_CLOEXEC,
  POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT,
  POLICYDB_CAP_NETLINK_XPERM,
+ POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
  __POLICYDB_CAP_MAX
 };
 #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
diff --git a/security/selinux/include/policycap_names.h
b/security/selinux/include/policycap_names.h
index e080827408c4..17e5c51f3cf4 100644
--- a/security/selinux/include/policycap_names.h
+++ b/security/selinux/include/policycap_names.h
@@ -18,6 +18,7 @@  const char *const
selinux_policycap_names[__POLICYDB_CAP_MAX] = {
  "ioctl_skip_cloexec",
  "userspace_initial_context",
  "netlink_xperm",
+ "genfs_wildcard",
 };
 /* clang-format on */

diff --git a/security/selinux/include/security.h
b/security/selinux/include/security.h
index c7f2731abd03..ccd80fb037d5 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -201,6 +201,12 @@  static inline bool selinux_policycap_netlink_xperm(void)
  selinux_state.policycap[POLICYDB_CAP_NETLINK_XPERM]);
 }

+static inline bool selinux_policycap_genfs_seclabel_wildcard(void)
+{
+ return READ_ONCE(
+ selinux_state.policycap[POLICYDB_CAP_GENFS_SECLABEL_WILDCARD]);
+}
+
 struct selinux_policy_convert_data;

 struct selinux_load_state {
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 383f3ae82a73..959e98fae3d5 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -1090,29 +1090,59 @@  static int context_read_and_validate(struct
context *c, struct policydb *p,
  * binary representation file.
  */

-static int str_read(char **strp, gfp_t flags, void *fp, u32 len)
+static int entry_read(char **bufp, gfp_t flags, void *fp, u32 entry_len,
+       u32 buf_len)
 {
  int rc;
- char *str;
+ char *buf;

- if ((len == 0) || (len == (u32)-1))
+ if ((buf_len == 0) || (buf_len == (u32)-1) || (buf_len < entry_len))
  return -EINVAL;

- str = kmalloc(len + 1, flags | __GFP_NOWARN);
- if (!str)
+ buf = kmalloc(buf_len, flags | __GFP_NOWARN);
+ if (!buf)
  return -ENOMEM;

- rc = next_entry(str, fp, len);
+ rc = next_entry(buf, fp, entry_len);
  if (rc) {
- kfree(str);
+ kfree(buf);
  return rc;
  }

+ *bufp = buf;
+ return 0;
+}
+
+static int str_read(char **strp, gfp_t flags, void *fp, u32 len)
+{
+ int rc;
+ char *str;
+
+ rc = entry_read(&str, flags, fp, len, len + 1);
+ if (rc)
+ return rc;
+
  str[len] = '\0';
  *strp = str;
  return 0;
 }

+static int str_read_with_padding(char **strp, gfp_t flags, void *fp, u32 len,
+ char padding)
+{
+ int rc;
+ char *str;
+
+ rc = entry_read(&str, flags, fp, len, len + 2);
+ if (rc)
+ return rc;
+
+ str[len] = padding;
+ str[len + 1] = '\0';
+ *strp = str;
+ return 0;
+}
+
 static int perm_read(struct policydb *p, struct symtab *s, void *fp)
 {
  char *key = NULL;
@@ -2193,7 +2223,17 @@  static int genfs_read(struct policydb *p, void *fp)
  if (!newc)
  goto out;

- rc = str_read(&newc->u.name, GFP_KERNEL, fp, len);
+ if (ebitmap_get_bit(
+     &p->policycaps,
+     POLICYDB_CAP_GENFS_SECLABEL_WILDCARD))
+ /* Append a wildcard '*' to make it a wildcard pattern */
+ rc = str_read_with_padding(&newc->u.name,
+    GFP_KERNEL, fp, len,
+    '*');
+ else
+ /* Prefix pattern */
+ rc = str_read(&newc->u.name, GFP_KERNEL, fp,
+       len);
  if (rc)
  goto out;

diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 971c45d576ba..da4d22220fe8 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -48,6 +48,7 @@ 
 #include <linux/audit.h>
 #include <linux/vmalloc.h>
 #include <linux/lsm_hooks.h>
+#include <linux/parser.h>
 #include <net/netlabel.h>

 #include "flask.h"
@@ -2861,9 +2862,15 @@  static inline int __security_genfs_sid(struct
selinux_policy *policy,

  for (c = genfs->head; c; c = c->next) {
  size_t len = strlen(c->u.name);
- if ((!c->v.sclass || sclass == c->v.sclass) &&
-     (strncmp(c->u.name, path, len) == 0))
- break;
+ if (selinux_policycap_genfs_seclabel_wildcard()) {
+ if ((!c->v.sclass || sclass == c->v.sclass) &&
+     (match_wildcard(c->u.name, path)))
+ break;
+ } else {
+ if ((!c->v.sclass || sclass == c->v.sclass) &&
+     (strncmp(c->u.name, path, len)))
+ break;
+ }
  }