478 lines
18 KiB
ReStructuredText
478 lines
18 KiB
ReStructuredText
|
.. SPDX-License-Identifier: GPL-2.0-only
|
||
|
|
||
|
============
|
||
|
UAPI Checker
|
||
|
============
|
||
|
|
||
|
The UAPI checker (``scripts/check-uapi.sh``) is a shell script which
|
||
|
checks UAPI header files for userspace backwards-compatibility across
|
||
|
the git tree.
|
||
|
|
||
|
Options
|
||
|
=======
|
||
|
|
||
|
This section will describe the options with which ``check-uapi.sh``
|
||
|
can be run.
|
||
|
|
||
|
Usage::
|
||
|
|
||
|
check-uapi.sh [-b BASE_REF] [-p PAST_REF] [-j N] [-l ERROR_LOG] [-i] [-q] [-v]
|
||
|
|
||
|
Available options::
|
||
|
|
||
|
-b BASE_REF Base git reference to use for comparison. If unspecified or empty,
|
||
|
will use any dirty changes in tree to UAPI files. If there are no
|
||
|
dirty changes, HEAD will be used.
|
||
|
-p PAST_REF Compare BASE_REF to PAST_REF (e.g. -p v6.1). If unspecified or empty,
|
||
|
will use BASE_REF^1. Must be an ancestor of BASE_REF. Only headers
|
||
|
that exist on PAST_REF will be checked for compatibility.
|
||
|
-j JOBS Number of checks to run in parallel (default: number of CPU cores).
|
||
|
-l ERROR_LOG Write error log to file (default: no error log is generated).
|
||
|
-i Ignore ambiguous changes that may or may not break UAPI compatibility.
|
||
|
-q Quiet operation.
|
||
|
-v Verbose operation (print more information about each header being checked).
|
||
|
|
||
|
Environmental args::
|
||
|
|
||
|
ABIDIFF Custom path to abidiff binary
|
||
|
CC C compiler (default is "gcc")
|
||
|
ARCH Target architecture of C compiler (default is host arch)
|
||
|
|
||
|
Exit codes::
|
||
|
|
||
|
0) Success
|
||
|
1) ABI difference detected
|
||
|
2) Prerequisite not met
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
Basic Usage
|
||
|
-----------
|
||
|
|
||
|
First, let's try making a change to a UAPI header file that obviously
|
||
|
won't break userspace::
|
||
|
|
||
|
cat << 'EOF' | patch -l -p1
|
||
|
--- a/include/uapi/linux/acct.h
|
||
|
+++ b/include/uapi/linux/acct.h
|
||
|
@@ -21,7 +21,9 @@
|
||
|
#include <asm/param.h>
|
||
|
#include <asm/byteorder.h>
|
||
|
|
||
|
-/*
|
||
|
+#define FOO
|
||
|
+
|
||
|
+/*
|
||
|
* comp_t is a 16-bit "floating" point number with a 3-bit base 8
|
||
|
* exponent and a 13-bit fraction.
|
||
|
* comp2_t is 24-bit with 5-bit base 2 exponent and 20 bit fraction
|
||
|
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
|
||
|
EOF
|
||
|
|
||
|
Now, let's use the script to validate::
|
||
|
|
||
|
% ./scripts/check-uapi.sh
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Checking changes to UAPI headers between HEAD and dirty tree...
|
||
|
All 912 UAPI headers compatible with x86 appear to be backwards compatible
|
||
|
|
||
|
Let's add another change that *might* break userspace::
|
||
|
|
||
|
cat << 'EOF' | patch -l -p1
|
||
|
--- a/include/uapi/linux/bpf.h
|
||
|
+++ b/include/uapi/linux/bpf.h
|
||
|
@@ -74,7 +74,7 @@ struct bpf_insn {
|
||
|
__u8 dst_reg:4; /* dest register */
|
||
|
__u8 src_reg:4; /* source register */
|
||
|
__s16 off; /* signed offset */
|
||
|
- __s32 imm; /* signed immediate constant */
|
||
|
+ __u32 imm; /* unsigned immediate constant */
|
||
|
};
|
||
|
|
||
|
/* Key of an a BPF_MAP_TYPE_LPM_TRIE entry */
|
||
|
EOF
|
||
|
|
||
|
The script will catch this::
|
||
|
|
||
|
% ./scripts/check-uapi.sh
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Checking changes to UAPI headers between HEAD and dirty tree...
|
||
|
==== ABI differences detected in include/linux/bpf.h from HEAD -> dirty tree ====
|
||
|
[C] 'struct bpf_insn' changed:
|
||
|
type size hasn't changed
|
||
|
1 data member change:
|
||
|
type of '__s32 imm' changed:
|
||
|
typedef name changed from __s32 to __u32 at int-ll64.h:27:1
|
||
|
underlying type 'int' changed:
|
||
|
type name changed from 'int' to 'unsigned int'
|
||
|
type size hasn't changed
|
||
|
==================================================================================
|
||
|
|
||
|
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
|
||
|
|
||
|
In this case, the script is reporting the type change because it could
|
||
|
break a userspace program that passes in a negative number. Now, let's
|
||
|
say you know that no userspace program could possibly be using a negative
|
||
|
value in ``imm``, so changing to an unsigned type there shouldn't hurt
|
||
|
anything. You can pass the ``-i`` flag to the script to ignore changes
|
||
|
in which the userspace backwards compatibility is ambiguous::
|
||
|
|
||
|
% ./scripts/check-uapi.sh -i
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Checking changes to UAPI headers between HEAD and dirty tree...
|
||
|
All 912 UAPI headers compatible with x86 appear to be backwards compatible
|
||
|
|
||
|
Now, let's make a similar change that *will* break userspace::
|
||
|
|
||
|
cat << 'EOF' | patch -l -p1
|
||
|
--- a/include/uapi/linux/bpf.h
|
||
|
+++ b/include/uapi/linux/bpf.h
|
||
|
@@ -71,8 +71,8 @@ enum {
|
||
|
|
||
|
struct bpf_insn {
|
||
|
__u8 code; /* opcode */
|
||
|
- __u8 dst_reg:4; /* dest register */
|
||
|
__u8 src_reg:4; /* source register */
|
||
|
+ __u8 dst_reg:4; /* dest register */
|
||
|
__s16 off; /* signed offset */
|
||
|
__s32 imm; /* signed immediate constant */
|
||
|
};
|
||
|
EOF
|
||
|
|
||
|
Since we're re-ordering an existing struct member, there's no ambiguity,
|
||
|
and the script will report the breakage even if you pass ``-i``::
|
||
|
|
||
|
% ./scripts/check-uapi.sh -i
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Checking changes to UAPI headers between HEAD and dirty tree...
|
||
|
==== ABI differences detected in include/linux/bpf.h from HEAD -> dirty tree ====
|
||
|
[C] 'struct bpf_insn' changed:
|
||
|
type size hasn't changed
|
||
|
2 data member changes:
|
||
|
'__u8 dst_reg' offset changed from 8 to 12 (in bits) (by +4 bits)
|
||
|
'__u8 src_reg' offset changed from 12 to 8 (in bits) (by -4 bits)
|
||
|
==================================================================================
|
||
|
|
||
|
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
|
||
|
|
||
|
Let's commit the breaking change, then commit the innocuous change::
|
||
|
|
||
|
% git commit -m 'Breaking UAPI change' include/uapi/linux/bpf.h
|
||
|
[detached HEAD f758e574663a] Breaking UAPI change
|
||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||
|
% git commit -m 'Innocuous UAPI change' include/uapi/linux/acct.h
|
||
|
[detached HEAD 2e87df769081] Innocuous UAPI change
|
||
|
1 file changed, 3 insertions(+), 1 deletion(-)
|
||
|
|
||
|
Now, let's run the script again with no arguments::
|
||
|
|
||
|
% ./scripts/check-uapi.sh
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Installing user-facing UAPI headers from HEAD^1... OK
|
||
|
Checking changes to UAPI headers between HEAD^1 and HEAD...
|
||
|
All 912 UAPI headers compatible with x86 appear to be backwards compatible
|
||
|
|
||
|
It doesn't catch any breaking change because, by default, it only
|
||
|
compares ``HEAD`` to ``HEAD^1``. The breaking change was committed on
|
||
|
``HEAD~2``. If we wanted the search scope to go back further, we'd have to
|
||
|
use the ``-p`` option to pass a different past reference. In this case,
|
||
|
let's pass ``-p HEAD~2`` to the script so it checks UAPI changes between
|
||
|
``HEAD~2`` and ``HEAD``::
|
||
|
|
||
|
% ./scripts/check-uapi.sh -p HEAD~2
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Installing user-facing UAPI headers from HEAD~2... OK
|
||
|
Checking changes to UAPI headers between HEAD~2 and HEAD...
|
||
|
==== ABI differences detected in include/linux/bpf.h from HEAD~2 -> HEAD ====
|
||
|
[C] 'struct bpf_insn' changed:
|
||
|
type size hasn't changed
|
||
|
2 data member changes:
|
||
|
'__u8 dst_reg' offset changed from 8 to 12 (in bits) (by +4 bits)
|
||
|
'__u8 src_reg' offset changed from 12 to 8 (in bits) (by -4 bits)
|
||
|
==============================================================================
|
||
|
|
||
|
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
|
||
|
|
||
|
Alternatively, we could have also run with ``-b HEAD~``. This would set the
|
||
|
base reference to ``HEAD~`` so then the script would compare it to ``HEAD~^1``.
|
||
|
|
||
|
Architecture-specific Headers
|
||
|
-----------------------------
|
||
|
|
||
|
Consider this change::
|
||
|
|
||
|
cat << 'EOF' | patch -l -p1
|
||
|
--- a/arch/arm64/include/uapi/asm/sigcontext.h
|
||
|
+++ b/arch/arm64/include/uapi/asm/sigcontext.h
|
||
|
@@ -70,6 +70,7 @@ struct sigcontext {
|
||
|
struct _aarch64_ctx {
|
||
|
__u32 magic;
|
||
|
__u32 size;
|
||
|
+ __u32 new_var;
|
||
|
};
|
||
|
|
||
|
#define FPSIMD_MAGIC 0x46508001
|
||
|
EOF
|
||
|
|
||
|
This is a change to an arm64-specific UAPI header file. In this example, I'm
|
||
|
running the script from an x86 machine with an x86 compiler, so, by default,
|
||
|
the script only checks x86-compatible UAPI header files::
|
||
|
|
||
|
% ./scripts/check-uapi.sh
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
No changes to UAPI headers were applied between HEAD and dirty tree
|
||
|
|
||
|
With an x86 compiler, we can't check header files in ``arch/arm64``, so the
|
||
|
script doesn't even try.
|
||
|
|
||
|
If we want to check the header file, we'll have to use an arm64 compiler and
|
||
|
set ``ARCH`` accordingly::
|
||
|
|
||
|
% CC=aarch64-linux-gnu-gcc ARCH=arm64 ./scripts/check-uapi.sh
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Checking changes to UAPI headers between HEAD and dirty tree...
|
||
|
==== ABI differences detected in include/asm/sigcontext.h from HEAD -> dirty tree ====
|
||
|
[C] 'struct _aarch64_ctx' changed:
|
||
|
type size changed from 64 to 96 (in bits)
|
||
|
1 data member insertion:
|
||
|
'__u32 new_var', at offset 64 (in bits) at sigcontext.h:73:1
|
||
|
-- snip --
|
||
|
[C] 'struct zt_context' changed:
|
||
|
type size changed from 128 to 160 (in bits)
|
||
|
2 data member changes (1 filtered):
|
||
|
'__u16 nregs' offset changed from 64 to 96 (in bits) (by +32 bits)
|
||
|
'__u16 __reserved[3]' offset changed from 80 to 112 (in bits) (by +32 bits)
|
||
|
=======================================================================================
|
||
|
|
||
|
error - 1/884 UAPI headers compatible with arm64 appear _not_ to be backwards compatible
|
||
|
|
||
|
We can see with ``ARCH`` and ``CC`` set properly for the file, the ABI
|
||
|
change is reported properly. Also notice that the total number of UAPI
|
||
|
header files checked by the script changes. This is because the number
|
||
|
of headers installed for arm64 platforms is different than x86.
|
||
|
|
||
|
Cross-Dependency Breakages
|
||
|
--------------------------
|
||
|
|
||
|
Consider this change::
|
||
|
|
||
|
cat << 'EOF' | patch -l -p1
|
||
|
--- a/include/uapi/linux/types.h
|
||
|
+++ b/include/uapi/linux/types.h
|
||
|
@@ -52,7 +52,7 @@ typedef __u32 __bitwise __wsum;
|
||
|
#define __aligned_be64 __be64 __attribute__((aligned(8)))
|
||
|
#define __aligned_le64 __le64 __attribute__((aligned(8)))
|
||
|
|
||
|
-typedef unsigned __bitwise __poll_t;
|
||
|
+typedef unsigned short __bitwise __poll_t;
|
||
|
|
||
|
#endif /* __ASSEMBLY__ */
|
||
|
#endif /* _UAPI_LINUX_TYPES_H */
|
||
|
EOF
|
||
|
|
||
|
Here, we're changing a ``typedef`` in ``types.h``. This doesn't break
|
||
|
a UAPI in ``types.h``, but other UAPIs in the tree may break due to
|
||
|
this change::
|
||
|
|
||
|
% ./scripts/check-uapi.sh
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Checking changes to UAPI headers between HEAD and dirty tree...
|
||
|
==== ABI differences detected in include/linux/eventpoll.h from HEAD -> dirty tree ====
|
||
|
[C] 'struct epoll_event' changed:
|
||
|
type size changed from 96 to 80 (in bits)
|
||
|
2 data member changes:
|
||
|
type of '__poll_t events' changed:
|
||
|
underlying type 'unsigned int' changed:
|
||
|
type name changed from 'unsigned int' to 'unsigned short int'
|
||
|
type size changed from 32 to 16 (in bits)
|
||
|
'__u64 data' offset changed from 32 to 16 (in bits) (by -16 bits)
|
||
|
========================================================================================
|
||
|
include/linux/eventpoll.h did not change between HEAD and dirty tree...
|
||
|
It's possible a change to one of the headers it includes caused this error:
|
||
|
#include <linux/fcntl.h>
|
||
|
#include <linux/types.h>
|
||
|
|
||
|
Note that the script noticed the failing header file did not change,
|
||
|
so it assumes one of its includes must have caused the breakage. Indeed,
|
||
|
we can see ``linux/types.h`` is used from ``eventpoll.h``.
|
||
|
|
||
|
UAPI Header Removals
|
||
|
--------------------
|
||
|
|
||
|
Consider this change::
|
||
|
|
||
|
cat << 'EOF' | patch -l -p1
|
||
|
diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-generic/Kbuild
|
||
|
index ebb180aac74e..a9c88b0a8b3b 100644
|
||
|
--- a/include/uapi/asm-generic/Kbuild
|
||
|
+++ b/include/uapi/asm-generic/Kbuild
|
||
|
@@ -31,6 +31,6 @@ mandatory-y += stat.h
|
||
|
mandatory-y += statfs.h
|
||
|
mandatory-y += swab.h
|
||
|
mandatory-y += termbits.h
|
||
|
-mandatory-y += termios.h
|
||
|
+#mandatory-y += termios.h
|
||
|
mandatory-y += types.h
|
||
|
mandatory-y += unistd.h
|
||
|
EOF
|
||
|
|
||
|
This script removes a UAPI header file from the install list. Let's run
|
||
|
the script::
|
||
|
|
||
|
% ./scripts/check-uapi.sh
|
||
|
Installing user-facing UAPI headers from dirty tree... OK
|
||
|
Installing user-facing UAPI headers from HEAD... OK
|
||
|
Checking changes to UAPI headers between HEAD and dirty tree...
|
||
|
==== UAPI header include/asm/termios.h was removed between HEAD and dirty tree ====
|
||
|
|
||
|
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
|
||
|
|
||
|
Removing a UAPI header is considered a breaking change, and the script
|
||
|
will flag it as such.
|
||
|
|
||
|
Checking Historic UAPI Compatibility
|
||
|
------------------------------------
|
||
|
|
||
|
You can use the ``-b`` and ``-p`` options to examine different chunks of your
|
||
|
git tree. For example, to check all changed UAPI header files between tags
|
||
|
v6.0 and v6.1, you'd run::
|
||
|
|
||
|
% ./scripts/check-uapi.sh -b v6.1 -p v6.0
|
||
|
Installing user-facing UAPI headers from v6.1... OK
|
||
|
Installing user-facing UAPI headers from v6.0... OK
|
||
|
Checking changes to UAPI headers between v6.0 and v6.1...
|
||
|
|
||
|
--- snip ---
|
||
|
error - 37/907 UAPI headers compatible with x86 appear _not_ to be backwards compatible
|
||
|
|
||
|
Note: Before v5.3, a header file needed by the script is not present,
|
||
|
so the script is unable to check changes before then.
|
||
|
|
||
|
You'll notice that the script detected many UAPI changes that are not
|
||
|
backwards compatible. Knowing that kernel UAPIs are supposed to be stable
|
||
|
forever, this is an alarming result. This brings us to the next section:
|
||
|
caveats.
|
||
|
|
||
|
Caveats
|
||
|
=======
|
||
|
|
||
|
The UAPI checker makes no assumptions about the author's intention, so some
|
||
|
types of changes may be flagged even though they intentionally break UAPI.
|
||
|
|
||
|
Removals For Refactoring or Deprecation
|
||
|
---------------------------------------
|
||
|
|
||
|
Sometimes drivers for very old hardware are removed, such as in this example::
|
||
|
|
||
|
% ./scripts/check-uapi.sh -b ba47652ba655
|
||
|
Installing user-facing UAPI headers from ba47652ba655... OK
|
||
|
Installing user-facing UAPI headers from ba47652ba655^1... OK
|
||
|
Checking changes to UAPI headers between ba47652ba655^1 and ba47652ba655...
|
||
|
==== UAPI header include/linux/meye.h was removed between ba47652ba655^1 and ba47652ba655 ====
|
||
|
|
||
|
error - 1/910 UAPI headers compatible with x86 appear _not_ to be backwards compatible
|
||
|
|
||
|
The script will always flag removals (even if they're intentional).
|
||
|
|
||
|
Struct Expansions
|
||
|
-----------------
|
||
|
|
||
|
Depending on how a structure is handled in kernelspace, a change which
|
||
|
expands a struct could be non-breaking.
|
||
|
|
||
|
If a struct is used as the argument to an ioctl, then the kernel driver
|
||
|
must be able to handle ioctl commands of any size. Beyond that, you need
|
||
|
to be careful when copying data from the user. Say, for example, that
|
||
|
``struct foo`` is changed like this::
|
||
|
|
||
|
struct foo {
|
||
|
__u64 a; /* added in version 1 */
|
||
|
+ __u32 b; /* added in version 2 */
|
||
|
+ __u32 c; /* added in version 2 */
|
||
|
}
|
||
|
|
||
|
By default, the script will flag this kind of change for further review::
|
||
|
|
||
|
[C] 'struct foo' changed:
|
||
|
type size changed from 64 to 128 (in bits)
|
||
|
2 data member insertions:
|
||
|
'__u32 b', at offset 64 (in bits)
|
||
|
'__u32 c', at offset 96 (in bits)
|
||
|
|
||
|
However, it is possible that this change was made safely.
|
||
|
|
||
|
If a userspace program was built with version 1, it will think
|
||
|
``sizeof(struct foo)`` is 8. That size will be encoded in the
|
||
|
ioctl value that gets sent to the kernel. If the kernel is built
|
||
|
with version 2, it will think the ``sizeof(struct foo)`` is 16.
|
||
|
|
||
|
The kernel can use the ``_IOC_SIZE`` macro to get the size encoded
|
||
|
in the ioctl code that the user passed in and then use
|
||
|
``copy_struct_from_user()`` to safely copy the value::
|
||
|
|
||
|
int handle_ioctl(unsigned long cmd, unsigned long arg)
|
||
|
{
|
||
|
switch _IOC_NR(cmd) {
|
||
|
0x01: {
|
||
|
struct foo my_cmd; /* size 16 in the kernel */
|
||
|
|
||
|
ret = copy_struct_from_user(&my_cmd, arg, sizeof(struct foo), _IOC_SIZE(cmd));
|
||
|
...
|
||
|
|
||
|
``copy_struct_from_user`` will zero the struct in the kernel and then copy
|
||
|
only the bytes passed in from the user (leaving new members zeroized).
|
||
|
If the user passed in a larger struct, the extra members are ignored.
|
||
|
|
||
|
If you know this situation is accounted for in the kernel code, you can
|
||
|
pass ``-i`` to the script, and struct expansions like this will be ignored.
|
||
|
|
||
|
Flex Array Migration
|
||
|
--------------------
|
||
|
|
||
|
While the script handles expansion into an existing flex array, it does
|
||
|
still flag initial migration to flex arrays from 1-element fake flex
|
||
|
arrays. For example::
|
||
|
|
||
|
struct foo {
|
||
|
__u32 x;
|
||
|
- __u32 flex[1]; /* fake flex */
|
||
|
+ __u32 flex[]; /* real flex */
|
||
|
};
|
||
|
|
||
|
This change would be flagged by the script::
|
||
|
|
||
|
[C] 'struct foo' changed:
|
||
|
type size changed from 64 to 32 (in bits)
|
||
|
1 data member change:
|
||
|
type of '__u32 flex[1]' changed:
|
||
|
type name changed from '__u32[1]' to '__u32[]'
|
||
|
array type size changed from 32 to 'unknown'
|
||
|
array type subrange 1 changed length from 1 to 'unknown'
|
||
|
|
||
|
At this time, there's no way to filter these types of changes, so be
|
||
|
aware of this possible false positive.
|
||
|
|
||
|
Summary
|
||
|
-------
|
||
|
|
||
|
While many types of false positives are filtered out by the script,
|
||
|
it's possible there are some cases where the script flags a change
|
||
|
which does not break UAPI. It's also possible a change which *does*
|
||
|
break userspace would not be flagged by this script. While the script
|
||
|
has been run on much of the kernel history, there could still be corner
|
||
|
cases that are not accounted for.
|
||
|
|
||
|
The intention is for this script to be used as a quick check for
|
||
|
maintainers or automated tooling, not as the end-all authority on
|
||
|
patch compatibility. It's best to remember: use your best judgment
|
||
|
(and ideally a unit test in userspace) to make sure your UAPI changes
|
||
|
are backwards-compatible!
|