userns: improve uid/gid map collision detection

Initial implementation of the uid/gid maps (/proc/<pid>/{u,g}id_map) will
enforce that the UID and GID maps be written in strict order as a simple
way to check for range collision:
        local id        mapped to       count/range
        0               1000            50
        (ids 0-50 get mapped to 1000-1050)
        100             2120            10
        500             5000            200
so for each new entry, local id must be bigger than last local id (plus
count) and the ids it maps to also needs to be bigger than the last
entry (plus count).

This makes impossible to have a use case like this:
        local id        mapped to       count/range
        0               1000            1
        48              500             20

because while 48+20 > 0+1, 500+20 < 1000+1.

This patch implements a more elaborate collision detection allowing any
order to be used.

v2: improved the patch description as requested by Andrew

Cc: Andrew Morton <a...@linux-foundation.org>
Cc: "Eric W. Biederman" <ebied...@xmission.com>
Cc: "Serge E. Hallyn" <se...@hallyn.com>
Cc: linux-security-mod...@vger.kernel.org
Signed-off-by: Aristeu Rozanski <a...@redhat.com>

diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 2b042c4..fb0e492 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -521,6 +521,28 @@ struct seq_operations proc_projid_seq_operations = {
 
 static DEFINE_MUTEX(id_map_mutex);
 
+#define in_range(b,first,len) ((b)>=(first)&&(b)<(first)+(len))
+static inline int extent_collision(struct uid_gid_map *new_map,
+                                  struct uid_gid_extent *extent)
+{
+       int i;
+       struct uid_gid_extent *cur;
+
+       for (i = 0; i < new_map->nr_extents; i++) {
+               cur = &new_map->extent[i];
+               if (in_range(extent->first, cur->first, cur->count) ||
+                   in_range(extent->first + extent->count, cur->first,
+                            cur->count))
+                       return 1;
+               if (in_range(extent->lower_first, cur->lower_first,
+                            cur->count) ||
+                   in_range(extent->lower_first + extent->count,
+                            cur->lower_first, cur->count))
+                       return 1;
+       }
+       return 0;
+}
+
 static ssize_t map_write(struct file *file, const char __user *buf,
                         size_t count, loff_t *ppos,
                         int cap_setid,
@@ -634,10 +656,7 @@ static ssize_t map_write(struct file *file, const char 
__user *buf,
                if ((extent->lower_first + extent->count) <= 
extent->lower_first)
                        goto out;
 
-               /* For now only accept extents that are strictly in order */
-               if (last &&
-                   (((last->first + last->count) > extent->first) ||
-                    ((last->lower_first + last->count) > extent->lower_first)))
+               if (extent_collision(&new_map, extent))
                        goto out;
 
                new_map.nr_extents++;
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to