A Kernel Developer's Guide to kABI
Abstract
There can be a burden associated with maintaining RHEL’s kABI (Kernel Application Binary Interface) commitment that persists throughout the release’s entire lifecycle if subsystem maintainers don’t pro-actively accommodate for upstream back-ports. RHEL kernel developers need to be aware of, and fully comprehend, such restrictions, which is the focus of this document.
Keywords: kABI, KABI, KMI, Kernel Application Binary Interface, Kernel Module Interface.
Overview
There are Red Hat partners and customers that want a means to deliver and receive updated drivers in a manner that allows continued operation across subsequent kernel updates. Out-of-band (OOB) driver modules are the most common, which includes updates such as what the Driver Update Program (DUP) provides. [1] Driver updates enable “stop gap” solutions for situations such as supporting new hardware, or new, or maturing, functionality, prior to such updates becoming available in a future kernel update.
Extending this concept, a subset of ISV’s have said they need the ability to build, and ship, their own, “third party” modules, independently of Red Hat Enterprise Linux (RHEL). These module types tend not to be open-source, but critical to various partners’ proprietary solutions – not drivers per say, but more filesystem, or system management, oriented – and will never go into a RHEL release.
These customer driven use cases necessitate the need for some level of binary compatibility with respect to the kernel. To meet these needs, Red Hat has publicly stated that it will provide “kernel ABI stability”, more commonly referred to as the “_kernel ABI_” (kABI), along minor updates of a RHEL release but not across major releases.
Partners cite Red Hat’s kABI commitment as a key differentiator, distinguishing it from other distributions.
Most of the effort concerning kABI derives from third party modules. The specific type of module – OOB, or third party – isn’t really important. What does matter is whether a module is linked with the kernel statically or dynamically. Modules are strongly tied to the data structures and function prototypes defined in a particular kernel version; the interface seen by a module can change significantly from one kernel version to the next.
Statically linked modules – “_built-in_” or “_internal modules_” –
are incorporated as part of the kernel so most, if not all, of the
concerns related to whether or not the module is compatible with the
specific kernel version – “_Kernel/module variance_” – don’t come into
play. Dynamically linked modules – “_external modules_”; sometimes
referred to as out-of-tree modules – are stored within the filesystem
as separate ELF object files. In order to run an external module on a
kernel the ’insmod(8)’ program is executed which compares the globally
exported symbols used by the module against the kernel’s symbol’s
versions and accepts, or rejects, linking the module to the kernel.
Thus, for external modules, kernel/module variance concerns are
paramount.
kABI’s role is to proactively disallow changes related to exported symbol’s ABI from occurring.
Red Hat’s partners wanting a commitment that they will not have to rewrite, or recompile, their modules identify the external symbols used within them and submit a request to Red Hat for protection. Such requests then have to be reviewed, and explicitly agreed to, by Red Hat’s kernel development team before the symbols are considered protected.
Maintaining kABI protected symbols can cause severe restrictions with respect to bringing in new content unless additions are made to the kernel to specifically accommodate future back-porting efforts to a RHEL release.
Technical Details: Linux Kernel - Modules
Module Versioning
The enabling of CONFIG_MODVERSIONS in a Linux kernel’s
configuration enforces compatibility of the Kernel Module Interface
(KMI), more commonly referred to as “_module versioning_”.
[2] [3] This
causes all kernel global symbols exported with the EXPORT_SYMBOL()
and EXPORT_SYMBOL_GPL() macros to be augmented with
8-hex-digit checksums computed from the names of all relevant C tokens
that define the interface. [4]
The checksumming augmentation – genksyms symbol versioning information – is calculated by expanding the symbol to its full definition where all structures, unions, enums, and typedefs are expanded down to their basic parts, recursively. This final string is then used as input to a CRC algorithm yielding an integer that will change when any constituent part(s) of the symbol’s included definitions change. [5]
Note that it’s not just the globally exported symbol’s names themselves that are versioned. Encompassed is everything associated with the symbol’s full prototype: “_all relevant C tokens that essentially define the interface_”.
Whenever there is a change to an exported symbol’s return type, name, any of its argument’s type, or to the type of any field of any structure contained in, or pointed to, by any of its arguments, including all data structures within data structures (recursively), then its checksum changes. When a module attempts loading, each symbol’s CRC value contained in the kernel’s symbol table is compared with the corresponding symbol’s CRC value in the module’s object file, and if they are not equal the kernel refuses to load the module. Thus, kernel interface changes cause the ’insmod’ of previously built modules to fail.
Kernel data structures are of particular interest with respect to KMI as they contain multiple members, some of which may be other data structures themselves, all of which become recursively included as part of the symbol’s versioning checksum, making the total scope of what must be preserved in order for an existing module to continue to load and run, potentially quite large.
As a concrete example, consider the exported symbol
’pci_bus_read_config_word()’ within the kernel’s PCI
core that accesses PCI device configuration space. As an exported
symbol, it’s available for external modules to call.
[6]
int pci_bus_read_config_word(struct pci_bus *bus, unsigned int devfn, int pos, type *value) EXPORT_SYMBOL(pci_bus_read_config_word) 0x1aa4c8bf pci_bus_read_config_word vmlinux EXPORT_SYMBOL
From its function prototype, the ’int’ return type, its name, and all
its arguments, must remain preserved, which is somewhat self-evident.
Much more subtle, however, is its ’bus’ argument which is a pointer to
a ’struct pci_bus’.
[7] If, as
the result of an upstream patch series incorporation, the type of any
member of ’struct pci_bus’, including members of structures within
’struct pci_bus’, were to change – including adding to, removing
from, or changing the name or ordering of any of its members – then the
’pci_bus_read_config_word()’ symbol’s versioning
checksum would correspondingly change.
Module versioning does not, nor does the Linux kernel, provide a stable kernel interface (a.k.a., a binary kernel interface). [8] The KMI is solely a module to kernel consistency checking mechanism for validating external kernel modules with respect to the kernels they are attempting to link (attach) to, and run against.
Technical Details: RHEL’s kABI
Throughout successive minor version update cycles of a RHEL release, partners and customers request bug fixes, and additional features, be applied to the kernel. Fulfilling these requests often requires the removal of a function, changes to a function’s return type or parameters, or changes to kernel data structures (adding, removing, and renaming members). These types of changes recurrently alter the CRC versioning information of globally exported symbols; negating the ability to continue running a previously runnable, external, module upon updated kernels.
Like Linux’s KMI, RHEL’s kABI focuses on exported symbols, utilizing the
same underlying ’genksyms’ based symbol versioning information in its
checking mechanisms. So, while they are inter-related (any symbol
changes affects both kABI and KMI), KMI is concerned with after-the-fact
module to kernel compatibility checking, whereas RHEL’s kABI is
concerned with before-the-fact preclusion of kernel to module
incompatibilities. kABI’s role is to maintain a stable kernel interface,
specifically in order that RHEL’s partner’s external modules continue to
operate along minor kernel updates. [9]
RHEL’s kernel build related processes have been augmented with a set of
additional rules and scripts to implement kernel ABI stability. RHEL’s
implementation utilizes the kernel’s existing GENKSYMS
directive to dynamically enable, and disable, checking. Additionally,
with respect to partners, there are user-level enforcement checking
utilities (’ksc(1)’), all of which act in concert to enact Red Hat’s
kABI commitment. [10]
What is Protected
RHEL’s kABI commitment does not cover all exported symbols but rather is limited to, and thus only concerned with, the subset of global symbols that partners have requested be preserved, and that Red Hat’s kernel development engineers have reviewed, and explicitly agreed to protect. Such symbols become white-listed [see: kABI Lists], which is one of the components used in implementing RHEL’s kABI compliance.
Agreeing to partners’ external symbol requests has significant consequences. More acutely, as a direct result of partners and customers developing and compiling their external modules against a release’s inception (i.e., X.0), kABI’s impacts, and restrictions, begin with its origin, and persist throughout its entire lifecycle.
While RHEL’s kABI is not limited to kernel data structures – like KMI, it focuses on kernel external symbols and their entire prototype’s makeup – they are of particular interest with respect to kABI also. Data structures have all the same impacts to kABI as they do to KMI: ramifications can be very subtle, often outright hidden, yet potentially encompass a very large scope.
Continuing with the example,
’pci_bus_read_config_word()’ is a kernel exported
symbol that is white-listed, thus considered protected with respect to
kABI.
0x1aa4c8bf pci_bus_read_config_word vmlinux EXPORT_SYMBOL` `[11]
The kernel’s ’pci_dev’ data structure, via its indirect
correlation from the ’bus’ argument (’struct pci_dev’ contains
members of type ’struct pci_bus’) of
’pci_bus_read_config_word()’, also becomes effectively
kABI protected. [12] So: “What does that imply with respect
to kABI?”
Consider upstream commit
31c2b8153c ‘‘PCI: Add support for DMA alias quirks’’. As a core aspect
of that commit, a new ’dma_alias_devfn’ member was added to
’struct pci_dev’.
diff git a/include/linux/pci.h b/include/linux/pci.h
index 545903df00dc..9d4035c276f4 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -268,6 +270,7 @@ struct pci_dev {
u8 rom_base_reg; /* which config register controls the ROM */
u8 pin; /* which interrupt pin this device uses */
u16 pcie_flags_reg; /* cached PCIe Capabilities Register */
+ u8 dma_alias_devfn;/* devfn of DMA alias, if any */
struct pci_driver *driver; /* which driver has allocated this device */
u64 dma_mask; /* Mask of the bits of bus address this
Prior to commit 31c2b8153c, ’struct pci_dev’ consumed N bytes of
memory, and the ’driver’ member was at some known offset from the
structure’s beginning - assume 0x148. After the commit,
’struct pci_dev’ now consumes N + 1 bytes of memory, and the
pre-existing ’driver’ member no longer resides at the same 0x148 byte
offset location in memory that backs the structure (all
’struct pci_dev’ members at, or past, the ’driver’ member will
have been shifted to new, larger, offset locations).
Now consider a partner’s external module which was compiled against
RHEL’s 7.0 kernel, and relies upon (i.e., utilizes) the kernel’s
’pci_bus_read_config_word()’ external symbol. If
upstream commit 31c2b8153c were back-ported into RHEL in a
straightforward manner, say during 7.1’s kernel development phase,
changes to the kernel’s version of ’struct pci_dev’ would affect
the module in two ways.
| RHEL 7.0 kernel | Offset | RHEL 7.1 kernel | Offset |
|---|---|---|---|
struct pci_dev {
...
u16 pcie_flags_reg;
struct pci_driver *driver;
u64 dma_mask;
...
...
|
... ... 0x146 0x148 0x150 ... ... |
struct pci_dev {
...
u16 pcie_flags_reg;
u8 dma_alias_devfn;
struct pci_driver *driver;
u64 dma_mask;
...
|
... ... 0x146 0x148 0x150 0x158 ... |
First, any module’s references to the kernel’s structure’s members up
to, but not including, the ’driver’ member would be fine, as none of
their offsets would have changed. However, unbeknownst to the external
module, a new ’dma_alias_devfn’ member now resides within the
kernel where the ’driver’ member used to in the memory backing
’struct pci_dev’. So, in effect, all member references by modules
of the ’driver’ member onward will dereference memory that no longer
corresponds to what the module expects. Second, since the structure is
effectively larger now, if the module accesses memory immediately after
an automatic allocation of the structure (i.e., a routine’s local
variable of type ’struct pci_dev’ within the module’s source file)
in any manner, those memory dereferences also will not correspond to
what the module is expecting. Both conditions – member dereferences at,
or past, the newly added member, and the structure’s overall size –
would cause external modules to dereference memory now backing other
content resulting in silent data corruption at best, and much more
likely, outright failure (i.e., a kernel panic), as a result of the
kernel’s kABI commitment regarding
’pci_bus_read_config_word()’ having been broken.
The broken kABI commitment is not limited to the
’pci_bus_read_config_word()’ symbol. The change to
’struct pci_dev’ would correspondingly cause all partners external
modules that utilized any external symbols with correlations to
’struct pci_dev’ (in any capacity, directly, or indirectly) to no
longer successfully link and run against such an updated kernel.
So, not only are kABI protected external symbols effectively locked down with respect to any changes – API permanence – all data structures correlated with such symbols also become entangled and are effectively locked down with respect to any changes, including member additions, deletions, and re-naming or re-arrangement - kernel data structure invariance. In general: kABI protected external symbols, and all kernel data structures that are correlated with such symbols, are effectively locked down with respect to any changes, starting with the symbol becoming protected (which may occur with the release’s origin), and persists throughout the release’s entire lifecycle. [13]
Due to the restrictive constraints that kABI imposes onto RHEL, Red Hat does not automatically guarantee 100% compliance of symbol protection requests received. Some requested symbols are purposely rejected, to allow the symbols to change in future minor releases in order to minimize the encumbrances of integrating new, or maturing, technologies from upstream. In such cases, partners may have to recompile their modules for every minor update kernel. [14]
Kernel Development Impacts
While not all types of kABI induced ramifications and repercussions
could ever be covered, reviewing and understanding some key examples
from past experiences should help developers gather enough knowledge
with which to tackle future kABI breakages they encounter on their own.
There are cases of frequently occurring issues that are naturally
coincident with, and known to be vulnerable to, kABI and kernel
development. The most common can be roughly categorized into general
areas susceptible to kABI and include: exported symbols, data
structures, header files, static inline functions, enumerations, and the
/proc (procfs) and /sys (sysfs) filesystems.
RHEL kernel developers must be aware of the conditions that may introduce kABI failures when developing, and back-porting, patches. This is mostly a trial and error process at first but, with time, and experience, there are general situations developers become familiar with, having learned that such types of changes are particularly susceptible to introducing kABI failures.
Within the classes outlined next are references to kABI case studies presented in Appendix B “kABI Case Studies: Methods of Working Around kABI’s Impacts“ - providing specific examples of methods developed for solving specific kABI failure types. It is recommended to read the class’ content first to gain an overall understanding, and context, and then reference the noted, class specific, case studies for further details.
What is Impacted
Exported Symbols
An exported symbol’s return type can be augmented with ’const’ (see:
RH_KABI_CONST()). Other than that, changing an exported
symbol’s prototype in any way – its name, return type, and parameters,
including their ordering – is disallowed if it’s white-listed: API
permanence.
Data Structures
Kernel data structure changes are by far the most common kABI breakage type. [15] Data structures are not symbols themselves, but become entangled with a symbol due to their direct, and indirect, correlations. Direct correlations are straightforward; the data structure is used as the symbol’s return type, or within the implementation by way of a parameter. Indirect correlations, where data structures include other data structures, are much more concealed. Such entangled data structure sets can become potentially large in scope, making them very subtle, and thus extremely hard, to associate as the specific cause of a breakage.
In general, developers cannot change kernel data structures correlated with white-listed symbols unless some type of kABI-specific extension mechanism has been pro-actively put into place. All kernel data structures that are correlated with kABI protected symbols, are effectively locked down with respect to any changes – member additions, deletions, re-naming, and re-arrangement – starting with the symbol becoming protected, and persisting throughout its entire lifecycle: kernel data structure invariance. Otherwise, if data structures were altered going forward, an external module built against a prior kernel would cease to link successfully due to Red Hat not having maintained its kABI commitment. This obviously severely impacts kernel development, especially when foundational data structures – structures that make up the core of some kernel subsystem’s basis – have become encumbered with a white-listed symbol, sometimes to the extent that no known recourse is available
Upstream data structure changes typically fall into one of three categories: adding new members, deleting existing members, and re-naming or re-arranging members. Deletions, and re-arrangements are fairly straightforward: don’t enact such changes as they are prohibited, and let RHEL’s kernel source vary from upstream in these specific cases. Additions of new members are much more problematic as new features depend upon such. Fortunately, some fairly inventive solutions have been developed over the years to help with these specific instances.
There is a technique, commonly referred to as an “extension”, that can be used for adding new members, but, such RHEL specific extensions to existing kernel structures must be put into place prior to, or coincident with, the correlated symbol becoming protected. This assures that any external modules will always have been compiled against a kernel that already has the extensions, thus relevant symbol version checksums will not have changed as far as any external modules are concerned.
While kernel data structures may be able to be extended there is implicit factor that has to be considered when developing an extension scheme. Extending kernel data structures correlated with protected symbols is all about enabling new feature content developed upstream, via back-ports into RHEL, without breaking RHEL’s kABI commitment. kABI related extension mechanisms enable this, specifically preparing for new members to be added to a structure encumbered with one or more protected symbols which can take place within any minor update’s development cycle. As a result, from the kernel’s perspective: protection encumbered data structures with extension mechanisms enabled pro-actively may change size (growing only) during any minor update cycle. However, external modules that were compiled against earlier kernel versions, and thus would not know about such structure growth (“kernel/module variance”), must continue to link (attach) and run upon newer kernels – RHEL’s kABI commitment. As such, the kernel’s data structure growth has to be enacted in a way that accounts for kernel/module variance.
The implication is that a symbol may become protected, and its encumbered data structures thus locked down, with the release’s origin (i.e., RHEL X.0). In fact, this is fairly common occurrence for the kernel’s foundational data structures as they are the most likely to become encumbered with symbols becoming protected. Therefore, pro-actively implementing extension members for key kernel data structures during the release’s origin is a prudent action that subsystem maintainers should strongly consider. Otherwise, there likely will be no recourse available: Adding extensions is a once per release event; living with kABI is day to day.
For examples, and details, of extension structures along with one other method related to “holes”, see the kABI Case Studies related to kernel data structures:
The extension implementation developed and used within the kernel’s networking subsystem is considered the canonical model to follow currently [see: Case Study: Pro-actively Extending Core Kernel Data Structures, and kABI Case Studies: Methods of Working Around kABI’s Impacts].
Kernel data structures not encumbered with any white-listed symbols are not protected by any kABI commitments.
Header Files
Including header files may change the parsing of structures - a source,
or header, file that had an indirect forward reference to a structure is
changed to now include another header which, via the inclusion, replaces
the indirect forward reference with the full declaration of the
structure (i.e., previously opaque symbols become transparent, as the
pre-processor now knows more about them). These types of occurrences can
result in a “_false-positive_” kABI breakage, and can be worked around
by the use of the GENKSYMS pre-processing directive.
kABI Case Studies related to kernel header files:
Static Inline Functions
Developers should be aware that modifying public inline functions might cause problems with external driver modules. Care should be taken to minimize those changes and when possible use exportable functions instead.
Enumerations
There are currently no hard requirements to protect these interfaces; however, changes to enumeration ordering can affect external modules. RHEL’s current kABI checking capabilities may not catch simple enumeration related changes that indeed affect modules, leading to silent kABI failures.
Enumerations can also be inherently problematic in nature as the C Standard says un-assigned enumeration lists can have any value assigned to each member of the list for any given compilation. Enumeration lists that explicitly assign values avoid this issue, and converting un-assigned lists to explicitly assigned lists can be used to obviate future kABI concerns. Developers should self-police and preserve them on a case-by-case basis.
kABI Case Studies related to enumerations:
For recurring situations where solutions have been developed over the years that solve, or work around, kABI failures, case studies have been written and are presented in Appendix B: “kABI Case Studies: Methods of Working Around kABI’s Impacts”. Each study outlines the case’s general type, specifics involved, techniques used, explanations as to why the solution works, and any known caveats to be cognizant of.
Note however, there are situations in which developers encounter circumstances where kABI precludes back-porting content from upstream and there is no known recourse.
What is Not Impacted
procfs / sysfs
There are currently no hard requirements to protect these interfaces; however, upstream generally disallows removals or non-compatible changes (additions are allowed when justified). Developers will occasionally self-police and preserve them on a case-by-case basis.
What is GENKSYMS and its Abuse
While GENKSYMS is not one of the common cases listed, it
is a key mechanism that must be used in effecting solutions in all cases
(i.e., most cases use GENKSYMS in some capacity). Working
around kABI is typically a two-pronged process. First, a strategy that
does not logically alter a kernel symbol’s ABI has to be developed
(assuming one can be crafted, or even exists). With a logical solution
implemented, the second part of masking the physical (i.e., code)
changes from both KMI’s, and kABI’s, symbol versioning based checking
mechanisms, must be enacted. This involves using the kernel’s same build
related pre-processing directive – GENKSYMS.
kABI workarounds must effectively mask the specific changes that would break kABI. To do such, RHEL implementations used to use a common idiom of surrounding changes via GENKSYMS. [16] [17].]
@@ -379,6 +381,7 @@ struct pci_dev {
*/
struct pci_dev_rh {
#ifndef __GENKSYMS__
+ u8 dma_alias_devfn;/* devfn of DMA alias, if any */
#endif
};
Over time, this idiom was becoming overused, leading to some negative
outcomes – from the commit message of RHEL7 commit 023ac646f29
“kabi: Remove GENKSYMS usage through the code”:
"A conversation started about the usage of GENKSYMS on a 7.0 kernel.
Normally this is not needed because kabi is not frozen until _after_ a .0 release is GA.
However folks were using this wrapper to clearly indicate which chunks of code were being
reserved for Red Hat purposes.
After thinking about this and discussing it with folks, we realized this is not a good idea.
Two reasons have stuck out as to why.
First, the idea was that hiding the symbols in the GENKSYSMS wrapper, we hide the true checksum
of the symbols. We should try to encompass the true checksum for GA. On the flip side,
removing the macro and doing things properly for 7.1 would entail:
struct foo {
int bar;
#ifndef __GENKSYMS__
int original;
#else
int new;
#endif
}
which leads to ugly and hard to read code. I agreed to create some macros in 7.1 that would
hide the ugliness, yet make it obvious what is going on.
Second, and this is more a social problem, most folks have come to assume that anything inside
__GENKSYMS__ is free to be modified/added. But in this particular example, this is _not_ the
case. So to prevent the confusion, we should avoid this usage for reserving memory.
As a result, I have removed all uses I believe were not correct. There should be no downside as
this only affects how genksyms pre-processes the symbols and generates checksums. The only
noticeable change will be new kabi checksums for a bunch of functions.
I did leave in a few usages, namely the PCI ones. I believe this is a correct usage as it
properly sets the stage for future additions to the reserved struct."
As a result of the actions behind the context of RHEL7 commit
023ac646f29 (too many kABI-naive engineers overusing, and abusing,
GENKSYMS; anything inside GENKSYSMS is not
free to be modified/added), RHEL specific kABI macros were developed,
and incorporated, during 7.1’s minor update development cycle [see:
Appendix C include/linux/rh_kabi.h include/linux/rh_kabi.h].
[18] The
kABI macros were introduced for use in kABI related development efforts
that effectively do the same thing but in a manner identical to the
kernel’s build processing - using macros that expose changes when
disabled yet mask changes (essentially turning them into a no-op, or
<null>, clause), when enabled.
#ifdef __GENKSYMS__ ... # define RH_KABI_EXTEND(_new) ... #else ... # define RH_KABI_EXTEND(_new) _new; ... #endif /* __GENKSYMS__ */
As should be evident, blindly using RHEL’s ’RH_KABI_EXTEND()’
macros in an attempt to workaround kABI is not sufficient – a common
mistake made by RHEL kernel developers not fully cognizant of kABI – as
the core ABI issues still exist (the “first” step mentioned above is
needed to solve the “logical” aspects). In fact, attempting to do such
just “hides” the breakage from the kABI enforcement tools’ detecting
such, a much worse problem.
All of the kABI case studies outlined in “kABI Case Studies: Methods of Working Around kABI’s Impacts” utilize common
GENKSYMS based idioms, either directly, or indirectly via
RHEL’s kABI macros, as one aspect in an overall solution.
Verification
When building a RHEL kernel, either from a Source RPM (SRPM) or via
’rh-brew’, a kABI checking script, ’check-kabi’, is automatically
run as part of the build process. [19]
The script attempts to check for any kABI breakages introduced by
changes to the kernel. The checking is constrained to RHEL’s
white-listed symbols only; it does not check the kernel’s non
white-listed exported symbols
kABI protection applies to all supported architectures. Hence building
via ’rh-brew’ is the recommended method, as it will run ’check-kabi’
on all architectures. If ’check-kabi’ is executed manually, only one
specific architecture is checked. When building via the ’rpmbuild’
process, the SPEC file sets the ’with_kabichk’ flag –
redhat/rpm/SPECS/kernel.spec::%define with_kabichk – enacting the
exact same ’check-kabi’ based checking.
To manually check if there is going to be a kABI issue, prepare by first enacting the patch series’ changes, and make the kernel:
$ make rh-configs-prep
$ cp configs/kernel-2.6.32-x86_64.config .config
$ make oldconfig
$ make -j16
With the kernel built, manually run ’check-kabi’ to check for kABI
failures:
$ redhat/kabi/check-kabi -h /* help */
$ redhat/kabi/check-kabi -k redhat/kabi/Module.kabi_x86_64 -s Module.symvers
If there is no output, then the build was successful and there were no
kABI related failures. Otherwise, ’check-kabi’ outputs a list of
broken symbols.
Debugging Breakages
Removal or change to white-listed symbols is considered a serious offense as partners and customers are actively using these symbols. Changing a white-listed symbol’s checksum would cause their modules to fail loading in future minor updates, negating Red Hat’s commitment. Red Hat has removed white-listed symbols in the past, but only after contacting partners and securing knowledge they were not using those symbols.
Developers will, on occasion, include a patch that incidentally modifies a white-listed symbol. This will lead to the checksum changing and is considered “_breaking kABI_”. In such cases, developers are expected to modify their patches to accommodate these symbols and patches will be rejected unless they are revised to work around such breakages.
via ’check-kabi’
Inevitability, every RHEL kernel developer will encounter the case where
a patch series is applied and building a kernel now fails due to kABI
(specifically, ’check-kabi’ fails). How can one determine which part
of a patch caused the breakage, especially if the broken symbol seems to
have nothing to do with the applied patch series?
Novell added some code a while ago that saves the ’genksyms’ versioning symbol(s) backtrace when compiling *.o files. Using this backtrace one can hone in and determine where the kABI broke by comparing the old backtrace with the new one. [20]
-
Make sure you have a clean master branch in git and another branch with your changes (it is just easier this way);
-
Make sure you have your .config setup;
-
Pick a symbol that brew said broke with respect to kABI, call it ’
foo’.
$ git checkout master # Need the original kABI cksum
$ git grep foo | grep EXPORT_SYMBOL # Find the file that exports foo
$ make file.symtypes # Generate backtrace
$ cp file.symtypes file.symref # Save the backtrace
$ git checkout branch_with_change # Switch to patched branch
$ KBUILD_PRESERVE=1 make file.symtypes # Generate and compare new backtrace
By way of concrete example, assume some changes have broken kABI and
’check-kabi’ indicates that
the ’ieee80211_restart_hw’ symbol now fails:
$ git grep <target_kABI_failure_symbol_from_check-kabi> | grep EXPORT
net/mac80211/main.c:EXPORT_SYMBOL(ieee80211_restart_hw);
According to ’check-kabi’, there looks to be an issue with the
’ieee802111_restart_hw’ symbol but in reviewing the patch
series’ content there are no changes to that function’s body of code.
Continuing with the approach outlined above:
$ make net/mac80211/main.symtypes # Note: strip the .c from source filename
This should fail and print out the structure that caused the breakage:
$ KBUILD_PRESERVE=1 make init/version.symtypes
...
init/version.c:37: error: init_uts_ns: modversion changed because of changes in struct uts_namespace
...
So the issue isn’t within the ’ieee80211_restart_hw’ symbol’s
function, but rather something indirectly involving
’struct uts_namespace’. Reviewing the patch series again, with
this new information, one now sees that it indeed added a new member to
include/linux/utsname.h::’struct uts_namespace’ which is the root
of the kABI failure:
-@ -40,6 +40,7 @@ struct new_utsname {
struct uts_namespace {
struct kref kref;
struct new_utsname name;
+ struct dummy;
};
extern struct uts_namespace init_uts_ns;
Most kABI breakages usually center on a single change, but sometimes more than one change breaks kABI. One can either perform the above steps for each symbol that broke kABI or just recompile and see what was missed. Recompiling is probably easiest but may not be enough.
via ’kabi-dw’
’kabi-dw’ is a new tool which allows checking for kABI changes based
on changes in DWARF debugging data. In short it loads the type
information from DWARF, from vmlinux and all kernel modules, and dumps
these types into a set of text files starting with a given list of
exported symbols and descending to all types reachable from these
symbols.
Consider the following example where we are interested only in
’pci_bus_read_config_word()’ symbol. First we’d build
the kernel tree with debug info enabled (which is on by default in RHEL
kernels):
$ make rh-configs
$ cp redhat/configs/kernel-3.10.0-$(uname -p).config .config
$ make && make modules
Then we’d generate the list of symbols we’re interested in:
$ echo 'pci_bus_read_config_word' > symbols
We build the kabi-dw tool:
$ make rh-kabi-dw
Finally we generate the files for all types reachable from our selected symbol:
$ redhat/kabi-dwarf/kabi-dw/kabi-dw generate -s symbols -o output .
In the ’output’ directory we can find the text file for function
’pci_bus_read_config_word’:
$ ls -l output/func-pci_bus_read_config_word.txt
-rw-r-r-. 1 root root 175 Apr 17 16:38 output/func-pci_bus_read_config_word.txt
Because the ’bus’ argument of
’pci_bus_read_config_word()’ is of type
’struct pci_bus `*’ there will also be a file for
’`struct pci_bus’:
$ ls -l output/struct-pci_bus.txt
-rw-r-r-. 1 root root 1051 Apr 17 16:38 output/struct-pci_bus.txt
Similarly the tool descends to all the reachable types that are defined in the DWARF debug data.
It is expected that the list of symbols provided to start the dump of
data will be the kABI white-list. There is Makefile target which
simplifies whole process with providing the current whitelist as the
input and storing the output under redhat/kabi-dwarf/base/$(uname -p):
$ make rh-kabi-dw-base
’kabi-dw’ tool can also be used for comparing two datasets. Typical
usage would then include generating baseline dataset (aka. last offical
RHEL build), re-building the kernel with your changes, generating new
dataset and comparing them with kabi-dw:
$ redhat/kabi-dwarf/kabi-dw/kabi-dw compare output1 output2
This can again be simplified with:
$ make rh-kabi-dw-base
# ... apply your patch
$ make && make modules
$ make rh-kabi-dw-check
Where can kabi-dw help?
In general, the ’kabi-dw’ approach is similar to the
’make <file>.symtypes’ approach described above. The main
added value is:
-
’
kabi-dw’ can “see” through all ’GENKSYMS’ macros. That is, even if certain parts of the code are already masked using ’GENKSYMS’ or some of the ’RH_KABI_’ macros, these changes will still be reported in ’*()kabi-dw’ output. -
’
kabi-dw’ looks at the whole kernel tree, ie.vmlinux+ all modules, together. This is in sharp contrast with ’genksyms’ which only looks at single C source file at a time. ’kabi-dw’ can help to answer questions like if given structure is embedded inside other structure elsewhere in the kernel.
Conclusion
Attempting to describe all the various ways that kABI impacts kernel development is impractical. Developers will inevitability run into new, unique, situations involving kABI. So while the content presented just scratches the surface, hopefully, it will have been enough to help developers in solving future kABI breakages that they encounter on their own (when possible).
Anyone who has further questions related to kABI should send questions to the core-kernel@redhat.com mail list.
Exercises
-
Q: Explain what a Linux kernel “exported symbol” is.
-
Q: What is KMI?
-
Q: What is “symbol versioning” and what does it do?
-
Q: What is kABI?
-
Q: How is kABI different from KMI?
-
Q: Does KMI catch all kABI breakages? Why?
-
Q: Is it possible to break kABI in a way where KMI’s symbol versioning doesn’t change and ’
insmod’ still loads the module? Or, does KMI symbol versioning causing modules to no longer load successfully also catch all kABI breakages? Answer: KMI should catch all breakages also but manual use (overuse) ofGENKSYMSrelated to kABI can hide changes causing the association to no longer hold true. Additionally,GENKSYMSwill not catch enumeration changes unless the enumeration is hardwired in an inline function (i.e., enumeration changes can silently break kABI). -
Q: Can all kernel data structures be extended? Why not?
-
Q: Is using a RHEL kABI macro such as ’
RH_KABI_EXTEND()’ sufficient to satisfy all requirements when dealing with a kABI breakage? Why?
kABI Lists
kABI tracks global symbols through two types of lists: white, and grey. These lists define which symbols are protected and which ones are not:
White
Exported symbols that Red Hat has committed to not break throughout the subsequent lifecycle of a RHEL major release make up the “white-list”. [21]
Each minor RHEL release has a set of symbols constituting a white-list. [22] Once a symbol has been added to the kABI white-list for a particular release it will not be removed, nor is it allowed to change during the major release’s remaining lifecycle. New symbols may be added to the kABI white-list in minor releases.
Grey
Exported symbols that Red Hat’s partners have requested for review and addition to the while-list but have not yet been explicitly accepted nor rejected (e.g., they are in the internal review phase where Red Hat’s developers discuss whether or not to add them to the white-list).
There is a database server that maintains grey-listed entries for each specific RH partner. This server also offers a monitoring service that notifies partners if any of the symbols on their respective grey-list changes.
Partners can request additions to RHEL’s kABI white-list by opening a RHEL Jira under the “kernel / kabi-whitelists” component. Symbol requests are sent to the Red Hat Kernel development team’s Mail List (RHKML) for internal review. Symbols explicitly accepted for protection are added to the kABI white-list at the end of the next minor release’s development phase. Symbols that were not explicitly accepted are automatically added to the grey-list.
kABI Case Studies: Methods of Working Around kABI’s Impacts
Attempting to describe all the various ways that kABI impacts kernel development is not practical. Nevertheless, there are sets of issues that are recurrent, falling within somewhat common areas, scenarios, or themes (i.e., classes of scenarios that can be roughly categorized). For the recurrent issues within these classes, there have been some fairly innovative methods developed over the years to work around kABI’s restrictions. So while not all types of issues could ever be covered, going over and understanding some key examples from past experiences should help developers gather enough knowledge with which to tackle future kABI breakages they run into on their own.
This appendix outlines some of the more interesting, and common, past experiences within the various classes via concrete examples. Each study outlines the case’s general scenario, specifics involved, techniques used, explanations as to why the solution works, and any known caveats to be cognizant of.
For each of the case studies, the referenced material (’git’ commits
and source files) should be accessed and studied in an effort to fully
comprehend how, and why, the methods are successful in working around
kABI while maintaining an external module to continue to operate across
RHEL’s successive minor update cycles despite kernel/module variance.
Case Study: Pro-actively Extending Core Kernel Data Structures
Upstream reference: N/A
RHEL7 reference: commit c9be03bbc1c [23] PCI: Add kABI shadow structs for the kernel’s PCI
subsystem c9be03bbc1c PCI: Create ’pci_dev_rh’ KABI shadowing
structure 9599688156b PCI: Convert ’pci_dev’ allocations and frees
5abdc3d2dac PCI: Convert specific ’pci_dev’ related constructs
829d829c1b7 PCI: Create ’pci_bus_rh’ KABI shadowing structure
8af377d9592 PCI: Convert ’pci_bus’ allocations and frees
610b1c95049 PCI: Create ’pci_driver_rh’ KABI shadowing
structure]
This case study covers the pre-requisite setup that enables logically adding new members to existing kernel data structures that are becoming, or expected to become, encumbered with kABI-protected symbols. While specific implementations have evolved over the years – hash based associations to shadow structures, directly associated opaque extension structures, and pre-reserved additions with directly associated opaque extension structures – its central tenant – adding a new data structure that is directly associated to an existing data structure unto which all future member additions will be added so as to not effect any symbol versioning aspects of the original data structure – has not.
During RHEL7’s initial kernel development phase – 7.0 – commit
c9be03bbc1c “PCI: Create ’pci_dev_rh’ KABI shadowing
structure” was pro-actively put into place adding a pointer to an
opaque, RHEL kABI-specific, extension structure onto the end of
’struct pci_dev’.[23] This pre-requisite step allowed
RHEL7 to maintain kABI with respect to ’pci_dev’ yet also
accommodates logical upstream updates to such – the addition of new
members only - via its ’pci_dev_rh’ extension structure.
diff -git a/include/linux/pci.h b/include/linux/pci.h
index e564932..45f1c16 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -364,6 +364,20 @@ struct pci_dev {
#endif
phys_addr_t rom; /* Phys addr of ROM if it's not from the BAR */
size_t romlen; /* Length of ROM if it's not from the BAR */
+
+ /* Extension to accomodate future upstream changes to this struct
+ * yet maintain RHEL7 kABI. For Red Hat internal use only!
+ */
+ struct pci_dev_rh *pci_dev_rh;
+};
+
+/*
+ * RHEL7 specific 'struct pci_dev' shadow structure to help maintain kABI
+ * going forward. This structure will never be under kABI restrictions.
+ */
+struct pci_dev_rh {
+#ifndef __GENKSYMS__
+#endif
};
static inline struct pci_dev *pci_physfn(struct pci_dev *dev)
The RHEL specific extension member addition to existing kernel structures – in this instance, a pointer to an opaque extension structure – must be put into place prior to, or coincident with, the correlated symbol becoming protected. This assures that any external modules will have always been compiled against a kernel that has the extension’s member included, prior to the symbol having become protected, and thus relevant symbol version checksums will not have changed.
Once a symbol becomes protected, any encumbered data structures are effectively locked down from any further changes for the remaining life cycle of the release. Otherwise, if data structures were altered going forward, an external module built against a prior kernel, would cease to link successfully due to Red Hat not having maintained its kABI commitment.
The implication is that a symbol may become protected, and its encumbered data structures thus locked down, with the release’s origin (i.e., RHEL X.0). In fact, this is fairly common occurrence for the kernel’s foundational data structures as they are the most likely to become encumbered with symbols becoming protected. Therefore, pro-actively implementing extension members for key kernel data structures during the release’s initial phase is a prudent action that subsystem maintainers should strongly consider.
The subsequent step of allocating the extension structure itself can be postponed until such time as needing to effectively add a new member to the existing structure occurs [see: Case Study: Adding Members to Centrally Allocated Kernel Data Structures and Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures].
Caveats
There are some fairly strict requirements that the existing kernel data structure predicted to need members added to at some future time must meet [see: Case Study: Adding Members to Centrally Allocated Kernel Data Structures and Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures].
This method is not allowed after an encumbered data structure’s symbol has become protected. The extension member must be implemented prior to becoming locked down, possibly pro-actively as part of the release’s origin. This implies that any kernel data structures that will need members added to them at some future date should be predicted, and extensions enacted, up front whenever possible, via “experience”.
Case Study: Adding Members to Centrally Allocated Kernel Data Structures
Upstream reference: commit 338c3149a22
RHEL7 back-port ref: commit 8e7c115586d [24]
The case study involves adding new members to existing kernel data structures. Its focus is the specific case where all allocations of the existing structure needing augmented are performed by a single, centralized, in-kernel-core routine. There are multiple aspects involved to making this technique work without breaking kABI. This technique requires a pre-requisite setup step [see: Case Study: Pro-actively Extending Core Kernel Data Structures] that itself must be fully comprehended before proceeding.
During RHEL 7.3’s development phase, upstream commit 338c3149a22
“PCI: Add support for multiple DMA aliases” was needed. The new
feature was dependent upon a new ’dma_alias_mask’ member being
added to ’struct pci_dev’.
diff -git a/include/linux/pci.h b/include/linux/pci.h
index 7e7019064437..5581d05f7833 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -273,7 +271,7 @@ struct pci_dev {
u8 rom_base_reg; /* which config register controls the ROM */
u8 pin; /* which interrupt pin this device uses */
u16 pcie_flags_reg; /* cached PCIe Capabilities Register */
- u8 dma_alias_devfn;/* devfn of DMA alias, if any */
+ unsigned long *dma_alias_mask;/* mask of enabled devfn aliases */
struct pci_driver *driver; /* which driver has allocated this device */
u64 dma_mask; /* Mask of the bits of bus address this
The ’pci_dev’ data structure from the kernel’s PCI subsystem is
foundational to its subsystem – thus is altered by upstream changes on a
consistent basis.[7] Additionally, being such a core data
structure, there are numerous kABI-protected symbols within PCI’s
subsystem that reference it, so any changes to it would normally break
kABI for many symbols. Fortunately, ’struct pci_dev’ meets very
strict requirements that allows new members to be logically added to it:
it is strictly allocated from the kernel’s heap via a common function -
’pci_alloc_dev()’.
[25]
If a data structure is strictly allocated from the kernel’s heap (i.e., no static definitions of the structure are allowed, nor are automatic allocations on the stack) then there is a technique that can be used for adding new members to it without breaking kABI. [26]
Intuitive, or experienced, developers will eventually ask: “What
prevents an external driver module from allocating a
’struct pci_dev’ within itself, either statically (within the
source file’s scope) or automatically (as an automatic variable within a
routine; thus resides on the routine’s stack)?” [27] Nothing, however if one researches, via ’cscope(1)’ or
some similar tool, all ’struct pci_dev’ allocations within the
kernel and it’s built-in modules, they will see that all allocations of
’pci_dev’ utilize ’pci_alloc_dev()’. This is where kABI
enters a “grey area”: nothing prevents an external driver module from
doing such, but, it would be considered ill formed; improper programming
by a module is not considered a kABI breakage.
With the aforementioned setup step – the extension mechanism from commit
c9be03bbc1c – in place, the subsystem’s common allocation related
functions next need to be augmented to include allocating the additional
extension structure along side its parent. While it makes sense to do
this coincident with the addition of the extension structure in this
particular case – where allocations are handled by a common
in-kernel-core function within the subsystem – it is not absolutely
necessary to perform this specific step until a specific need arises as
is covered in a different case study [see: Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures]. This was accomplished via RHEL7 commit 9599688156b
“PCI: Convert ’pci_dev’ allocations and frees”.[23]
diff -git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 38e403dddf6..24da7642d3c 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1170,6 +1170,7 @@ static void pci_release_dev(struct device *dev)
pci_release_of_node(pci_dev);
pcibios_release_device(pci_dev);
pci_bus_put(pci_dev->bus);
+ kfree(pci_dev->pci_dev_rh);
kfree(pci_dev);
}
@@ -1234,6 +1235,12 @@ struct pci_dev *pci_alloc_dev(struct pci_bus *bus)
if (!dev)
return NULL;
+ dev->pci_dev_rh = kzalloc(sizeof(struct pci_dev_rh), GFP_KERNEL);
+ if (!dev->pci_dev_rh) {
+ kfree(dev);
+ return NULL;
+ }
+
INIT_LIST_HEAD(&dev->bus_list);
dev->dev.type = &pci_dev_type;
dev->bus = pci_bus_get(bus);
@@ -1308,6 +1315,7 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
if (pci_setup_device(dev)) {
pci_bus_put(dev->bus);
+ kfree(dev->pci_dev_rh);
kfree(dev);
return NULL;
}
With an opaque, RHEL specific extension structure now in place, and
fully backed (allocated), future upstream changes to the structure which
would normally break kABI can now be brought into RHEL by placing new
members into the corresponding extension structure such as was done in
this case study’s RHEL7 commit - 8e7c115586d “PCI: Add support for
multiple DMA aliases”.
@@ -398,7 +396,7 @@ struct pci_dev {
* going forward. This structure will never be under KABI restrictions.
*/
struct pci_dev_rh {
- RH_KABI_EXTEND(u8 dma_alias_devfn) /* devfn of DMA alias, ..
+ RH_KABI_EXTEND(unsigned long *dma_alias_mask) /* mask of ..
RH_KABI_EXTEND(char *driver_override) /* Driver name to ..
#ifdef CONFIG_PPC64
RH_KABI_EXTEND(struct pci_dn *pci_data) /* PCI device node*/
Any existing external module references to members of
’struct pci_dev’ will remain valid. Yes, there will effectively be
new members to ’struct pci_dev’, placed into the extension
structure; however, external modules compiled against any earlier RHEL
kernel will not be aware of such additions and so will never attempt to
access the member or its backing memory.
With a solution in place that is understood not to logically break
existing external modules, the change would still end up altering all
symbols with associations to ’struct pci_dev’. The final piece of
the overall solution is to use RHEL’s ’RH_KABI_EXTEND()’ macro
to circumvent GENKSYMS based checking from detecting the
change.[18]
Caveats
There are some fairly strict requirements that the existing kernel data structure predicted to need members added to at some future time must meet.
This method is not allowed after an encumbered data structure’s symbol has become protected. The extension member must be implemented prior to becoming locked down, possibly pro-actively as part of the release’s origin.
One concerning caveat to this method is bringing in references to any
new members introduced in this fashion from upstream. As a direct result
of new members being added to RHEL’s specific extension structure,
references to any such members will have to be adjusted to account for
the extra level of indirection incurred when back-porting (i.e.,
upstream references like ’pci_dev→<member>’ have to be
converted to ’pci_dev→pci_dev_rh→<member>’).
Only the addition of new members is allowed. Member deletions, and even re-arrangements, are prohibited.
Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures
Upstream reference: commit 2d984ad132a
RHEL7 back-port reference: commit bef916144ab [28]
This case study is similar to Case Study: Adding Members to Centrally Allocated Kernel Data Structures in that it is
concerned with adding new members to an existing kernel data structure,
in this case ’struct device’.
[29]What
differs, however, is that the existing kernel data structure does not
have a centralized allocation routine with which to collocate the
extension’s allocation, introducing an additional consideration that has
to be taken into account, making things quite a bit more complex, and
worth specifically looking at.
The same, pre-requisite, associated extension structure setup is
necessary and took place during RHEL7’s initial kernel development
phase, via commit 9f84482488d “device: Create ’device_rh’ KABI
shadowing structure”, which put a pointer to a RHEL specific kABI
extension structure, ’struct device_rh’, onto the end of
’struct device’. [30] driver-core: Add
KABI shadow structs to kernel’s driver subsys 3ba6b481780 device:
Create ’device_driver_rh’ KABI shadowing structure 9f84482488d
device: Create ’device_rh’ KABI shadowing structure]
diff -git a/include/linux/device.h b/include/linux/device.h
index 19f4329b539..f8374e30669 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -754,6 +754,19 @@ struct device {
bool offline_disabled:1;
bool offline:1;
+
+ /* Extension to accomodate future upstream changes to this structure
+ * yet maintain RHEL7 KABI. For Red Hat internal use only!
+ */
+ struct device_rh *device_rh;
+};
+
+/* RHEL7 specific 'struct device_rh' shadow structure to help maintain KABI
+ * going forward. This structure will never be under KABI restrictions.
+ */
+struct device_rh {
+#ifndef __GENKSYMS__
+#endif
};
static inline struct device *kobj_to_dev(struct kobject *kobj)
The ’device’ structure, from the kernel’s device driver subsystem,
underlies every device that runs on Linux. The sequence that device
drivers typically use with respect to getting a device initialized –
device registration – is to first obtain an allocated ’device’
structure, typically via inheritance from a device specific subsystem,
and then, at some later time, call ’device_add()’ which adds the
device entry to the kernel with reference counting. As such,
’struct device’ does not have a centralized function for its
allocation; allocations are distributed throughout the kernel.
Distributed allocations of ’device’ structures, typically from within
each of the kernel’s specific device related subsystem’s themselves,
introduces an additional consideration that must be taken into account:
“Where is a good place to add the allocation of the corresponding kABI
extension structure?” When the need to add a member to RHEL’s
’device’ structure finally arose – during the kernel development phase
of RHEL 7.2 – commit 928a66966c2 “device: allocate/free Red Hat only
struct device_rh” took the approach of not collocating RHEL’s
extension structure allocations with their respective ’device’
structure allocations, but instead, to augment the ’device_add()’
function to perform the extension’s allocation.
diff -git a/drivers/base/core.c b/drivers/base/core.c
index aa6cc6f7f2d..75ed70b3a78 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -1060,6 +1060,10 @@ int device_add(struct device *dev)
if (!dev)
goto done;
+ /* allocate the Red Hat only struct */
+ dev->device_rh = kzalloc(sizeof(struct device_rh),
+ GFP_KERNEL | __GFP_NOFAIL);
+
if (!dev->p) {
error = device_private_init(dev);
if (error)
@@ -1304,6 +1308,8 @@ void device_del(struct device *dev)
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
cleanup_device_parent(dev);
kobject_del(&dev->kobj);
+ /* This free's the allocation done in device_add() */
+ kfree(dev->device_rh);
put_device(parent);
}
As device registration first obtains an allocated ’device’ structure
and then, some separate time later, calls ’device_add()’, there is
a window of time in which ’struct device’ exists, and can be accessed,
prior to device registration’s completion via ’device_add()’, and
there are driver, and kernel, paths that access members of
’struct device’ within this window. This is not an issue for upstream,
as all accesses obviously occur after the allocation. A consequence of
the approach taken in this case for RHEL, however, is accesses within
this window that, now by extension, reference ’struct device_rh’.
Such accesses cause the kernel to panic de-referencing a NULL pointer
with RHEL as a consequence of ’struct device_rh’ not yet having
been allocated. As such, some code paths need the extension structure
allocated prior to ’device_add()’ being called, which subsequently
requires extra checking within RHEL’s customized ’device_add()’
routine to see if ’struct device_rh’ has already been allocated or
not. This was covered with commit ef52e4a544c “device: add
device_rh_alloc()”.
diff --git a/drivers/base/core.c b/drivers/base/core.c
index d142ffcefc2..2b210cf4b0f 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -1039,6 +1039,16 @@ int device_private_init(struct device *dev)
return 0;
}
+/** device_rh_alloc - allocate a device_rh
+ * @dev: device to attach device_rh to.
+ */
+void device_rh_alloc(struct device *dev)
+{
+ dev->device_rh = kzalloc(sizeof(struct device_rh),
+ GFP_KERNEL | __GFP_NOFAIL);
+}
+EXPORT_SYMBOL_GPL(device_rh_alloc);
+
/**
* device_add - add device to device hierarchy.
* @dev: device.
@@ -1072,9 +1082,17 @@ int device_add(struct device *dev)
if (!dev)
goto done;
- /* allocate the Red Hat only struct */
- dev->device_rh = kzalloc(sizeof(struct device_rh),
- GFP_KERNEL | __GFP_NOFAIL);
+ /* In general, this is where we allocate the Red Hat only struct.
+ * A known exception is the acpi_bus_scan() which requires elements
+ * from the device_rh struct during initialization. This test will
+ * allow code to allocate & use the device_rh struct prior to calling
+ * device_add().
+ *
+ * Regardless of when allocated, the device_rh will be free'd at the
+ * end of device_del().
+ */
+ if (!dev->device_rh)
+ device_rh_alloc(dev);
if (!dev->p) {
error = device_private_init(dev);
The default extension related allocation remains within
’device_add()’; however, extension allocations necessary prior to
such now have to be enacted throughout the kernel in a somewhat ad hoc,
case-by-case, manner; usually upon each occurrence of a de-referencing
panic (along with the additional work of root cause analysis and
re-working of the path’s specific, allocation related, need).
[31] [32]
diff --git a/drivers/ata/libata-transport.c b/drivers/ata/libata-transport.c
index fd29b722408..6c52db6e941 100644
--- a/drivers/ata/libata-transport.c
+++ b/drivers/ata/libata-transport.c
@@ -660,6 +660,7 @@ static int ata_tdev_add(struct ata_device *ata_dev)
int error;
device_initialize(dev);
+ device_rh_alloc(dev);
dev->parent = get_device(&link->tdev);
dev->release = ata_tdev_release;
if (ata_is_host_link(link))
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index 55684f06283..f6a50dbe3bd 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -209,6 +209,7 @@ struct platform_device *platform_device_alloc(const char *name, int id)
device_initialize(&pa->pdev.dev);
pa->pdev.dev.release = platform_device_release;
arch_setup_pdev_archdata(&pa->pdev);
+ device_rh_alloc(&pa->pdev.dev);
}
return pa ? &pa->pdev : NULL;
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 18c5a9c2b82..6fa2f418ea3 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -568,6 +568,7 @@ static struct pci_host_bridge *pci_alloc_host_bridge(struct pci_bus *b)
bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
if (!bridge)
return NULL;
+ device_rh_alloc(&bridge->dev);
INIT_LIST_HEAD(&bridge->windows);
bridge->bus = b;
diff --git a/drivers/pnp/core.c b/drivers/pnp/core.c
index cb6ce42f8e7..0c8d8f8977f 100644
--- a/drivers/pnp/core.c
+++ b/drivers/pnp/core.c
@@ -133,6 +133,7 @@ struct pnp_dev *pnp_alloc_dev(struct pnp_protocol *protocol, int id,
dev = kzalloc(sizeof(struct pnp_dev), GFP_KERNEL);
if (!dev)
return NULL;
+ device_rh_alloc(&dev->dev);
INIT_LIST_HEAD(&dev->resources);
INIT_LIST_HEAD(&dev->options);
From here on out the case study is similar to Case Study: Adding Members to Centrally Allocated Kernel Data Structures with the additional caveat that additional latent issues yet uncovered may yet occur and need addressed as outlined.
Caveats
This method is not allowed after an encumbered data structure’s symbol has become protected. The extension member must be implemented prior to becoming locked down, possibly pro-actively as part of the release’s origin.
One caveat to this method concerns bringing in future references to any
new members introduced in this fashion from upstream. As a direct result
of new members being added to RHEL’s specific extension structure,
references to any such members will have to be adjusted to account for
the extra level of indirection incurred when back-porting (i.e.,
upstream references like ’dev→<member>’ have to be converted
to ’dev→device_rh→<member>’).
Only the addition of new members is allowed. Member deletions, re-naming, and even re-arrangements, are prohibited.
Case Study: Exploiting Holes within Kernel Data Structures
Upstream reference: commit 9d26d3a8f1b
RHEL7 back-port reference: commit a040fb72b14 [33]
This case study covers the easiest approach for adding members to existing kernel data structures without breaking kABI. As the study’s title suggests, it involves exploiting existing holes within kernel data structures. Kernel developers typically take data structure member alignment and padding aspects into consideration during development, attempting to both align, and pack, structures as efficiently, and tightly, as possible. [34] Even so, holes within structures sometimes exist.
If an existing hole can be found within a protected kernel data structure that, including all alignment concerns, is equal to or greater than the size of a new member needing to be added, the hole can be exploited for adding the member while still maintaining kABI.
An upstream patch, targeted for back-porting into RHEL, added a new
’bridge_d3’ member to ’struct pci_dev’.[7]
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 8d748345b158..8597b423cb63 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -294,6 +294,7 @@ struct pci_dev {
unsigned int d2_support:1; /* Low power state D2 is supported */
unsigned int no_d1d2:1; /* D1 and D2 are forbidden */
unsigned int no_d3cold:1; /* D3cold is forbidden */
+ unsigned int bridge_d3:1; /* Allow D3 for bridge */
unsigned int d3cold_allowed:1; /* D3cold is allowed by user */
unsigned int mmio_always_on:1; /* disallow turning off io/mem
decoding during bar sizing */
The ’pci_dev’ structure is the base data structure of the kernel’s
PCI subsystem, and as such, there are numerous kABI-protected symbols
with associations to it. A straight back-port would break kABI due to
its side-effect of changing the version checksums of all symbols
’pci_dev’ is correlated with.
The new member is a single bit bit-field declaration, and with careful
inspection of RHEL’s ’struct pci_dev’ (RHEL 7’s structure varies
from upstream’s), there looked to be a “hole” between the end of the
previously existing bit-fields and the ’d3_delay’ member. Running
the ’pahole(1)’ utility against ’struct pci_dev’ confirmed that
a 14-bit hole existed:
/* Bitfield combined with previous fields */ unsigned int pme_support:5; /* 124:19 4 */ ... unsigned int bridge_d3:1; /* 124: 7 4 */ /* XXX 14 bits hole, try to pack */
RHEL7 Patch 09/23 “PCI: Put PCIe ports into D3 during suspend” used just such an exploitation. During the back-port, the new member was inserted within the existing hole. As the hole already existed, both the size of the overall structure, and the offsets of all existing members within the structure, were not altered as long as the insertion was performed at the end of the bit fields. Incidentally, any existing external modules would not know about the newly introduced member, thus should never attempt to access that part of the structure’s memory.
While the solution is understood not to logically effect existing
external modules, the change would still end up altering all symbols
with associations to ’struct pci_dev’. The final piece of the
overall solution is to use RHEL’s ’RH_KABI_FILL_HOLE()’
macro to specifically circumvent GENKSYMS based checking
from detecting the change.[18]
Caveats
A benefit of this tactic is that all code references of the new member do not have to be modified during the initial, and all future subsequent, back-ports.
Holes within existing kernel data structures that can be utilized are very rare. One may want to consider saving them for performance-sensitive code additions, if another workaround is possible. If there are no other methods available and the hole is the last one available, one may want to consider not back-porting the code in question, instead saving the hole for use with a more critical back-port requiring a kABI workaround.
Case Study: Kernel Header Files
Upstream reference: commit 589fcc23074
RHEL7 back-port reference: commit 0c9beb71177 [35] Re-base PCI subsystem with v3.18 content
0c9beb71177 [RHEL7.3 PATCH 15/16] PCI: Move
pci_configure_slot() to drivers/pci/probe.c]
There is an interesting situation concerning forward declarations of
kernel data structures that yields “false kABI failures” from the
checking tools. The scenario typically involves header files in which
prior to the patch series’ changes, a source file only knows about a
specific data structure via an incomplete forward declaration (i.e.,
struct pci_dev;). Then, as a direct result of the patch including
an additional header file, the source file now knows the structure’s
full declaration (i.e., struct pci_dev { … };).
Basically what has occurred with respect to the source file is that what was an opaque reference to a structure subsequently becomes non-opaque due to the new header file inclusion that adds – directly, or indirectly – the structure’s full declaration.
Including header files may change the parsing of structures, which can result in a false-positive kABI failure.
These situations can be particularly hard to track down and isolate as the root of the kABI failure.
During RHEL7.3’s kernel development phase, commit 0c9beb71177 "` PCI:
Move pci_configure_slot() to drivers/pci/probe.c`" was
pro-actively back-ported, moving a function from one source file to
another.[35] As a necessary result of the move, an additional
header file was included within the destination’s source file.
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 4c22517..3c01e49 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -6,6 +6,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/pci.h> +#include <linux/pci_hotplug.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/cpumask.h>
What was particularly interesting about this circumstance was that
RHEL’s build related kABI checking did not flag a kABI failure whereas
the ’ksc(1)’ kABI checking tool, which was used by a partner,
indicated that the following symbol’s kABI checksums changed and affects
(breaks) their modules, leading them to request for the symbols to
become protected:
pci_read_config_dword pci_read_config_word pci_write_config_byte pci_read_config_byte pci_write_config_word pci_write_config_dword
These specific symbols were not kABI white-listed (thus why this
situation was not caught during the back-port’s kernel build related
checking); however, the pci_read/write_config which are kABI
white-listed. [36] functions
are really just wrappers around
pci_bus_read/write_config_
This specific instance’s circumstances are unique. As the symbols were not white-listed, RHEL kernel developers were very reluctant to add them as the partner had requested as this could be a slippery slope that leading to RHEL being requested to preserve all the kernel’s exported symbols.
Regardless of the specific circumstances, once isolated, these types of
false-failures are generally fairly easy to rectify as they can be
worked around by the use of the GENKSYMS pre-processing
directive.
For this specific case, it exposed a bug in the ’ksc(1)’ tool, so
RHEL’s kernel development team acquiesced, enacting the easy fix to
restore the original checksums, but in a manner explicitly not adding
the symbols to the white-list, via RHEL7 commit 0c348a6cd87.
[37]
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 6fa2f418ea3..cba8d30d4e6 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -6,7 +6,9 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/pci.h> +#ifndef __GENKSYMS__ #include <linux/pci_hotplug.h> +#endif #include <linux/slab.h> #include <linux/module.h> #include <linux/cpumask.h>
This same technique can often be used to work around false kABI failures with similar – indirect forward declarations of data structures becoming full forward declarations – scenarios.
Caveats
If a header file has kABI protected structures in it, and is the only
inclusive path to an EXPORT_SYMBOL{_GPL}(), the
GENKSYMS masking scenario as outlined will now hide all
future kABI check failures that should have caught kABI protected
structure changes within the header. Masking header files in this manner
should be carefully done and may require splitting the header, or
specific content therein, in a RHEL-only manner to limit the scope of
the GENKSYMS wrapping.
Case Study: Enumerations
Upstream reference: commit 95e300c052f
RHEL7 back-port reference: commit a57906f6a8f [38] ipmi: Make the DMI probe into a
generic platform probe]
Changes to enumeration ordering can affect external modules. RHEL’s current kABI checking capabilities may not catch simple enumeration related changes that indeed affect modules, leading to silent kABI failures.
Note how RHEL7.6 commit a57906f6a8f “IPMI: Make the DMI probe into a
generic platform probe” was able to maintain the values of previously
existing enumerations to preserve kABI.
diff --git a/include/linux/ipmi.h b/include/linux/ipmi.h
index c0c3aed..eac5879 100644
--- a/include/linux/ipmi.h
+++ b/include/linux/ipmi.h
@@ -281,7 +281,10 @@ int ipmi_validate_addr(struct ipmi_addr *addr, int len);
*/
enum ipmi_addr_src {
SI_INVALID = 0, SI_HOTMOD, SI_HARDCODED, SI_SPMI, SI_ACPI, SI_SMBIOS,
- SI_PCI, SI_DEVICETREE, RH_KABI_RENAME(SI_DEFAULT, SI_LAST)
+ SI_PCI, SI_DEVICETREE, SI_DEFAULT
+#ifndef __GENKSYMS__
+ , SI_PLATFORM = SI_DEFAULT, SI_LAST
+#endif
};
const char *ipmi_addr_src_to_str(enum ipmi_addr_src src);
Fortunately, updates of enumerations are fairly easy to adhere to kABI’s requirements. The most common tactic is to convert un-assigned enumeration lists into explicitly assigned lists, and then if adding new entries is needed, do so in a manner that does not affect any previous entries (i.e., if a new, upstream, enumeration was added within an existing list, keep the list ordered as it was and add the new entry to the end of the list instead).
kABI: Canonical Method for Extending Kernel Data Structures
kABI’s impact on kernel data structures is its most subtle, yet arguably also its most severe, aspect that kernel developers must be aware of. To circumvent this particularly onerous problem, a number of innovative implementations have evolved over the years – hash based associations to shadow structures, directly associated opaque extension structures, and pre-reserved additions with directly associated opaque extension structures – to name a few.
So, while there have been many iterative solutions developed over the years, the consensus is that the method developed and used within the kernel’s networking subsystem – a combination of pre-reserving entries with an additional “extension” structure – is considered to be the canonical model to follow currently.
One of the key data structures within the kernel’s networking subsystem
is ’net_device_ops’.
[39]
This structure is altered upstream on a consistent basis, and there are
numerous kABI-protected symbols within networking’s subsystem that
reference it. Normally, any changes to ’struct net_device_ops’
would break kABI (and KMI), for all symbols it is correlated with. As
such, let’s analyze, via software archeology, the evolution of enabling
extensions to it over the lifecycle of RHEL 7’s release, specifically
focusing on the tactics it implemented, including explanations as to
“why” the tactics were necessary. While there is some overlap, it
would be best to have the background knowledge, and context, from the
kernel data structure related case studies in Appendix B (Case Study: Pro-actively Extending Core Kernel Data Structures,
Case Study: Adding Members to Centrally Allocated Kernel Data Structures, Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures & Case Study: Exploiting Holes within Kernel Data Structures) before continuing on.
As should be evident, kABI does not come into play until the originating
major release’s General Availability (GA) (e.g., 7.0).[13] Prior
to such, external modules do not yet have a kernel basis against which
to build against, so all kernel development activities are unencumbered.
RHEL developers, especially maintainers of core kernel subsystems,
should take advantage of this possibly once-in-a-release opportunity to
get their subsystem’s header files updated to upstream’s ’tip’. RHEL 7
commit 4a59d5e8211 “[net] core: Add VF link state control”
did just that, pulling in recent updates to
’struct net_device_ops’ from upstream (commit 1d8faf48c74)
prior to the release’s launch. [40]
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index f7ec329dd33..553d6be0159 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -839,6 +839,7 @@ struct netdev_phys_port_id {
* int (*ndo_set_vf_spoofchk)(struct net_device *dev, int vf, bool setting);
* int (*ndo_get_vf_config)(struct net_device *dev,
* int vf, struct ifla_vf_info *ivf);
+ * int (*ndo_set_vf_link_state)(struct net_device *dev, int vf, int link_state);
* int (*ndo_set_vf_port)(struct net_device *dev, int vf,
* struct nlattr *port[]);
* int (*ndo_get_vf_port)(struct net_device *dev, int vf, struct sk_buff *skb);
@@ -1002,6 +1003,8 @@ struct net_device_ops {
int (*ndo_get_vf_config)(struct net_device *dev,
int vf,
struct ifla_vf_info *ivf);
+ int (*ndo_set_vf_link_state)(struct net_device *dev,
+ int vf, int link_state);
int (*ndo_set_vf_port)(struct net_device *dev,
int vf,
struct nlattr *port[]);
Subsequently, as a necessary action prior to the release’s launch, RHEL7
specific commit 259662d355d “net: Reserve extension space for ’struct
net_device_ops’“ was incorporated. This pro-active step was
enacted specifically to obviate concerns surrounding kABI - in order
that additions to ’struct net_device_ops’ could occur going
forward without affecting any future protected symbols with which the
structure is correlated.
Upon the occurrence of a symbol with which a structure is correlated
with becoming protected, which for ’net_device_ops’ is an all
but forgone conclusion, the structure becomes locked down from any
future changes if not for actions such as this. As such, it is
especially important for maintainers of externally facing core kernel
subsystems such as networking, and PCI, (to name a couple) to take
similar actions as their foundational data structures are the most
likely to become encumbered with symbols becoming protected.
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 553d6be0159..6d6007e2df9 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1078,6 +1078,32 @@ struct net_device_ops {
bool new_carrier);
int (*ndo_get_phys_port_id)(struct net_device *dev,
struct netdev_phys_port_id *ppid);
+
+ /* RHEL SPECIFIC
+ *
+ * The following padding has been inserted before ABI freeze to
+ * allow extending the structure while preserve ABI. Feel free
+ * to replace reserved slots with required structure field
+ * additions of your backport.
+ */
+#ifndef __GENKSYMS__
+ void (*rh_reserved1)(void);
+ void (*rh_reserved2)(void);
+ void (*rh_reserved3)(void);
...
+ void (*rh_reserved14)(void);
+ void (*rh_reserved15)(void);
+ void (*rh_reserved16)(void);
+#endif
};
/*
At the last minute, just prior to the release’s GA, RHEL 7 specific
commit 023ac646f29 “kabi: Remove GENKSYMS usage through the code”
was enacted. [41]
…
However folks were using this wrapper to clearly indicate which chunks of code were being reserved for Red Hat purposes.
After thinking about this and discussing it with folks, we realized this is not a good idea. Two reasons have stuck out as to why.
First, the idea was that hiding the symbols in the GENKSYSMS wrapper, we hide the true checksum of the symbols. We should try to encompass the true checksum for GA. On the flip side, removing the macro and doing things properly for 7.1 would entail:
struct foo {
int bar;
#ifndef __GENKSYMS__
int original;
#else
int new;
#endif
}
which leads to ugly and hard to read code. I agreed to create some macros in 7.1 that would hide the ugliness, yet make it obvious what is going on.
Second, and this is more a social problem, most folks have come to assume that anything inside GENKSYMS is free to be modiifed/added. But in this particular example, this is _not the case. So to prevent the confusion, we should avoid this usage for reserving memory._
As a result, I have removed all uses I believe were not correct. There should be no downside as this only affects how genksyms pre-processes the symbols and generates checksums. The only noticable change will be new kabi checksums for a bunch of functions.
…
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index c23d772f8b3..42e58d229b1 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1105,7 +1105,6 @@ struct net_device_ops {
* to replace reserved slots with required structure field
* additions of your backport.
*/
-#ifndef __GENKSYMS__
void (*rh_reserved1)(void);
void (*rh_reserved2)(void);
void (*rh_reserved3)(void);
@@ -1122,7 +1121,6 @@ struct net_device_ops {
void (*rh_reserved14)(void);
void (*rh_reserved15)(void);
void (*rh_reserved16)(void);
-#endif
};
/*
With these actions (specifically commit 259662d355d), RHEL’s
networking subsystem’s ’struct net_device_ops’ was equipped
for taking in future extensions (new members) while not affecting
(breaking) kABI. These actions are similar in effect to what case study
Case Study: Pro-actively Extending Core Kernel Data Structures – enacted. Such actions are predictive, based on
experience, and must be pro-active due to symbols starting to become
protected and external modules beginning to build against a just
released major version kernel.
As a result of the actions behind the context of RHEL7 commit
023ac646f29 (too many kABI-naive engineers overusing, and abusing,
GENKSYMS; anything inside GENKSYMS is not
free to be modified/added), RHEL specific kABI macros were developed,
and incorporated, during 7.1’s minor update development cycle [see:
Appendix C include/linux/rh_kabi.h
include/linux/rh_kabi.h].[13] Subsequently, commit
2d1a734afb0 “kernel: Use new KABI macros” converted
’struct net_device_ops’ reserved entries over to the new
macros, specifically ’RH_KABI_RESERVE_P()’.
@@ -1105,22 +1107,22 @@ struct net_device_ops {
* to replace reserved slots with required structure field
* additions of your backport.
*/
- void (*rh_reserved1)(void);
- void (*rh_reserved2)(void);
- void (*rh_reserved3)(void);
...
- void (*rh_reserved14)(void);
- void (*rh_reserved15)(void);
- void (*rh_reserved16)(void);
+ RH_KABI_RESERVE_P(1)
+ RH_KABI_RESERVE_P(2)
+ RH_KABI_RESERVE_P(3)
...
+ RH_KABI_RESERVE_P(14)
+ RH_KABI_RESERVE_P(15)
+ RH_KABI_RESERVE_P(16)
};
/*
With the kABI related reserved extension placeholders in place, along
with the inevitable upstream changes to
’struct net_device_ops’ beginning to take place, exchanges
such as commit 332b89c4cae “[net] introduce
dev_get_iflink()” started occurring, adding new functionality
via the new members introduced by such exchanges.
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 239157b4ebb..02b67f7e3c4 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -970,6 +970,9 @@ struct netdev_phys_port_id {
* Called by vxlan to notify the driver about a UDP port and socket
* address family that vxlan is not listening to anymore. The operation
* is protected by the vxlan_net->sock_lock.
+ *
+ * int (*ndo_get_iflink)(const struct net_device *dev);
+ * Called to get the iflink value of this device.
*/
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
@@ -1114,7 +1117,7 @@ struct net_device_ops {
* to replace reserved slots with required structure field
* additions of your backport.
*/
- RH_KABI_RESERVE_P(1)
+ RH_KABI_USE_P(1, int (*ndo_get_iflink)(const struct net_device *dev))
RH_KABI_RESERVE_P(2)
RH_KABI_RESERVE_P(3)
RH_KABI_RESERVE_P(4)
Normally adding new functionality (members) to the structure would affect kABI but the placeholders circumvented such unintended consequences.
The ’net_device_ops’ data structure exhibits a unique aspect
in that it consists exclusively of function pointers to network device
operations (NDO’s): each, and every one, of its members is going to be
the same size - 8 Bytes. So later, with a back-port like 332b89c4cae
where ’RH_KABI_RESERVE_P(1)’ is exchanged for
“`int (*ndo_get_iflink)(…)”, the conversion is
exchanging 8 Bytes (i.e., “`unsigned long rh_reserved##n”) with 8
Bytes, and due to the structure’s nature, exchanges are always going to
be similarly sized, thus the structure’s overall size remains constant.
As such it’s too easy to miss the subtle, yet important, aspects that
’RH_KABI_USE_P()’ plays in such exchanges.
In the back-port of upstream commit 125c99bc8b6 “scsi: add new
scsi-command flag for tagged commands” (RHEL7 commit 1a89161c09a), a
similar kABI related exchange scenario was enacted. In this specific
case however, the exchange’s resultant type was an ’int’ which is 4
Bytes. [42]
diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h
index 9b1ebe610b5..bc2c64ce04f 100644
--- a/include/scsi/scsi_cmnd.h
+++ b/include/scsi/scsi_cmnd.h
@@ -54,6 +54,9 @@ struct scsi_pointer {
volatile int phase;
};
+/* for scmd-flags */
+#define SCMD_TAGGED (1 0)
+
struct scsi_cmnd {
struct scsi_device *device;
struct list_head list; /* scsi_cmnd participates in queue lists */
@@ -140,7 +143,7 @@ struct scsi_cmnd {
* The following padding has been inserted before ABI freeze to
* allow extending the structure while preserve ABI.
*/
- RH_KABI_RESERVE_P(1)
+ RH_KABI_USE_P(1, int flags) /* Command flags */
RH_KABI_RESERVE_P(2)
RH_KABI_RESERVE_P(3)
RH_KABI_RESERVE_P(4)
An exchange of 8 Bytes with 4 Bytes would change the overall size of
’struct scsi_pointer’, resulting in a kABI failure. Perhaps
natural alignment aspects would make this work (i.e., there will be a 4
Byte hole included after ’flags’ and before
’RH_KABI_RESERVE_P(2)’) but that is likely architecture
dependent and should not be relied upon. Careful inspection of
’RH_KABI_USE_P()’ reveals that its replacement mechanism
includes putting the new member into a union with an ’unsigned long’
member which takes care of two things. It ensures there is proper
padding so the resulting size doesn’t “shrink” and remains 8 Bytes. It
also checks that the result didn’t “grow” over 8 Bytes
(’sizeof(unsigned long)’).
So RH_KABI_USE_P() (and RH_KABI_USE()) play an
important role that developers could easily overlook in such kABI
related exchange scenarios: they help maintain the structure’s overall
size for all various exchange types so as not to inadvertently break
kABI. More specifically: From the ’check-kabi’ standpoint, things
might be fine, due to GENKSYMS wrappers, but we’d actually
end up with silent kABI breakage – we’d just be lying that kABI wasn’t
broken, effectively.
During RHEL 7.3’s kernel development phase it was recognized that
’struct net_device_ops’ was beginning to run low on kABI’s
pre-reserved entries (’RH_KABI_RESERVE_P(n)’) put in place
earlier via commit 259662d355d. As a result, commit e33a3c136b5
“net: introduce net_device_ops_extended” was developed.
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index e599072f4c0..750dc19d214 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -805,6 +805,9 @@ struct tc_to_netdev {
};
+struct net_device_ops_extended {
+};
+
/*
* This structure defines the management hooks for network devices.
* The following hooks can be defined; unless noted otherwise, they are
@@ -1286,9 +1289,25 @@ struct net_device_ops {
sa_family_t sa_family,
__be16 port))
RH_KABI_RESERVE_P(15)
- RH_KABI_RESERVE_P(16)
+ RH_KABI_USE_P(16, size_t ndo_size)
+ /* RHEL: put all new non-performance critical ndo's into
+ * net_device_ops_extended. The reserved slots above can be used
+ * only for performance critical operations.
+ * Drivers may access the extended fields directly from
+ * net_device_ops, if they allocated the net_device_ops structure
+ * themselves (usually statically). The kernel core and drivers
+ * using others' net_device_ops must access the extended fields
+ * using the get_ndo_ext macro.
+ */
+ RH_KABI_EXTEND(struct net_device_ops_extended extended)
};
+#define get_ndo_ext(ops, field) ({ \
+ const struct net_device_ops *__ops = (ops); \
+ size_t __off = offsetof(struct net_device_ops, extended.field); \
+ __ops->ndo_size > __off ? __ops->extended.field : NULL; \
+ })
+
/**
* enum net_device_priv_flags - &struct net_device priv_flags
*
Commit 259662d355d accomplishes a number of inter-related, yet very
subtle, aspects. Similar to what was accomplished in Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures’s kABI
case study, a ’struct net_device_ops_extended’ extension
data structure was added onto the end of
’struct net_device_ops()’ via ’RH_KABI_EXTEND()’
which effectively masks the extension from ’genksyms’ symbol
versioning so as to not break kABI as a unintended consequence of
changing the structure’s overall size. What is different in this
instance, however, is that the extension structure is put into place
after the fact (i.e., after the structure had been locked down with
respect to kABI), and is appended onto the end of the originating
structure (’net_device_ops’) as opposed to adding a pointer to
a kABI specific opaque extension structure. In this particular
application, that’s an important tactic to the overall approaches’
strategy as it obviates all the allocation considerations, and
subsequent resultant issues, that Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures suffered.
Prior to this commit, new functionality (NDOs) were enacted via
substitutions - exchanging a pre-reserved slot with a new NDO function
pointer. After this commit, similar substitutions introducing new
functionality could occur as before, or via the
’struct net_device_ops_extended’ extension structure
introduced. As noted in the patch’s comment, the idea was add
“performance critical operations” via pre-reserved slot exchanges, and
to add “non-performance critical ndo’s” via the newly inserted
’net_device_ops_extended’ extension structure.
A critical component of this implementation relates to its multi-faceted guard mechanism. Extending kernel data structures correlated with protected symbols is all about enabling new feature content developed upstream – via back-ports into RHEL – without breaking RHEL’s kABI commitment. The kABI related extension mechanisms enable this, specifically allowing new members to be added to a structure encumbered with one or more protected symbols which can take place within any minor update’s development cycle. As a result, from the kernel’s perspective: protection encumbered data structures with extension mechanisms enabled pro-actively may change size (growing – from additions – only) during any minor update cycle.
| Kernel vn | Num members | Total size | Member offsets |
|---|---|---|---|
7.0 |
72 |
0x240 |
0x000, 0x008, …, 0x238 |
7.3 |
79 |
0x278 |
0x000, 0x008, …, 0x270 |
7.5 |
94 |
0x2F0 |
0x000, 0x008, …, 0x2E8 |
However, external modules that were compiled against earlier kernel versions, and thus would not know about such structure growth (“kernel/module variance”), must continue to load (attach) and run upon newer kernels - RHEL’s kABI commitment.[9] This implies that the kernel’s data structure growth has to be enacted in a way that accounts for this type of “kernel/module variance”. This is the concern that the guard mechanism involves.
As outlined in the commit’s message: going forward, all NDO additions
using ’net_device_ops_extended’ must have their references
“wrapped in the provided ’get_ndo_ext()’ macro”. Otherwise,
back-ported core kernel references to such NDO based extensions will not
even compile successfully. Additionally, again going forward, all
back-ported driver code needing to reference such NDO based extensions
must also be modified – which includes external modules – otherwise they
will not compile successfully. So inherent to the implementations
design, is this “built-in” guard mechanism’s implicit facet.
The guard mechanism’s other facet is explicit and has to do with its
’ndo_size’ member addition and corresponding
’get_ndo_ext()’ macro which act in concert with each other. As
covered in the commit’s message: “It’s intentionally a separate struct
because calling anything in the extended area is unsafe (because
’net_device_ops’ is allocated by drivers).” If driver code
needs to reference any of the NDO extensions, it has to set
’ndo_size’ as part of its allocation phase tasks as was done in
commit b62901b5257 “[net] move
ndo_dfwd_add/del_station to
net_device_ops_extended”.
diff --git a/drivers/net/.../ixgbe/ixgbe_main.c b/drivers/net/.../ixgbe/ixgbe_main.c
index 5168b849df4..159b5bbadcc 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -8655,6 +8655,7 @@ ixgbe_features_check(struct sk_buff *skb, struct net_device *dev,
}
static const struct net_device_ops ixgbe_netdev_ops = {
+ .ndo_size = sizeof(struct net_device_ops),
.ndo_open = ixgbe_open,
.ndo_stop = ixgbe_close,
.ndo_start_xmit = ixgbe_xmit_frame,
Consider the scenario where an external module that is some type of
networking related driver defines within itself a
’static struct net_device_ops <instance_name>;’
structure. [43] From the perspective of
this driver module, when compiled against a RHEL 7.0 kernel, its local
version of ’struct net_device_ops’ has 72 members and consumes
0x240 Bytes. Now assume that this module has attached to, and is running
on a newer kernel and calls a routine within the networking subsystem’s
core, passing in as an argument, a pointer to its copy of
’struct net_device_ops’ (i.e. <instance_name>).
The kernel’s point of view of ’struct net_device_ops’ includes
all the extensions that have taken place throughout all its updates
cycles. As such, code within it may attempt to access any one of the NDO
members added after 7.0’s GA.
To make the scenario concrete, assume:
-
the driver module was compiled against a 7.0 kernel;
-
the driver module attaches to, and is running on, a 7.5 kernel;
-
a routine within the kernel’s networking subsystem’s core, called by the module, accesses the last function pointer added onto the ’
net_device_ops_extended’ extension structure – member 94’s NDO – via a pointer the module defined within itself.
Without the guard mechanism in place, the resulting dereference will not
obtain what was intended, it would be accessing some completely
unrelated memory that is somewhere past the module’s allocated memory
backing
’net_device_ops’. At best, chaos will ensue and the kernel
will panic. At worst, data corruption will silently occur. This is where
the commit’s (259662d355d) guard mechanism explicitly comes into play,
circumventing this type of unintended kernel/module variance induced
occurrence.
Now consider the same scenario and concrete example, but, with the guard
mechanism – ’ndo_size’ and
’get_ndo_ext()’ – enacted by commit 259662d355d in place.
Even though the driver module has no knowledge of the new
’ndo_size’ member (as it was compiled against a 7.0 kernel), its
idea of the corresponding memory location backing
’net_device_ops’ – ’RH_KABI_RESERVE_P(16)’ –
will contain the value ’0’ as a result of the module’s
’struct net_device_ops’ allocation having been ’static’.
However, from the 7.5 kernel’s perspective, the memory offset
corresponds to the ’ndo_size’ member. As a result, when the kernel
attempts to access the member (94) via ’get_ndo_ext()’, the
macro’s computation will fail the guard’s test and return a NULL
value:
get_ndo_ext(ops, field) {
const struct net_device_ops *__ops = (ops); // Module's pointer
size_t __off = offsetof(struct net_device_ops, extended.field); // 0x2E8
__ops->ndo_size > __off ? __ops->extended.field : NULL; // 0x0 > 0x2E8 ?:=> NULL
}
As 259662d355d’s commit message states – “This is of somehow lesser
concern, as forgetting it won’t cause crashes, the pointer would just be
ignored.” – the dereference (pointer) obtains a NULL value and can be
ignored.
Finally, consider the scenario, but, with respect to a driver module that is cognizant of NDO extensions. Assume, as a concrete example:
-
the driver module was compiled against a 7.3 kernel;
-
the driver module attaches to, and is running on, a 7.5 kernel;
-
a routine within the kernel’s networking subsystem’s core, called by the module, accesses the last function pointer added onto the ’
net_device_ops_extended’ extension structure that the module is aware of - member 79’s NDO – via a pointer the module defined within itself.
When the kernel attempts to obtain member 79’s NDO via
’get_ndo_ext()’, the access passes the guard’s test and
successfully obtains the ’__ops→extended.field’ pointer:
get_ndo_ext(ops, field) {
const struct net_device_ops *__ops = (ops); // Kernel's pointer
size_t __off = offsetof(struct net_device_ops, extended.field); // 0x270
__ops->ndo_size > __off ? __ops->extended.field : NULL; // 0x278 > 0x270 ?:=> field
}
As the analysis shows, there are a myriad of subtle, inter-related, aspects involved in successfully extending kernel data structures encumbered with protected symbols in a manner that accounts for kernel/module variance, and that doesn’t break kABI (and KMI). These actions are similar, but expand upon such considerably, to the actions taken in case study Case Study: Adding Members to Non-Centrally Allocated Kernel Data Structures, and represent the best currently known approach to date.
One size does not necessarily fit all however, so actions similar to
what were taken in case study Case Study: Adding Members to Centrally Allocated Kernel Data Structures may be completely adequate
[note the networking subsystem’s similar approach that was taken in
commit 112e7b85d12 “Generalize ndo_gso_check to
ndo_features_check”].
Caveats
With respect to substitutions (exchanges) using the pre-reserved entries:
-
In applications similar to commit
259662d355d, the reserved slots above can be used only for performance critical operations. -
All code references of the new member(s) do not have to be modified during the initial, and all future subsequent, back-ports.
With respect to substitutions (exchanges) using the “extension” structure:
-
In applications similar to commit
259662d355d, all new non-performance critical member(s) should be installed into the “extension” structure. -
As a direct result of new members being added to RHEL’s specific extension structure, references to any such members will have to be adjusted during the initial, and all future subsequent, back-ports.
kmod(8).
CONFIG_MODVERSIONS is enabled in all RHEL configurations.
EXPORT_SYMBOL{_GPL}()
GENKSYMS pre-processing directive. When symbol versioning is needed, GENKSYMS is defined, and the ’genksyms’ script is invoked. ./scripts/Makefile.build: $(CPP) -DGENKSYMS $(c_flags) $< $(GENKSYMS) …
pci_bus_read_config_word()
include/linux/pci.h: struct pci_bus { … }
gcc’, ’glibc’, etc.) from a single RHEL OS release. Modules compiled with a mix of toolchain components are not supported.
struct pci_dev { … }
4f994b1e3fc “PCI: Add support for DMA alias quirks”
struct pci_dev’ [see: Appendix AB.1 , B.3, and B.4
include/linux/rh_kabi.h (via commit 22f7116921c “kabi: Add generic kabi macros to use for kabi workarounds”).
redhat/kabi/check-kabi
409236c741a Makefile.build: make KBUILD_SYMTYPES work again
/boot/symvers.$(uname -r).gz kernel-devel package - /usr/src/kernels/$(uname -r)/Module.symvers kernel’s source tree - redhat/kabi/kabi-module/kabi_{ppc64,ppc64le,s390x,x86_64}/`*`
8e7c115586d PCI: Add support for multiple DMA aliases
bef916144ab “PM: add Red Hat dev_pm_info_rh struct”
include/linux/device.h::struct device { … }
ef52e4a544c there have been similar occurrences encountered: see commits 320d729cf76, 3f726c5d045, e03b2d39fce, e25bbd31c16, a3929deae7e, 731dc052b42. Commit b369cf3d0d5 is also related.
struct device_rh’ allocations with ’struct device’ allocations would have been much more invasive, it would have prevented the introduction of the RHEL specific window in which references to ’struct device_rh’ can occur. Perhaps the invasiveness could be handled in the future by leaving the allocations to the device drivers themselves: when a driver recognizes the need for a new member residing within ’struct device_rh’, it then performs the allocation of ’struct device_rh’ where appropriate if it has not yet done so already.
a040fb72b14 “PCI: Put PCIe ports into D3 during suspend”
N byte access must be aligned on a memory address boundary of N which can be expressed in terms of the modulus operator: (addr % N) must be zero.
struct pci_dev’ in the wrapper functions, whereas the protected functions are using ’dev→bus’ and ’dev→devfn’.
0c348a6cd87 “PCI: Restore original checksums of pci symbols”
4a59d5e8211 came in with ’kernel-3.10.0-35.el7’ whereas RHEL 7.0’s release was ’kernel-3.10.0-123.el7’.
023ac646f29 came in with ’kernel-3.10.0-120.el7’ whereas RHEL 7.0’s release was ’kernel-3.10.0-123.el7’.
gcc’, with respect to 64-bit Linux on Intel architectures, produces 32-bit (4 Bytes) ’int’ types.