2019-12-20 14:05:37

by Aleksa Sarai

[permalink] [raw]
Subject: [PATCH v2 0/2] openat2: minor uapi cleanups

Patch changelog:
v2:
* Add include <linux/types.h> to openat2.h. [Florian Weimer]
* Move OPEN_HOW_SIZE_* constants out of UAPI. [Florian Weimer]
* Switch from __aligned_u64 to __u64 since it isn't necessary.
[David Laight]
v1: <https://lore.kernel.org/lkml/[email protected]/>

While openat2(2) is still not yet in Linus's tree, we can take this
opportunity to iron out some small warts that weren't noticed earlier:

* A fix was suggested by Florian Weimer, to separate the openat2
definitions so glibc can use the header directly. I've put the
maintainership under VFS but let me know if you'd prefer it belong
ot the fcntl folks.

* Having heterogenous field sizes in an extensible struct results in
"padding hole" problems when adding new fields (in addition the
correct error to use for non-zero padding isn't entirely clear ).
The simplest solution is to just copy clone(3)'s model -- always use
u64s. It will waste a little more space in the struct, but it
removes a possible future headache.

Aleksa Sarai (2):
openat2: drop open_how->__padding field
uapi: split openat2(2) definitions from fcntl.h

MAINTAINERS | 1 +
fs/open.c | 2 -
include/linux/fcntl.h | 4 ++
include/uapi/linux/fcntl.h | 37 +-----------------
include/uapi/linux/openat2.h | 39 +++++++++++++++++++
tools/testing/selftests/openat2/helpers.h | 7 ++--
.../testing/selftests/openat2/openat2_test.c | 24 ++++--------
7 files changed, 56 insertions(+), 58 deletions(-)
create mode 100644 include/uapi/linux/openat2.h


base-commit: 912dfe068c43fa13c587b8d30e73d335c5ba7d44
--
2.24.0


2019-12-20 14:05:44

by Aleksa Sarai

[permalink] [raw]
Subject: [PATCH v2 1/2] openat2: drop open_how->__padding field

The purpose of explicit padding was to allow us to use the space in the
future (C provides no guarantee about the value of padding bytes and
thus userspace could've provided garbage).

However, the downside of explicit padding is that any extension we wish
to add should fit the space exactly (otherwise we may end up with a u16
which will never be used). In addition, the correct error to return for
non-zero padding is not clear (-EINVAL doesn't imply "you're using an
extension field unsupported by this kernel", but -E2BIG seems a bit odd
if the structure size isn't different).

The simplest solution is to just match the design of clone3(2) -- use
u64s for all fields. The extra few-bytes cost of extra fields is not
significant (it's unlikely configuration structs will ever be extremely
large) and it allows for more flag space if necessary. There is also no
need to align the u64s because we will not permit any padding in the
structure.

As openat2(2) is not yet in Linus's tree, we can iron out these minor
warts before we commit to this as a stable ABI.

Acked-by: Christian Brauner <[email protected]>
Suggested-by: David Laight <[email protected]>
Signed-off-by: Aleksa Sarai <[email protected]>
---
fs/open.c | 2 --
include/uapi/linux/fcntl.h | 17 +++++++------
tools/testing/selftests/openat2/helpers.h | 7 +++---
.../testing/selftests/openat2/openat2_test.c | 24 +++++++------------
4 files changed, 19 insertions(+), 31 deletions(-)

diff --git a/fs/open.c b/fs/open.c
index 50a46501bcc9..8cdb2b675867 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -993,8 +993,6 @@ static inline int build_open_flags(const struct open_how *how,
return -EINVAL;
if (how->resolve & ~VALID_RESOLVE_FLAGS)
return -EINVAL;
- if (memchr_inv(how->__padding, 0, sizeof(how->__padding)))
- return -EINVAL;

/* Deal with the mode. */
if (WILL_CREATE(flags)) {
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index d886bdb585e4..5aaadfd79dd5 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -101,22 +101,21 @@
#define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */

/*
- * Arguments for how openat2(2) should open the target path. If @resolve is
- * zero, then openat2(2) operates very similarly to openat(2).
+ * Arguments for how openat2(2) should open the target path. If only @flags and
+ * @mode are non-zero, then openat2(2) operates very similarly to openat(2).
*
- * However, unlike openat(2), unknown bits in @flags result in -EINVAL rather
- * than being silently ignored. @mode must be zero unless one of {O_CREAT,
- * O_TMPFILE} are set.
+ * However, unlike openat(2), unknown or invalid bits in @flags result in
+ * -EINVAL rather than being silently ignored. @mode must be zero unless one of
+ * {O_CREAT, O_TMPFILE} are set.
*
* @flags: O_* flags.
* @mode: O_CREAT/O_TMPFILE file mode.
* @resolve: RESOLVE_* flags.
*/
struct open_how {
- __aligned_u64 flags;
- __u16 mode;
- __u16 __padding[3]; /* must be zeroed */
- __aligned_u64 resolve;
+ __u64 flags;
+ __u64 mode;
+ __u64 resolve;
};

#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
diff --git a/tools/testing/selftests/openat2/helpers.h b/tools/testing/selftests/openat2/helpers.h
index 43ca5ceab6e3..a6ea27344db2 100644
--- a/tools/testing/selftests/openat2/helpers.h
+++ b/tools/testing/selftests/openat2/helpers.h
@@ -36,10 +36,9 @@
* @resolve: RESOLVE_* flags.
*/
struct open_how {
- __aligned_u64 flags;
- __u16 mode;
- __u16 __padding[3]; /* must be zeroed */
- __aligned_u64 resolve;
+ __u64 flags;
+ __u64 mode;
+ __u64 resolve;
};

#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
diff --git a/tools/testing/selftests/openat2/openat2_test.c b/tools/testing/selftests/openat2/openat2_test.c
index 0b64fedc008b..b386367c606b 100644
--- a/tools/testing/selftests/openat2/openat2_test.c
+++ b/tools/testing/selftests/openat2/openat2_test.c
@@ -40,7 +40,7 @@ struct struct_test {
int err;
};

-#define NUM_OPENAT2_STRUCT_TESTS 10
+#define NUM_OPENAT2_STRUCT_TESTS 7
#define NUM_OPENAT2_STRUCT_VARIATIONS 13

void test_openat2_struct(void)
@@ -57,20 +57,6 @@ void test_openat2_struct(void)
.arg.inner.flags = O_RDONLY,
.size = sizeof(struct open_how_ext) },

- /* Normal struct with broken padding. */
- { .name = "normal struct (non-zero padding[0])",
- .arg.inner.flags = O_RDONLY,
- .arg.inner.__padding = {0xa0, 0x00, 0x00},
- .size = sizeof(struct open_how_ext), .err = -EINVAL },
- { .name = "normal struct (non-zero padding[1])",
- .arg.inner.flags = O_RDONLY,
- .arg.inner.__padding = {0x00, 0x1a, 0x00},
- .size = sizeof(struct open_how_ext), .err = -EINVAL },
- { .name = "normal struct (non-zero padding[2])",
- .arg.inner.flags = O_RDONLY,
- .arg.inner.__padding = {0x00, 0x00, 0xef},
- .size = sizeof(struct open_how_ext), .err = -EINVAL },
-
/* TODO: Once expanded, check zero-padding. */

/* Smaller than version-0 struct. */
@@ -169,7 +155,7 @@ struct flag_test {
int err;
};

-#define NUM_OPENAT2_FLAG_TESTS 21
+#define NUM_OPENAT2_FLAG_TESTS 23

void test_openat2_flags(void)
{
@@ -214,9 +200,15 @@ void test_openat2_flags(void)
{ .name = "invalid how.mode and O_CREAT",
.how.flags = O_CREAT,
.how.mode = 0xFFFF, .err = -EINVAL },
+ { .name = "invalid (very large) how.mode and O_CREAT",
+ .how.flags = O_CREAT,
+ .how.mode = 0xC000000000000000ULL, .err = -EINVAL },
{ .name = "invalid how.mode and O_TMPFILE",
.how.flags = O_TMPFILE | O_RDWR,
.how.mode = 0x1337, .err = -EINVAL },
+ { .name = "invalid (very large) how.mode and O_TMPFILE",
+ .how.flags = O_TMPFILE | O_RDWR,
+ .how.mode = 0x0000A00000000000ULL, .err = -EINVAL },

/* ->resolve must only contain RESOLVE_* flags. */
{ .name = "invalid how.resolve and O_RDONLY",
--
2.24.0

2019-12-20 14:08:20

by Aleksa Sarai

[permalink] [raw]
Subject: [PATCH v2 2/2] uapi: split openat2(2) definitions from fcntl.h

Florian mentioned that glibc doesn't use fcntl.h because it has some
issues with namespace cleanliness, and that we should have a separate
header for openat2(2) if possible. In addition, userspace has no real
use for the OPEN_HOW_SIZE_* constants so move them to the in-kernel
headers.

Suggested-by: Florian Weimer <[email protected]>
Signed-off-by: Aleksa Sarai <[email protected]>
---
MAINTAINERS | 1 +
include/linux/fcntl.h | 4 ++++
include/uapi/linux/fcntl.h | 36 +--------------------------------
include/uapi/linux/openat2.h | 39 ++++++++++++++++++++++++++++++++++++
4 files changed, 45 insertions(+), 35 deletions(-)
create mode 100644 include/uapi/linux/openat2.h

diff --git a/MAINTAINERS b/MAINTAINERS
index bd5847e802de..737ada377ac3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6397,6 +6397,7 @@ F: fs/*
F: include/linux/fs.h
F: include/linux/fs_types.h
F: include/uapi/linux/fs.h
+F: include/uapi/linux/openat2.h

FINTEK F75375S HARDWARE MONITOR AND FAN CONTROLLER DRIVER
M: Riku Voipio <[email protected]>
diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h
index f2eb05bd3af3..7bcdcf4f6ab2 100644
--- a/include/linux/fcntl.h
+++ b/include/linux/fcntl.h
@@ -21,6 +21,10 @@
(RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \
RESOLVE_BENEATH | RESOLVE_IN_ROOT)

+/* List of all open_how "versions". */
+#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
+#define OPEN_HOW_SIZE_LATEST OPEN_HOW_SIZE_VER0
+
#ifndef force_o_largefile
#define force_o_largefile() (!IS_ENABLED(CONFIG_ARCH_32BIT_OFF_T))
#endif
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 5aaadfd79dd5..ca88b7bce553 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -3,6 +3,7 @@
#define _UAPI_LINUX_FCNTL_H

#include <asm/fcntl.h>
+#include <linux/openat2.h>

#define F_SETLEASE (F_LINUX_SPECIFIC_BASE + 0)
#define F_GETLEASE (F_LINUX_SPECIFIC_BASE + 1)
@@ -100,39 +101,4 @@

#define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */

-/*
- * Arguments for how openat2(2) should open the target path. If only @flags and
- * @mode are non-zero, then openat2(2) operates very similarly to openat(2).
- *
- * However, unlike openat(2), unknown or invalid bits in @flags result in
- * -EINVAL rather than being silently ignored. @mode must be zero unless one of
- * {O_CREAT, O_TMPFILE} are set.
- *
- * @flags: O_* flags.
- * @mode: O_CREAT/O_TMPFILE file mode.
- * @resolve: RESOLVE_* flags.
- */
-struct open_how {
- __u64 flags;
- __u64 mode;
- __u64 resolve;
-};
-
-#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
-#define OPEN_HOW_SIZE_LATEST OPEN_HOW_SIZE_VER0
-
-/* how->resolve flags for openat2(2). */
-#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings
- (includes bind-mounts). */
-#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style
- "magic-links". */
-#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks
- (implies OEXT_NO_MAGICLINKS) */
-#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like
- "..", symlinks, and absolute
- paths which escape the dirfd. */
-#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".."
- be scoped inside the dirfd
- (similar to chroot(2)). */
-
#endif /* _UAPI_LINUX_FCNTL_H */
diff --git a/include/uapi/linux/openat2.h b/include/uapi/linux/openat2.h
new file mode 100644
index 000000000000..58b1eb711360
--- /dev/null
+++ b/include/uapi/linux/openat2.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_OPENAT2_H
+#define _UAPI_LINUX_OPENAT2_H
+
+#include <linux/types.h>
+
+/*
+ * Arguments for how openat2(2) should open the target path. If only @flags and
+ * @mode are non-zero, then openat2(2) operates very similarly to openat(2).
+ *
+ * However, unlike openat(2), unknown or invalid bits in @flags result in
+ * -EINVAL rather than being silently ignored. @mode must be zero unless one of
+ * {O_CREAT, O_TMPFILE} are set.
+ *
+ * @flags: O_* flags.
+ * @mode: O_CREAT/O_TMPFILE file mode.
+ * @resolve: RESOLVE_* flags.
+ */
+struct open_how {
+ __u64 flags;
+ __u64 mode;
+ __u64 resolve;
+};
+
+/* how->resolve flags for openat2(2). */
+#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings
+ (includes bind-mounts). */
+#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style
+ "magic-links". */
+#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks
+ (implies OEXT_NO_MAGICLINKS) */
+#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like
+ "..", symlinks, and absolute
+ paths which escape the dirfd. */
+#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".."
+ be scoped inside the dirfd
+ (similar to chroot(2)). */
+
+#endif /* _UAPI_LINUX_OPENAT2_H */
--
2.24.0

2019-12-20 14:36:16

by Christian Brauner

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] openat2: drop open_how->__padding field

[Cc Arnd for struct layout sanity checking]

On December 20, 2019 3:03:27 PM GMT+01:00, Aleksa Sarai <[email protected]> wrote:
>The purpose of explicit padding was to allow us to use the space in the
>future (C provides no guarantee about the value of padding bytes and
>thus userspace could've provided garbage).
>
>However, the downside of explicit padding is that any extension we wish
>to add should fit the space exactly (otherwise we may end up with a u16
>which will never be used). In addition, the correct error to return for
>non-zero padding is not clear (-EINVAL doesn't imply "you're using an
>extension field unsupported by this kernel", but -E2BIG seems a bit odd
>if the structure size isn't different).
>
>The simplest solution is to just match the design of clone3(2) -- use
>u64s for all fields. The extra few-bytes cost of extra fields is not
>significant (it's unlikely configuration structs will ever be extremely
>large) and it allows for more flag space if necessary. There is also no
>need to align the u64s because we will not permit any padding in the
>structure.
>
>As openat2(2) is not yet in Linus's tree, we can iron out these minor
>warts before we commit to this as a stable ABI.
>
>Acked-by: Christian Brauner <[email protected]>
>Suggested-by: David Laight <[email protected]>
>Signed-off-by: Aleksa Sarai <[email protected]>
>---
> fs/open.c | 2 --
> include/uapi/linux/fcntl.h | 17 +++++++------
> tools/testing/selftests/openat2/helpers.h | 7 +++---
> .../testing/selftests/openat2/openat2_test.c | 24 +++++++------------
> 4 files changed, 19 insertions(+), 31 deletions(-)
>
>diff --git a/fs/open.c b/fs/open.c
>index 50a46501bcc9..8cdb2b675867 100644
>--- a/fs/open.c
>+++ b/fs/open.c
>@@ -993,8 +993,6 @@ static inline int build_open_flags(const struct
>open_how *how,
> return -EINVAL;
> if (how->resolve & ~VALID_RESOLVE_FLAGS)
> return -EINVAL;
>- if (memchr_inv(how->__padding, 0, sizeof(how->__padding)))
>- return -EINVAL;
>
> /* Deal with the mode. */
> if (WILL_CREATE(flags)) {
>diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
>index d886bdb585e4..5aaadfd79dd5 100644
>--- a/include/uapi/linux/fcntl.h
>+++ b/include/uapi/linux/fcntl.h
>@@ -101,22 +101,21 @@
> #define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */
>
> /*
>- * Arguments for how openat2(2) should open the target path. If
>@resolve is
>- * zero, then openat2(2) operates very similarly to openat(2).
>+ * Arguments for how openat2(2) should open the target path. If only
>@flags and
>+ * @mode are non-zero, then openat2(2) operates very similarly to
>openat(2).
> *
>- * However, unlike openat(2), unknown bits in @flags result in -EINVAL
>rather
>- * than being silently ignored. @mode must be zero unless one of
>{O_CREAT,
>- * O_TMPFILE} are set.
>+ * However, unlike openat(2), unknown or invalid bits in @flags result
>in
>+ * -EINVAL rather than being silently ignored. @mode must be zero
>unless one of
>+ * {O_CREAT, O_TMPFILE} are set.
> *
> * @flags: O_* flags.
> * @mode: O_CREAT/O_TMPFILE file mode.
> * @resolve: RESOLVE_* flags.
> */
> struct open_how {
>- __aligned_u64 flags;
>- __u16 mode;
>- __u16 __padding[3]; /* must be zeroed */
>- __aligned_u64 resolve;
>+ __u64 flags;
>+ __u64 mode;
>+ __u64 resolve;
> };
>
> #define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
>diff --git a/tools/testing/selftests/openat2/helpers.h
>b/tools/testing/selftests/openat2/helpers.h
>index 43ca5ceab6e3..a6ea27344db2 100644
>--- a/tools/testing/selftests/openat2/helpers.h
>+++ b/tools/testing/selftests/openat2/helpers.h
>@@ -36,10 +36,9 @@
> * @resolve: RESOLVE_* flags.
> */
> struct open_how {
>- __aligned_u64 flags;
>- __u16 mode;
>- __u16 __padding[3]; /* must be zeroed */
>- __aligned_u64 resolve;
>+ __u64 flags;
>+ __u64 mode;
>+ __u64 resolve;
> };
>
> #define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
>diff --git a/tools/testing/selftests/openat2/openat2_test.c
>b/tools/testing/selftests/openat2/openat2_test.c
>index 0b64fedc008b..b386367c606b 100644
>--- a/tools/testing/selftests/openat2/openat2_test.c
>+++ b/tools/testing/selftests/openat2/openat2_test.c
>@@ -40,7 +40,7 @@ struct struct_test {
> int err;
> };
>
>-#define NUM_OPENAT2_STRUCT_TESTS 10
>+#define NUM_OPENAT2_STRUCT_TESTS 7
> #define NUM_OPENAT2_STRUCT_VARIATIONS 13
>
> void test_openat2_struct(void)
>@@ -57,20 +57,6 @@ void test_openat2_struct(void)
> .arg.inner.flags = O_RDONLY,
> .size = sizeof(struct open_how_ext) },
>
>- /* Normal struct with broken padding. */
>- { .name = "normal struct (non-zero padding[0])",
>- .arg.inner.flags = O_RDONLY,
>- .arg.inner.__padding = {0xa0, 0x00, 0x00},
>- .size = sizeof(struct open_how_ext), .err = -EINVAL },
>- { .name = "normal struct (non-zero padding[1])",
>- .arg.inner.flags = O_RDONLY,
>- .arg.inner.__padding = {0x00, 0x1a, 0x00},
>- .size = sizeof(struct open_how_ext), .err = -EINVAL },
>- { .name = "normal struct (non-zero padding[2])",
>- .arg.inner.flags = O_RDONLY,
>- .arg.inner.__padding = {0x00, 0x00, 0xef},
>- .size = sizeof(struct open_how_ext), .err = -EINVAL },
>-
> /* TODO: Once expanded, check zero-padding. */
>
> /* Smaller than version-0 struct. */
>@@ -169,7 +155,7 @@ struct flag_test {
> int err;
> };
>
>-#define NUM_OPENAT2_FLAG_TESTS 21
>+#define NUM_OPENAT2_FLAG_TESTS 23
>
> void test_openat2_flags(void)
> {
>@@ -214,9 +200,15 @@ void test_openat2_flags(void)
> { .name = "invalid how.mode and O_CREAT",
> .how.flags = O_CREAT,
> .how.mode = 0xFFFF, .err = -EINVAL },
>+ { .name = "invalid (very large) how.mode and O_CREAT",
>+ .how.flags = O_CREAT,
>+ .how.mode = 0xC000000000000000ULL, .err = -EINVAL },
> { .name = "invalid how.mode and O_TMPFILE",
> .how.flags = O_TMPFILE | O_RDWR,
> .how.mode = 0x1337, .err = -EINVAL },
>+ { .name = "invalid (very large) how.mode and O_TMPFILE",
>+ .how.flags = O_TMPFILE | O_RDWR,
>+ .how.mode = 0x0000A00000000000ULL, .err = -EINVAL },
>
> /* ->resolve must only contain RESOLVE_* flags. */
> { .name = "invalid how.resolve and O_RDONLY",