On 02/11/2022 19:25, Tudor Cretu wrote:
@@ -9389,13
+9561,29 @@ static void *io_mem_alloc(size_t size)
return (void *) __get_free_pages(gfp, get_order(size));
}
-static unsigned long rings_size(unsigned sq_entries, unsigned
cq_entries,
- size_t *sq_offset)
+static unsigned long rings_size(struct io_ring_ctx *ctx,
unsigned int sq_entries,
+ unsigned int cq_entries, size_t *cq_offset,
size_t *sq_offset)
{
struct io_rings *rings;
- size_t off, sq_array_size;
+ size_t off, cq_array_size, sq_array_size;
+
+ off = sizeof(*rings);
+
+#ifdef CONFIG_SMP
+ off = ALIGN(off, SMP_CACHE_BYTES);
+ if (off == 0)
+ return SIZE_MAX;
+#endif
+
+ if (cq_offset)
+ *cq_offset = off;
+
+ cq_array_size = array_size(ctx->compat ? sizeof(struct
compat_io_uring_cqe)
+ : sizeof(struct io_uring_cqe),
cq_entries);
+ if (cq_array_size == SIZE_MAX)
+ return SIZE_MAX;
- off = struct_size(rings, cqes, cq_entries);
+ off = size_add(off, cq_array_size);
if (off == SIZE_MAX)
return SIZE_MAX;
@@ -9412,7 +9600,8 @@ static unsigned long rings_size(unsigned
sq_entries, unsigned cq_entries,
if (sq_array_size == SIZE_MAX)
return SIZE_MAX;
- if (check_add_overflow(off, sq_array_size, &off))
+ off = size_add(off, sq_array_size);
+ if (off == SIZE_MAX)
Could you elaborate on why we need this change?
It's not needed, just wanted to align it with the rest of the
checks in the function and I find it more readable. I can make it
again check_add_overflow, as well with the check at "size_add(off,
cq_array_size)" if you prefer.
check_add_overflow() is already used in many places in this file,
and size_add() is not, so yes I think it's better to use
check_add_overflow(). Not that I disagree that size_add() is
clearer, but consistency first :)
return
SIZE_MAX;
return off;
@@ -9972,7 +10161,7 @@ static __cold void
io_ring_ctx_free(struct io_ring_ctx *ctx)
WARN_ON_ONCE(!list_empty(&ctx->ltimeout_list));
io_mem_free(ctx->rings);
- io_mem_free(ctx->sq_sqes);
+ io_mem_free(ctx->compat ? (void
*)ctx->sq_sqes_compat : (void *)ctx->sq_sqes);
I find this sort of diff slightly frustrating as sq_sqes and
sq_sqes_compat are both pointers, simply to a different type. If
I got this correctly, you are making use of the type in two
cases, while it is irrelevant in three (like here). However it
is technically UB to refer to the inactive member of the union,
so just getting rid of this diff wouldn't be recommended.
Fortunately there is an alternative: instead of having a union
of pointers, make it a pointer to a union, e.g.:
union {
struct io_uring_cqe native;
struct compat_io_uring_cqe compat;
} *sq_sqes;
This way sq_sqes can still be viewed as a void * without change,
and the cases where the type matters have to be changed anyway.
Might as well do the same with cqes/cqes_compat to be
consistent.
I think this change would be misleading. sq_sqes is not a pointer
to an element which is either native or compat, but rather is an
array of {compat_}io_uring_sqe structs, so it's not really the
same thing. The compat member of the union would never be used. In
the compat case, I would need to cast sq_sqes to an array of
struct compat_io_uring_sqe and then get the element by index. Is
this what intended to suggest?
You are absolutely right, I somehow missed this "detail"... I don't
think it is possible to have a different approach to the union then.
That said I have realised that I was also wrong on the fact that
using the inactive member is UB: it is in C++ but not in C, where it
is explicitly supported since C99 (this is known as "type punning",
see [1]). Therefore we can have the best of both worlds: use
ctx->sq_sqes unconditionally if the actual type doesn't matter,
like here or when initialising the member, and otherwise use the
right member.
percpu_ref_exit(&ctx->refs);
free_uid(ctx->user);
[...]
@@ -11799,6 +12158,13 @@ static int __init io_uring_init(void)
BUILD_BUG_ON(sizeof(etype) != sizeof_field(stype,
ename)); \
} while (0)
+ BUILD_BUG_ON(__builtin_types_compatible_p(struct
io_uring_sqe, struct compat_io_uring_sqe));
The condition is reversed 😄
Actually, this __builtin_types_compatible_p returns 0 😅, so it
doesn't do what I expected. Copying all the layout checks for the
struct compat_io_uring_sqe doesn't feel right, so I am unsure what
to do here. Any suggestion?
Right, that makes sense in fact, as two distinct structs are
incompatible regardless of their layout (you don't want to
inadvertently allow assigning a struct to another just because the
number and types of their members happen to match).
I think you might as well copy all the checks. [2] did that for
siginfo_t and I think being explicit makes sense here, it doesn't
really hurt to bloat this function a bit.
+
+ BUILD_BUG_ON(sizeof(struct compat_io_uring_files_update)
!=
+ sizeof(struct compat_io_uring_rsrc_update));
+ BUILD_BUG_ON(sizeof(struct compat_io_uring_rsrc_update)
>
+ sizeof(struct compat_io_uring_rsrc_update2));
Nit: I'd rather have the compat checks after the native ones.
Ack
Kevin
Many thanks for the in-depth review and suggestions!
You are very welcome :)
Kevin
[1] https://en.cppreference.com/w/c/language/union
[2]
https://lkml.kernel.org/r/20210429190734.624918-3-elver@google.com