ve_devmnt_check() kstrdup()s the option string and then walks it with
strsep(&tmp_options, ","). strsep() advances the pointer it is given past
each returned token, so on the mismatch (-EPERM) path

        kfree(tmp_options);

frees a pointer that points into the *middle* of the kstrdup() allocation
(or is NULL if the rejected token had no following separator). SLUB's
non-debug kfree() does no object-boundary check: it derives the cache from
the page and links the interior address onto the freelist as if it were a
valid object, corrupting the kmalloc-16 slab. A subsequent kmalloc() of
that size then hands out an interior pointer; the corruption typically
surfaces far away, e.g. as

        usercopy: Kernel memory overwrite attempt detected to SLUB object
        'kmalloc-16' (offset 11, size 15)!  kernel BUG at mm/usercopy.c:102

in an unrelated kernfs write. KASAN pinpoints it directly:

        BUG: KASAN: invalid-free in ve_devmnt_check+0x135
          kfree <- ve_devmnt_check <- ve_devmnt_process
        Allocated by task N: kstrdup <- ve_devmnt_check

This is reachable by any container root via mount(2)/fsconfig() with mount
data of the form "allowedopt,disallowedopt" for a device that has a
ve.mount_opts entry, so an unprivileged CT can corrupt host kernel slab.

Walk a throwaway cursor and always kfree() the original allocation. This
also fixes a leak on the success path, where strsep() leaves tmp_options
NULL and the old code did kfree(NULL), leaking the buffer on every allowed
container mount.

Fixes: 263467c864c5 ("ve/fs/devmnt: process mount options")
Feature: fs/mnt: hidden and allowed mount options in CT
https://virtuozzo.atlassian.net/browse/VSTOR-137234
Signed-off-by: Konstantin Khorenko <[email protected]>
---
 fs/namespace.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 566f11a222fc..c04b9f05f79f 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3140,7 +3140,7 @@ static char *strstr_separated(char *haystack, char 
*needle, char sep)
 static int ve_devmnt_check(char *options, char *allowed)
 {
        char *p;
-       char *tmp_options;
+       char *tmp_options, *tmp;
 
        if (!options || !*options)
                return 0;
@@ -3153,7 +3153,16 @@ static int ve_devmnt_check(char *options, char *allowed)
        if (!tmp_options)
                return -ENOMEM;
 
-       while ((p = strsep(&tmp_options, ",")) != NULL) {
+       /*
+        * strsep() advances the pointer it is given past each token, so walk a
+        * throwaway cursor and always free the original kstrdup() allocation.
+        * Freeing the advanced pointer would kfree() an address in the middle
+        * of the object (an interior/invalid free that corrupts the slab), and
+        * on the success path strsep() leaves the cursor NULL so the original
+        * buffer would leak.
+        */
+       tmp = tmp_options;
+       while ((p = strsep(&tmp, ",")) != NULL) {
                if (!*p)
                        continue;
 
-- 
2.47.1

_______________________________________________
Devel mailing list
[email protected]
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to