| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- .. 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!
|