pax_global_header00006660000000000000000000000064151657503450014525gustar00rootroot0000000000000052 comment=0ab44af5a23e8edb69a39c0dc803ce4395ff2086 stevegrubb-libcap-ng-0ab44af/000077500000000000000000000000001516575034500162125ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/.gitignore000066400000000000000000000015331516575034500202040ustar00rootroot00000000000000*~ *.[oa] *.lo *.la Makefile Makefile.in NEWS aclocal.m4 bindings/Makefile bindings/Makefile.in bindings/python3/.deps/ bindings/python3/Makefile bindings/python3/Makefile.in bindings/python3/test/Makefile bindings/python3/test/Makefile.in bindings/src/Makefile bindings/src/Makefile.in bindings/test/Makefile bindings/test/Makefile.in compile config.guess config.h config.h.in config.log config.status config.sub configure depcomp docs/Makefile docs/Makefile.in install-sh libtool ltmain.sh m4/Makefile m4/Makefile.in m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 missing py-compile src/.deps/ src/Makefile src/Makefile.in src/libcap-ng.pc src/test/.deps/ src/test/Makefile src/test/Makefile.in stamp-h1 test-driver utils/Makefile utils/Makefile.in utils/cap-audit/.deps/ utils/cap-audit/Makefile utils/cap-audit/Makefile.in stevegrubb-libcap-ng-0ab44af/AGENTS.md000066400000000000000000000037461516575034500175270ustar00rootroot00000000000000# Repository Guidelines This project contains a library aimed at manipulating posix capabilities on the Linux operating system. The repository uses autotools and has optional self-tests. Follow the instructions below when making changes. ## Building 1. Bootstrap and configure the build. The README shows an example: ``` cd libcap-ng ./autogen.sh ./configure --with-python3 make ``` 2. Tests can be run with `make check` as described in INSTALL: ``` 2. Type 'make' to compile the package. 3. Optionally, type 'make check' to run any self-tests that come with the package, generally using the just-built uninstalled binaries. ``` 3. Installation (`make install`) is typically performed only after successful tests. ## Project Structure for Navigation - `/src`: This is where the code that makes up libcap-ng is located - `/utils`: This holds the code for pscap, netcap, and filecap - `/docs`: This holds all of the man pages - `/bindings`: This holds swig based python bindings for libcap-ng ## Code Style Contributions should follow the Linux Kernel coding style: ``` So, if you would like to test it and report issues or even contribute code feel free to do so. But please discuss the contribution first to ensure that its acceptable. This project uses the Linux Kernel Style Guideline. Please follow it if you wish to contribute. ``` In practice this means: - Indent with tabs (8 spaces per tab). - Keep lines within ~80 columns. - Place braces and other formatting as in the kernel style. ## Commit Messages - Use a concise one-line summary followed by a blank line and additional details if needed (similar to existing commits). ## Summary - Build with `autogen.sh`, `configure`, and `make`. - Run `make check` to execute the self-tests. - Follow Linux Kernel coding style (tabs, 80 columns). - Keep commit messages short and descriptive. These guidelines should help future contributors and automated tools work consistently within the libcap-ng repository. stevegrubb-libcap-ng-0ab44af/AUTHORS000066400000000000000000000001031516575034500172540ustar00rootroot00000000000000This library has been written by: Steve Grubb stevegrubb-libcap-ng-0ab44af/COPYING000066400000000000000000000431101516575034500172440ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. stevegrubb-libcap-ng-0ab44af/COPYING.LIB000066400000000000000000000636561516575034500176720ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. ^L Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. ^L GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. ^L Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. ^L 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. ^L 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. ^L 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. ^L 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ^L How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! stevegrubb-libcap-ng-0ab44af/ChangeLog000066400000000000000000000166611516575034500177760ustar00rootroot000000000000000.9.3 - In cap-audit, split capability analysis across init and runtime phases - If vm_sockets.h and others are not available, remove "netcap --advanced" - Add netcap --list-interfaces & --interface to restrict output to 1 interface - Put bash completions in /usr/share/bash-completion/completions/ - capng_change_id now detects it added setpcap and drops only if it added it - Add capng_stage_additional_groups and its support in capng_change_id - Add CAPNG_APPLY_BOUNDING flag to capng_change_id to direct it to apply changes - filecap: add path-to-fd consistency check in capability write path 0.9.2 - Added netcap --advanced option for attack surface discovery and inventory - Added acct name to pscap --tree output - Code cleanups and deep review of all functions for correctness - Add colorized output to netcap --advanced - Improve correctness of cap-audit captures - Update man pages 0.9.1 - Deprecate captest - In cap-audit, if tested app uses file system based capabilities, drop setpcap - In cap-audit, fully resolve paths before classifying - In cap-audit, add JSON escaping to output - In cap-audit, filter pre-exec, startup, and shutdown capability noise - pscap now has a --tree disply mode - More code cleanups - Improve output alignment of various utilities (Miroslav Koškár) 0.9 - Fix python path when invoking py-compile (Jan Palus) - Drop python2 bindings (Rudi Heitbaum) - Optimize capability name translation lookups - Add cap-audit utility 0.8.5 - Remove python global exception handler since it's deprecated - Make the utilities link against just built libraries - Remove unused macro in cap-ng.h 0.8.4 - In capng_change_id, clear PR_SET_KEEPCAPS if returning an error - pscap: add -p option for reporting a specified process (Masatake Yamato) - Annotate function prototypes to warn if results are unused - Drop python2 support 0.8.3 - Fix parameters to capng_updatev python bindings to be signed - Detect capability options at runtime to make containerization easier (ntkme) - Initialize the library when linked statically - Add gcc function attributes for deallocation 0.8.2 - In capng_apply, if we blew up in bounding set, allow setting capabilities - If PR_CAP_AMBIENT is not available, do not build libdrop_ambient - Improve last_cap check 0.8.1 - If procfs is not available, leave last_cap as CAP_LAST_CAP - If bounding and ambient not found in status, try prctl method - In capng_apply, move ambient caps to the end of the transaction - In capng_apply, return errors more aggressively. - In capng_apply, if the action includes the bounding set,resync with the kernel - Fix signed/unsigned warning in cap-ng.c - In capng_apply, return a unique error code to diagnose any failure - In capng_have_capability, return 0 for failure - Add the libdrop_ambient admin tool 0.8 - Add vararg support to python bindings for capng_updatev - Add support for ambient capabilities - Add support for V3 filesystem capabilities 0.7.11 - Really clear bounding set if asked in capng_change_id - Add CAP_PERFMON, CAP_BPF, & CAP_CHECKPOINT_RESTORE - Avoid malloc/free in capng_apply (Natanael Copa) - If procfs is not available, get bounding set via prctl - Cleanup some compiler warnings 0.7.10 - Update capng_change_id man page - Add capng_have_permitted_capabilities function - Update filecap to output which set the capabilities are in - Fix filecap to not output an error when a file has no capabilities - Add udplite support to netcap - Fix usage of pthread_atfork (Joe Orton) - Mark processes in child user namespaces with * (Danila Kiver) 0.7.9 - Fix byte compiling python3 bindings - Detect and output a couple errors in filecap - Use pthread_atfork to optionally reset the pid and related info on fork - Rework spec file to show new python2/3 separation 0.7.8 - Improve Python3 support - Fix the thread separation test - Correct typo in cap_pacct text - Update man page for captest - Fix sscanf string lengths in netcap - Correct linking of python3 module 0.7.7 - Make sure all types used in _lnode are defined in proc-llist.h - Fix python binding test for old kernels - Fix leaked FD in library init 0.7.6 - Fix python3 support 0.7.5 - Make python3 supported - In python bindings test, clamp CAP_LAST_CAP with /proc/.../cap_last_cap - Update table for 3.16 kernel 0.7.4 - In pscap, remove unused code - Add CAPNG_INIT_SUPP_GRP to capng_change_id - Drop CAP_COMPROMISE_KERNEL - Update the autotools components - Dynamically detect last capability (#895105) - Add PR_SET_NO_NEW_PRIVS to capng_lock if kernel supports it 0.7.3 - Make sure stderr is used consistently in utils - Fix logic causing file based capabilities to not be supported when it should 0.7.1 - Add CAP_COMPROMISE_KERNEL - Define FTW_CONTINUE in case its not defined in libc - Use glibc for xattr.h if available 0.7 - Make file opens use the cloexec flag (Cristian Rodríguez) - Add CAP_BLOCK_SUSPEND - Fix possible segfaults when CAP_LAST_CAP is larger than the lookup table - In pscap, don't drop capabilities when running with capabilities 0.6.6 - In netcap, make sure readlink is handled properly - Add CAP_SYSLOG - In netcap and pscap, ensure euid is initialized - Add CAP_WAKE_ALARM 0.6.5 - Fix self test build problem on clean system (Sterling X. Winter) - Only open regular files in filecap - Make building Python bindings optional - Python bindings update (arfrever.fta) - Fix filecap segfault when checking a specific file - Add define for missing XATTR_NAME_CAPS since 2.6.36 makes it private 0.6.4 - Update packet socket code to print interface - Fix effective capabilities read from file descriptor - Use thread ID for capget/set calls 0.6.3 - In netcap and pscap use the effective uid - In capng_change_id, only retain setpcap if clearing the bounding set 0.6.2 - Make pscap drop capabilities so its not listed in report - Review prctl calls to make sure we are passing 5 args - Add package config support 0.6.1 - In netcap, don't complain about missing udp or raw network files - Adjusted data read in for file based capabilities 0.6 - In netcap, don't complain about missing network files - Add python bindings - Add m4 macro file to help developers configure libcap-ng in their apps - Fake applying bounding set for old OS - Ignore setpcap for old OS when changing id - Remove capabilities v1 data handling from reading file attributes - Set the SECURE_NO_SETUID_FIXUP and LOCKED securebits flags in capng_lock 0.5.1 - Remove unnecessary uid check in change_uid when dropping supplemental groups - Add credential printout and other improvements to captest - In the init routine, set hdr.pid to current process - Use bit mask on effective capabilities check in have_capabilities - Numeric printing of bounding set bits were in wrong order - In update function, reverse the order of bounding set vs capabilities - Revise the tests used to determine if bounding set should be updated 0.5 - If attr/xattr.h is not available disable file system capabilities - Initialize capng_have_capability with capng_get_caps_process if unknown - Make capng_change_id drop the gid if given - Fixed cap_update for bounding set - Fix have_capability for bounding set - Added more tests to the make check target - Remove CAPNG_LOCK_PERMS for change_id flags - Added captest program 0.4.2 - Fix missing includes for various OS and platforms - Correct misplaced #ifdef for older OS - Reorder clearing of bounding set in capng_change_id - Make locking a noop in capng_change_id for the moment 0.4.1 - spec file clean ups - Man pages for all library functions 0.4 - Initial public release stevegrubb-libcap-ng-0ab44af/INSTALL000066400000000000000000000377711516575034500172620ustar00rootroot00000000000000Installation Instructions ************************* Basic Installation ================== The following shell commands: test -f configure || ./bootstrap ./configure make make install should configure, build, and install this package. The first line, which bootstraps, is intended for developers; when building from distribution tarballs it does nothing and can be skipped. The following more-detailed instructions are generic; see the ‘README’ file for instructions specific to this package. Some packages provide this ‘INSTALL’ file but do not implement all of the features documented below. The lack of an optional feature in a given package is not necessarily a bug. More recommendations for GNU packages can be found in the GNU Coding Standards. Many packages have scripts meant for developers instead of ordinary builders, as they may use developer tools that are less commonly installed, or they may access the network, which has privacy implications. If the ‘bootstrap’ shell script exists, it attempts to build the ‘configure’ shell script and related files, possibly using developer tools or the network. Because the output of ‘bootstrap’ is system-independent, it is normally run by a package developer so that its output can be put into the distribution tarball and ordinary builders and users need not run ‘bootstrap’. Some packages have commands like ‘./autopull.sh’ and ‘./autogen.sh’ that you can run instead of ‘./bootstrap’, for more fine-grained control over bootstrapping. The ‘configure’ shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a ‘Makefile’ in each directory of the package. It may also create one or more ‘.h’ files containing system-dependent definitions. Finally, it creates a shell script ‘config.status’ that you can run in the future to recreate the current configuration, and a file ‘config.log’ containing output useful for debugging ‘configure’. It can also use an optional file (typically called ‘config.cache’ and enabled with ‘--cache-file=config.cache’ or simply ‘-C’) that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how ‘configure’ could check whether to do them, and mail diffs or instructions to the address given in the ‘README’ so they can be considered for the next release. If you are using the cache, and at some point ‘config.cache’ contains results you don’t want to keep, you may remove or edit it. The ‘autoconf’ program generates ‘configure’ from the file ‘configure.ac’. Normally you should edit ‘configure.ac’ instead of editing ‘configure’ directly. The simplest way to compile this package is: 1. ‘cd’ to the directory containing the package’s source code. 2. If this is a developer checkout and file ‘configure’ does not yet exist, type ‘./bootstrap’ to create it. You may need special developer tools and network access to bootstrap, and the network access may have privacy implications. 3. Type ‘./configure’ to configure the package for your system. This might take a while. While running, ‘configure’ prints messages telling which features it is checking for. 4. Type ‘make’ to compile the package. 5. Optionally, type ‘make check’ to run any self-tests that come with the package, generally using the just-built uninstalled binaries. 6. Type ‘make install’ to install the programs and any data files and documentation. When installing into a prefix owned by root, it is recommended that the package be configured and built as a regular user, and only the ‘make install’ phase executed with root privileges. 7. Optionally, type ‘make installcheck’ to repeat any self-tests, but this time using the binaries in their final installed location. This target does not install anything. Running this target as a regular user, particularly if the prior ‘make install’ required root privileges, verifies that the installation completed correctly. 8. You can remove the program binaries and object files from the source code directory by typing ‘make clean’. To also remove the files that ‘configure’ created (so you can compile the package for a different kind of computer), type ‘make distclean’. There is also a ‘make maintainer-clean’ target, but that is intended mainly for the package’s developers. If you use it, you may have to bootstrap again. 9. If the package follows the GNU Coding Standards, you can type ‘make uninstall’ to remove the installed files. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the ‘configure’ script does not know about. Run ‘./configure --help’ for details on some of the pertinent environment variables. You can give ‘configure’ initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=gcc CFLAGS=-g LIBS=-lposix See “Defining Variables” for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each system in their own directory. To do this, you can use GNU ‘make’. ‘cd’ to the directory where you want the object files and executables to go and run the ‘configure’ script. ‘configure’ automatically checks for the source code in the directory that ‘configure’ is in and in ‘..’. This is known as a “VPATH” build. With a non-GNU ‘make’, it is safer to compile the package for one system at a time in the source code directory. After you have installed the package for one system, use ‘make distclean’ before reconfiguring for another system. Some platforms, notably macOS, support “fat” or “universal” binaries, where a single binary can execute on different architectures. On these platforms you can configure and compile just once, with options specific to that platform. Installation Names ================== By default, ‘make install’ installs the package’s commands under ‘/usr/local/bin’, include files under ‘/usr/local/include’, etc. You can specify an installation prefix other than ‘/usr/local’ by giving ‘configure’ the option ‘--prefix=PREFIX’, where PREFIX must be an absolute file name. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option ‘--exec-prefix=PREFIX’ to ‘configure’, the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like ‘--bindir=DIR’ to specify different values for particular kinds of files. Run ‘configure --help’ for a list of the directories you can set and what kinds of files go in them. In general, the default for these options is expressed in terms of ‘${prefix}’, so that specifying just ‘--prefix’ will affect all of the other directory specifications that were not explicitly provided. The most portable way to affect installation locations is to pass the correct locations to ‘configure’; however, many packages provide one or both of the following shortcuts of passing variable assignments to the ‘make install’ command line to change installation locations without having to reconfigure or recompile. The first method involves providing an override variable for each affected directory. For example, ‘make install prefix=/alternate/directory’ will choose an alternate location for all directory configuration variables that were expressed in terms of ‘${prefix}’. Any directories that were specified during ‘configure’, but not in terms of ‘${prefix}’, must each be overridden at install time for the entire installation to be relocated. The approach of makefile variable overrides for each directory variable is required by the GNU Coding Standards, and ideally causes no recompilation. However, some platforms have known limitations with the semantics of shared libraries that end up requiring recompilation when using this method, particularly noticeable in packages that use GNU Libtool. The second method involves providing the ‘DESTDIR’ variable. For example, ‘make install DESTDIR=/alternate/directory’ will prepend ‘/alternate/directory’ before all installation names. The approach of ‘DESTDIR’ overrides is not required by the GNU Coding Standards, and does not work on platforms that have drive letters. On the other hand, it does better at avoiding recompilation issues, and works well even when some directory options were not specified in terms of ‘${prefix}’ at ‘configure’ time. Optional Features ================= If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving ‘configure’ the option ‘--program-prefix=PREFIX’ or ‘--program-suffix=SUFFIX’. Some packages pay attention to ‘--enable-FEATURE’ and ‘--disable-FEATURE’ options to ‘configure’, where FEATURE indicates an optional part of the package. They may also pay attention to ‘--with-PACKAGE’ and ‘--without-PACKAGE’ options, where PACKAGE is something like ‘gnu-ld’. ‘./configure --help’ should mention the ‘--enable-...’ and ‘--with-...’ options that the package recognizes. Some packages offer the ability to configure how verbose the execution of ‘make’ will be. For these packages, running ‘./configure --enable-silent-rules’ sets the default to minimal output, which can be overridden with ‘make V=1’; while running ‘./configure --disable-silent-rules’ sets the default to verbose, which can be overridden with ‘make V=0’. Specifying a System Type ======================== By default ‘configure’ builds for the current system. To create binaries that can run on a different system type, specify a ‘--host=TYPE’ option along with compiler variables that specify how to generate object code for TYPE. For example, to create binaries intended to run on a 64-bit ARM processor: ./configure --host=aarch64-linux-gnu \ CC=aarch64-linux-gnu-gcc \ CXX=aarch64-linux-gnu-g++ If done on a machine that can execute these binaries (e.g., via ‘qemu-aarch64’, ‘$QEMU_LD_PREFIX’, and Linux’s ‘binfmt_misc’ capability), the build behaves like a native build. Otherwise it is a cross-build: ‘configure’ will make cross-compilation guesses instead of running test programs, and ‘make check’ will not work. A system type can either be a short name like ‘mingw64’, or a canonical name like ‘x86_64-pc-linux-gnu’. Canonical names have the form CPU-COMPANY-SYSTEM where SYSTEM is either OS or KERNEL-OS. To canonicalize and validate a system type, you can run the command ‘config.sub’, which is often squirreled away in a subdirectory like ‘build-aux’. For example: $ build-aux/config.sub arm64-linux aarch64-unknown-linux-gnu $ build-aux/config.sub riscv-lnx Invalid configuration 'riscv-lnx': OS 'lnx' not recognized You can look at the ‘config.sub’ file to see which types are recognized. If the file is absent, this package does not need the system type. If ‘configure’ fails with the diagnostic “cannot guess build type”. ‘config.sub’ did not recognize your system’s type. In this case, first fetch the newest versions of these files from the GNU config package (https://savannah.gnu.org/projects/config). If that fixes things, please report it to the maintainers of the package containing ‘configure’. Otherwise, you can try the configure option ‘--build=TYPE’ where TYPE comes close to your system type; also, please report the problem to . For more details about configuring system types, see the Autoconf documentation. Sharing Defaults ================ If you want to set default values for ‘configure’ scripts to share, you can create a site shell script called ‘config.site’ that gives default values for variables like ‘CC’, ‘cache_file’, and ‘prefix’. ‘configure’ looks for ‘PREFIX/share/config.site’ if it exists, then ‘PREFIX/etc/config.site’ if it exists. Or, you can set the ‘CONFIG_SITE’ environment variable to the location of the site script. A warning: not all ‘configure’ scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to ‘configure’. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the ‘configure’ command line, using ‘VAR=value’. For example: ./configure CC=/usr/local2/bin/gcc causes the specified ‘gcc’ to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for ‘CONFIG_SHELL’ due to an Autoconf limitation. Until the limitation is lifted, you can use this workaround: CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash ‘configure’ Invocation ====================== ‘configure’ recognizes the following options to control how it operates. ‘--help’ ‘-h’ Print a summary of all of the options to ‘configure’, and exit. ‘--help=short’ ‘--help=recursive’ Print a summary of the options unique to this package’s ‘configure’, and exit. The ‘short’ variant lists options used only in the top level, while the ‘recursive’ variant lists options also present in any nested packages. ‘--version’ ‘-V’ Print the version of Autoconf used to generate the ‘configure’ script, and exit. ‘--cache-file=FILE’ Enable the cache: use and save the results of the tests in FILE, traditionally ‘config.cache’. FILE defaults to ‘/dev/null’ to disable caching. ‘--config-cache’ ‘-C’ Alias for ‘--cache-file=config.cache’. ‘--srcdir=DIR’ Look for the package’s source code in directory DIR. Usually ‘configure’ can determine that directory automatically. ‘--prefix=DIR’ Use DIR as the installation prefix. See “Installation Names” for more details, including other options available for fine-tuning the installation locations. ‘--host=TYPE’ Build binaries for system TYPE. See “Specifying a System Type”. ‘--enable-FEATURE’ ‘--disable-FEATURE’ Enable or disable the optional FEATURE. See “Optional Features”. ‘--with-PACKAGE’ ‘--without-PACKAGE’ Use or omit PACKAGE when building. See “Optional Features”. ‘--quiet’ ‘--silent’ ‘-q’ Do not print messages saying which checks are being made. To suppress all normal output, redirect it to ‘/dev/null’ (any error messages will still be shown). ‘--no-create’ ‘-n’ Run the configure checks, but stop before creating any output files. ‘configure’ also recognizes several environment variables, and accepts some other, less widely useful, options. Run ‘configure --help’ for more details. Copyright notice ================ Copyright © 1994–1996, 1999–2002, 2004–2017, 2020–2024 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. stevegrubb-libcap-ng-0ab44af/Makefile.am000066400000000000000000000021671516575034500202540ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009 Red Hat Inc. # All Rights Reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # ACLOCAL_AMFLAGS = -I m4 SUBDIRS = src utils m4 docs if HAVE_SWIG SUBDIRS += bindings endif EXTRA_DIST = ChangeLog AUTHORS README.md INSTALL COPYING COPYING.LIB \ libcap-ng.spec autogen.sh CONFIG_CLEAN_FILES = debug*.list clean-generic: rm -rf autom4te*.cache rm -f *.rej *.orig *.lang stevegrubb-libcap-ng-0ab44af/README.md000066400000000000000000000330301516575034500174700ustar00rootroot00000000000000libcap-ng ========= The libcap-ng library should make programming with POSIX capabilities easier. The library has some utilities to help you analyze a system for apps that may have too much privileges. The included utilities are designed to let admins and developers spot apps from various ways that may be running with too much privilege. For example, any investigation should start with network facing apps since they would be prime targets for intrusion. The **netcap** program will check all running apps that have listening socket and display the results. Sample output from netcap: ``` ppid pid acct command type port capabilities 1 2295 root nasd tcp 8000 full 2323 2383 root dnsmasq tcp 53 net_admin, net_raw + 1 2286 root sshd tcp 22 full 1 2365 root cupsd tcp 631 full 1 2286 root sshd tcp6 22 full 1 2365 root cupsd tcp6 631 full 2323 2383 root dnsmasq udp 53 net_admin, net_raw + 2323 2383 root dnsmasq udp 67 net_admin, net_raw + 1 2365 root cupsd udp 631 full ``` After checking the networking apps, you should check all running apps with **pscap**. If you are a developer and have to give your application CAP_DAC_OVERRIDE, you must be accessing files for which you have no permission to access. This typically can be resolved by having membership in the correct groups. Try to avoid needing CAP_DAC_OVERRIDE...you may as well be root if you need it. Some application developers have chosen to use file system based capabilities rather than be setuid root and have to drop capabilities. Libcap-ng provides **filecap** to recursively search directories and show you which ones have capabilities and exactly what those are. There is a new utility, **cap-audit** which can audit a program for necessary capabilities. It is discussed in more detail below. C Examples ---------- As an application developer, there are probably 6 use cases that you are interested in: drop all capabilities, keep one capability, keep several capabilities, check if you have any capabilities at all, check for certain capabilities, and retain capabilities across a uid change. 1) Drop all capabilities ```c capng_clear(CAPNG_SELECT_BOTH); capng_apply(CAPNG_SELECT_BOTH); ``` 2) Keep one capability ```c capng_clear(CAPNG_SELECT_BOTH); capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_CHOWN); capng_apply(CAPNG_SELECT_BOTH); ``` 3) Keep several capabilities ```c capng_clear(CAPNG_SELECT_BOTH); capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETUID, CAP_SETGID, -1); capng_apply(CAPNG_SELECT_BOTH); ``` 4) Check if you have any capabilities ```c if (capng_have_capabilities(CAPNG_SELECT_CAPS) > CAPNG_NONE) do_something(); ``` 5) Check for a specific capability ```c if (capng_have_capability(CAPNG_EFFECTIVE, CAP_CHOWN)) do_something(); ``` 6) Retain capabilities across a uid change ```c capng_clear(CAPNG_SELECT_BOTH); capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_CHOWN); if (capng_change_id(99, 99, CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) error(); ``` Now, isn't that a lot simpler? Note that the last example takes about 60 lines of code using the older capabilities library. As of the 0.6 release, there is a m4 macro file to help add libcap-ng to your autotools config system. In configure.ac, add LIBCAP_NG_PATH. Then in Makefile.am locate the apps that link to libcap-ng, add $(CAPNG_LDADD) to their LDADD entries. And lastly, surround the optional capabilities code with #ifdef HAVE_LIBCAP_NG. Python ------ Libcap-ng 0.6 and later has python bindings. (Only python3 is supported from 0.8.4 onward.) You simply add 'import capng' in your script. Here are the same examples as above in python: 1) Drop all capabilities ```python capng.capng_clear(capng.CAPNG_SELECT_BOTH) capng.capng_apply(capng.CAPNG_SELECT_BOTH) ``` 2) Keep one capability ```python capng.capng_clear(capng.CAPNG_SELECT_BOTH) capng.capng_update(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED, capng.CAP_CHOWN) capng.capng_apply(capng.CAPNG_SELECT_BOTH) ``` 3) Keep several capabilities ```python capng.capng_clear(capng.CAPNG_SELECT_BOTH) capng.capng_updatev(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED, capng.CAP_SETUID, capng.CAP_SETGID, -1) capng.capng_apply(capng.CAPNG_SELECT_BOTH) ``` 4) Check if you have any capabilities ```python if capng.capng_have_capabilities(capng.CAPNG_SELECT_CAPS) > capng.CAPNG_NONE: do_something() ``` 5) Check for a specific capability ```python if capng.capng_have_capability(capng.CAPNG_EFFECTIVE, capng.CAP_CHOWN): do_something() ``` 6) Retain capabilities across a uid change ```python capng.capng_clear(capng.CAPNG_SELECT_BOTH) capng.capng_update(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED, capng.CAP_CHOWN) if capng.capng_change_id(99, 99, capng.CAPNG_DROP_SUPP_GRP | capng.CAPNG_CLEAR_BOUNDING) < 0: error() ``` The one caveat is that printing capabilities from python does not work. But you can still manipulate capabilities, though. Ambient Capabilities -------------------- Ambient capabilities arrived in the 4.3 Linux kernel. Ambient capabilities allow a privileged process to bestow capabilities to a child process. This is how systemd grants capabilities to a daemon running in a service account. The problem with ambient capabilities is they are inherited forever. Every process exec'ed from the original service also has the capabilities. This is a security issue. To find and fix this, you can run the pscap program and grep for '@'. The '@' symbol denotes processes that have ambient capabilities. For example: ``` # pscap | grep @ 1 1655 systemd-oom systemd-oomd dac_override, kill @ + 1 1656 systemd-resolve systemd-resolve net_raw @ + ``` To fix this, libcap-ng 0.8.3 and later ships libdrop_ambient.so.0. It is designed to be used with LD_PRELOAD. It has a constructor function that forces the dropping of ambient capabilities. By the time the application starts, it has both effective and ambient capabilities - meaning is safe to drop ambient capabilities very early. You can either link it to an application run as a systemd service (using ld), or create a wrapper script that then starts the daemon. Building -------- Note: As of the 0.9 release, libcap-ng is no longer being distributed from people.redhat.com/sgrubb/libcap-ng/ please adjust any scripts to watch this github page for new releases. After cloning libcap-ng, run: ``` cd libcap-ng ./autogen.sh ./configure make make install ``` If you want python bindings, add that option to the configure command. The `netcap --advanced` feature also depends on newer Linux kernel headers, including `linux/vm_sockets.h`. When those headers are not available, such as on older build roots, `configure` will automatically opt out of advanced mode and report that in its output while still building the rest of libcap-ng. The cap-audit program has to be specifically enabled and defaults to not being built. There is also a spec file to use if you are on a rpm based distribution. To do that, run "make dist" instead of make in the above instructions. Then use the resulting tar file with the spec file. When advanced mode is available, `netcap --advanced --list-interfaces` prints the current network namespace interface names. Add `--json` to get a machine-readable list of names. NOTE: to distributions ---------------------- There is a "make check" target. It only works if the available kernel headers roughly match the build root kernel. Iow, if you have a chroot build system that is using a much older kernel, the macros in the kernel header files will describe functionality that does not exist in the build root. The capng_init function will probe the kernel and decide we can only do v1 rather than v3 capabilities instead of what the kernel headers said was possible. If that is your case, just don't do the "make check" as part of the build process. This problem should go away as build roots eventually switch to the 5.0 or later kernels. cap-audit --------- As of the 0.9 release of libcap-ng, there is a new utility **cap-audit**. This program can be used to determine the actual capabilities that a program needs. To do this, use it to run the application kind of the way one would use strace. Use '--' to separate the options to cap-audit from the program being audited. You need to use cap-audit as root because it places an eBPF program in the kernel to hook the capability checks to determine what was requested, was it granted, and what syscall did it originate from. When testing a daemon, pass command line options that keep it in the foreground. The following is an example checking sshd: ``` cap-audit -- /usr/sbin/sshd -D [*] Capability auditor started [*] Tracing application: /usr/sbin/sshd (PID 30581) [*] Press Ctrl-C to stop ^C[*] Analyzing results... ====================================================================== CAPABILITY ANALYSIS FOR: /usr/sbin/sshd (PID 30581) ====================================================================== SYSTEM CONTEXT: ---------------------------------------------------------------------- Kernel version: 6.18.3-100.fc42.x86_64 kernel.yama.ptrace_scope: 1 kernel.kptr_restrict: 1 kernel.dmesg_restrict: 1 kernel.modules_disabled: 0 kernel.perf_event_paranoid: 2 kernel.unprivileged_bpf_disabled: 2 net.core.bpf_jit_enable: 1 net.core.bpf_jit_harden: 1 net.core.bpf_jit_kallsyms: 1 vm.mmap_min_addr: 65536 fs.protected_hardlinks: 1 fs.protected_symlinks: 1 fs.suid_dumpable: 2 REQUIRED CAPABILITIES: ---------------------------------------------------------------------- chown (#0) Checks: 1 granted, 0 denied Reason: Used by chown (syscall 92) dac_read_search (#2) Checks: 2 granted, 1 denied Reason: Used by openat (syscall 257) setgid (#6) Checks: 30 granted, 2 denied Reason: Used by setgroups (syscall 116) setuid (#7) Checks: 13 granted, 4 denied Reason: Used by setresuid (syscall 117) net_bind_service (#10) Checks: 2 granted, 0 denied Reason: Used by bind (syscall 49) net_admin (#12) Checks: 2 granted, 2 denied Reason: Used by setsockopt (syscall 54) sys_chroot (#18) Checks: 1 granted, 0 denied Reason: Used by chroot (syscall 161) sys_admin (#21) Checks: 597 granted, 1656 denied Reason: Used by brk (syscall 12) sys_resource (#24) Checks: 3 granted, 0 denied Reason: Used by write (syscall 1) audit_write (#29) Checks: 27 granted, 2 denied Reason: Used by sendto (syscall 44) mac_admin (#33) Checks: 1 granted, 0 denied Reason: Used by getxattr (syscall 191) CONDITIONAL CAPABILITIES: ---------------------------------------------------------------------- CAP_DAC_OVERRIDE Needed when fs.protected_symlinks = 1 for symlinks in world-writable directories Current value: 1 (capability needed) ATTEMPTED BUT DENIED: ---------------------------------------------------------------------- dac_override (#1) Attempts: 2 (all denied) Syscalls: readlink, faccessat2 Impact: Application may have reduced functionality bpf (#39) Attempts: 2 (all denied) Syscalls: prctl Impact: Application may have reduced functionality SUMMARY: ---------------------------------------------------------------------- Total capability checks: 2349 Required capabilities: 11 Conditional capabilities: 1 Denied operations: 2 RECOMMENDATIONS: ---------------------------------------------------------------------- Programmatic solution (C with libcap-ng): #include ... capng_clear(CAPNG_SELECT_BOTH); capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CHOWN, DAC_READ_SEARCH, SETGID, SETUID, NET_BIND_SERVICE, NET_ADMIN, SYS_CHROOT, SYS_ADMIN, SYS_RESOURCE, AUDIT_WRITE, MAC_ADMIN, -1); if (capng_change_id(uid, gid, CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) perror("capng_change_id"); For systemd service: [Service] User= Group= AmbientCapabilities=chown dac_read_search setgid setuid net_bind_service net_admin sys_chroot sys_admin sys_resource audit_write mac_admin CapabilityBoundingSet=chown dac_read_search setgid setuid net_bind_service net_admin sys_chroot sys_admin sys_resource audit_write mac_admin For file capabilities (via filecap): filecap /path/to/binary chown dac_read_search setgid setuid net_bind_service net_admin sys_chroot sys_admin sys_resource audit_write mac_admin For Docker/Podman: docker run --user $(id -u):$(id -g) \ --cap-drop=ALL \ --cap-add=chown \ --cap-add=dac_read_search \ --cap-add=setgid \ --cap-add=setuid \ --cap-add=net_bind_service \ --cap-add=net_admin \ --cap-add=sys_chroot \ --cap-add=sys_admin \ --cap-add=sys_resource \ --cap-add=audit_write \ --cap-add=mac_admin \ your-image:tag For Kubernetes: securityContext: runAsUser: 1000 runAsGroup: 1000 capabilities: drop: - ALL add: - chown - dac_read_search - setgid - setuid - net_bind_service - net_admin - sys_chroot - sys_admin - sys_resource - audit_write - mac_admin ``` Reporting --------- Report any bugs in this package to: https://github.com/stevegrubb/libcap-ng/issue stevegrubb-libcap-ng-0ab44af/autogen.sh000077500000000000000000000001701516575034500202110ustar00rootroot00000000000000#! /bin/sh set -x -e # --no-recursive is available only in recent autoconf versions touch NEWS autoreconf -fv --install stevegrubb-libcap-ng-0ab44af/bindings/000077500000000000000000000000001516575034500200075ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/bindings/Makefile.am000066400000000000000000000017251516575034500220500ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009,2015 Red Hat Inc. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # CONFIG_CLEAN_FILES = *.loT *.rej *.orig SUBDIRS = src if USE_PYTHON3 SUBDIRS += python3 test endif stevegrubb-libcap-ng-0ab44af/bindings/python3/000077500000000000000000000000001516575034500214135ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/bindings/python3/Makefile.am000066400000000000000000000042751516575034500234570ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009,2014-17 Red Hat Inc. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # SUBDIRS = test CONFIG_CLEAN_FILES = *.loT *.rej *.orig AM_CFLAGS = -fPIC -DPIC $(PYTHON3_CFLAGS) AM_CPPFLAGS = -I. -I$(top_builddir) $(PYTHON3_INCLUDES) LIBS = ${top_builddir}/src/libcap-ng.la SWIG_FLAGS = -python SWIG_INCLUDES = ${AM_CPPFLAGS} pyexec_PYTHON = capng.py pyexec_LTLIBRARIES = _capng.la _capng_la_CFLAGS = -shared _capng_la_LDFLAGS = -module -avoid-version -Wl,-z,relro _capng_la_HEADERS: $(top_builddir)/config.h _capng_la_DEPENDENCIES =${top_srcdir}/src/cap-ng.h ${top_builddir}/src/libcap-ng.la _capng_la_LIBADD = ${top_builddir}/src/libcap-ng.la $(PYTHON3_LIBS) nodist__capng_la_SOURCES = capng_wrap.c capng.py capng_wrap.c: ${srcdir}/../src/capng_swig.i caps.h capng.h swig -o capng_wrap.c ${SWIG_FLAGS} ${SWIG_INCLUDES} ${srcdir}/../src/capng_swig.i caps.h: cat $(CAPABILITY_HEADER) | grep '^#define CAP' | grep -v '[()]' > caps.h capng.h: cat ${top_srcdir}/src/cap-ng.h | grep -v '_state' > capng.h CLEANFILES = capng.py* capng_wrap.c caps.h capng.h *~ clean-local: $(RM) -rf __pycache__ check-local: @python3_embed_libs=`python3-config --embed --libs 2>/dev/null`; \ if test -z "$$python3_embed_libs"; then \ echo "SKIP: python3-config --embed --libs is unavailable"; \ exit 77; \ fi; \ $(MAKE) clean >/dev/null; \ $(MAKE) all-am LDFLAGS="$(LDFLAGS) -Wl,--no-undefined" \ PYTHON3_LIBS="$$python3_embed_libs" stevegrubb-libcap-ng-0ab44af/bindings/python3/test/000077500000000000000000000000001516575034500223725ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/bindings/python3/test/Makefile.am000066400000000000000000000020251516575034500244250ustar00rootroot00000000000000# Makefile.am -- # Copyright 2023 Red Hat Inc. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # dist_check_SCRIPTS = capng-test.py TESTS = $(dist_check_SCRIPTS) TESTS_ENVIRONMENT = PYTHONPATH=${top_builddir}/bindings/python3/:${top_builddir}/bindings/python3/.libs stevegrubb-libcap-ng-0ab44af/bindings/python3/test/capng-test.py000077500000000000000000000043171516575034500250210ustar00rootroot00000000000000#!/usr/bin/env python3 import os import sys import time load_path = '../' if False: sys.path.insert(0, load_path) import capng last = capng.CAP_LAST_CAP try: with open('/proc/sys/kernel/cap_last_cap', 'r') as f: last = int(f.readline()) except IOError as e: print("Error opening /proc/sys/kernel/cap_last_cap: {0}".format(e.strerror)) print("Doing basic bit tests...") capng.capng_clear(capng.CAPNG_SELECT_BOTH) if capng.capng_have_capabilities(capng.CAPNG_SELECT_BOTH) != capng.CAPNG_NONE: print("Failed clearing capabilities\n") sys.exit(1) capng.capng_fill(capng.CAPNG_SELECT_BOTH) if capng.capng_have_capabilities(capng.CAPNG_SELECT_BOTH) != capng.CAPNG_FULL: print("Failed filling capabilities") sys.exit(1) text = capng.capng_print_caps_numeric(capng.CAPNG_PRINT_BUFFER, capng.CAPNG_SELECT_CAPS) len = len(text) if len < 80 and last > 30: last = 30 print("Doing advanced bit tests for %d capabilities...\n" % (last)) for i in range(last+1): capng.capng_clear(capng.CAPNG_SELECT_BOTH) rc = capng.capng_update(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE, i) if rc: print("Failed update test 1") sys.exit(1) rc = capng.capng_have_capability(capng.CAPNG_EFFECTIVE, int(i)) if rc <= capng.CAPNG_NONE: print("Failed have capability test 1") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) if capng.capng_have_capabilities(capng.CAPNG_SELECT_CAPS) != capng.CAPNG_PARTIAL: print("Failed have capabilities test 1") sys.exit(1) capng.capng_fill(capng.CAPNG_SELECT_BOTH) rc = capng.capng_update(capng.CAPNG_DROP, capng.CAPNG_EFFECTIVE, i) if rc: print("Failed update test 3") sys.exit(1) if capng.capng_have_capabilities(capng.CAPNG_SELECT_CAPS)!=capng.CAPNG_PARTIAL: print("Failed have capabilities test 3") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) rc = capng.capng_update(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE, i) if rc: print("Failed update test 4") sys.exit(1) if capng.capng_have_capabilities(capng.CAPNG_SELECT_CAPS) != capng.CAPNG_FULL: print("Failed have capabilities test 4") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) sys.exit(0) stevegrubb-libcap-ng-0ab44af/bindings/src/000077500000000000000000000000001516575034500205765ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/bindings/src/Makefile.am000066400000000000000000000016521516575034500226360ustar00rootroot00000000000000# Makefile.am -- # Copyright 2015 Red Hat Inc., Durham, North Carolina. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Authors: # Steve Grubb # EXTRA_DIST = capng_swig.i CONFIG_CLEAN_FILES = *.loT *.rej *.orig stevegrubb-libcap-ng-0ab44af/bindings/src/capng_swig.i000066400000000000000000000032201516575034500230660ustar00rootroot00000000000000/* capngswig.i -- * Copyright 2009 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ %module capng %{ #include "./capng.h" %} #if defined(SWIGPYTHON) /* * SWIG expands varargs into a fixed argument list. Any omitted optional * arguments are passed to capng_updatev() using this default value. * * capng_updatev() requires (unsigned)-1 as the varargs terminator, so the * default must also be -1 or the function keeps consuming arguments past the * generated wrapper list. * * The old cap of 16 optional entries predates current capability counts and * is easy to hit. Allow up to 64 varargs entries in the wrapper. */ %varargs(64, signed capability = -1) capng_updatev; #endif %define __signed__ signed %enddef #define __attribute(X) /*nothing*/ typedef unsigned __u32; #define __extension__ /*nothing*/ %include "./caps.h" %include "./capng.h" stevegrubb-libcap-ng-0ab44af/bindings/test/000077500000000000000000000000001516575034500207665ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/bindings/test/Makefile.am000066400000000000000000000003141516575034500230200ustar00rootroot00000000000000# Makefile.am -- Test suite for python bindings TESTS = lib_test.py TESTS_ENVIRONMENT = PYTHONPATH=${top_builddir}/bindings/python3/:${top_builddir}/bindings/python3/.libs dist_check_SCRIPTS = $(TESTS) stevegrubb-libcap-ng-0ab44af/bindings/test/lib_test.py000077500000000000000000000114401516575034500231500ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import capng def get_last_cap(): last = capng.CAP_LAST_CAP try: with open('/proc/sys/kernel/cap_last_cap', 'r') as f: last = int(f.readline()) except OSError: pass return last def main(): last = get_last_cap() print("Doing basic bit tests...") capng.capng_clear(capng.CAPNG_SELECT_BOTH) if capng.capng_have_capabilities(capng.CAPNG_SELECT_BOTH) != capng.CAPNG_NONE: print("Failed clearing capabilities") sys.exit(1) # capng_save_state/capng_restore_state are not available in python bindings capng.capng_fill(capng.CAPNG_SELECT_BOTH) if capng.capng_have_capabilities(capng.CAPNG_SELECT_BOTH) != capng.CAPNG_FULL: print("Failed filling capabilities") sys.exit(1) text = capng.capng_print_caps_numeric(capng.CAPNG_PRINT_BUFFER, capng.CAPNG_SELECT_CAPS) if len(text) < 80 and last > 30: last = 30 print("Doing advanced bit tests for %d capabilities..." % last) for i in range(last + 1): capng.capng_clear(capng.CAPNG_SELECT_BOTH) rc = capng.capng_update(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE, i) if rc: print("Failed update test 1") sys.exit(1) rc = capng.capng_have_capability(capng.CAPNG_EFFECTIVE, i) if rc <= capng.CAPNG_NONE: print("Failed have capability test 1") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) if capng.capng_have_capabilities(capng.CAPNG_SELECT_CAPS) != capng.CAPNG_PARTIAL: print("Failed have capabilities test 1") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) if capng.CAP_LAST_CAP > 31: rc = capng.capng_update(capng.CAPNG_ADD, capng.CAPNG_BOUNDING_SET, i) if rc: print("Failed bset update test 2") sys.exit(1) rc = capng.capng_have_capability(capng.CAPNG_BOUNDING_SET, i) if rc <= capng.CAPNG_NONE: print("Failed bset have capability test 2") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_BOTH) sys.exit(1) if capng.capng_have_capabilities(capng.CAPNG_SELECT_BOUNDS) != capng.CAPNG_PARTIAL: print("Failed bset have capabilities test 2") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_BOTH) sys.exit(1) text = capng.capng_print_caps_text(capng.CAPNG_PRINT_BUFFER, capng.CAPNG_EFFECTIVE) name = capng.capng_capability_to_name(i) if text != name: print("Failed print text comparison") print("%s != %s" % (text, name)) sys.exit(1) capng.capng_fill(capng.CAPNG_SELECT_BOTH) rc = capng.capng_update(capng.CAPNG_DROP, capng.CAPNG_EFFECTIVE, i) if rc: print("Failed update test 3") sys.exit(1) if capng.capng_have_capabilities(capng.CAPNG_SELECT_CAPS) != capng.CAPNG_PARTIAL: print("Failed have capabilities test 3") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) rc = capng.capng_update(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE, i) if rc: print("Failed update test 4") sys.exit(1) if capng.capng_have_capabilities(capng.CAPNG_SELECT_CAPS) != capng.CAPNG_FULL: print("Failed have capabilities test 4") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) capng.capng_clear(capng.CAPNG_SELECT_BOTH) rc = capng.capng_updatev(capng.CAPNG_ADD, capng.CAPNG_EFFECTIVE, capng.CAP_CHOWN, capng.CAP_FOWNER, capng.CAP_KILL, -1) if rc: print("Failed updatev test") sys.exit(1) rc = (capng.capng_have_capability(capng.CAPNG_EFFECTIVE, capng.CAP_CHOWN) and capng.capng_have_capability(capng.CAPNG_EFFECTIVE, capng.CAP_FOWNER) and capng.capng_have_capability(capng.CAPNG_EFFECTIVE, capng.CAP_KILL)) if not rc: print("Failed have updatev capability test") capng.capng_print_caps_numeric(capng.CAPNG_PRINT_STDOUT, capng.CAPNG_SELECT_CAPS) sys.exit(1) sys.exit(0) if __name__ == '__main__': main() stevegrubb-libcap-ng-0ab44af/configure.ac000066400000000000000000000245631516575034500205120ustar00rootroot00000000000000 dnl define([AC_INIT_NOTICE], [### Generated automatically using autoconf version] AC_ACVERSION [ ### Copyright 2009-2026 Steve Grubb ### ### Permission is hereby granted, free of charge, to any person obtaining a ### copy of this software and associated documentation files (the "Software"), ### to deal in the Software without restriction, including without limitation ### the rights to use, copy, modify, merge, publish, distribute, sublicense, ### and/or sell copies of the Software, and to permit persons to whom the ### Software is furnished to do so, subject to the following conditions: ### ### The above copyright notice and this permission notice shall be included ### in all copies or substantial portions of the Software. ### ### THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ### IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ### FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ### THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR ### OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ### ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ### OTHER DEALINGS IN THE SOFTWARE. ### ### For usage, run `./configure --help' ### For more detailed information on installation, read the file `INSTALL'. ### ### If configuration succeeds, status is in the file `config.status'. ### A log of configuration tests is in `config.log'. ]) AC_REVISION($Revision: 1.3 $)dnl AC_INIT([libcap-ng],[0.9.3]) AC_PREREQ([2.50])dnl AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) echo Configuring libcap-ng $VERSION AC_CANONICAL_TARGET AM_INIT_AUTOMAKE LT_INIT AC_SUBST(LIBTOOL_DEPS) PKG_PROG_PKG_CONFIG echo . echo Checking for programs AC_PROG_CC AC_PROG_INSTALL AC_PROG_AWK LIBBPF_CFLAGS="" LIBBPF_LIBS="" LIBAUDIT_CFLAGS="" LIBAUDIT_LIBS="" CLANG="" BPFTOOL="" BPF_ARCH="" AC_CANONICAL_HOST AC_ARG_WITH([bpf-arch], AS_HELP_STRING([--with-bpf-arch=ARCH], [override detected BPF target arch (x86, arm64, arm, powerpc, s390, riscv, mips, loongarch)]), [BPF_ARCH="$withval"]) AC_ARG_ENABLE([cap-audit], AS_HELP_STRING([--enable-cap-audit],[build cap-audit [[default=no]]]), [enable_cap_audit=$enableval], [enable_cap_audit=no]) if test "x$enable_cap_audit" = "xyes"; then missing_cap_audit_deps=no AC_CHECK_PROG([CLANG],[clang],[clang],[no]) AC_CHECK_PROG([BPFTOOL],[bpftool],[bpftool],[no]) PKG_CHECK_MODULES([LIBBPF],[libbpf], [have_libbpf=yes],[have_libbpf=no]) PKG_CHECK_MODULES([LIBAUDIT],[audit], [have_libaudit=yes],[have_libaudit=no]) AC_CHECK_HEADERS([bpf/libbpf.h bpf/bpf.h linux/bpf.h libaudit.h], [], [missing_cap_audit_deps=yes]) if test "$CLANG" = "no" -o "$BPFTOOL" = "no" -o \ "x$have_libbpf" != "xyes" -o "x$have_libaudit" != "xyes" -o \ "x$missing_cap_audit_deps" = "xyes"; then AC_MSG_ERROR([cap-audit requires clang, bpftool, libbpf, and libaudit]) fi fi if test "x$BPF_ARCH" = "x"; then case "$host_cpu" in x86_64|i?86) BPF_ARCH=x86 ;; aarch64|arm64) BPF_ARCH=arm64 ;; arm*) BPF_ARCH=arm ;; ppc64le|ppc64|powerpc*) BPF_ARCH=powerpc ;; s390x|s390) BPF_ARCH=s390 ;; riscv64|riscv32|riscv*) BPF_ARCH=riscv ;; mips64|mips64el|mips|mipsel) BPF_ARCH=mips ;; loongarch64) BPF_ARCH=loongarch ;; *) BPF_ARCH=x86 ;; esac fi if test "x$BPF_ARCH" = "x"; then AC_MSG_ERROR([failed to determine BPF target arch; use --with-bpf-arch]) fi AC_MSG_CHECKING(enable-deprecated) AC_ARG_ENABLE([deprecated], AS_HELP_STRING([--enable-deprecated], [enable deprecated utilities such as captest [[default=no]]]), [enable_deprecated=$enableval], [enable_deprecated=no]) if test x$enable_deprecated = xno ; then AC_MSG_RESULT(no) else AC_MSG_RESULT(yes) fi AC_SUBST(BPF_ARCH) AC_SUBST(CLANG) AC_SUBST(BPFTOOL) AC_SUBST(LIBBPF_CFLAGS) AC_SUBST(LIBBPF_LIBS) AC_SUBST(LIBAUDIT_CFLAGS) AC_SUBST(LIBAUDIT_LIBS) AM_CONDITIONAL([BUILD_CAP_AUDIT], [test "x$enable_cap_audit" = "xyes"]) AM_CONDITIONAL([BUILD_DEPRECATED], [test "x$enable_deprecated" = "xyes"]) echo . echo Checking for header files AC_CHECK_HEADERS(linux/capability.h, [], [AC_MSG_ERROR(linux/capability.h is required in order to build libcap-ng.)]) AC_CHECK_HEADERS(sys/xattr.h, [], [ AC_CHECK_HEADERS(attr/xattr.h, [], [AC_MSG_WARN(attr/xattr.h not found, disabling file system capabilities.)]) ]) AC_CHECK_HEADERS(linux/securebits.h, [], []) AC_CHECK_HEADERS(linux/inet_diag.h linux/netlink.h linux/sock_diag.h linux/vm_sockets.h, [], []) AC_CHECK_HEADERS(linux/vm_sockets_diag.h, [], []) AC_CHECK_HEADERS(pthread.h, [AC_SEARCH_LIBS(pthread_atfork, pthread)], [AC_MSG_WARN(pthread.h not found, disabling pthread_atfork.)]) AC_CHECK_HEADERS(sys/vfs.h, [ AC_CHECK_HEADERS(linux/magic.h, [], [AC_MSG_WARN(linux/magic.h is required in order to verify procfs.)]) ], [AC_MSG_WARN(sys/vfs.h is required in order to verify procfs.)]) build_netcap_advanced=yes netcap_advanced_missing="" for netcap_hdr in \ linux/inet_diag.h:$ac_cv_header_linux_inet_diag_h \ linux/netlink.h:$ac_cv_header_linux_netlink_h \ linux/sock_diag.h:$ac_cv_header_linux_sock_diag_h \ linux/vm_sockets.h:$ac_cv_header_linux_vm_sockets_h; do netcap_name=${netcap_hdr%%:*} netcap_have=${netcap_hdr#*:} AS_IF([test "x$netcap_have" != xyes], [ build_netcap_advanced=no AS_IF([test "x$netcap_advanced_missing" = x], [ netcap_advanced_missing="$netcap_name" ], [ netcap_advanced_missing="$netcap_advanced_missing, $netcap_name" ]) ]) done AS_IF([test "x$build_netcap_advanced" = xyes], [ AC_DEFINE([HAVE_NETCAP_ADVANCED], [1], [Define to 1 if netcap advanced mode can be built.]) netcap_advanced_status="enabled" AC_MSG_NOTICE([netcap advanced mode enabled]) ], [ netcap_advanced_status="disabled (missing headers: $netcap_advanced_missing)" AC_MSG_WARN([netcap advanced mode disabled; missing headers: $netcap_advanced_missing]) ]) AC_SUBST([NETCAP_ADVANCED_ENABLED], [$build_netcap_advanced]) AC_SUBST([NETCAP_ADVANCED_STATUS], [$netcap_advanced_status]) AM_CONDITIONAL([BUILD_NETCAP_ADVANCED], [test "x$build_netcap_advanced" = xyes]) AC_ARG_WITH([capability_header], [AS_HELP_STRING([--with-capability_header=path : path to capability.h])], [CAPABILITY_HEADER=$withval], [CAPABILITY_HEADER=/usr/include/linux/capability.h]) AC_SUBST(CAPABILITY_HEADER) AC_MSG_CHECKING(__attr_dealloc_free support) AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[ #include extern char *test(const char *name) __attr_dealloc_free; int main(void) { return 0; }]])], [DEALLOC="yes"], [DEALLOC="no"] ) AC_MSG_RESULT($DEALLOC) AC_ARG_WITH(warn, [ --with-warn turn on warnings [[default=yes]]], [ if test "x${withval}" = xyes; then WARNS="$ALLWARNS" else WARNS="" fi ],WARNS="$ALLWARNS") AC_SUBST(DEBUG) WFLAGS="" AC_MSG_CHECKING(for -Wformat-truncation) TMPCFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -Wformat-truncation" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[WFLAGS="-Wno-format-truncation" AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no)]) CFLAGS="${TMPCFLAGS}" AC_SUBST(WFLAGS) AC_CHECK_HEADER(sys/syscall.h, [AC_DEFINE([HAVE_SYSCALL_H], [1], [Define to 1 if you have .])], [],) dnl; This is to record in the build logs what options are being taken AC_CHECK_DECLS([PR_CAP_AMBIENT], [], [], [[#include ]]) AC_CHECK_DECLS([VFS_CAP_REVISION_2], [], [], [[#include ]]) AC_CHECK_DECLS([VFS_CAP_REVISION_3], [], [], [[#include ]]) AC_CHECK_DECLS([CAP_EPOLLWAKEUP], [], [], [[#include ]]) AC_CHECK_DECLS([CAP_WAKE_ALARM], [], [], [[#include ]]) AC_CHECK_DECLS([CAP_BLOCK_SUSPEND], [], [], [[#include ]]) AC_CHECK_DECLS([CAP_AUDIT_READ], [], [], [[#include ]]) AC_CHECK_DECLS([CAP_PERFMON], [], [], [[#include ]]) AC_CHECK_DECLS([CAP_BPF], [], [], [[#include ]]) AC_CHECK_DECLS([CAP_CHECKPOINT_RESTORE], [], [], [[#include ]]) dnl only build libdrop_ambient if support for ambient capabilities was found (which is normal) if test x"${ac_cv_have_decl_PR_CAP_AMBIENT}" = x"no" ; then AC_MSG_WARN("PR_CAP_AMBIENT not available - libdrop_ambient will not be built") fi AM_CONDITIONAL(BUILD_LIBDROP_AMBIENT, test x"${ac_cv_have_decl_PR_CAP_AMBIENT}" = x"yes") AC_CHECK_PROG(swig_found, swig, yes, no) if test x"${swig_found}" = x"no" ; then AC_MSG_WARN("Swig not found - python bindings will not be made") fi AM_CONDITIONAL(HAVE_SWIG, test x"${swig_found}" = x"yes") withval="" AC_MSG_CHECKING(whether to create python3 bindings) AC_ARG_WITH(python3, AS_HELP_STRING([--with-python3],[enable building python3 bindings]), use_python3=$withval, use_python3=auto) if test x$use_python3 = xno ; then AC_MSG_RESULT(no) else AC_MSG_RESULT(investigating) AC_PATH_PROG([use_python3], [python3-config], [no]) if test ${use_python3} = no ; then if test ${withval} = yes ; then echo "Python3 bindings were selected but python3-config was not found." echo "Please ensure that it's installed or pass --without-python3 to ./configure" exit 1 fi echo "Python3 bindings will NOT be built" else echo "Python3 bindings WILL be built" use_python3=yes AM_PATH_PYTHON([3.1],, [:]) PYTHON3_CFLAGS=`python3-config --cflags 2> /dev/null` PYTHON3_LIBS=`python3-config --libs 2> /dev/null` PYTHON3_INCLUDES=`python3-config --includes 2> /dev/null` AC_SUBST(PYTHON3_CFLAGS) AC_SUBST(PYTHON3_LIBS) AC_SUBST(PYTHON3_INCLUDES) fi fi AM_CONDITIONAL(USE_PYTHON3, test ${use_python3} = "yes") AC_CONFIG_FILES([Makefile src/Makefile src/libcap-ng.pc src/test/Makefile bindings/Makefile bindings/python3/Makefile bindings/src/Makefile bindings/python3/test/Makefile bindings/test/Makefile utils/Makefile utils/cap-audit/Makefile m4/Makefile docs/Makefile]) AC_CONFIG_COMMANDS([utils/netcap.8], [ if test "x$NETCAP_ADVANCED_ENABLED" = xyes; then sed '/^.\\" BEGIN_ADVANCED$/d; /^.\\" END_ADVANCED$/d' \ "$srcdir/utils/netcap.8.in" > utils/netcap.8 else sed '/^.\\" BEGIN_ADVANCED$/,/^.\\" END_ADVANCED$/d' \ "$srcdir/utils/netcap.8.in" > utils/netcap.8 fi ], [NETCAP_ADVANCED_ENABLED="$NETCAP_ADVANCED_ENABLED" srcdir="$srcdir"]) AC_OUTPUT echo . echo " libcap-ng Version: $VERSION Target: $target Installation prefix: $prefix Build Kernel: `uname -r` Compiler: $CC Compiler flags: `echo $CFLAGS | fmt -w 50 | sed 's,^, ,'` __attr_dealloc_free support: $DEALLOC netcap advanced mode: $netcap_advanced_status " stevegrubb-libcap-ng-0ab44af/docs/000077500000000000000000000000001516575034500171425ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/docs/Makefile.am000066400000000000000000000025631516575034500212040ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009,2020 Red Hat Inc. # All Rights Reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Authors: # Steve Grubb # CONFIG_CLEAN_FILES = *.rej *.orig EXTRA_DIST = $(man_MANS) man_MANS = capng_clear.3 capng_fill.3 capng_setpid.3 \ capng_get_caps_process.3 capng_update.3 capng_updatev.3 \ capng_apply.3 capng_lock.3 capng_change_id.3 capng_get_caps_fd.3 \ capng_stage_additional_groups.3 \ capng_apply_caps_fd.3 capng_have_capabilities.3 \ capng_get_rootid.3 capng_set_rootid.3 capng_have_capability.3 \ capng_print_caps_numeric.3 capng_print_caps_text.3 \ capng_name_to_capability.3 capng_capability_to_name.3 \ capng_save_state.3 capng_restore_state.3 libdrop_ambient.7 stevegrubb-libcap-ng-0ab44af/docs/capng_apply.3000066400000000000000000000037211516575034500215260ustar00rootroot00000000000000.TH "CAPNG_APPLY" "3" "Sept 2023" "Red Hat" "Libcap-ng API" .SH NAME capng_apply \- apply the stored capabilities settings .SH "SYNOPSIS" .B #include .sp int capng_apply(capng_select_t set); .SH "DESCRIPTION" capng_apply will transfer the specified internal POSIX capabilities settings to the kernel. The options are CAPNG_SELECT_CAPS for the traditional capabilities, CAPNG_SELECT_BOUNDS for the bounding set, CAPNG_SELECT_BOTH if transferring both is desired, CAPNG_SELECT_AMBIENT if only operating on the ambient capabilities, or CAPNG_SELECT_ALL if applying all is desired. .SH "RETURN VALUE" This returns 0 on success and a negative value on failure. The values are: .RS .TP -1 not initialized .TP -2 CAPNG_SELECT_BOUNDS and failure to drop a bounding set capability .TP -3 CAPNG_SELECT_BOUNDS and failure to re-read bounding set .TP -4 CAPNG_SELECT_BOUNDS and process does not have CAP_SETPCAP .TP -5 CAPNG_SELECT_CAPS and failure in capset syscall .TP -6 CAPNG_SELECT_AMBIENT and process has no capabilities and failed clearing ambient capabilities .TP -7 CAPNG_SELECT_AMBIENT and process has capabilities and failed clearing ambient capabilities .TP -8 CAPNG_SELECT_AMBIENT and process has capabilities and failed setting an ambient capability .TP -9 Unable to acquire process capabilities to check if CAP_SETPCAP is set. .RE .SH NOTES If you are doing multi-threaded programming, calling this function will only set capabilities on the calling thread. All other threads are unaffected. If you want to set overall capabilities for a multi-threaded process, you will need to do that before creating any threads. See the capset syscall for more information on this topic. Also, bits in the bounding set can only be dropped. You cannot set them. After dropping bounding set capabilities, the bounding set is synchronized with the kernel to reflect the true state in the kernel. .SH "SEE ALSO" .BR capset (2), .BR capng_update (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_apply_caps_fd.3000066400000000000000000000014731516575034500232070ustar00rootroot00000000000000.TH "CAPNG_APPLY_CAPS_FD" "3" "Sept 2020" "Red Hat" "Libcap-ng API" .SH NAME capng_apply_caps_fd \- write file-based capabilities to extended attributes .SH "SYNOPSIS" .B #include .sp int capng_apply_caps_fd(int fd); .SH "DESCRIPTION" This function will write the file based capabilities to the extended attributes of the file that the descriptor was opened against. The bounding set is not included in file based capabilities operations. Note that this function will only work if compiled on a kernel that supports file based capabilities such as 2.6.2 6 and later. .SH "RETURN VALUE" This returns 0 on success, -1 if something besides a regular file is passed, and -2 if a non-root namespace id is being used for rootid. .SH "SEE ALSO" .BR capng_get_caps_fd (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_capability_to_name.3000066400000000000000000000016141516575034500242230ustar00rootroot00000000000000.TH "CAPNG_CAPABILITY_TO_NAME" "3" "June 2009" "Red Hat" "Libcap-ng API" .SH NAME capng_capability_to_name \- convert capability integer to text .SH "SYNOPSIS" .B #include .sp const char *capng_capability_to_name(unsigned int capability); .SH "DESCRIPTION" capng_capability_to_name will take the integer being passed and look it up to see what its text string representation would be. The integer being input must be in the valid range defined in linux/capability.h. The string that is output is the same as the define text from linux/capability.h with the CAP_ prefix removed and lower case. This is useful for taking integer representation and converting it to something more user friendly for display. .SH "RETURN VALUE" This returns a NULL pointer on failure and the correct string otherwise. .SH "SEE ALSO" .BR capng_name_to_capability (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_change_id.3000066400000000000000000000116521516575034500223040ustar00rootroot00000000000000.TH "CAPNG_CHANGE_ID" "3" "Mar 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_change_id \- change the credentials retaining capabilities .SH "SYNOPSIS" .B #include .sp int capng_change_id(int uid, int gid, capng_flags_t flag); .SH "DESCRIPTION" This function will change uid and gid to the ones given while retaining the capabilities previously specified in capng_update. It is also possible to specify -1 for either the uid or gid in which case the function will not change the uid or gid and leave it "as is". This is useful if you just want the flag options to be applied (assuming the option doesn't require more privileges that you currently have). It is not necessary and perhaps better if capng_apply has not been called prior to this function so that all necessary privileges are still intact. The caller may be required to have CAP_SETPCAP capability still active before calling this function or capabilities cannot be changed. Do not specify CAP_SETPCAP in the capability sets unless you intended to have it after the uid change. The library will silently add and remove it if it can. But if its specified, it will leave it alone. This function also takes a flag parameter that helps to tailor the exact actions performed by the function to secure the environment. The option may be or'ed together. The legal values are: .RS .TP .B CAPNG_NO_FLAG Simply change uid and retain specified capabilities and that's all. .TP .B CAPNG_DROP_SUPP_GRP After changing the primary group ID, remove all supplementary groups. If no new primary group ID is supplied (gid == -1), this flag has no effect. .TP .B CAPNG_INIT_SUPP_GRP After changing id, initialize any supplemental groups that may come with the new account. If given with .B CAPNG_DROP_SUPP_GRP it will have no effect. .TP .B CAPNG_APPLY_STAGED_GROUPS Available in libcap-ng 0.9.3 and later. Apply additional groups previously staged with .BR capng_stage_additional_groups (3). If used by itself, the staged list is applied exactly. If combined with .B CAPNG_INIT_SUPP_GRP the account's natural supplemental groups are initialized first and the staged gids are then merged in without duplicates. If combined with .B CAPNG_DROP_SUPP_GRP the call fails. If this flag is set and no staged gids are present, the call fails. Staged gids are intended to be used once and are always cleared before .B capng_change_id returns, even when the flag is omitted or the call fails. .TP .B CAPNG_CLEAR_BOUNDING Clear the bounding set regardless to the internal representation already setup prior to changing the uid/gid. .TP .B CAPNG_APPLY_BOUNDING Available in libcap-ng 0.9.3 and later. Apply the internally prepared bounding set during .BR capng_change_id . This only acts on bounding-set state explicitly prepared with .BR capng_clear (3), .BR capng_fill (3), or .BR capng_update (3) using .BR CAPNG_BOUNDING_SET . If no prepared bounding-set state exists, this flag is a no-op. If combined with .B CAPNG_CLEAR_BOUNDING the call fails. .TP .B CAPNG_CLEAR_AMBIENT Clear ambient capabilities regardless of the internal representation already setup prior to changing the uid/gid. .RE .SH "RETURN VALUE" This returns 0 on success and a negative number on failure. .RS .TP -1 means capng has not been initted properly .TP -2 means a failure requesting to keep capabilities across the uid change .TP -3 means that applying the intermediate capabilities failed .TP -4 means changing gid failed .TP -5 means initializing supplemental groups failed .TP -6 means changing the uid failed .TP -7 means dropping the ability to retain caps across a uid change failed .TP -8 means clearing the bounding set failed .TP -9 means dropping CAP_SETPCAP or ambient capabilities failed .TP -10 means resolving the target account for supplemental groups failed .TP -11 means dropping supplemental groups failed .TP -12 means CAPNG_DROP_SUPP_GRP and CAPNG_APPLY_STAGED_GROUPS were both set .TP -13 means CAPNG_APPLY_STAGED_GROUPS was requested with no staged gids .TP -14 means applying staged additional groups failed .TP -15 means looking up a group account in additional groups failed .TP -16 means merging initialized and staged additional groups failed .TP -17 means CAPNG_APPLY_BOUNDING and CAPNG_CLEAR_BOUNDING were both set .RE Applications that need to support libcap-ng releases older than 0.9.3 should detect the availability of .B CAPNG_APPLY_BOUNDING and .B CAPNG_APPLY_STAGED_SUPP_GRP and .BR capng_stage_additional_groups (3) before using this flow. Callers that need the prepared bounding-set behavior must request it explicitly with .B CAPNG_APPLY_BOUNDING so older callers continue to see the historical .B capng_change_id behavior. Note: the only safe action to do upon failure of this function is to probably exit. This is because you are likely in a situation with partial permissions and not what you intended. .SH "SEE ALSO" .BR capng_update (3), .BR capng_apply (3), .BR capng_stage_additional_groups (3), .BR prctl (2), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_clear.3000066400000000000000000000012071516575034500214640ustar00rootroot00000000000000.TH "CAPNG_CLEAR" "3" "Sept 2020" "Red Hat" "Libcap-ng API" .SH NAME capng_clear \- clear chosen capabilities set .SH "SYNOPSIS" .B #include .sp void capng_clear(capng_select_t set); .SH "DESCRIPTION" capng_clear sets to 0 all bits in the selected POSIX capabilities set. The options are CAPNG_SELECT_CAPS for the traditional capabilities, CAPNG_SELECT_BOUNDS for the bounding set, CAPNG_SELECT_BOTH if clearing both is desired, CAPNG_SELECT_AMBIENT if only operating on the ambient capabilities, or CAPNG_SELECT_ALL if clearing all is desired. .SH "RETURN VALUE" None. .SH "SEE ALSO" .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_fill.3000066400000000000000000000013731516575034500213300ustar00rootroot00000000000000.TH "CAPNG_FILL" "3" "March 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_fill \- fill chosen capabilities set .SH "SYNOPSIS" .B #include .sp void capng_fill(capng_select_t set); .SH "DESCRIPTION" capng_fill sets all bits to a 1 in the selected POSIX capabilities set. The options are CAPNG_SELECT_CAPS for the traditional capabilities, CAPNG_SELECT_BOUNDS for the bounding set, CAPNG_SELECT_BOTH if filling both is desired, CAPNG_SELECT_AMBIENT if only operating on the ambient capabilities, or CAPNG_SELECT_ALL if filling all is desired. .SH "RETURN VALUE" None. .SH NOTES During capng_apply, bits in the bounding set can only be dropped. You cannot set them. .SH "SEE ALSO" .BR capng_apply (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_get_caps_fd.3000066400000000000000000000015051516575034500226350ustar00rootroot00000000000000.TH "CAPNG_GET_CAPS_FD" "3" "March 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_get_caps_fd \- read file-based capabilities from extended attributes .SH "SYNOPSIS" .B #include .sp int capng_get_caps_fd(int fd); .SH "DESCRIPTION" This function will read the file based capabilities stored in extended attributes of the file that the descriptor was opened against. The bounding set is not included in file based capabilities operations. Note that this function will only work if compiled on a kernel that supports file based capabilities such as 2.6.26 and later. If the "magic" bit is set, then all effective capability bits are set. Otherwise the bits are cleared. .SH "RETURN VALUE" This returns 0 on success and -1 on failure. .SH "SEE ALSO" .BR capng_apply_caps_fd (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_get_caps_process.3000066400000000000000000000020031516575034500237140ustar00rootroot00000000000000.TH "CAPNG_GET_CAPS_PROCESS" "3" "June 2009" "Red Hat" "Libcap-ng API" .SH NAME capng_get_caps_process \- get the capabilities from a process .SH "SYNOPSIS" .B #include .sp int capng_get_caps_process(void); .SH "DESCRIPTION" capng_get_caps_process will get the capabilities and bounding set of the pid stored inside libcap-ng's state table. The default is the pid of the running process. This can be changed by using the capng_setpid function. .SH "RETURN VALUE" This returns 0 on success and -1 on failure. .SH NOTES If you are doing multi-threaded programming, calling this function will only get capabilities on the calling thread. If you want to get overall capabilities for a multi-threaded process, you can only do that before creating any threads. Afterwards, threads may be able to independently set capabilities. capng_get_caps_process needs a mounted /proc to read the current bounding set, otherwise it will fail. .SH "SEE ALSO" .BR capng_setpid (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_get_rootid.3000066400000000000000000000011501516575034500225320ustar00rootroot00000000000000.TH "CAPNG_GET_ROOTID" "3" "Sept 2020" "Red Hat" "Libcap-ng API" .SH NAME capng_get_rootid \- get namespace root id .SH "SYNOPSIS" .B #include .sp int capng_get_rootid(void); .SH "DESCRIPTION" capng_get_rootid gets the rootid for capabilities operations. This is only applicable for file system operations. .SH "RETURN VALUE" If the file is in the init namespace or the kernel does not support V3 file system capabilities, it returns CAPNG_UNSET_ROOTID. Otherwise it return an integer for the namespace root id. .SH "SEE ALSO" .BR capng_get_caps_fd (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_have_capabilities.3000066400000000000000000000027451516575034500240420ustar00rootroot00000000000000.TH "CAPNG_HAVE_CAPABILITIES" "3" "Sept 2020" "Red Hat" "Libcap-ng API" .SH NAME .nf capng_have_capabilities, capng_have_permitted_capabilities \- check for capabilities .SH "SYNOPSIS" .B #include .sp capng_results_t capng_have_capabilities(capng_select_t set); capng_results_t capng_have_permitted_capabilities(void); .SH "DESCRIPTION" capng_have_capabilities will check the selected internal capabilities sets to see what the status is. The capabilities sets must be previously setup with calls to capng_get_caps_process, capng_get_caps_fd, or in some other way setup. The options are CAPNG_SELECT_CAPS for the traditional capabilities, CAPNG_SELECT_BOUNDS for the bounding set, CAPNG_SELECT_BOTH if checking both are desired, CAPNG_SELECT_AMBIENT if only checking the ambient capabilities, or CAPNG_SELECT_ALL if testing all sets is desired. When capabilities are checked, it will only look at the effective capabilities. If, however, the source of capabilities comes from a file, then you may need to additionally check the permitted capabilities. It's for this reason that .B capng_have_permitted_capabilities was created. It takes no arguments because it simply checks the permitted set. .SH "RETURN VALUE" This function will return one of the following four self explanatory values: CAPNG_FAIL, CAPNG_NONE, CAPNG_PARTIAL, CAPNG_FULL. .SH "SEE ALSO" .BR capng_get_caps_process (3), .BR capng_get_caps_fd (3), .BR capng_have_capability (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_have_capability.3000066400000000000000000000015511516575034500235240ustar00rootroot00000000000000.TH "CAPNG_HAVE_CAPABILITY" "3" "June 2009" "Red Hat" "Libcap-ng API" .SH NAME capng_have_capability \- check for specific capability .SH "SYNOPSIS" .B #include .sp int capng_have_capability(capng_type_t which, unsigned int capability); .SH "DESCRIPTION" capng_have_capability will check the specified internal capabilities set to see if the specified capability is set. The capabilities sets must be previously setup with calls to capng_get_caps_process, capng_get_caps_fd, or in some other way setup. The values for which should be one of: CAPNG_EFFECTIVE, CAPNG_PERMITTED, CAPNG_INHERITABLE, CAPNG_BOUNDING_SET, or CAPNG_AMBIENT. .SH "RETURN VALUE" This function will return 1 if yes and 0 otherwise. .SH "SEE ALSO" .BR capng_get_caps_process (3), .BR capng_get_caps_fd (3), .BR capng_have_capabilities (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_lock.3000066400000000000000000000023671516575034500213360ustar00rootroot00000000000000.TH "CAPNG_LOCK" "3" "Mar 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_lock \- lock the current process capabilities settings .SH "SYNOPSIS" .B #include .sp int capng_lock(void); .SH "DESCRIPTION" capng_lock will take steps to prevent children of the current process to regain full privileges if the uid is 0. This should be called while possessing the CAP_SETPCAP capability in the kernel. This function will do the following if permitted by the kernel: .IP \[bu] 2 Set the NOROOT option on for PR_SET_SECUREBITS. .IP \[bu] 2 Set the NOROOT_LOCKED option to on for PR_SET_SECUREBITS. .IP \[bu] 2 Set the PR_NO_SETUID_FIXUP option on for PR_SET_SECUREBITS. .IP \[bu] 2 Set the PR_NO_SETUID_FIXUP_LOCKED option on for PR_SET_SECUREBITS. .PP It does not enable PR_SET_KEEPCAPS or the KEEP_CAPS/KEEP_CAPS_LOCKED securebits; after a successful call those usually remain off unless the caller changed them separately. .SH "RETURN VALUE" This returns 0 on success and a negative number on failure. -1 means a failure setting any of the PR_SET_SECUREBITS options. -2 means failure setting PR_SET_NO_NEW_PRIVS. These are additive meaning -3 is a failure of both. .SH "SEE ALSO" .BR capng_apply (3), .BR prctl (2), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_name_to_capability.3000066400000000000000000000015731516575034500242270ustar00rootroot00000000000000.TH "CAPNG_NAME_TO_CAPABILITY" "3" "June 2009" "Red Hat" "Libcap-ng API" .SH NAME capng_name_to_capability \- convert capability text to integer .SH "SYNOPSIS" .B #include .sp int capng_name_to_capability(const char *name); .SH "DESCRIPTION" capng_name_to_capability will take the string being passed and look it up to see what its integer value would be. The string being input is the same name as the define in linux/capability.h with the CAP_ prefix removed. The string case does not matter. The integer that is output is the same as the define would be from linux/capability.h. This is useful for taking string input and converting to something that can be used with capng_update. .SH "RETURN VALUE" This returns a negative number on failure and the correct define otherwise. .SH "SEE ALSO" .BR capng_capability_to_name (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_print_caps_numeric.3000066400000000000000000000024011516575034500242570ustar00rootroot00000000000000.TH "CAPNG_PRINT_CAPS_NUMERIC" "3" "Sept 2020" "Red Hat" "Libcap-ng API" .SH NAME capng_print_caps_numeric \- print numeric values for capabilities set .SH "SYNOPSIS" .B #include .sp char *capng_print_caps_numeric(capng_print_t where, capng_select_t set); .SH "DESCRIPTION" capng_print_caps_numeric will create a numeric representation of the internal capabilities. The representation can be sent to either stdout or a buffer by passing CAPNG_PRINT_STDOUT or CAPNG_PRINT_BUFFER respectively for the where parameter. If the option was for a buffer, this function will malloc a buffer that the caller must free. The set parameter controls what is included in the representation. The legal options are CAPNG_SELECT_CAPS for the traditional capabilities, CAPNG_SELECT_BOUNDS for the bounding set, CAPNG_SELECT_BOTH if printing both is desired, CAPNG_SELECT_AMBIENT if only printing the ambient capabilities, or CAPNG_SELECT_ALL if printing all is desired. .SH "RETURN VALUE" If CAPNG_PRINT_BUFFER was selected for where, this will be the text buffer and NULL on failure. Don't forget to free it. If CAPNG_PRINT_STDOUT was selected then this value will be NULL no matter what. .SH "SEE ALSO" .BR capng_print_caps_text (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_print_caps_text.3000066400000000000000000000020731516575034500236060ustar00rootroot00000000000000.TH "CAPNG_PRINT_CAPS_TEXT" "3" "March 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_print_caps_text \- print names of values for capabilities set .SH "SYNOPSIS" .B #include .sp char *capng_print_caps_text(capng_print_t where, capng_type_t which); .SH "DESCRIPTION" capng_print_caps_text will create a text string representation of the internal capability set specified. The representation can be sent to either stdout or a buffer by passing CAPNG_PRINT_STDOUT or CAPNG_PRINT_BUFFER respectively for the where parameter. If the option was for a buffer, this function will malloc a buffer that the caller must free. The legal values for the which parameter is CAPNG_EFFECTIVE, CAPNG_PERMITTED, CAPNG_INHERITABLE, CAPNG_BOUNDING_SET, or CAPNG_AMBIENT. .SH "RETURN VALUE" If CAPNG_PRINT_BUFFER was selected for where, this will be the text buffer and NULL on failure. Don't forget to free it. If CAPNG_PRINT_STDOUT was selected then this value will be NULL no matter what. .SH "SEE ALSO" .BR capng_print_caps_numeric (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_restore_state.3000066400000000000000000000014431516575034500232630ustar00rootroot00000000000000.TH "CAPNG_RESTORE_STATE" "3" "March 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_restore_state \- set the internal library state .SH "SYNOPSIS" .B #include .sp void capng_restore_state(void **state); .SH "DESCRIPTION" capng_restore_state is a function that takes the state information previously saved by capng_save_state and restores the libraries internal state. This function is not available in the python bindings. .SH "RETURN VALUE" None. .SH NOTES capng_restore_state free's the previously malloc'd state, thus the state can't be restored multiple times. The working pid is part of the restored state, if restoring the state to a different thread, capng_setpid can be used to update it. .SH "SEE ALSO" .BR capng_save_state (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_save_state.3000066400000000000000000000015421516575034500225360ustar00rootroot00000000000000.TH "CAPNG_SAVE_STATE" "3" "June 2009" "Red Hat" "Libcap-ng API" .SH NAME capng_save_state \- get the internal library state .SH "SYNOPSIS" .B #include .sp void *capng_save_state(void); .SH "DESCRIPTION" capng_save_state is a function that returns a pointer to the internal state of the libcap-ng library. It should be considered opaque and not for alteration directly. This function should be used when you suspect a third party library may use libcap-ng also and want to make sure it doesn't alter something important. This function is not available in the python bindings. .SH "RETURN VALUE" This returns NULL on failure and a non-NULL pointer otherwise. .SH NOTES The structure returned by capng_save_state is malloc'd; it should be free'd if not used. .SH "SEE ALSO" .BR capng_restore_state (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_set_rootid.3000066400000000000000000000012471516575034500225550ustar00rootroot00000000000000.TH "CAPNG_SET_ROOTID" "3" "March 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_set_rootid \- set namespace root id .SH "SYNOPSIS" .B #include .sp int capng_set_rootid(int rootid); .SH "DESCRIPTION" capng_set_rootid sets the rootid for file capabilities operations. Pass CAPNG_UNSET_ROOTID to clear a previously selected namespace root id and go back to writing regular V2 file capabilities. .SH "RETURN VALUE" On success, it returns 0. It returns -1 if there is an internal error, the kernel does not support V3 filesystem capabilities, or an invalid rootid is supplied. .SH "SEE ALSO" .BR capng_apply_caps_fd (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_setpid.3000066400000000000000000000017631516575034500216750ustar00rootroot00000000000000.TH "CAPNG_SETPID" "3" "March 2026" "Red Hat" "Libcap-ng API" .SH NAME capng_setpid \- set working pid .SH "SYNOPSIS" .B #include .sp void capng_setpid(int pid); .SH "DESCRIPTION" capng_setpid sets the working pid for capabilities operations. This is useful if you want to get the capabilities of a different process. .SH NOTES If your process calls .B fork , then the child process will still have the pid of the parent process stored in libcap-ng's internal data. It is disallowed to do any kind of setcap operations because you would be crossing process boundaries. To correct this, if your program links against pthreads, then libcap-ng will use the .B pthread_atfork function (as a weak symbol) to reset the pid information to the new process automatically. You are not required to link against pthreads. You can call .B capng_setpid and adjust the stored pid manually. .SH "RETURN VALUE" None. .SH "SEE ALSO" .BR capng_get_caps_process (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_stage_additional_groups.3000066400000000000000000000024161516575034500252730ustar00rootroot00000000000000.TH "CAPNG_STAGE_ADDITIONAL_GROUPS" "3" "Mar 2026" "Red Hat" \ "Libcap-ng API" .SH NAME capng_stage_additional_groups \- stage additional gids for capng_change_id .SH "SYNOPSIS" .B #include .sp int capng_stage_additional_groups(const gid_t *gids, size_t count); .SH "DESCRIPTION" This function stages an additional gid list for the next .BR capng_change_id (3) call that uses .BR CAPNG_APPLY_STAGED_GROUPS . The gids are copied into libcap-ng internal state. Once .B capng_change_id returns, libcap-ng clears the staged gids whether they were applied, ignored because the flag was not set, or discarded because the call failed. If staged gids remain unused, libcap-ng also clears them when the thread-local state is deinitialized. Passing a .I count of 0 clears any currently staged gids. Normally, you should stick to using the supplemantal groups that are associated with the uid's account that is being changed to. This is provided to simulate the gid model that systemd uses. This interface is available in libcap-ng 0.9.3 and later. .SH "RETURN VALUE" This returns 0 on success and -1 on failure. A non-zero .I count with a NULL .I gids pointer fails with .B errno set to .BR EINVAL . .SH "SEE ALSO" .BR capng_change_id (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_update.3000066400000000000000000000017441516575034500216660ustar00rootroot00000000000000.TH "CAPNG_UPDATE" "3" "June 2009" "Red Hat" "Libcap-ng API" .SH NAME capng_update \- update the stored capabilities settings .SH "SYNOPSIS" .B #include .sp int capng_update(capng_act_t action, capng_type_t type,unsigned int capability); .SH "DESCRIPTION" capng_update will update the internal POSIX capabilities settings based on the options passed to it. The action should be either CAPNG_DROP to set the capability bit to 0, or CAPNG_ADD to set the capability bit to 1. The operation is performed on the capability set specified in the type parameter. The values are: CAPNG_EFFECTIVE, CAPNG_PERMITTED, CAPNG_INHERITABLE, CAPNG_BOUNDING_SET, or CAPNG_AMBIENT. The values may be or'ed together to perform the same operation on multiple sets. The last parameter, capability, is the capability define as given in linux/capability.h. .SH "RETURN VALUE" This returns 0 on success and -1 on failure. .SH "SEE ALSO" .BR capng_updatev (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/capng_updatev.3000066400000000000000000000021621516575034500220470ustar00rootroot00000000000000.TH "CAPNG_UPDATEV" "3" "June 2009" "Red Hat" "Libcap-ng API" .SH NAME capng_updatev \- update the stored capabilities settings .SH "SYNOPSIS" .B #include .sp int capng_updatev(capng_act_t action, capng_type_t type, unsigned int capability, ...); .SH "DESCRIPTION" capng_updatev will update the internal POSIX capabilities settings based on the options passed to it. The action should be either CAPNG_DROP to set the capability bit to 0, or CAPNG_ADD to set the capability bit to 1. The operation is performed on the capability set specified in the type parameter. The values are: CAPNG_EFFECTIVE, CAPNG_PERMITTED, CAPNG_INHERITABLE, CAPNG_BOUNDING_SET, or CAPNG_AMBIENT. The values may be or'ed together to perform the same operation on multiple sets. The last parameter, capability, is the capability define as given in linux/capability.h. This function differs from capng_update in that you may pass a list of capabilities. This list must be terminated with a -1 value. .SH "RETURN VALUE" This returns 0 on success and -1 on failure. .SH "SEE ALSO" .BR capng_update (3), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/docs/libdrop_ambient.7000066400000000000000000000011241516575034500223620ustar00rootroot00000000000000.TH "LIBDROP_AMBIENT" "7" "March 2026" "Red Hat" "Libcap-ng API" .SH NAME libdrop_ambient \- force application started with ambient capabilities to drop them .SH "DESCRIPTION" This library can be used via LD_PRELOAD to force an application started with ambient capabilities to drop them. It leaves other capabilities intact. This can also be linked against and automatically does the right thing. You do not need to make any calls into the library because all the work is done in the constructor which runs before main() is called. .SH "SEE ALSO" .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/libcap-ng.spec000066400000000000000000000067611516575034500207340ustar00rootroot00000000000000%global bpf_supported_arches aarch64 x86_64 ppc64le riscv64 s390x Summary: An alternate POSIX capabilities library Name: libcap-ng Version: 0.9.3 Release: 1%{?dist} License: LGPL-2.0-or-later URL: https://github.com/stevegrubb/libcap-ng Source0: %{name}-%{version}.tar.gz BuildRequires: gcc make BuildRequires: autoconf automake libtool BuildRequires: kernel-headers >= 2.6.11 BuildRequires: libattr-devel %ifarch %{bpf_supported_arches} # These next ones are only if --enable-cap-audit is configured BuildRequires: clang BuildRequires: bpftool libbpf-devel BuildRequires: audit-libs-devel %endif %description Libcap-ng is a library that makes using POSIX capabilities easier %package devel Summary: Header files for libcap-ng library License: LGPL-2.0-or-later Requires: kernel-headers >= 2.6.11 Requires: %{name} = %{version}-%{release} Requires: pkgconfig %description devel The libcap-ng-devel package contains the files needed for developing applications that need to use the libcap-ng library. %package python3 Summary: Python3 bindings for libcap-ng library License: LGPL-2.0-or-later BuildRequires: python3-devel swig Requires: %{name} = %{version}-%{release} %description python3 The libcap-ng-python3 package contains the bindings so that libcap-ng and can be used by python3 applications. %package utils Summary: Utilities for analyzing and setting file capabilities License: GPL-2.0-or-later Requires: %{name} = %{version}-%{release} %ifarch %{bpf_supported_arches} Provides: %{name}-audit Obsoletes: %{name}-audit < %{version}-%{release} %endif %description utils The libcap-ng-utils package contains applications to analyze the POSIX capabilities of all the program running on a system. It also lets you set the file system based capabilities, and use cap-audit to determine the necessary capabilities for a program. %prep %setup -q touch NEWS autoreconf -fv --install %build %configure --libdir=%{_libdir} %ifarch %{bpf_supported_arches} \ --enable-cap-audit=yes \ %endif --with-python3 make CFLAGS="%{optflags}" %{?_smp_mflags} %install %make_install # Remove a couple things so they don't get picked up rm -f $RPM_BUILD_ROOT/%{_libdir}/libcap-ng.la rm -f $RPM_BUILD_ROOT/%{_libdir}/libcap-ng.a rm -f $RPM_BUILD_ROOT/%{_libdir}/libdrop_ambient.la rm -f $RPM_BUILD_ROOT/%{_libdir}/libdrop_ambient.a rm -f $RPM_BUILD_ROOT/%{_libdir}/python%{python3_version}/site-packages/_capng.a rm -f $RPM_BUILD_ROOT/%{_libdir}/python%{python3_version}/site-packages/_capng.la %check make check %ldconfig_scriptlets %files %doc COPYING.LIB /%{_libdir}/libcap-ng.so.* /%{_libdir}/libdrop_ambient.so.* %attr(0644,root,root) %{_mandir}/man7/* %files devel %attr(0644,root,root) %{_mandir}/man3/* %attr(0644,root,root) %{_includedir}/cap-ng.h %{_libdir}/libcap-ng.so %{_libdir}/libdrop_ambient.so %attr(0644,root,root) %{_datadir}/aclocal/cap-ng.m4 %{_libdir}/pkgconfig/libcap-ng.pc %files python3 %attr(755,root,root) %{python3_sitearch}/* %files utils %doc COPYING %attr(0755,root,root) %{_bindir}/filecap %attr(0755,root,root) %{_bindir}/netcap %attr(0755,root,root) %{_bindir}/pscap %attr(0644,root,root) %{_mandir}/man8/filecap.8.gz %attr(0644,root,root) %{_mandir}/man8/netcap.8.gz %attr(0644,root,root) %{_mandir}/man8/pscap.8.gz %attr(0644,root,root) %{_datadir}/bash-completion/completions/libcap-ng.bash_completion %ifarch %{bpf_supported_arches} %attr(0755,root,root) %{_bindir}/cap-audit %attr(0644,root,root) %{_mandir}/man8/cap-audit.8.gz %endif %changelog * Thu Apr 09 2026 Steve Grubb 0.9.3-1 - New upstream release stevegrubb-libcap-ng-0ab44af/m4/000077500000000000000000000000001516575034500165325ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/m4/Makefile.am000066400000000000000000000017161516575034500205730ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009 Red Hat Inc., Durham, North Carolina. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Authors: # Steve Grubb # CONFIG_CLEAN_FILES = *.loT *.rej *.orig m4datadir = $(datadir)/aclocal dist_m4data_DATA = cap-ng.m4 stevegrubb-libcap-ng-0ab44af/m4/cap-ng.m4000066400000000000000000000023501516575034500201410ustar00rootroot00000000000000# libcap-ng.m4 - Checks for the libcap-ng support # Copyright (c) 2009 Steve Grubb sgrubb@redhat.com # AC_DEFUN([LIBCAP_NG_PATH], [ AC_ARG_WITH(libcap-ng, [ --with-libcap-ng=[auto/yes/no] Add Libcap-ng support [default=auto]],, with_libcap_ng=auto) # Check for Libcap-ng API # # libcap-ng detection if test x$with_libcap_ng = xno ; then have_libcap_ng=no; else # Start by checking for header file AC_CHECK_HEADER(cap-ng.h, capng_headers=yes, capng_headers=no) # See if we have libcap-ng library AC_CHECK_LIB(cap-ng, capng_clear, CAPNG_LDADD=-lcap-ng,) # Check results are usable if test x$with_libcap_ng = xyes -a x$CAPNG_LDADD = x ; then AC_MSG_ERROR(libcap-ng support was requested and the library was not found) fi if test x$CAPNG_LDADD != x -a $capng_headers = no ; then AC_MSG_ERROR(libcap-ng libraries found but headers are missing) fi fi AC_SUBST(CAPNG_LDADD) AC_MSG_CHECKING(whether to use libcap-ng) if test x$CAPNG_LDADD != x ; then AC_DEFINE(HAVE_LIBCAP_NG,1,[libcap-ng support]) AC_CHECK_DECLS([CAPNG_AMBIENT], [], [], [[#include ]]) AC_MSG_RESULT(yes) else AC_MSG_RESULT(no) fi ]) stevegrubb-libcap-ng-0ab44af/src/000077500000000000000000000000001516575034500170015ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/src/Makefile.am000066400000000000000000000030451516575034500210370ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009,2015,2020 Red Hat Inc. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; see the file COPYING.LIB. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # SUBDIRS = test pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libcap-ng.pc DISTCLEANFILES = $(pkgconfig_DATA) CLEANFILES = $(BUILT_SOURCES) CONFIG_CLEAN_FILES = *.loT *.rej *.orig AM_CFLAGS = -fPIC -DPIC -W -Wall -Wshadow ${WFLAGS} -Wundef -D_GNU_SOURCE AM_CPPFLAGS = -I. -I${top_srcdir} lib_LTLIBRARIES = libcap-ng.la if BUILD_LIBDROP_AMBIENT lib_LTLIBRARIES += libdrop_ambient.la endif include_HEADERS = cap-ng.h noinst_HEADERS = captab.h libcap_ng_la_SOURCES = cap-ng.c lookup_table.c libcap_ng_la_LIBADD = libcap_ng_la_DEPENDENCIES = $(libcap_ng_la_SOURCES) ../config.h libcap_ng_la_LDFLAGS = -Wl,-z,relro -Wl,-z,nodelete libdrop_ambient_la_SOURCES = libdrop_ambient.c stevegrubb-libcap-ng-0ab44af/src/cap-ng.c000066400000000000000000001216531516575034500203220ustar00rootroot00000000000000/* libcap-ng.c -- * Copyright 2009-10, 2013, 2017, 2020-26 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include "cap-ng.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTHREAD_H #include // For pthread_atfork #endif #ifdef HAVE_SYSCALL_H #include #endif #ifdef HAVE_LINUX_SECUREBITS_H #include #endif #ifdef HAVE_LINUX_MAGIC_H #include #include #endif # define hidden __attribute__ ((visibility ("hidden"))) unsigned int last_cap hidden = 0; /* * Some milestones of when things became available: * 2.6.24 kernel XATTR_NAME_CAPS * 2.6.25 kernel PR_CAPBSET_DROP, CAPABILITY_VERSION_2 * 2.6.26 kernel PR_SET_SECUREBITS, SECURE_*_LOCKED, VERSION_3 * 3.5 kernel PR_SET_NO_NEW_PRIVS * 4.3 kernel PR_CAP_AMBIENT * 4.14 kernel VFS_CAP_REVISION_3 */ #ifdef PR_CAPBSET_DROP static int HAVE_PR_CAPBSET_DROP = 0; #endif #ifdef PR_CAP_AMBIENT static int HAVE_PR_CAP_AMBIENT = 0; #endif /* External syscall prototypes */ extern int capset(cap_user_header_t header, cap_user_data_t data); extern int capget(cap_user_header_t header, const cap_user_data_t data); // Local functions static void update_bounding_set(capng_act_t action, unsigned int capability, unsigned int idx); static void update_ambient_set(capng_act_t action, unsigned int capability, unsigned int idx); // Local defines #define MASK(x) (1U << (x)) #ifdef PR_CAPBSET_DROP #define UPPER_MASK ~((~0U)<<(last_cap-31)) #else // For v1 systems UPPER_MASK will never be used #define UPPER_MASK (unsigned)(~0U) #endif // Re-define cap_valid so its uniform between V1 and V3 #undef cap_valid #define cap_valid(x) ((x) <= last_cap) // If we don't have the xattr library, then we can't // compile-in file system capabilities #if !defined(HAVE_ATTR_XATTR_H) && !defined (HAVE_SYS_XATTR_H) #undef VFS_CAP_U32 #endif #ifdef VFS_CAP_U32 #ifdef HAVE_SYS_XATTR_H #include #else #ifdef HAVE_ATTR_XATTR_H #include #endif #endif #if __BYTE_ORDER == __BIG_ENDIAN #define FIXUP(x) bswap_32(x) #else #define FIXUP(x) (x) #endif #endif #ifndef _LINUX_CAPABILITY_VERSION_1 #define _LINUX_CAPABILITY_VERSION_1 0x19980330 #endif #ifndef _LINUX_CAPABILITY_VERSION_2 #define _LINUX_CAPABILITY_VERSION_2 0x20071026 #endif #ifndef _LINUX_CAPABILITY_VERSION_3 #define _LINUX_CAPABILITY_VERSION_3 0x20080522 #endif // This public API went private in the 2.6.36 kernel - hope it never changes #ifndef XATTR_CAPS_SUFFIX #define XATTR_CAPS_SUFFIX "capability" #endif #ifndef XATTR_SECURITY_PREFIX #define XATTR_SECURITY_PREFIX "security." #endif #ifndef XATTR_NAME_CAPS #define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX #endif /* Child processes can't get caps back */ #ifndef SECURE_NOROOT #define SECURE_NOROOT 0 #endif #ifndef SECURE_NOROOT_LOCKED #define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ #endif /* Setuid apps run by uid 0 don't get caps back */ #ifndef SECURE_NO_SETUID_FIXUP #define SECURE_NO_SETUID_FIXUP 2 #endif #ifndef SECURE_NO_SETUID_FIXUP_LOCKED #define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ #endif #ifndef VFS_CAP_U32 #define VFS_CAP_U32 2 #endif #if (VFS_CAP_U32 != 2) #error VFS_CAP_U32 does not match the library, you need a new version #endif // States: new, allocated, initted, updated, applied typedef enum { CAPNG_NEW, CAPNG_ERROR, CAPNG_ALLOCATED, CAPNG_INIT, CAPNG_UPDATED, CAPNG_APPLIED } capng_states_t; // Create an easy data struct out of the kernel definitions typedef union { struct __user_cap_data_struct v1; struct __user_cap_data_struct v3[VFS_CAP_U32]; } cap_data_t; // This struct keeps all state info struct cap_ng { int cap_ver; int vfs_cap_ver; struct __user_cap_header_struct hdr; cap_data_t data; capng_states_t state; __le32 rootid; __u32 bounds[VFS_CAP_U32]; __u32 ambient[VFS_CAP_U32]; unsigned char bounds_state_changed; gid_t *add_groups; size_t add_group_cnt; }; // Global variables with per thread uniqueness static __thread struct cap_ng m = { 1, 1, {0, 0}, { {0, 0, 0} }, CAPNG_NEW, CAPNG_UNSET_ROOTID, {0, 0}, {0, 0}, 0, NULL, 0 }; static void clear_staged_additional_groups(struct cap_ng *c) { free(c->add_groups); c->add_groups = NULL; c->add_group_cnt = 0; } static inline void mark_bounding_set_changed(struct cap_ng *c) { c->bounds_state_changed = 1; } static inline void clear_bounding_set_changed(struct cap_ng *c) { c->bounds_state_changed = 0; } static int copy_staged_additional_groups(struct cap_ng *dst, const struct cap_ng *src) { size_t len; dst->add_groups = NULL; dst->add_group_cnt = 0; if (src->add_group_cnt == 0) return 0; len = src->add_group_cnt * sizeof(gid_t); dst->add_groups = malloc(len); if (dst->add_groups == NULL) return -1; memcpy(dst->add_groups, src->add_groups, len); dst->add_group_cnt = src->add_group_cnt; return 0; } static int gid_in_list(const gid_t *gids, size_t count, gid_t gid) { size_t i; for (i = 0; i < count; i++) { if (gids[i] == gid) return 1; } return 0; } static int get_additional_groups(const struct passwd *pw, gid_t gid, gid_t **gids, size_t *count) { gid_t *list; int ngroups = 1; int rc; *gids = NULL; *count = 0; list = malloc(sizeof(gid_t)); if (list == NULL) return -1; rc = getgrouplist(pw->pw_name, gid, list, &ngroups); if (rc == -1) { gid_t *tmp; tmp = realloc(list, sizeof(gid_t) * ngroups); if (tmp == NULL) { free(list); return -1; } list = tmp; rc = getgrouplist(pw->pw_name, gid, list, &ngroups); } if (rc == -1) { free(list); return -1; } *gids = list; *count = ngroups; return 0; } static int merge_additional_groups(const gid_t *base, size_t base_cnt, const gid_t *extra, size_t extra_cnt, gid_t **merged, size_t *merged_cnt) { gid_t *list; size_t i, count = 0, total = base_cnt + extra_cnt; *merged = NULL; *merged_cnt = 0; if (total == 0) return 0; list = malloc(sizeof(gid_t) * total); if (list == NULL) return -1; for (i = 0; i < base_cnt; i++) { if (gid_in_list(list, count, base[i]) == 0) list[count++] = base[i]; } for (i = 0; i < extra_cnt; i++) { if (gid_in_list(list, count, extra[i]) == 0) list[count++] = extra[i]; } *merged = list; *merged_cnt = count; return 0; } /* * Reset the state so that init gets called to erase everything */ static void deinit(void) { clear_staged_additional_groups(&m); m.state = CAPNG_NEW; } static inline int test_cap(unsigned int cap) { // prctl returns 0 or 1 for valid caps, -1 otherwise return prctl(PR_CAPBSET_READ, cap) >= 0; } // The capability storage is fixed by VFS_CAP_U32 #define MAX_CAP_BITS (VFS_CAP_U32 * sizeof(__le32) * 8) #define MAX_CAP_VALUE (MAX_CAP_BITS - 1) static void init_lib(void) __attribute__ ((constructor)); static void init_lib(void) { /* This is so that dynamic or static libraries don't re-init */ static unsigned int run_once; if (__atomic_load_n(&run_once, __ATOMIC_ACQUIRE) == 2) return; if (!__sync_bool_compare_and_swap(&run_once, 0, 1)) { while (__atomic_load_n(&run_once, __ATOMIC_ACQUIRE) != 2) ; return; } #ifdef HAVE_PTHREAD_H pthread_atfork(NULL, NULL, deinit); #endif // Detect last cap if (last_cap == 0) { int fd; // Try to read last cap from procfs fd = open("/proc/sys/kernel/cap_last_cap", O_RDONLY); if (fd >= 0) { #ifdef HAVE_LINUX_MAGIC_H struct statfs st; // Bail out if procfs is invalid or fstatfs fails if (fstatfs(fd, &st) || st.f_type != PROC_SUPER_MAGIC) goto fail; #endif char buf[8]; int num = read(fd, buf, sizeof(buf) - 1); if (num > 0) { buf[num] = 0; errno = 0; unsigned int val = strtoul(buf, NULL, 10); if (errno == 0) last_cap = val; } fail: close(fd); } if (last_cap >= MAX_CAP_BITS) last_cap = MAX_CAP_VALUE; // Run a binary search over capabilities if (last_cap == 0) { // starting with last_cap=MAX_CAP_BITS means we always know // that cap1 is invalid after the first iteration last_cap = MAX_CAP_BITS; unsigned int cap0 = 0, cap1 = MAX_CAP_BITS; while (cap0 < last_cap) { if (test_cap(last_cap)) cap0 = last_cap; else cap1 = last_cap; last_cap = (cap0 + cap1) / 2U; } } } // Detect prctl options at runtime #ifdef PR_CAPBSET_DROP errno = 0; prctl(PR_CAPBSET_READ, 0, 0, 0, 0); if (errno != EINVAL) HAVE_PR_CAPBSET_DROP = 1; #endif #ifdef PR_CAP_AMBIENT errno = 0; prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0); if (errno != EINVAL) HAVE_PR_CAP_AMBIENT = 1; #endif __atomic_store_n(&run_once, 2, __ATOMIC_RELEASE); } static void init(void) { // This is so static libs get initialized init_lib(); if (m.state != CAPNG_NEW) return; memset(&m.hdr, 0, sizeof(m.hdr)); (void)capget(&m.hdr, NULL); // Returns -EINVAL if (m.hdr.version == _LINUX_CAPABILITY_VERSION_3 || m.hdr.version == _LINUX_CAPABILITY_VERSION_2) { m.cap_ver = 3; } else if (m.hdr.version == _LINUX_CAPABILITY_VERSION_1) { m.cap_ver = 1; } else { m.state = CAPNG_ERROR; return; } #if VFS_CAP_REVISION == VFS_CAP_REVISION_1 m.vfs_cap_ver = 1; #else m.vfs_cap_ver = 2; // Intentionally set to 2 for both 2 & 3 #endif memset(&m.data, 0, sizeof(cap_data_t)); #ifdef HAVE_SYSCALL_H m.hdr.pid = (unsigned)syscall(__NR_gettid); #else m.hdr.pid = (unsigned)getpid(); #endif m.rootid = CAPNG_UNSET_ROOTID; m.state = CAPNG_ALLOCATED; } void capng_clear(capng_select_t set) { if (m.state == CAPNG_NEW) init(); if (m.state == CAPNG_ERROR) return; if (set & CAPNG_SELECT_CAPS) memset(&m.data, 0, sizeof(cap_data_t)); #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { if (set & CAPNG_SELECT_BOUNDS) { memset(m.bounds, 0, sizeof(m.bounds)); mark_bounding_set_changed(&m); } } #endif #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { if (set & CAPNG_SELECT_AMBIENT) memset(m.ambient, 0, sizeof(m.ambient)); } #endif m.state = CAPNG_INIT; } void capng_fill(capng_select_t set) { if (m.state == CAPNG_NEW) init(); if (m.state == CAPNG_ERROR) return; if (set & CAPNG_SELECT_CAPS) { if (m.cap_ver == 1) { m.data.v1.effective = 0x7FFFFFFFU; m.data.v1.permitted = 0x7FFFFFFFU; m.data.v1.inheritable = 0; } else { m.data.v3[0].effective = 0xFFFFFFFFU; m.data.v3[0].permitted = 0xFFFFFFFFU; m.data.v3[0].inheritable = 0; m.data.v3[1].effective = 0xFFFFFFFFU; m.data.v3[1].permitted = 0xFFFFFFFFU; m.data.v3[1].inheritable = 0; } } #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { if (set & CAPNG_SELECT_BOUNDS) { unsigned i; for (i=0; i NGROUPS_MAX) { errno = EINVAL; return -1; } if (count) { size_t len = count * sizeof(gid_t); list = malloc(len); if (list == NULL) return -1; memcpy(list, gids, len); } clear_staged_additional_groups(&m); m.add_groups = list; m.add_group_cnt = count; return 0; } #ifdef PR_CAPBSET_DROP static int get_bounding_set(void) { char buf[64]; FILE *f; int rc; snprintf(buf, sizeof(buf), "/proc/%d/status", m.hdr.pid ? m.hdr.pid : #ifdef HAVE_SYSCALL_H (int)syscall(__NR_gettid)); #else (int)getpid()); #endif f = fopen(buf, "re"); if (f) { __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, sizeof(buf), f)) { if (strncmp(buf, "CapB", 4)) continue; int num = sscanf(buf, "CapBnd: %08x%08x", &m.bounds[1], &m.bounds[0]); fclose(f); if (num != 2) return -1; clear_bounding_set_changed(&m); return 0; } // Didn't find bounding set, fall through and try prctl way fclose(f); } // Might be in a container with no procfs - do it the hard way memset(m.bounds, 0, sizeof(m.bounds)); unsigned int i = 0; do { rc = prctl(PR_CAPBSET_READ, i, 0, 0, 0); if (rc < 0) return -1; // Just add set bits if (rc) update_bounding_set(CAPNG_ADD, i%32, i>>5); i++; } while (cap_valid(i)); clear_bounding_set_changed(&m); return 0; } #endif #ifdef PR_CAP_AMBIENT static int get_ambient_set(void) { char buf[64]; FILE *f; int rc; snprintf(buf, sizeof(buf), "/proc/%d/status", m.hdr.pid ? m.hdr.pid : #ifdef HAVE_SYSCALL_H (int)syscall(__NR_gettid)); #else (int)getpid()); #endif f = fopen(buf, "re"); if (f) { __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, sizeof(buf), f)) { if (strncmp(buf, "CapA", 4)) continue; int num = sscanf(buf, "CapAmb: %08x%08x", &m.ambient[1], &m.ambient[0]); fclose(f); if (num != 2) return -1; return 0; } fclose(f); // Didn't find ambient set, fall through and try prctl way } // Might be in a container with no procfs - do it the hard way memset(m.ambient, 0, sizeof(m.ambient)); unsigned int i = 0; do { rc = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, i, 0, 0); if (rc < 0) return -1; // Just add set bits if (rc) update_ambient_set(CAPNG_ADD, i%32, i>>5); i++; } while (cap_valid(i)); return 0; } #endif /* * Returns 0 on success and -1 on failure */ int capng_get_caps_process(void) { int rc; if (m.state == CAPNG_NEW) init(); if (m.state == CAPNG_ERROR) return -1; rc = capget((cap_user_header_t)&m.hdr, (cap_user_data_t)&m.data); if (rc == 0) { m.state = CAPNG_INIT; #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { rc = get_bounding_set(); if (rc < 0) m.state = CAPNG_ERROR; } #endif #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { rc = get_ambient_set(); if (rc < 0) m.state = CAPNG_ERROR; } #endif } return rc; } #ifdef VFS_CAP_U32 #ifdef VFS_CAP_REVISION_3 static int load_data(const struct vfs_ns_cap_data *filedata, int size) #else static int load_data(const struct vfs_cap_data *filedata, int size) #endif { unsigned int magic; if (m.cap_ver == 1) return -1; // Should never get here but just in case magic = FIXUP(filedata->magic_etc); switch (magic & VFS_CAP_REVISION_MASK) { case VFS_CAP_REVISION_1: m.vfs_cap_ver = 1; if (size != XATTR_CAPS_SZ_1) return -1; break; case VFS_CAP_REVISION_2: m.vfs_cap_ver = 2; if (size != XATTR_CAPS_SZ_2) return -1; break; #ifdef VFS_CAP_REVISION_3 case VFS_CAP_REVISION_3: m.vfs_cap_ver = 3; if (size != XATTR_CAPS_SZ_3) return -1; break; #endif default: return -1; } // Now stuff the data structures m.data.v3[0].permitted = FIXUP(filedata->data[0].permitted); m.data.v3[1].permitted = FIXUP(filedata->data[1].permitted); m.data.v3[0].inheritable = FIXUP(filedata->data[0].inheritable); m.data.v3[1].inheritable = FIXUP(filedata->data[1].inheritable); if (magic & VFS_CAP_FLAGS_EFFECTIVE) { m.data.v3[0].effective = m.data.v3[0].permitted | m.data.v3[0].inheritable; m.data.v3[1].effective = m.data.v3[1].permitted | m.data.v3[1].inheritable; } else { m.data.v3[0].effective = 0; m.data.v3[1].effective = 0; } #ifdef VFS_CAP_REVISION_3 if (size == XATTR_CAPS_SZ_3) { struct vfs_ns_cap_data *d = (struct vfs_ns_cap_data *)filedata; m.rootid = FIXUP(d->rootid); } #endif return 0; } #endif int capng_get_caps_fd(int fd) { #ifndef VFS_CAP_U32 return -1; #else int rc; #ifdef VFS_CAP_REVISION_3 struct vfs_ns_cap_data filedata; #else struct vfs_cap_data filedata; #endif if (m.state == CAPNG_NEW) init(); if (m.state == CAPNG_ERROR) return -1; rc = fgetxattr(fd, XATTR_NAME_CAPS, &filedata, sizeof(filedata)); if (rc <= 0) return -1; rc = load_data(&filedata, rc); if (rc == 0) m.state = CAPNG_INIT; else m.state = CAPNG_ERROR; // If load data failed, malformed data return rc; #endif } static void v1_update(capng_act_t action, unsigned int capability, __u32 *data) { if (action == CAPNG_ADD) *data |= MASK(capability); else *data &= ~(MASK(capability)); } static void update_effective(capng_act_t action, unsigned int capability, unsigned int idx) { if (action == CAPNG_ADD) m.data.v3[idx].effective |= MASK(capability); else m.data.v3[idx].effective &= ~(MASK(capability)); } static void update_permitted(capng_act_t action, unsigned int capability, unsigned int idx) { if (action == CAPNG_ADD) m.data.v3[idx].permitted |= MASK(capability); else m.data.v3[idx].permitted &= ~(MASK(capability)); } static void update_inheritable(capng_act_t action, unsigned int capability, unsigned int idx) { if (action == CAPNG_ADD) m.data.v3[idx].inheritable |= MASK(capability); else m.data.v3[idx].inheritable &= ~(MASK(capability)); } static void update_bounding_set(capng_act_t action, unsigned int capability, unsigned int idx) { #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { if (action == CAPNG_ADD) m.bounds[idx] |= MASK(capability); else m.bounds[idx] &= ~(MASK(capability)); } #endif } static void update_ambient_set(capng_act_t action, unsigned int capability, unsigned int idx) { #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { if (action == CAPNG_ADD) m.ambient[idx] |= MASK(capability); else m.ambient[idx] &= ~(MASK(capability)); } #endif } int capng_update(capng_act_t action, capng_type_t type, unsigned int capability) { // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) return -1; if (!cap_valid(capability)) { errno = EINVAL; return -1; } if (m.cap_ver == 1) { if (CAPNG_EFFECTIVE & type) v1_update(action, capability, &m.data.v1.effective); if (CAPNG_PERMITTED & type) v1_update(action, capability, &m.data.v1.permitted); if (CAPNG_INHERITABLE & type) v1_update(action, capability, &m.data.v1.inheritable); } else { unsigned int idx; if (capability > 31) { idx = capability>>5; capability %= 32; } else idx = 0; if (CAPNG_EFFECTIVE & type) update_effective(action, capability, idx); if (CAPNG_PERMITTED & type) update_permitted(action, capability, idx); if (CAPNG_INHERITABLE & type) update_inheritable(action, capability, idx); if (CAPNG_BOUNDING_SET & type) update_bounding_set(action, capability, idx); if (CAPNG_AMBIENT & type) update_ambient_set(action, capability, idx); } if (CAPNG_BOUNDING_SET & type) mark_bounding_set_changed(&m); m.state = CAPNG_UPDATED; return 0; } int capng_updatev(capng_act_t action, capng_type_t type, unsigned int capability, ...) { int rc; unsigned int cap; va_list ap; rc = capng_update(action, type, capability); if (rc) return rc; va_start(ap, capability); cap = va_arg(ap, unsigned int); while (cap_valid(cap)) { rc = capng_update(action, type, cap); if (rc) break; cap = va_arg(ap, unsigned int); } va_end(ap); // See if planned exit or invalid if (cap == (unsigned)-1) rc = 0; else { rc = -1; errno = EINVAL; } return rc; } int capng_apply(capng_select_t set) { int rc = 0; // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) return -1; if (set == 0) { errno = EINVAL; return -1; } if (set & CAPNG_SELECT_BOUNDS) { #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { struct cap_ng state; memcpy(&state, &m, sizeof(state)); /* save state */ if (capng_get_caps_process()) return -9; if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP)) { unsigned int i; memcpy(&m, &state, sizeof(m)); /* restore state */ for (i=0; i <= last_cap; i++) { if (capng_have_capability(CAPNG_BOUNDING_SET, i) == 0) { if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) <0) { rc = -2; goto try_caps; } } } if (get_bounding_set() < 0) { rc = -3; goto try_caps; } clear_bounding_set_changed(&m); m.state = CAPNG_APPLIED; } else { memcpy(&m, &state, sizeof(m)); /* restore state */ rc = -4; goto try_caps; } } #endif } // Try caps is here so that if someone had SELECT_BOTH and we blew up // doing the bounding set, we at least try to set any capabilities // before returning in case the caller also doesn't bother checking // the return code. try_caps: if (set & CAPNG_SELECT_CAPS) { if (capset((cap_user_header_t)&m.hdr, (cap_user_data_t)&m.data) == 0) m.state = CAPNG_APPLIED; else rc = -5; } // Most programs do not and should not mess with ambient capabilities. // Instead of returning here if rc is set, we'll let it try to // do something with ambient capabilities in hopes that it's lowering // capabilities. Again, this is for people that don't check their // return codes. // // Do ambient last so that inheritable and permitted are set by the // time we get here. if (set & CAPNG_SELECT_AMBIENT) { #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { if (capng_have_capabilities(CAPNG_SELECT_AMBIENT) == CAPNG_NONE) { if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) < 0) { rc = -6; goto out; } } else { unsigned int i; // Clear them all if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) < 0) { rc = -7; goto out; } for (i=0; i <= last_cap; i++) { if (capng_have_capability(CAPNG_AMBIENT, i)) if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0){ rc = -8; goto out; } } } m.state = CAPNG_APPLIED; } #endif } out: return rc; } #ifdef VFS_CAP_U32 #ifdef VFS_CAP_REVISION_3 static int save_data(struct vfs_ns_cap_data *filedata, int *size) #else static int save_data(struct vfs_cap_data *filedata, int *size) #endif { // Now stuff the data structures if (m.vfs_cap_ver == 1) { filedata->data[0].permitted = FIXUP(m.data.v1.permitted); filedata->data[0].inheritable = FIXUP(m.data.v1.inheritable); filedata->magic_etc = FIXUP(VFS_CAP_REVISION_1); *size = XATTR_CAPS_SZ_1; } else if (m.vfs_cap_ver == 2 || m.vfs_cap_ver == 3) { int eff; if (m.data.v3[0].effective || m.data.v3[1].effective) eff = VFS_CAP_FLAGS_EFFECTIVE; else eff = 0; filedata->data[0].permitted = FIXUP(m.data.v3[0].permitted); filedata->data[0].inheritable = FIXUP(m.data.v3[0].inheritable); filedata->data[1].permitted = FIXUP(m.data.v3[1].permitted); filedata->data[1].inheritable = FIXUP(m.data.v3[1].inheritable); filedata->magic_etc = FIXUP(VFS_CAP_REVISION_2 | eff); *size = XATTR_CAPS_SZ_2; } #ifdef VFS_CAP_REVISION_3 if (m.vfs_cap_ver == 3) { // Kernel doesn't support namespaces with non-0 rootid if (m.rootid != 0) return -1; filedata->rootid = FIXUP(m.rootid); *size = XATTR_CAPS_SZ_3; } #endif return 0; } #endif int capng_apply_caps_fd(int fd) { #ifndef VFS_CAP_U32 return -1; #else int rc, size = 0; #ifdef VFS_CAP_REVISION_3 struct vfs_ns_cap_data filedata; #else struct vfs_cap_data filedata; #endif struct stat buf; // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) return -1; if (fstat(fd, &buf) != 0) return -1; if (S_ISLNK(buf.st_mode) || !S_ISREG(buf.st_mode)) { errno = EINVAL; return -1; } if (capng_have_capabilities(CAPNG_SELECT_CAPS) == CAPNG_NONE) rc = fremovexattr(fd, XATTR_NAME_CAPS); else { if (save_data(&filedata, &size)) { m.state = CAPNG_ERROR; errno = EINVAL; return -2; } rc = fsetxattr(fd, XATTR_NAME_CAPS, &filedata, size, 0); } if (rc == 0) m.state = CAPNG_APPLIED; return rc; #endif } // Change uids keeping/removing only certain capabilities int capng_change_id(int uid, int gid, capng_flags_t flag) { enum { TMP_SETPCAP_E = 1 << 0, TMP_SETPCAP_P = 1 << 1, TMP_SETUID_E = 1 << 2, TMP_SETUID_P = 1 << 3, TMP_SETGID_E = 1 << 4, TMP_SETGID_P = 1 << 5, }; unsigned int tmp_caps = 0; int rc, ret; struct passwd *pw = NULL; gid_t *gids = NULL, *merged = NULL; size_t gid_cnt = 0, merged_cnt = 0; // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) { ret = -1; goto out; } // Validate mutually exclusive staged-group combinations up front. if ((flag & CAPNG_APPLY_STAGED_GROUPS) && (flag & CAPNG_DROP_SUPP_GRP)) { ret = -12; goto out; } // Validate mutually exclusive bounding-set operations up front. if ((flag & CAPNG_APPLY_BOUNDING) && (flag & CAPNG_CLEAR_BOUNDING)) { ret = -17; goto out; } // Staged application only makes sense when a list was staged earlier. if ((flag & CAPNG_APPLY_STAGED_GROUPS) && m.add_group_cnt == 0) { ret = -13; goto out; } // Make sure the temporary transition caps we may need are present. #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { // If newer kernel, we need setpcap to change the bounding set if ((flag & CAPNG_CLEAR_BOUNDING) || ((flag & CAPNG_APPLY_BOUNDING) && m.bounds_state_changed)) { if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP) == 0) { tmp_caps |= TMP_SETPCAP_E; capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_SETPCAP); } if (capng_have_capability(CAPNG_PERMITTED, CAP_SETPCAP) == 0) { tmp_caps |= TMP_SETPCAP_P; capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_SETPCAP); } } } #endif if (gid != -1) { if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETGID) == 0) { tmp_caps |= TMP_SETGID_E; capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_SETGID); } if (capng_have_capability(CAPNG_PERMITTED, CAP_SETGID) == 0) { tmp_caps |= TMP_SETGID_P; capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_SETGID); } } if (uid != -1) { if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETUID) == 0) { tmp_caps |= TMP_SETUID_E; capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_SETUID); } if (capng_have_capability(CAPNG_PERMITTED, CAP_SETUID) == 0) { tmp_caps |= TMP_SETUID_P; capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_SETUID); } } // Enable keepcaps before changing credentials so final caps survive. if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { ret = -2; goto out; } // Apply the temporary working set before touching ids or groups. rc = capng_apply(CAPNG_SELECT_CAPS); if (rc < 0) { ret = -3; goto err_out; } // If we are clearing ambient, only clear since its applied at the end if (flag & CAPNG_CLEAR_AMBIENT) capng_clear(CAPNG_SELECT_AMBIENT); // Clear bounding set if needed while we have CAP_SETPCAP if (flag & CAPNG_CLEAR_BOUNDING) { capng_clear(CAPNG_SELECT_BOUNDS); rc = capng_apply(CAPNG_SELECT_BOUNDS); if (rc) { ret = -8; goto err_out; } } // Apply a caller-prepared bounding set only when it is explicitly dirty. if ((flag & CAPNG_APPLY_BOUNDING) && m.bounds_state_changed) { rc = capng_apply(CAPNG_SELECT_BOUNDS); if (rc) { ret = -8; goto err_out; } } // Change gid before supplemental groups so kernel permission checks // see the target primary group. if (gid != -1) { rc = setresgid(gid, gid, gid); if (rc) { ret = -4; goto err_out; } } // Resolve the passwd entry once when natural group setup is requested. if ((flag & CAPNG_INIT_SUPP_GRP) && uid != -1) { pw = getpwuid(uid); if (pw == NULL) { ret = -10; goto err_out; } } // There are four supplemental/additional group modes: // init+staged merge, init only, drop only, or staged only. if ((flag & CAPNG_INIT_SUPP_GRP) && (flag & CAPNG_APPLY_STAGED_GROUPS)) { if (uid != -1) { gid_t base_gid = gid != -1 ? (gid_t)gid : pw->pw_gid; // Build the natural target account group list first. rc = get_additional_groups(pw, base_gid, &gids, &gid_cnt); if (rc) { ret = -15; goto err_out; } } // Then append staged gids without duplicates and apply the result. rc = merge_additional_groups(gids, gid_cnt, m.add_groups, m.add_group_cnt, &merged, &merged_cnt); if (rc) { ret = -16; goto err_out; } if (setgroups(merged_cnt, merged)) { ret = -14; goto err_out; } } else if ((flag & CAPNG_INIT_SUPP_GRP) && uid != -1) { gid_t base_gid = gid != -1 ? (gid_t)gid : pw->pw_gid; // Preserve the long-standing initgroups-only behavior. if (initgroups(pw->pw_name, base_gid)) { ret = -5; goto err_out; } } else if ((flag & CAPNG_DROP_SUPP_GRP) && gid != -1) { // Drop all supplemental groups for the target identity. if (setgroups(0, NULL)) { ret = -11; goto err_out; } } else if (flag & CAPNG_APPLY_STAGED_GROUPS) { // Apply exactly the caller-staged gid list. if (setgroups(m.add_group_cnt, m.add_groups)) { ret = -14; goto err_out; } } // Change uid if (uid != -1) { rc = setresuid(uid, uid, uid); if (rc) { ret = -6; goto err_out; } } // Credential changes are complete, so disable keepcaps again. rc = prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0); if (rc < 0) { ret = -7; goto out; } // Now throw away CAP_SETPCAP so no more changes if (tmp_caps & TMP_SETGID_E) capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, CAP_SETGID); if (tmp_caps & TMP_SETGID_P) capng_update(CAPNG_DROP, CAPNG_PERMITTED, CAP_SETGID); if (tmp_caps & TMP_SETUID_E) capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, CAP_SETUID); if (tmp_caps & TMP_SETUID_P) capng_update(CAPNG_DROP, CAPNG_PERMITTED, CAP_SETUID); // Drop any temporary transition caps and install the final sets. if (tmp_caps & TMP_SETPCAP_E) capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, CAP_SETPCAP); if (tmp_caps & TMP_SETPCAP_P) capng_update(CAPNG_DROP, CAPNG_PERMITTED, CAP_SETPCAP); rc = capng_apply(CAPNG_SELECT_CAPS|CAPNG_SELECT_AMBIENT); if (rc < 0) { ret = -9; goto out; } ret = 0; goto out; err_out: prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0); out: // Staged gids are one-shot state, so always clear them before return. free(gids); free(merged); clear_staged_additional_groups(&m); return ret; } int capng_lock(void) { int rc = 0; // If either fail, return -1 since something is not right #ifdef PR_SET_SECUREBITS if (prctl(PR_SET_SECUREBITS, 1 << SECURE_NOROOT | 1 << SECURE_NOROOT_LOCKED | 1 << SECURE_NO_SETUID_FIXUP | 1 << SECURE_NO_SETUID_FIXUP_LOCKED, 0, 0, 0) < 0) rc = -1; #endif #ifdef PR_SET_NO_NEW_PRIVS if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) rc += -2; #endif return rc; } // -1 - error, 0 - no caps, 1 partial caps, 2 full caps capng_results_t capng_have_capabilities(capng_select_t set) { int empty = 0, full = 0; // First, try to init with current set if (m.state < CAPNG_INIT) { if (capng_get_caps_process()) return CAPNG_FAIL; } // If we still don't have anything, error out if (m.state < CAPNG_INIT) return CAPNG_FAIL; if (set & CAPNG_SELECT_CAPS) { if (m.cap_ver == 1) { if (m.data.v1.effective == 0) empty = 1; // after fill, 30 bits starts from upper to lower else if (m.data.v1.effective == 0x7FFFFFFFU) full = 1; // actual capabilities read from system else if (m.data.v1.effective == 0xFFFFFEFFU) full = 1; else return CAPNG_PARTIAL; } else { if (m.data.v3[0].effective == 0) empty = 1; else if (m.data.v3[0].effective == 0xFFFFFFFFU) full = 1; else return CAPNG_PARTIAL; if ((m.data.v3[1].effective & UPPER_MASK) == 0 && !full) empty = 1; else if ((m.data.v3[1].effective & UPPER_MASK) == UPPER_MASK && !empty) full = 1; else return CAPNG_PARTIAL; } } #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { if (set & CAPNG_SELECT_BOUNDS) { if (m.bounds[0] == 0) empty = 1; else if (m.bounds[0] == 0xFFFFFFFFU) full = 1; else return CAPNG_PARTIAL; if ((m.bounds[1] & UPPER_MASK) == 0) empty = 1; else if ((m.bounds[1] & UPPER_MASK) == UPPER_MASK) full = 1; else return CAPNG_PARTIAL; } } else if (set & CAPNG_SELECT_BOUNDS) empty = 1; // Only report empty if they asked about it #endif #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { if (set & CAPNG_SELECT_AMBIENT) { if (m.ambient[0] == 0) empty = 1; else if (m.ambient[0] == 0xFFFFFFFFU) full = 1; else return CAPNG_PARTIAL; if ((m.ambient[1] & UPPER_MASK) == 0) empty = 1; else if ((m.ambient[1] & UPPER_MASK) == UPPER_MASK) full = 1; else return CAPNG_PARTIAL; } } else if (set & CAPNG_SELECT_AMBIENT) empty = 1; // Only report empty if they asked about it #endif if (empty == 1 && full == 0) return CAPNG_NONE; else if (empty == 0 && full == 1) return CAPNG_FULL; return CAPNG_PARTIAL; } // -1 - error, 0 - no caps, 1 partial caps, 2 full caps capng_results_t capng_have_permitted_capabilities(void) { int empty = 0; int lower_full = 0; // First, try to init with current set if (m.state < CAPNG_INIT) { if (capng_get_caps_process()) return CAPNG_FAIL; } // If we still don't have anything, error out if (m.state < CAPNG_INIT) return CAPNG_FAIL; if (m.data.v3[0].permitted == 0) empty = 1; else if (m.data.v3[0].permitted == 0xFFFFFFFFU) lower_full = 1; else return CAPNG_PARTIAL; // At this point, lower 32 bits are either full or empty if ((m.data.v3[1].permitted & UPPER_MASK) == 0 && !lower_full) empty = 1; else if ((m.data.v3[1].permitted & UPPER_MASK) != UPPER_MASK || empty) return CAPNG_PARTIAL; // Partial is already handled, it's either empty or full now if (empty) return CAPNG_NONE; return CAPNG_FULL; } static int check_effective(unsigned int capability, unsigned int idx) { return MASK(capability) & m.data.v3[idx].effective ? 1 : 0; } static int check_permitted(unsigned int capability, unsigned int idx) { return MASK(capability) & m.data.v3[idx].permitted ? 1 : 0; } static int check_inheritable(unsigned int capability, unsigned int idx) { return MASK(capability) & m.data.v3[idx].inheritable ? 1 : 0; } static int bounds_bit_check(unsigned int capability, unsigned int idx) { #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { return MASK(capability) & m.bounds[idx] ? 1 : 0; } #endif return 0; } static int ambient_bit_check(unsigned int capability, unsigned int idx) { #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { return MASK(capability) & m.ambient[idx] ? 1 : 0; } #endif return 0; } static int v1_check(unsigned int capability, __u32 data) { return MASK(capability) & data ? 1 : 0; } int capng_have_capability(capng_type_t which, unsigned int capability) { // First, try to init with current set if (m.state < CAPNG_INIT) { if (capng_get_caps_process()) return 0; } // If we still don't have anything, error out if (m.state < CAPNG_INIT) return 0; if (m.cap_ver == 1 && capability > 31) return 0; if (!cap_valid(capability)) return 0; if (m.cap_ver == 1) { if (which == CAPNG_EFFECTIVE) return v1_check(capability, m.data.v1.effective); else if (which == CAPNG_PERMITTED) return v1_check(capability, m.data.v1.permitted); else if (which == CAPNG_INHERITABLE) return v1_check(capability, m.data.v1.inheritable); } else { unsigned int idx; if (capability > 31) { idx = capability>>5; capability %= 32; } else idx = 0; if (which == CAPNG_EFFECTIVE) return check_effective(capability, idx); else if (which == CAPNG_PERMITTED) return check_permitted(capability, idx); else if (which == CAPNG_INHERITABLE) return check_inheritable(capability, idx); else if (which == CAPNG_BOUNDING_SET) return bounds_bit_check(capability, idx); else if (which == CAPNG_AMBIENT) return ambient_bit_check(capability, idx); } return 0; } char *capng_print_caps_numeric(capng_print_t where, capng_select_t set) { char *ptr = NULL; if (m.state < CAPNG_INIT) return ptr; if (where == CAPNG_PRINT_STDOUT) { if (set & CAPNG_SELECT_CAPS) { if (m.cap_ver == 1) { printf( "Effective: %08X\n" "Permitted: %08X\n" "Inheritable: %08X\n", m.data.v1.effective, m.data.v1.permitted, m.data.v1.inheritable); } else { printf( "Effective: %08X, %08X\n" "Permitted: %08X, %08X\n" "Inheritable: %08X, %08X\n", m.data.v3[1].effective & UPPER_MASK, m.data.v3[0].effective, m.data.v3[1].permitted & UPPER_MASK, m.data.v3[0].permitted, m.data.v3[1].inheritable & UPPER_MASK, m.data.v3[0].inheritable); } } #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { if (set & CAPNG_SELECT_BOUNDS) printf("Bounding Set: %08X, %08X\n", m.bounds[1] & UPPER_MASK, m.bounds[0]); } #endif #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { if (set & CAPNG_SELECT_AMBIENT) printf("Ambient: %08X, %08X\n", m.ambient[1] & UPPER_MASK, m.ambient[0]); } #endif } else if (where == CAPNG_PRINT_BUFFER) { if (set & CAPNG_SELECT_CAPS) { // Make it big enough for bounding & ambient set, too size_t buf_size = 180; ptr = malloc(buf_size); if (m.cap_ver == 1) { // 22 * 3 + 1 snprintf(ptr, buf_size, "Effective: %08X\n" "Permitted: %08X\n" "Inheritable: %08X\n", m.data.v1.effective, m.data.v1.permitted, m.data.v1.inheritable); } else { // 35 * 5 + 1 (bounding is 35) snprintf(ptr, buf_size, "Effective: %08X, %08X\n" "Permitted: %08X, %08X\n" "Inheritable: %08X, %08X\n", m.data.v3[1].effective & UPPER_MASK, m.data.v3[0].effective, m.data.v3[1].permitted & UPPER_MASK, m.data.v3[0].permitted, m.data.v3[1].inheritable & UPPER_MASK, m.data.v3[0].inheritable); } } if (set & CAPNG_SELECT_BOUNDS) { #ifdef PR_CAPBSET_DROP if (HAVE_PR_CAPBSET_DROP) { char *s; // If ptr is NULL, we only room for bounding and ambient if (ptr == NULL ) { ptr = malloc(80); if (ptr == NULL) return ptr; *ptr = 0; s = ptr; } else s = ptr + strlen(ptr); // prints 34 + 1 chars snprintf(s, 40, "Bounding Set: %08X, %08X\n", m.bounds[1] & UPPER_MASK, m.bounds[0]); } #endif } if (set & CAPNG_SELECT_AMBIENT) { #ifdef PR_CAP_AMBIENT if (HAVE_PR_CAP_AMBIENT) { char *s; // If ptr is NULL, we only have room for ambient if (ptr == NULL ) { ptr = malloc(40); if (ptr == NULL) return ptr; *ptr = 0; s = ptr; } else s = ptr + strlen(ptr); // prints 33 + 1 chars snprintf(s, 40, "Ambient Set: %08X, %08X\n", m.ambient[1] & UPPER_MASK, m.ambient[0]); } #endif } } return ptr; } char *capng_print_caps_text(capng_print_t where, capng_type_t which) { unsigned int i, cnt = 0; size_t total = 1; int found = 0; char *ptr = NULL; if (m.state < CAPNG_INIT) return ptr; if (where == CAPNG_PRINT_BUFFER) { for (i=0; i<=last_cap; i++) { if (capng_have_capability(which, i)) { const char *n = capng_capability_to_name(i); size_t len; if (n == NULL) n = "unknown"; len = strlen(n); total += len; if (found) total += 2; found = 1; } } ptr = malloc(found ? total : sizeof("none")); if (ptr == NULL) return ptr; *ptr = 0; found = 0; } for (i=0; i<=last_cap; i++) { if (capng_have_capability(which, i)) { const char *n = capng_capability_to_name(i); size_t len; if (n == NULL) n = "unknown"; len = strlen(n); if (where == CAPNG_PRINT_STDOUT) { if (found == 0) printf("%s", n); else printf(", %s", n); } else if (where == CAPNG_PRINT_BUFFER) { if (ptr == NULL) return NULL; if (found) { ptr[cnt++] = ','; ptr[cnt++] = ' '; } memcpy(ptr + cnt, n, len + 1); cnt += len; } found = 1; } } if (found == 0) { if (where == CAPNG_PRINT_STDOUT) printf("none"); else if (where == CAPNG_PRINT_BUFFER && ptr) strcpy(ptr, "none"); else return NULL; } return ptr; } void *capng_save_state(void) { void *ptr = malloc(sizeof(m)); if (ptr) { memcpy(ptr, &m, sizeof(m)); if (copy_staged_additional_groups(ptr, &m)) { free(ptr); ptr = NULL; } } return ptr; } void capng_restore_state(void **state) { if (state) { struct cap_ng *ptr = *state; if (ptr) { clear_staged_additional_groups(&m); memcpy(&m, ptr, sizeof(m)); if (copy_staged_additional_groups(&m, ptr)) m.state = CAPNG_ERROR; clear_staged_additional_groups(ptr); } free(ptr); *state = NULL; } } stevegrubb-libcap-ng-0ab44af/src/cap-ng.h000066400000000000000000000073541516575034500203300ustar00rootroot00000000000000/* libcap-ng.h -- * Copyright 2009,2013,2020-26 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef LIBCAP_NG_HEADER #define LIBCAP_NG_HEADER #include #include #include #include // The next 2 macros originate in sys/cdefs.h // gcc-analyzer notation #ifndef __attr_dealloc_free # define __attr_dealloc_free #endif // Warn unused result #ifndef __wur # define __wur #endif #ifdef __cplusplus extern "C" { #endif typedef enum { CAPNG_DROP, CAPNG_ADD } capng_act_t; typedef enum { CAPNG_EFFECTIVE=1, CAPNG_PERMITTED=2, CAPNG_INHERITABLE=4, CAPNG_BOUNDING_SET=8, CAPNG_AMBIENT=16 } capng_type_t; typedef enum { CAPNG_SELECT_CAPS = 16, CAPNG_SELECT_BOUNDS = 32, CAPNG_SELECT_BOTH = 48, CAPNG_SELECT_AMBIENT = 64, CAPNG_SELECT_ALL = 112 } capng_select_t; typedef enum { CAPNG_FAIL=-1, CAPNG_NONE, CAPNG_PARTIAL, CAPNG_FULL } capng_results_t; typedef enum { CAPNG_PRINT_STDOUT, CAPNG_PRINT_BUFFER } capng_print_t; typedef enum { CAPNG_NO_FLAG=0, CAPNG_DROP_SUPP_GRP=1, CAPNG_CLEAR_BOUNDING=2, CAPNG_INIT_SUPP_GRP=4, CAPNG_CLEAR_AMBIENT=8, CAPNG_APPLY_STAGED_GROUPS=16, CAPNG_APPLY_BOUNDING=32 } capng_flags_t; #define CAPNG_UNSET_ROOTID -1 #define CAPNG_SUPPORTS_AMBIENT 1 // These functions manipulate process capabilities void capng_clear(capng_select_t set); void capng_fill(capng_select_t set); void capng_setpid(int pid); int capng_get_caps_process(void) __wur; int capng_update(capng_act_t action, capng_type_t type,unsigned int capability); int capng_updatev(capng_act_t action, capng_type_t type, unsigned int capability, ...); // These functions apply the capabilities previously setup to a process int capng_apply(capng_select_t set) __wur; int capng_lock(void) __wur; int capng_change_id(int uid, int gid, capng_flags_t flag) __wur; int capng_stage_additional_groups(const gid_t *gids, size_t count) __wur; // These functions are used for file based capabilities int capng_get_rootid(void); int capng_set_rootid(int rootid); int capng_get_caps_fd(int fd) __wur; int capng_apply_caps_fd(int fd) __wur; // These functions check capability bits capng_results_t capng_have_capabilities(capng_select_t set); capng_results_t capng_have_permitted_capabilities(void); int capng_have_capability(capng_type_t which, unsigned int capability); // These functions printout capabilities char *capng_print_caps_numeric(capng_print_t where, capng_select_t set); char *capng_print_caps_text(capng_print_t where, capng_type_t which); // These functions convert between numeric and text string int capng_name_to_capability(const char *name); const char *capng_capability_to_name(unsigned int capability); // These function should be used when you suspect a third party library // may use libcap-ng also and want to make sure it doesn't alter something // important. Otherwise you shouldn't need to call these. void capng_restore_state(void **state); void *capng_save_state(void); #ifdef __cplusplus } #endif #endif stevegrubb-libcap-ng-0ab44af/src/captab.h000066400000000000000000000055341516575034500204130ustar00rootroot00000000000000/* captab.h -- * Copyright 2009,2011-14,2020 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ _S(CAP_CHOWN, "chown" ) _S(CAP_DAC_OVERRIDE, "dac_override" ) _S(CAP_DAC_READ_SEARCH, "dac_read_search" ) _S(CAP_FOWNER, "fowner" ) _S(CAP_FSETID, "fsetid" ) _S(CAP_KILL, "kill" ) _S(CAP_SETGID, "setgid" ) _S(CAP_SETUID, "setuid" ) _S(CAP_SETPCAP, "setpcap" ) _S(CAP_LINUX_IMMUTABLE, "linux_immutable" ) _S(CAP_NET_BIND_SERVICE, "net_bind_service" ) _S(CAP_NET_BROADCAST, "net_broadcast" ) _S(CAP_NET_ADMIN, "net_admin" ) _S(CAP_NET_RAW, "net_raw" ) _S(CAP_IPC_LOCK, "ipc_lock" ) _S(CAP_IPC_OWNER, "ipc_owner" ) _S(CAP_SYS_MODULE, "sys_module" ) _S(CAP_SYS_RAWIO, "sys_rawio" ) _S(CAP_SYS_CHROOT, "sys_chroot" ) _S(CAP_SYS_PTRACE, "sys_ptrace" ) _S(CAP_SYS_PACCT, "sys_pacct" ) _S(CAP_SYS_ADMIN, "sys_admin" ) _S(CAP_SYS_BOOT, "sys_boot" ) _S(CAP_SYS_NICE, "sys_nice" ) _S(CAP_SYS_RESOURCE, "sys_resource" ) _S(CAP_SYS_TIME, "sys_time" ) _S(CAP_SYS_TTY_CONFIG, "sys_tty_config" ) _S(CAP_MKNOD, "mknod" ) _S(CAP_LEASE, "lease" ) _S(CAP_AUDIT_WRITE, "audit_write" ) _S(CAP_AUDIT_CONTROL, "audit_control" ) #ifdef CAP_SETFCAP _S(CAP_SETFCAP, "setfcap" ) #endif #ifdef CAP_MAC_OVERRIDE _S(CAP_MAC_OVERRIDE, "mac_override" ) #endif #ifdef CAP_MAC_ADMIN _S(CAP_MAC_ADMIN, "mac_admin" ) #endif #ifdef CAP_SYSLOG _S(CAP_SYSLOG, "syslog" ) #endif #if defined(CAP_EPOLLWAKEUP) && defined(CAP_BLOCK_SUSPEND) #error "Both CAP_EPOLLWAKEUP and CAP_BLOCK_SUSPEND are defined" #endif #ifdef CAP_EPOLLWAKEUP _S(CAP_EPOLLWAKEUP, "epollwakeup" ) #endif #ifdef CAP_WAKE_ALARM _S(CAP_WAKE_ALARM, "wake_alarm" ) #endif #ifdef CAP_BLOCK_SUSPEND _S(CAP_BLOCK_SUSPEND, "block_suspend" ) #endif #ifdef CAP_AUDIT_READ _S(CAP_AUDIT_READ, "audit_read" ) #endif #ifdef CAP_PERFMON _S(CAP_PERFMON, "perfmon" ) #endif #ifdef CAP_BPF _S(CAP_BPF, "bpf" ) #endif #ifdef CAP_CHECKPOINT_RESTORE _S(CAP_CHECKPOINT_RESTORE, "checkpoint_restore") #endif stevegrubb-libcap-ng-0ab44af/src/libcap-ng.pc.in000066400000000000000000000003731516575034500215710ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libcap-ng Description: An alternate POSIX capabilities library. Version: @VERSION@ Libs: -L${libdir} -lcap-ng Cflags: -I${includedir} License: LGPL-2.1-or-later stevegrubb-libcap-ng-0ab44af/src/libdrop_ambient.c000066400000000000000000000020511516575034500222750ustar00rootroot00000000000000/* libdrop_ambient.c -- * Copyright 2020 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include void __attribute__ ((constructor)) init(void) { prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); } stevegrubb-libcap-ng-0ab44af/src/lookup_table.c000066400000000000000000000073261516575034500216350ustar00rootroot00000000000000/* lookup_table.c -- * Copyright 2009, 2013, 2025 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #pragma GCC optimize("O3") #define hidden __attribute__ ((visibility ("hidden"))) extern unsigned int last_cap hidden; #undef cap_valid #define cap_valid(x) ((x) <= last_cap) struct transtab { unsigned int value; unsigned int offset; }; #define MSGSTRFIELD(line) MSGSTRFIELD1(line) #define MSGSTRFIELD1(line) str##line /* To create the following tables in a DSO-friendly way we split them in two separate variables: a long string which is created by concatenating all strings referenced in the table and the table itself, which uses offsets instead of string pointers. To do this without increasing the maintenance burden we use a lot of preprocessor magic. All the maintainer has to do is to add a new entry to the included file and recompile. */ static const union captab_msgstr_t { struct { #define _S(n, s) char MSGSTRFIELD(__LINE__)[sizeof (s)]; #include "captab.h" #undef _S }; char str[0]; } captab_msgstr = { { #define _S(n, s) s, #include "captab.h" #undef _S } }; static const struct transtab captab[] = { #define _S(n, s) { n, offsetof(union captab_msgstr_t, \ MSGSTRFIELD(__LINE__)) }, #include "captab.h" #undef _S }; #define CAP_NG_CAPABILITY_NAMES (sizeof(captab)/sizeof(captab[0])) static inline int capng_lookup_name(const char *name) { // brute force search for (size_t i = 0; i < CAP_NG_CAPABILITY_NAMES; i++) { if (!strcasecmp(captab_msgstr.str + captab[i].offset, name)) return captab[i].value; } return -1; } static inline const char *capng_lookup_number(unsigned int number) { if (number >= CAP_NG_CAPABILITY_NAMES) return NULL; if (captab[number].value == number) return captab_msgstr.str + captab[number].offset; // Fallback to old search in case a capability is retired for (size_t i = 0; i < CAP_NG_CAPABILITY_NAMES; i++) { if (captab[i].value == number) return captab_msgstr.str + captab[i].offset; } return NULL; } /* * capng_name_to_capability - given a string with the name of the capabilty * return its number. * @name - the name of the capability to lookup * * returns the number associated with the string. */ int capng_name_to_capability(const char *name) { return capng_lookup_name(name); } /* * capng_capability_to_name - given a number, return a string with the name of * the capabilty. * @capability - the number of the capability to lookup * * returns the string associated with the number. */ static __thread char ptr2[32]; const char *capng_capability_to_name(unsigned int capability) { const char *ptr; if (!cap_valid(capability)) return NULL; ptr = capng_lookup_number(capability); if (ptr == NULL) { snprintf(ptr2, sizeof(ptr2), "cap_%u", capability); ptr = ptr2; } return ptr; } stevegrubb-libcap-ng-0ab44af/src/test/000077500000000000000000000000001516575034500177605ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/src/test/Makefile.am000066400000000000000000000022401516575034500220120ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009,10,15 Red Hat Inc. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; see the file COPYING.LIB. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/src check_PROGRAMS = lib_test thread_test change_id_test TESTS = $(check_PROGRAMS) lib_test_LDADD = ${top_builddir}/src/libcap-ng.la thread_test_LDADD = ${top_builddir}/src/libcap-ng.la -lpthread change_id_test_LDADD = ${top_builddir}/src/libcap-ng.la stevegrubb-libcap-ng-0ab44af/src/test/change_id_test.c000066400000000000000000000345341516575034500230750ustar00rootroot00000000000000/* change_id_test.c -- capng_change_id additional group tests * Copyright 2026 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. */ #include "config.h" #include "../cap-ng.h" #include #include #include #include #include #include #include #include static void fail(const char *msg) { fprintf(stderr, "%s\n", msg); _exit(EXIT_FAILURE); } static int gid_in_list(const gid_t *gids, size_t count, gid_t gid) { size_t i; for (i = 0; i < count; i++) { if (gids[i] == gid) return 1; } return 0; } static int rc_in_list(int rc, const int *allowed, size_t count) { size_t i; for (i = 0; i < count; i++) { if (rc == allowed[i]) return 1; } return 0; } static int get_current_groups(gid_t **gids, size_t *count) { int ngroups; *gids = NULL; *count = 0; ngroups = getgroups(0, NULL); if (ngroups < 0) return -1; if (ngroups == 0) return 0; *gids = malloc(sizeof(gid_t) * ngroups); if (*gids == NULL) return -1; ngroups = getgroups(ngroups, *gids); if (ngroups < 0) { free(*gids); *gids = NULL; return -1; } *count = ngroups; return 0; } static int get_natural_groups(uid_t uid, gid_t gid, gid_t **gids, size_t *count) { struct passwd *pw; gid_t *list; int ngroups = 1; int rc; *gids = NULL; *count = 0; pw = getpwuid(uid); if (pw == NULL) return -1; list = malloc(sizeof(gid_t)); if (list == NULL) return -1; rc = getgrouplist(pw->pw_name, gid, list, &ngroups); if (rc == -1) { gid_t *tmp; tmp = realloc(list, sizeof(gid_t) * ngroups); if (tmp == NULL) { free(list); return -1; } list = tmp; rc = getgrouplist(pw->pw_name, gid, list, &ngroups); } if (rc == -1) { free(list); return -1; } *gids = list; *count = ngroups; return 0; } static int merge_groups(const gid_t *base, size_t base_cnt, const gid_t *extra, size_t extra_cnt, gid_t **merged, size_t *merged_cnt) { gid_t *list; size_t i, count = 0, total = base_cnt + extra_cnt; *merged = NULL; *merged_cnt = 0; if (total == 0) return 0; list = malloc(sizeof(gid_t) * total); if (list == NULL) return -1; for (i = 0; i < base_cnt; i++) { if (gid_in_list(list, count, base[i]) == 0) list[count++] = base[i]; } for (i = 0; i < extra_cnt; i++) { if (gid_in_list(list, count, extra[i]) == 0) list[count++] = extra[i]; } *merged = list; *merged_cnt = count; return 0; } static void check_groups(const gid_t *expected, size_t expected_cnt) { gid_t *actual; size_t actual_cnt; size_t i; if (get_current_groups(&actual, &actual_cnt)) fail("Failed to get current additional groups"); if (actual_cnt != expected_cnt) fail("Unexpected additional group count"); for (i = 0; i < expected_cnt; i++) { if (actual[i] != expected[i]) fail("Unexpected additional group value"); } free(actual); } static int choose_extra_gid(const gid_t *skip, size_t skip_cnt, gid_t *gid) { struct group *gr; setgrent(); while ((gr = getgrent()) != NULL) { if (gid_in_list(skip, skip_cnt, gr->gr_gid) == 0) { *gid = gr->gr_gid; endgrent(); return 0; } } endgrent(); return -1; } static int read_bounding_cap(unsigned int cap) { #ifdef PR_CAPBSET_READ return prctl(PR_CAPBSET_READ, cap, 0, 0, 0); #else (void)cap; return -1; #endif } static int find_drop_test_bounding_cap(unsigned int *cap) { unsigned int i; for (i = 0; cap_valid(i); i++) { int rc; if (i == CAP_SETPCAP || i == CAP_SETUID || i == CAP_SETGID) continue; rc = read_bounding_cap(i); if (rc < 0) return -1; if (rc == 1) { *cap = i; return 0; } } for (i = 0; cap_valid(i); i++) { int rc; rc = read_bounding_cap(i); if (rc < 0) return -1; if (rc == 1) { *cap = i; return 0; } } return -1; } static void test_staged_only(void) { static const int allowed[] = { -2, -3, -14 }; gid_t staged[2]; gid_t *natural = NULL; size_t natural_cnt = 0; size_t staged_cnt = 1; int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (get_natural_groups(getuid(), getgid(), &natural, &natural_cnt)) fail("Failed to resolve natural groups"); staged[0] = getgid(); if (choose_extra_gid(natural, natural_cnt, &staged[1]) == 0) staged_cnt = 2; free(natural); rc = capng_stage_additional_groups(staged, staged_cnt); if (rc) fail("Failed to stage additional groups"); rc = capng_change_id(-1, -1, CAPNG_APPLY_STAGED_GROUPS); if (rc == 0) check_groups(staged, staged_cnt); else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected staged-only return code"); } static void test_init_only(void) { static const int allowed[] = { -2, -3, -5 }; gid_t *natural = NULL; size_t natural_cnt = 0; int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (get_natural_groups(getuid(), getgid(), &natural, &natural_cnt)) fail("Failed to resolve natural groups"); rc = capng_change_id(getuid(), -1, CAPNG_INIT_SUPP_GRP); if (rc == 0) check_groups(natural, natural_cnt); else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected init-only return code"); free(natural); } static void test_init_and_staged(void) { static const int allowed[] = { -2, -3, -14 }; gid_t staged[2]; gid_t *natural = NULL, *merged = NULL; size_t natural_cnt = 0, merged_cnt = 0; size_t staged_cnt = 1; int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (get_natural_groups(getuid(), getgid(), &natural, &natural_cnt)) fail("Failed to resolve natural groups"); staged[0] = getgid(); if (choose_extra_gid(natural, natural_cnt, &staged[1]) == 0) staged_cnt = 2; if (merge_groups(natural, natural_cnt, staged, staged_cnt, &merged, &merged_cnt)) fail("Failed to merge expected additional groups"); rc = capng_stage_additional_groups(staged, staged_cnt); if (rc) fail("Failed to stage additional groups"); rc = capng_change_id(getuid(), -1, CAPNG_INIT_SUPP_GRP | CAPNG_APPLY_STAGED_GROUPS); if (rc == 0) check_groups(merged, merged_cnt); else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected init+staged return code"); free(natural); free(merged); } static void test_invalid_drop_and_staged(void) { gid_t gid = getgid(); int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); rc = capng_stage_additional_groups(&gid, 1); if (rc) fail("Failed to stage additional groups"); rc = capng_change_id(-1, -1, CAPNG_DROP_SUPP_GRP | CAPNG_APPLY_STAGED_GROUPS); if (rc != -12) fail("Unexpected drop+staged return code"); } static void test_staged_ignored_without_flag(void) { static const int allowed[] = { -2, -3 }; gid_t gid = getgid(); int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); rc = capng_stage_additional_groups(&gid, 1); if (rc) fail("Failed to stage additional groups"); rc = capng_change_id(-1, -1, CAPNG_NO_FLAG); if (rc && rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected return code when staged groups are ignored"); rc = capng_change_id(-1, -1, CAPNG_APPLY_STAGED_GROUPS); if (rc != -13) fail("Staged groups were not cleared when ignored"); } static void test_staged_cleared_after_use(void) { static const int allowed[] = { -2, -3, -14 }; gid_t gid = getgid(); int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); rc = capng_stage_additional_groups(&gid, 1); if (rc) fail("Failed to stage additional groups"); rc = capng_change_id(-1, -1, CAPNG_APPLY_STAGED_GROUPS); if (rc && rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected return code when consuming staged groups"); rc = capng_change_id(-1, -1, CAPNG_APPLY_STAGED_GROUPS); if (rc != -13) fail("Staged groups were not cleared after use"); } static void test_apply_bounding_during_change_id(void) { static const int allowed[] = { -2, -3, -8, -9 }; unsigned int cap; int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (find_drop_test_bounding_cap(&cap)) return; if (capng_update(CAPNG_DROP, CAPNG_BOUNDING_SET, cap)) fail("Failed to prepare bounding set change"); rc = capng_change_id(-1, -1, CAPNG_APPLY_BOUNDING); if (rc == 0) { if (read_bounding_cap(cap) != 0) fail("Prepared bounding set was not applied"); } else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected apply-bounding return code"); } static void test_apply_bounding_noop_without_state(void) { static const int allowed[] = { -2, -3 }; unsigned int cap; int before, rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (find_drop_test_bounding_cap(&cap)) return; before = read_bounding_cap(cap); if (before != 1) fail("Expected selected bounding capability to be present"); rc = capng_change_id(-1, -1, CAPNG_APPLY_BOUNDING); if (rc == 0) { if (read_bounding_cap(cap) != before) fail("Unprepared bounding set should be a no-op"); } else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected no-op apply-bounding return code"); } static void test_invalid_apply_and_clear_bounding(void) { if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (capng_change_id(-1, -1, CAPNG_APPLY_BOUNDING | CAPNG_CLEAR_BOUNDING) != -17) fail("Unexpected apply+clear bounding return code"); } static void test_apply_bounding_preserves_requested_helper_cap(void) { static const int allowed[] = { -2, -3, -8, -9 }; unsigned int cap; int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (find_drop_test_bounding_cap(&cap)) return; if (capng_have_capability(CAPNG_PERMITTED, CAP_SETPCAP) == 0) return; if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETPCAP)) fail("Failed to request CAP_SETPCAP"); if (capng_update(CAPNG_DROP, CAPNG_BOUNDING_SET, cap)) fail("Failed to prepare bounding set change"); rc = capng_change_id(-1, -1, CAPNG_APPLY_BOUNDING); if (rc == 0) { if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP) == 0) fail("Requested CAP_SETPCAP was removed from effective"); if (capng_have_capability(CAPNG_PERMITTED, CAP_SETPCAP) == 0) fail("Requested CAP_SETPCAP was removed from permitted"); } else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected helper capability cleanup return code"); } static void test_apply_bounding_preserves_permitted_only_helper_cap(void) { static const int allowed[] = { -2, -3, -8, -9 }; unsigned int cap; int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (find_drop_test_bounding_cap(&cap)) return; if (capng_have_capability(CAPNG_PERMITTED, CAP_SETPCAP) == 0) return; if (capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, CAP_SETPCAP)) fail("Failed to clear effective CAP_SETPCAP request"); if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_SETPCAP)) fail("Failed to request permitted CAP_SETPCAP"); if (capng_update(CAPNG_DROP, CAPNG_BOUNDING_SET, cap)) fail("Failed to prepare bounding set change"); rc = capng_change_id(-1, -1, CAPNG_APPLY_BOUNDING); if (rc == 0) { if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP)) fail("Permitted-only CAP_SETPCAP became effective"); if (capng_have_capability(CAPNG_PERMITTED, CAP_SETPCAP) == 0) fail("Permitted-only CAP_SETPCAP was removed"); } else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected permitted-only helper cleanup return code"); } static void test_bounding_state_ignored_without_flag(void) { static const int allowed[] = { -2, -3 }; unsigned int cap; int rc; if (capng_get_caps_process()) fail("Failed to initialize libcap-ng state"); if (find_drop_test_bounding_cap(&cap)) return; if (capng_update(CAPNG_DROP, CAPNG_BOUNDING_SET, cap)) fail("Failed to prepare bounding set change"); rc = capng_change_id(-1, -1, CAPNG_NO_FLAG); if (rc == 0) { if (read_bounding_cap(cap) != 1) fail("Bounding set changed without CAPNG_APPLY_BOUNDING"); } else if (rc_in_list(rc, allowed, sizeof(allowed) / sizeof(int)) == 0) fail("Unexpected backward compatibility return code"); } static void run_test(const char *name, void (*test)(void)) { pid_t pid; int status; fflush(NULL); pid = fork(); if (pid < 0) { perror("fork"); abort(); } if (pid == 0) { test(); _exit(EXIT_SUCCESS); } if (waitpid(pid, &status, 0) < 0) { perror("waitpid"); abort(); } if (WIFEXITED(status) == 0 || WEXITSTATUS(status) != EXIT_SUCCESS) { printf("Failed %s test\n", name); abort(); } } int main(void) { puts("Doing capng_change_id additional group tests..."); run_test("staged-only additional groups", test_staged_only); run_test("init-only additional groups", test_init_only); run_test("init+staged additional groups", test_init_and_staged); run_test("invalid drop+staged combination", test_invalid_drop_and_staged); run_test("staged groups ignored without flag", test_staged_ignored_without_flag); run_test("staged state cleared after use", test_staged_cleared_after_use); run_test("apply prepared bounding set", test_apply_bounding_during_change_id); run_test("apply bounding is no-op without prepared state", test_apply_bounding_noop_without_state); run_test("invalid apply+clear bounding combination", test_invalid_apply_and_clear_bounding); run_test("helper cleanup keeps requested capability", test_apply_bounding_preserves_requested_helper_cap); run_test("helper cleanup keeps permitted-only capability", test_apply_bounding_preserves_permitted_only_helper_cap); run_test("prepared bounding state ignored without flag", test_bounding_state_ignored_without_flag); return EXIT_SUCCESS; } stevegrubb-libcap-ng-0ab44af/src/test/lib_test.c000066400000000000000000000236371516575034500217440ustar00rootroot00000000000000/* lib_test.c -- simple libcap-ng test suite * Copyright 2009,2012-13 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include "../cap-ng.h" #include #include #include #include #include #include #include #include struct proc_caps { uint64_t effective; uint64_t permitted; uint64_t inheritable; }; static unsigned int get_last_cap(void) { int fd; fd = open("/proc/sys/kernel/cap_last_cap", O_RDONLY); if (fd == -1) { return CAP_LAST_CAP; } else { char buf[8]; int num = read(fd, buf, sizeof(buf)); if (num > 0) { errno = 0; unsigned int val = strtoul(buf, NULL, 10); if (errno == 0) return val; } close(fd); } return CAP_LAST_CAP; } static int read_process_caps(struct proc_caps *caps) { FILE *f; char buf[128]; unsigned long long val; int found = 0; memset(caps, 0, sizeof(*caps)); f = fopen("/proc/self/status", "re"); if (f == NULL) return -1; while (fgets(buf, sizeof(buf), f)) { if (sscanf(buf, "CapEff:\t%llx", &val) == 1) { caps->effective = val; found |= 1; } else if (sscanf(buf, "CapPrm:\t%llx", &val) == 1) { caps->permitted = val; found |= 2; } else if (sscanf(buf, "CapInh:\t%llx", &val) == 1) { caps->inheritable = val; found |= 4; } } fclose(f); return found == 7 ? 0 : -1; } static int expected_cap(uint64_t set, unsigned int capability) { return (set & (1ULL << capability)) ? 1 : 0; } static capng_results_t expected_caps_result(uint64_t set, unsigned int last) { unsigned int i; int found = 0, missing = 0; for (i = 0; i <= last; i++) { if (expected_cap(set, i)) found = 1; else missing = 1; } if (!found) return CAPNG_NONE; if (!missing) return CAPNG_FULL; return CAPNG_PARTIAL; } int main(void) { int rc; unsigned int i, len, last = get_last_cap(); struct proc_caps caps; char expected[128]; char *text; const char *name; void *saved; puts("Doing process capability tests..."); if (read_process_caps(&caps)) { puts("Failed reading process capabilities from procfs"); abort(); } if (capng_get_caps_process()) { puts("Failed getting process capabilities"); abort(); } for (i = 0; i <= last; i++) { if (capng_have_capability(CAPNG_PERMITTED, i) != expected_cap(caps.permitted, i)) { puts("Failed process permitted capabilities test"); abort(); } if (capng_have_capability(CAPNG_EFFECTIVE, i) != expected_cap(caps.effective, i)) { puts("Failed process effective capabilities test"); abort(); } if (capng_have_capability(CAPNG_INHERITABLE, i) != expected_cap(caps.inheritable, i)) { puts("Failed process inheritable capabilities test"); abort(); } } if (capng_have_permitted_capabilities() != expected_caps_result(caps.permitted, last)) { puts("Failed process permitted capabilities aggregate test"); abort(); } if (capng_have_capabilities(CAPNG_SELECT_CAPS) != expected_caps_result(caps.effective, last)) { puts("Failed process effective capabilities aggregate test"); abort(); } text = capng_print_caps_numeric(CAPNG_PRINT_BUFFER, CAPNG_SELECT_CAPS); if (text == NULL) { puts("Failed getting process numeric capabilities"); abort(); } snprintf(expected, sizeof(expected), "Effective: %08X, %08X\n" "Permitted: %08X, %08X\n" "Inheritable: %08X, %08X\n", (unsigned int)(caps.effective >> 32), (unsigned int)(caps.effective & 0xFFFFFFFFU), (unsigned int)(caps.permitted >> 32), (unsigned int)(caps.permitted & 0xFFFFFFFFU), (unsigned int)(caps.inheritable >> 32), (unsigned int)(caps.inheritable & 0xFFFFFFFFU)); if (strcmp(text, expected)) { snprintf(expected, sizeof(expected), "Effective: %08X\n" "Permitted: %08X\n" "Inheritable: %08X\n", (unsigned int)caps.effective, (unsigned int)caps.permitted, (unsigned int)caps.inheritable); if (strcmp(text, expected)) { puts("Failed process numeric capabilities test"); free(text); abort(); } } free(text); puts("Doing basic bit tests..."); capng_clear(CAPNG_SELECT_BOTH); errno = 0; rc = capng_apply(0); if (rc != -1 || errno != EINVAL) { puts("Failed apply empty selection test"); abort(); } if (capng_have_permitted_capabilities() != CAPNG_NONE) { puts("Failed permitted capabilities none test"); abort(); } saved = capng_save_state(); capng_fill(CAPNG_SELECT_BOTH); if (capng_have_permitted_capabilities() != CAPNG_FULL) { puts("Failed permitted capabilities full test"); abort(); } capng_restore_state(&saved); capng_clear(CAPNG_SELECT_BOTH); rc = capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_CHOWN); if (rc) { puts("Failed update permitted test"); abort(); } if (capng_have_permitted_capabilities() != CAPNG_PARTIAL) { puts("Failed permitted capabilities partial test"); abort(); } if (capng_have_capabilities(CAPNG_SELECT_BOTH) != CAPNG_NONE) { puts("Failed clearing capabilities"); abort(); } saved = capng_save_state(); capng_fill(CAPNG_SELECT_BOTH); if (capng_have_capabilities(CAPNG_SELECT_BOTH) != CAPNG_FULL) { puts("Failed filling capabilities"); abort(); } // Need to detect if version 1 or 2 capabilities text = capng_print_caps_numeric(CAPNG_PRINT_BUFFER, CAPNG_SELECT_CAPS); len = strlen(text); free(text); if (len < 80 && last > 30) // The kernel & headers are mismatched last = 30; // Now test that restore still works capng_restore_state(&saved); if (capng_have_capabilities(CAPNG_SELECT_BOTH) != CAPNG_NONE) { puts("Failed restoring capabilities"); abort(); } printf("Doing advanced bit tests for %d capabilities...\n", last); for (i=0; i<=last; i++) { capng_clear(CAPNG_SELECT_BOTH); rc = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, i); if (rc) { puts("Failed update test 1"); abort(); } rc = capng_have_capability(CAPNG_EFFECTIVE, i); if (rc == 0) { puts("Failed have capability test 1"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_CAPS); abort(); } if(capng_have_capabilities(CAPNG_SELECT_CAPS)!=CAPNG_PARTIAL){ puts("Failed have capabilities test 1"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_CAPS); abort(); } #if CAP_LAST_CAP > 31 rc = capng_update(CAPNG_ADD, CAPNG_BOUNDING_SET, i); if (rc) { puts("Failed bset update test 2"); abort(); } rc = capng_have_capability(CAPNG_BOUNDING_SET, i); if (rc == 0) { puts("Failed bset have capability test 2"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_BOTH); abort(); } if(capng_have_capabilities(CAPNG_SELECT_BOUNDS)!=CAPNG_PARTIAL){ puts("Failed bset have capabilities test 2"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_BOTH); abort(); } #endif text=capng_print_caps_text(CAPNG_PRINT_BUFFER, CAPNG_EFFECTIVE); if (text == NULL) { puts("Failed getting print text to buffer"); abort(); } name = capng_capability_to_name(i); if (name == NULL) { printf("Failed converting capability %d to name\n", i); abort(); } if (strcmp(text, name)) { puts("Failed print text comparison"); printf("%s != %s\n", text, name); abort(); } free(text); // Now make sure the mask part is working capng_fill(CAPNG_SELECT_BOTH); rc = capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, i); if (rc) { puts("Failed update test 3"); abort(); } // Should be partial if(capng_have_capabilities(CAPNG_SELECT_CAPS)!=CAPNG_PARTIAL){ puts("Failed have capabilities test 3"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_CAPS); abort(); } // Add back the bit and should be full capabilities rc = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, i); if (rc) { puts("Failed update test 4"); abort(); } if (capng_have_capabilities(CAPNG_SELECT_CAPS) != CAPNG_FULL){ puts("Failed have capabilities test 4"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_CAPS); abort(); } } // Verify text formatting when many capabilities are present capng_clear(CAPNG_SELECT_BOTH); for (i=0; i<=last; i++) { rc = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, i); if (rc) { puts("Failed setup test for print buffer length"); abort(); } } text = capng_print_caps_text(CAPNG_PRINT_BUFFER, CAPNG_EFFECTIVE); if (text == NULL) { puts("Failed getting full print text to buffer"); abort(); } len = 0; for (i=0; i<=last; i++) { if (capng_have_capability(CAPNG_EFFECTIVE, i)) { name = capng_capability_to_name(i); if (name == NULL) name = "unknown"; if (len) len += 2; len += strlen(name); } } if (strlen(text) != len) { puts("Failed print text length comparison"); printf("%zu != %u\n", strlen(text), len); abort(); } free(text); // Now test the updatev function capng_clear(CAPNG_SELECT_BOTH); rc = capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_CHOWN, CAP_FOWNER, CAP_KILL, -1); if (rc) { puts("Failed updatev test"); abort(); } rc = capng_have_capability(CAPNG_EFFECTIVE, CAP_CHOWN) && capng_have_capability(CAPNG_EFFECTIVE, CAP_FOWNER) && capng_have_capability(CAPNG_EFFECTIVE, CAP_KILL); if (rc == 0) { puts("Failed have updatev capability test"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_CAPS); abort(); } return EXIT_SUCCESS; } stevegrubb-libcap-ng-0ab44af/src/test/thread_test.c000066400000000000000000000027001516575034500224310ustar00rootroot00000000000000#include "config.h" #include #include #include #include //#define DEBUG 1 pthread_t thread1, thread2; void *thread1_main(void *arg) { capng_fill(CAPNG_SELECT_BOTH); #ifdef DEBUG printf("thread1 filled capabilities\n"); #endif sleep(2); if (capng_have_capabilities(CAPNG_SELECT_CAPS) < CAPNG_FULL) { printf("Capabilities missing when there should be some\n"); exit(1); } #ifdef DEBUG printf("SUCCESS: Full capabilities reported\n"); #endif return NULL; } void *thread2_main(void *arg) { sleep(1); #ifdef DEBUG printf("thread2 getting capabilities\n"); #endif if (capng_get_caps_process()) { printf("Unable to get process capabilities"); exit(1); } if (capng_have_capabilities(CAPNG_SELECT_CAPS) != CAPNG_NONE) { printf("Detected capabilities when there should not be any\n"); exit(1); } capng_clear(CAPNG_SELECT_BOTH); #ifdef DEBUG printf("SUCCESS: No capabilities reported\n"); #endif return NULL; } int main(void) { // This test must be run as root which naturally has all capabilities // set. So, we need to clear the capabilities so that we can see if // the test works. capng_clear(CAPNG_SELECT_CAPS); if (capng_apply(CAPNG_SELECT_CAPS)) { printf("Clearing capabilities failed"); return 1; } printf("Testing thread separation of capabilities\n"); pthread_create(&thread1, NULL, thread1_main, NULL); pthread_create(&thread2, NULL, thread2_main, NULL); sleep(3); return 0; } stevegrubb-libcap-ng-0ab44af/utils/000077500000000000000000000000001516575034500173525ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/utils/Makefile.am000066400000000000000000000041011516575034500214020ustar00rootroot00000000000000# Makefile.am -- # Copyright 2009,2015 Red Hat Inc. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # CONFIG_CLEAN_FILES = *.loT *.rej *.orig DISTCLEANFILES = netcap.8 AUTOMAKE_OPTIONS = no-dependencies if BUILD_CAP_AUDIT SUBDIRS = cap-audit else SUBDIRS = endif DIST_SUBDIRS = cap-audit EXTRA_DIST = pscap.8 filecap.8 netcap.8.in captest.c captest.8 \ libcap-ng.bash_completion AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/src LDADD = ${top_builddir}/src/libcap-ng.la AM_CFLAGS = -W -Wall -Wshadow ${WFLAGS} -Wundef -D_GNU_SOURCE NETCAP_ADVANCED_ENABLED = @NETCAP_ADVANCED_ENABLED@ bin_PROGRAMS = pscap netcap filecap man_MANS = pscap.8 netcap.8 filecap.8 bashcompletiondir = ${datadir}/bash-completion/completions pscap_SOURCES = pscap.c proc-sanitize.c proc-sanitize.h netcap_SOURCES = netcap.c netcap-advanced.h proc-llist.c \ proc-llist.h proc-sanitize.c proc-sanitize.h if BUILD_NETCAP_ADVANCED netcap_SOURCES += netcap-advanced.c endif filecap_SOURCES = filecap.c if BUILD_DEPRECATED bin_PROGRAMS += captest man_MANS += captest.8 captest_SOURCES = captest.c endif install-exec-hook: $(MKDIR_P) ${DESTDIR}${bashcompletiondir} $(INSTALL_DATA) ${srcdir}/libcap-ng.bash_completion \ ${DESTDIR}${bashcompletiondir}/libcap-ng.bash_completion uninstall-hook: rm -f ${DESTDIR}${bashcompletiondir}/libcap-ng.bash_completion stevegrubb-libcap-ng-0ab44af/utils/cap-audit/000077500000000000000000000000001516575034500212215ustar00rootroot00000000000000stevegrubb-libcap-ng-0ab44af/utils/cap-audit/Makefile.am000066400000000000000000000035041516575034500232570ustar00rootroot00000000000000# Makefile.am -- cap-audit utility # # Copyright 2026 Red Hat Inc. # All Rights Reserved. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # Authors: # Steve Grubb # AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/src ${LIBBPF_CFLAGS} ${LIBAUDIT_CFLAGS} AM_CFLAGS = -W -Wall -Wshadow ${WFLAGS} -Wundef -D_GNU_SOURCE bin_PROGRAMS = cap-audit cap_audit_SOURCES = cap_audit.c \ cap_audit_analyze.c \ cap_audit_event.c \ cap_audit_json.c \ cap_audit_util.c \ cap_audit_yaml.c \ cap_audit.h nodist_cap_audit_SOURCES = cap_audit.skel.h cap_audit_LDADD = ${top_builddir}/src/libcap-ng.la ${LIBBPF_LIBS} ${LIBAUDIT_LIBS} EXTRA_DIST = cap_audit.bpf.c cap-audit.8 CLEANFILES = cap_audit.bpf.o cap_audit.skel.h vmlinux.h if BUILD_CAP_AUDIT BUILT_SOURCES = cap_audit.bpf.o cap_audit.skel.h vmlinux.h endif cap_audit.$(OBJEXT) cap_audit_analyze.$(OBJEXT) \ cap_audit_event.$(OBJEXT) cap_audit_json.$(OBJEXT) \ cap_audit_util.$(OBJEXT) cap_audit_yaml.$(OBJEXT): cap_audit.skel.h BPF_ARCH = @BPF_ARCH@ BPFTOOL = @BPFTOOL@ CLANG = @CLANG@ BPF_CFLAGS = -g -O2 -target bpf -D__TARGET_ARCH_${BPF_ARCH} ${AM_CPPFLAGS} cap_audit.skel.h: cap_audit.bpf.o $(AM_V_GEN)$(BPFTOOL) gen skeleton $< > $@ cap_audit.bpf.o: cap_audit.bpf.c vmlinux.h $(AM_V_CC)$(CLANG) $(BPF_CFLAGS) -c $< -o $@ vmlinux.h: $(AM_V_GEN)$(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > $@ man_MANS = cap-audit.8 stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap-audit.8000066400000000000000000000057651516575034500231760ustar00rootroot00000000000000.TH CAP-AUDIT "8" "March 2026" "Red Hat" "System Administration Utilities" .SH NAME cap-audit \- discover the minimal capability set for a program .SH SYNOPSIS .B cap-audit .RI [ options ] .B -- .I command .RI [ args ...] .SH DESCRIPTION \fBcap-audit\fP traces a target process and its descendants to record every kernel capability check it performs. The tool uses eBPF to hook capability verification paths in the kernel and libcap-ng to format the results. Events are filtered to only the traced process tree to reduce overhead. .PP The auditor fork/execs the requested command, registers its PID for tracing before exec, and automatically tracks child PIDs. At the end of execution it reports which capabilities were granted, which were attempted and denied, and presents deployment snippets showing how to grant the minimal set. .PP Runtime tracing requires root or the CAP_BPF, CAP_PERFMON, and CAP_SYS_PTRACE capabilities, along with kernel BTF data in \fI/sys/kernel/btf/vmlinux\fP. .SH OPTIONS .TP .BR -h , " --help" Show a help message and exit. .TP .BR -v , " --verbose" Print each capability check as it occurs. .TP .BR -j , " --json" Emit the analysis as JSON. .TP .BR -y , " --yaml" Emit the analysis as YAML. .TP .BR -- End option parsing and treat the rest of the command line as the program to execute under audit. .SH EXIT STATUS The program returns 0 on success. Non-zero values indicate an error occurred while setting up tracing or executing the target command. .SH NOTES 1. This utility observes the application behavior to decide what is needed. If you do not exercise all functionality, it can result in an incomplete inventory of needed capabilities. If it can't see it, cap-audit can't record it. 2. There are hard memory constraints for event collection. The limit for the number of children that can be traced is 8192. If an application is fork heavy, such as shell scripts or spawns a lot of short lived helper applications, the tracer will stop collecting events on new children once the buffer fills. Also, the tracer only has space to hold 20k unique stack traces. This is not as problematic as the other constraint as the same code paths often repeat. If this fills up, new code paths will not be collected. This buffer is used to map a capability request to the syscall needing it. Knowing the syscall causing the needed capability is a "nice to have". What's more important is the capability needed - which is in a different memory area and safe from the described issue. In both cases, there is no indication to user space that the tracer is out of memory. 3. When auditing a daemon, pass options to keep it in the foreground. If it forks and exits, that will end the session and you will only get the capabilities traced up to the exit. Here's an example keeping the daemon in the foreground: .nf .B cap-audit /usr/sbin/sshd -D .fi .PP \fBEXPERIMENTAL:\fP cap-audit output is experimental, but very close. .SH FILES .I /sys/kernel/btf/vmlinux .SH "SEE ALSO" .BR capabilities (7), .BR filecap (8). .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit.bpf.c000066400000000000000000000502461516575034500240730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap_audit.bpf.c - Capture capability checks for a target application * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ #include "vmlinux.h" #ifndef CAP_OPT_NOAUDIT #define CAP_OPT_NOAUDIT 2 #endif #include #include #include /* * BPF overview: * The BPF side attaches to capability helpers (cap_capable, ns_capable, * capable) and syscall tracepoints to capture capability checks only for a * target process tree. * * The design challenge is noise filtering: distinguish capability checks * caused by the target application from kernel-internal checks running under * the same PID. The first filtering layer lives entirely in this BPF program * and is encoded in target_pids map values. * * target_pids values are phases, not just booleans: * 0 = not traced * 1 = pre-exec (PID registered, but exec transition not complete) * 2 = post-exec (target image is running; capability events are recordable) * * Phase 1 suppresses all capability events for the initial child so checks * between fork() and execve() are dropped. That removes pre-exec noise such * as DAC_READ_SEARCH from PATH traversal while resolving the target binary. * * The transition point is sched_process_exec. That tracepoint fires in * begin_new_exec() after the point-of-no-return where the old image is gone * and the new executable is committed. This is a kernel-level signal, so it * works regardless of libc/toolchain choice (glibc, musl, static, scripts). * * Fork inherits the parent's phase value. Children of an already running * target (phase 2) begin recording immediately, while children spawned before * the initial exec remain in phase 1 until their own exec transition. * * raw_syscalls/sys_enter and sys_exit use should_trace_pid() (phase > 0) * so syscall context is available the instant the exec gate opens. * Capability hooks use should_record_pid() (phase >= 2), so only post-exec * capability checks are emitted. * * For traced tasks, the program builds cap_event records with task identity, * syscall context, namespace inode, the CAP_OPT_* flags passed to * cap_capable(), and stack id; tracks per-capability statistics; and * streams finalized events to userspace through a ring buffer. The cap_opts * field carries the CAP_OPT_* flags so userspace can correlate known * advisory call sites without treating CAP_OPT_NOAUDIT as a standalone * filter. CAP_OPT_NOAUDIT means "do not audit" and is only a confirming * signal alongside syscall and capability matching. Fork/exit tracepoints * keep the PID filter in sync so children are traced and exits are pruned. */ #ifndef PERF_MAX_STACK_DEPTH #define PERF_MAX_STACK_DEPTH 127 #endif #if !defined(__TARGET_ARCH_x86) && !defined(__TARGET_ARCH_arm64) && \ !defined(__TARGET_ARCH_arm) && !defined(__TARGET_ARCH_powerpc) && \ !defined(__TARGET_ARCH_s390) && !defined(__TARGET_ARCH_riscv) && \ !defined(__TARGET_ARCH_mips) && !defined(__TARGET_ARCH_loongarch) #if defined(__x86_64__) || defined(__i386__) #define __TARGET_ARCH_x86 #elif defined(__aarch64__) #define __TARGET_ARCH_arm64 #elif defined(__arm__) #define __TARGET_ARCH_arm #elif defined(__powerpc__) #define __TARGET_ARCH_powerpc #elif defined(__s390x__) || defined(__s390__) #define __TARGET_ARCH_s390 #elif defined(__riscv) #define __TARGET_ARCH_riscv #elif defined(__mips__) #define __TARGET_ARCH_mips #elif defined(__loongarch64) #define __TARGET_ARCH_loongarch #else #define __TARGET_ARCH_x86 #endif #endif char LICENSE[] SEC("license") = "GPL"; struct cap_event { __u64 timestamp_ns; __u32 pid; __u32 tid; __u32 uid; __u32 gid; int capability; int result; int syscall_nr; char comm[TASK_COMM_LEN]; __u64 stack_id; __u32 targ_ns_inum; __u32 cap_opts; }; struct cap_stats { __u64 checks; __u64 granted; __u64 denied; }; // This sets the limit for how many child processes can be traced. // Because of this limit, the tracer may not be suitable for shell // scripts or long running process that fork child handlers that // terminate soon after launching. When this fills up, no more // children will be traced. This is the breaking point for long // running apps. The other limits aren't as likely to be broken. // This is approx 16 bytes per entry. (Default uses 128K of memory) struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, __u32); __type(value, __u8); __uint(max_entries, 8192); } target_pids SEC(".maps"); // This declares the size of the ring buf that holds events for // userspace to access. struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256 * 1024); } cap_events SEC(".maps"); // This declares how many unique stack traces to hold. This is used // to determine which syscall a capability was requested from. If this // fills up, no more stack traces will be collected. This is about // 1K per entry. (Default uses 20 MB of memory) struct { __uint(type, BPF_MAP_TYPE_STACK_TRACE); __uint(key_size, sizeof(__u32)); __uint(value_size, PERF_MAX_STACK_DEPTH * sizeof(__u64)); __uint(max_entries, 20000); } stack_traces SEC(".maps"); // This declares how many capabilities can be watched. As of the 6.18 // kernel, it only uses 40. So, 64 is future proof as none have been added // in a while. struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, __u32); __type(value, struct cap_stats); __uint(max_entries, 64); } capability_stats SEC(".maps"); // The cap_events_inflight map uses pid_tgid as the key. There is a race // scenario when deep syscall chains that check multiple capabilities // or nested function calls where each checks a capability. In these cases // because it uses the same pid_tgid, it can overwrite a previous event. // example: // // In kernel, during mount(): // sys_mount() { // First check // if (!capable(CAP_SYS_ADMIN)) // ← kprobe #1 fires // return -EPERM; // // Path resolution might trigger // if (!capable(CAP_DAC_OVERRIDE)) // ← kprobe #2 fires BEFORE kretprobe #1! // return -EACCES; // ... more work ... // // return 0; // ← kretprobe #1 and #2 fire //} // // Statistics are SAFE: The capability_stats map is updated immediately in // the kprobe using atomic operations, so counts are always accurate. The // probability is higher for complex syscalls like mount, setuid, or network // operations. // // Possible solutions // Option 1: Per-CPU Map - change to BPF_MAP_TYPE_PERCPU_HASH. Drawback is // it uses a map for each CPU so if 64 cores, map is 64KB of memory. // // Option 2: Include Stack Pointer in Key - // key[0] = bpf_get_current_pid_tgid(); // key[1] = PT_REGS_SP(ctx); // Stack pointer makes it unique // bpf_map_update_elem(&cap_events_inflight, &key, ev, BPF_ANY); // // Option 3: Accept the Race (Current Approach) // Rationale: // * The capability_stats map is always correct (atomic updates) // * Individual event details might be wrong, but aggregate data is right // * For the tool's purpose (determining required capabilities), statistics // are what matter // * Individual events are mainly for debugging/verbose mode struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, __u64); __type(value, struct cap_event); __uint(max_entries, 1024); } cap_events_inflight SEC(".maps"); // In theory, if sys_exit is not called, a syscall can leak. This can // happen due to SIGKILL or a core dump. This might matter if this is // tracing a long running with many threads some of which get SIGKILL. // Might be more likely if its a child process rather than a thread. // Because each run of the tracer is a new instance, the only concern // is long tracing sessions. If this really was a concern, we could // change to BPF_MAP_TYPE_PERCPU_HASH so that a leak on CPU0 doesn't // affect tracing on CPU1. This is just mentioned here because it is // an esoteric problem and not likely to show up. But this documents // it and a possible solution. The drawback is that it uses more memory. struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, __u64); __type(value, int); __uint(max_entries, 4096); } current_syscalls SEC(".maps"); static __always_inline int get_pid_phase(__u32 pid) { __u8 *val = bpf_map_lookup_elem(&target_pids, &pid); return val ? *val : 0; } /* * should_trace_pid - check if the current PID is in the target set. * @pid: process ID of the current task. * * Looks up the PID in target_pids and returns 1 when tracing is enabled for * it, otherwise 0. */ static __always_inline int should_trace_pid(__u32 pid) { return get_pid_phase(pid) > 0; } /* * should_record_pid - check if the current PID is post-exec. * @pid: process ID of the current task. * * Returns 1 for traced PIDs that have completed exec. */ static __always_inline int should_record_pid(__u32 pid) { return get_pid_phase(pid) >= 2; } /* * record_stats - increment capability check count. * @cap: capability number. * * Increments the "checks" counter for the capability in capability_stats. * Out-of-range capability numbers are ignored. Returns nothing. */ static __always_inline void record_stats(int cap) { __u32 key; struct cap_stats *stats; if (cap < 0 || cap >= 64) return; key = (__u32)cap; stats = bpf_map_lookup_elem(&capability_stats, &key); if (stats) __sync_fetch_and_add(&stats->checks, 1); else { struct cap_stats new_stats = { 0 }; new_stats.checks = 1; if (!bpf_map_update_elem(&capability_stats, &key, &new_stats, BPF_NOEXIST)) return; stats = bpf_map_lookup_elem(&capability_stats, &key); if (stats) __sync_fetch_and_add(&stats->checks, 1); } } /* * update_result_stats - record whether a capability check succeeded. * @cap: capability number from the in-flight event. * @ret: return value from the capability helper (0 = granted). * * Updates the granted/denied counters for the capability when a matching * entry already exists. Out-of-range capability numbers are ignored. */ static __always_inline void update_result_stats(int cap, int ret) { __u32 key; struct cap_stats *stats; if (cap < 0 || cap >= 64) return; key = (__u32)cap; stats = bpf_map_lookup_elem(&capability_stats, &key); if (!stats) return; if (!ret) __sync_fetch_and_add(&stats->granted, 1); else __sync_fetch_and_add(&stats->denied, 1); } /* * read_syscall - fetch the syscall number for the current task. * @ctx: pt_regs provided by the kprobe. * * Uses a per-thread map populated by sys_enter tracepoints when available, * and falls back to architecture-specific pt_regs fields. Returns the syscall * number or -1 when it cannot be determined. */ static __always_inline int read_syscall(struct pt_regs *ctx) { __u64 pid_tgid; int *nr; pid_tgid = bpf_get_current_pid_tgid(); nr = bpf_map_lookup_elem(¤t_syscalls, &pid_tgid); if (nr) return *nr; #ifdef __TARGET_ARCH_x86 return BPF_CORE_READ(ctx, orig_ax); #elif defined(__TARGET_ARCH_arm64) return BPF_CORE_READ(ctx, syscallno); #elif defined(__TARGET_ARCH_powerpc) return BPF_CORE_READ(ctx, gpr[0]); #elif defined(__TARGET_ARCH_s390) return BPF_CORE_READ(ctx, gprs[2]); #else return -1; #endif } /* * fill_event_common - populate the static fields of a cap_event. * @ev: event structure to fill. * @ctx: pt_regs from the capability hook. * @cap: capability number being checked. * * Captures PID/TID, timestamp, UID/GID, command name, syscall number, and * user stack id for the current task. */ static __always_inline void fill_event_common(struct cap_event *ev, struct pt_regs *ctx, int cap) { __u64 pid_tgid; __u64 uid_gid; pid_tgid = bpf_get_current_pid_tgid(); ev->pid = pid_tgid >> 32; ev->tid = (__u32)pid_tgid; ev->capability = cap; ev->timestamp_ns = bpf_ktime_get_ns(); uid_gid = bpf_get_current_uid_gid(); ev->uid = uid_gid >> 32; ev->gid = (__u32)uid_gid; bpf_get_current_comm(&ev->comm, sizeof(ev->comm)); ev->syscall_nr = read_syscall(ctx); ev->stack_id = bpf_get_stackid(ctx, &stack_traces, BPF_F_USER_STACK); } /* * stash_event - store a partially filled event until the kretprobe fires. * @ev: event to stash. * * Keeps the event keyed by pid_tgid in cap_events_inflight so the return * probe can finalize result status before emitting to userspace. */ static __always_inline void stash_event(struct cap_event *ev) { __u64 pid_tgid; pid_tgid = bpf_get_current_pid_tgid(); bpf_map_update_elem(&cap_events_inflight, &pid_tgid, ev, BPF_ANY); } /* * submit_event - finalize and emit a stashed event to the ring buffer. * @ret: return code from the capability helper (0 = granted). * * Looks up the in-flight event, copies it to the ring buffer, sets the result * flag (1 = granted, 0 = denied), and removes the temporary entry. Returns 0 * whether or not an event was emitted. */ static __always_inline int submit_event(int ret) { __u64 pid_tgid; struct cap_event *stored; struct cap_event *out; pid_tgid = bpf_get_current_pid_tgid(); stored = bpf_map_lookup_elem(&cap_events_inflight, &pid_tgid); if (!stored) return 0; out = bpf_ringbuf_reserve(&cap_events, sizeof(*out), 0); if (!out) goto cleanup; __builtin_memcpy(out, stored, sizeof(*out)); out->result = ret ? 0 : 1; bpf_ringbuf_submit(out, 0); cleanup: bpf_map_delete_elem(&cap_events_inflight, &pid_tgid); return 0; } /* * handle_capable - common logic for capability helper entry probes. * @ctx: pt_regs for the probed function. * @cap: capability number under evaluation. * @targ_ns: optional target namespace pointer (may be NULL). * * Filters by PID first; for traced tasks it records a stats increment, * populates a cap_event with contextual information, and stashes it so the * return probe can attach the result. Returns 0 to indicate the kprobe should * allow normal execution to continue. */ static __always_inline int handle_capable(struct pt_regs *ctx, int cap, struct user_namespace *targ_ns, unsigned int cap_opts) { struct cap_event ev = { 0 }; __u32 pid; pid = bpf_get_current_pid_tgid() >> 32; if (!should_record_pid(pid)) return 0; /* Track how many times this capability was inspected. */ record_stats(cap); /* Collect task identity, syscall, and stack trace information. */ fill_event_common(&ev, ctx, cap); ev.cap_opts = (__u32)cap_opts; if (targ_ns) { struct ns_common *ns; ns = (struct ns_common *)targ_ns; ev.targ_ns_inum = BPF_CORE_READ(ns, inum); } /* Save event so the kretprobe can add the success/failure result. */ stash_event(&ev); return 0; } /* * trace_cap_capable - entry probe for cap_capable(). * * Delegates to handle_capable(). Arguments mirror the kernel helper and the * capability number, namespace pointer, and opts value are recorded for the * in-flight event. Returns 0. */ SEC("kprobe/cap_capable") int BPF_KPROBE(trace_cap_capable, const struct cred *cred, struct user_namespace *targ_ns, int cap, unsigned int opts) { return handle_capable(ctx, cap, targ_ns, opts); } /* * trace_cap_capable_ret - return probe for cap_capable(). * @ret: kernel return value (0 = granted). * * Updates result statistics for the capability tied to this pid_tgid and * emits the finalized event to userspace. Always returns 0. */ SEC("kretprobe/cap_capable") int BPF_KRETPROBE(trace_cap_capable_ret, int ret) { __u64 pid_tgid; struct cap_event *stored; pid_tgid = bpf_get_current_pid_tgid(); stored = bpf_map_lookup_elem(&cap_events_inflight, &pid_tgid); if (stored) update_result_stats(stored->capability, ret); return submit_event(ret); } /* * trace_ns_capable - entry probe for ns_capable(). * * Uses handle_capable() to capture namespace-aware capability checks. Returns * 0. */ SEC("kprobe/ns_capable") int BPF_KPROBE(trace_ns_capable, struct user_namespace *ns, int cap) { return handle_capable(ctx, cap, ns, 0); } /* * trace_ns_capable_ret - return probe for ns_capable(). * @ret: kernel return value. * * Emits the stored event with the grant/deny result. Returns 0. */ SEC("kretprobe/ns_capable") int BPF_KRETPROBE(trace_ns_capable_ret, int ret) { return submit_event(ret); } /* * trace_ns_capable_noaudit - entry probe for ns_capable_noaudit(). * * Captures capability checks that bypass kernel audit logging but should be * observed by the auditor. Returns 0. */ SEC("kprobe/ns_capable_noaudit") int BPF_KPROBE(trace_ns_capable_noaudit, struct user_namespace *ns, int cap) { return handle_capable(ctx, cap, ns, 0); } /* * trace_ns_capable_noaudit_ret - return probe for ns_capable_noaudit(). * @ret: kernel return value. * * Finalizes and emits the stored event. Returns 0. */ SEC("kretprobe/ns_capable_noaudit") int BPF_KRETPROBE(trace_ns_capable_noaudit_ret, int ret) { return submit_event(ret); } /* * trace_capable - entry probe for capable(). * * Handles capability checks that do not involve namespaces. Returns 0. */ SEC("kprobe/capable") int BPF_KPROBE(trace_capable, int cap) { return handle_capable(ctx, cap, NULL, 0); } /* * trace_capable_ret - return probe for capable(). * @ret: kernel return value. * * Emits the stored capability event. Returns 0. */ SEC("kretprobe/capable") int BPF_KRETPROBE(trace_capable_ret, int ret) { return submit_event(ret); } /* * trace_sys_enter - remember syscall numbers on entry. * @ctx: raw_syscalls/sys_enter tracepoint context. * * Stores the syscall number in a per-thread map for later lookup by the * capability probes. No-op for non-traced PIDs. Returns 0. */ SEC("tracepoint/raw_syscalls/sys_enter") int trace_sys_enter(struct trace_event_raw_sys_enter *ctx) { __u64 pid_tgid; __u32 pid; __u32 id; pid_tgid = bpf_get_current_pid_tgid(); pid = pid_tgid >> 32; if (!should_trace_pid(pid)) return 0; id = ctx->id; bpf_map_update_elem(¤t_syscalls, &pid_tgid, &id, BPF_ANY); return 0; } /* * trace_sys_exit - clear syscall tracking on exit. * @ctx: raw_syscalls/sys_exit tracepoint context. * * Removes the stored syscall number for the thread when tracing. Returns 0. */ SEC("tracepoint/raw_syscalls/sys_exit") int trace_sys_exit(struct trace_event_raw_sys_exit *ctx) { __u64 pid_tgid; __u32 pid; pid_tgid = bpf_get_current_pid_tgid(); pid = pid_tgid >> 32; if (!should_trace_pid(pid)) return 0; bpf_map_delete_elem(¤t_syscalls, &pid_tgid); return 0; } /* * trace_sched_process_fork - follow new child processes. * @ctx: sched_process_fork tracepoint data. * * When a traced parent forks, automatically add the child PID to the filter * map so subsequent capability checks are captured. Returns 0. */ SEC("tracepoint/sched/sched_process_fork") int trace_sched_process_fork(struct trace_event_raw_sched_process_fork *ctx) { __u32 parent_pid; __u32 child_pid; __u8 *parent_val; parent_pid = ctx->parent_pid; child_pid = ctx->child_pid; parent_val = bpf_map_lookup_elem(&target_pids, &parent_pid); if (parent_val) { __u8 child_val = *parent_val; bpf_map_update_elem(&target_pids, &child_pid, &child_val, BPF_ANY); } return 0; } /* * trace_sched_process_exec - mark a traced PID as post-exec. * @ctx: sched_process_exec tracepoint data. * * Transitions the PID from pre-exec (phase 1) to post-exec (phase 2) once * the new binary image is loaded. */ SEC("tracepoint/sched/sched_process_exec") int trace_sched_process_exec(struct trace_event_raw_sched_process_exec *ctx) { __u32 pid; __u8 *val; __u8 new_val = 2; pid = bpf_get_current_pid_tgid() >> 32; val = bpf_map_lookup_elem(&target_pids, &pid); if (val) bpf_map_update_elem(&target_pids, &pid, &new_val, BPF_ANY); return 0; } /* * trace_sched_process_exit - prune exited processes from the target set. * @ctx: sched_process_exit tracepoint data. * * Removes the exiting PID from the target_pids map to prevent stale entries. * Returns 0. */ SEC("tracepoint/sched/sched_process_exit") int trace_sched_process_exit(struct trace_event_raw_sched_process_template *ctx) { __u32 pid; pid = ctx->pid; bpf_map_delete_elem(&target_pids, &pid); return 0; } stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit.c000066400000000000000000000320041516575034500233150ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap-audit - Trace a target process to discover required capabilities. * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ #include "cap_audit.h" #include #include #include #include #include #include #include #include #include #include /* * Overview: * cap-audit launches a target application, traces that process tree using * eBPF hooks, and reports which Linux capabilities were actually exercised. * The userspace side performs three major jobs: * * (1) prepare the runtime environment by checking our own capabilities and * raising rlimits; * (2) coordinate with the eBPF program by registering the target PID before * exec() and consuming capability check events from the ring buffer; and * (3) analyze collected data to present required, conditional, and denied * capabilities in human and machine-readable formats. * * When the tool observes a capset syscall from the initial PID, it splits * capability accounting into initialization and operational phases. The * initialization phase covers all capability checks from process start to * the first capset. The operational phase covers everything after. This * separation allows the tool to distinguish capabilities needed for one-time * setup (binding privileged ports, chroot, loading restricted configuration) * from capabilities needed for ongoing operation. Recommendations for * programmatic capability dropping use only the operational set, while * deployment recommendations (file capabilities, systemd, containers) use * the union since the process must start with sufficient capabilities for * initialization. Programs that never call capset produce an undifferentiated * report identical to previous versions. * * Core design problem: * when tracing as root, many capability checks come from kernel-internal * work under the same PID rather than from app logic. The tool uses a * two-layer noise filter pipeline, split between BPF and userspace, to * separate real requirements from incidental checks. * * Layer 1 - pre-exec noise (BPF phase gate): * after fork() but before execve() completes, the child is still this * auditor image. PATH lookup, directory traversal, and exec machinery can * trigger checks like DAC_READ_SEARCH/SYS_ADMIN/SETPCAP that are unrelated * to the target app. The BPF side tracks PID phases and suppresses all * capability events until sched_process_exec transitions the PID from * phase 1 (pre-exec) to phase 2 (post-exec). * * Layer 2 - userspace always-noise filter: * handle_cap_event() unconditionally drops two classes of capability * checks that never represent real application requirements. First, * execve-triggered SYS_ADMIN and SETPCAP checks are kernel-internal * credential-transition noise. Second, cap-audit identifies the known * advisory memory overcommit probe by combining syscall, capability, and * CAP_OPT_NOAUDIT: cap_vm_enough_memory checks CAP_SYS_ADMIN on * brk/mmap/mprotect/mremap and the operation succeeds regardless of the * result. CAP_OPT_NOAUDIT means "do not audit", not "advisory", so it * is only a confirming signal here. Other enforcement checks also use * CAP_OPT_NOAUDIT to avoid audit spam, and must still be reported. The * syscall + capability match identifies the specific advisory call site, * while CAP_OPT_NOAUDIT confirms that the event followed the advisory path * rather than a security-gating path on the same syscall. * * Layer 3 - final drain shutdown backstop: * after waitpid() reports that the initial PID has exited, cap-audit does a * short final ring-buffer drain before analyzing results. That drain should * stay conservative because queued late SYS_ADMIN/SETPCAP checks from the * exiting interpreter/runtime are more likely to be teardown chatter than a * meaningful application requirement. The shutdown backstop is intentionally * limited to the initial PID and only applies during that final drain. * * PID filtering remains central: parent registers the child immediately after * fork(), BPF follows forks/exits to keep the target set precise, and each * event carries capability, syscall context, namespace info, and result for * per-capability aggregation. * * Parent/child startup is synchronized with a pipe. The child blocks in * read(sync_pipe) until the parent has inserted its PID in target_pids and * written a go byte. That ordering prevents missed events and prevents events * from arriving before the PID is registered. */ struct audit_state state; int audit_machine = -1; static void sig_handler(int sig __attribute__((unused))) { state.stop = 1; } static int set_memlock_rlimit(void) { struct rlimit rlim_new = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY, }; if (setrlimit(RLIMIT_MEMLOCK, &rlim_new) < 0) { fprintf(stderr, "Error: Failed to raise memlock rlimit: %s\n", errno == EPERM ? "insufficient privileges" : strerror(errno)); return -1; } return 0; } static int init_capng(void) { capng_clear(CAPNG_SELECT_BOTH); if (capng_get_caps_process() != 0) { fprintf(stderr, "Error: Failed to get process capabilities\n"); return -1; } return 0; } static int check_auditor_caps(void) { if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_BPF) && !capng_have_capability(CAPNG_EFFECTIVE, CAP_SYS_ADMIN)) { fprintf( stderr, "Error: Need CAP_BPF or CAP_SYS_ADMIN to run auditor\n"); return -1; } if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_PERFMON) && !capng_have_capability(CAPNG_EFFECTIVE, CAP_SYS_ADMIN)) { fprintf(stderr, "Error: Need CAP_PERFMON or CAP_SYS_ADMIN for " "perf events\n"); return -1; } if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_SYS_PTRACE)) fprintf(stderr, "Warning: CAP_SYS_PTRACE not available, stack " "traces may be limited\n"); return 0; } static int set_target_pid(pid_t pid) { int map_fd; __u8 val = 1; map_fd = bpf_map__fd(state.skel->maps.target_pids); if (map_fd < 0) { fprintf(stderr, "Error: Failed to get target_pids map fd\n"); return -1; } if (bpf_map_update_elem(map_fd, &pid, &val, BPF_ANY) != 0) { fprintf(stderr, "Error: Failed to register target PID %d: %s\n", pid, strerror(errno)); return -1; } if (state.verbose) printf("[*] Registered PID %d for tracing\n", pid); return 0; } static void usage(FILE *out, const char *prog) { fprintf(out, "Usage: %s [options] -- command [args...]\n", prog); fprintf(out, "Options:\n"); fprintf(out, " -h, --help Show this help message\n"); fprintf(out, " -v, --verbose Verbose output\n"); fprintf(out, " -j, --json JSON output\n"); fprintf(out, " -y, --yaml YAML output\n"); } int main(int argc, char **argv) { int err; int arg_idx; pid_t child; pid_t ret_pid; int wstatus; if (argc < 2) { usage(stderr, argv[0]); return 1; } arg_idx = 1; while (arg_idx < argc && argv[arg_idx][0] == '-') { if (!strcmp(argv[arg_idx], "-h") || !strcmp(argv[arg_idx], "--help")) { usage(stdout, argv[0]); return 0; } else if (!strcmp(argv[arg_idx], "-v") || !strcmp(argv[arg_idx], "--verbose")) state.verbose = 1; else if (!strcmp(argv[arg_idx], "-j") || !strcmp(argv[arg_idx], "--json")) state.json_output = 1; else if (!strcmp(argv[arg_idx], "-y") || !strcmp(argv[arg_idx], "--yaml")) state.yaml_output = 1; else if (!strcmp(argv[arg_idx], "--")) { arg_idx++; break; } else { fprintf(stderr, "Error: Unknown option '%s'\n", argv[arg_idx]); usage(stderr, argv[0]); return 1; } arg_idx++; } if (arg_idx >= argc) { fprintf(stderr, "Error: No command specified\n"); usage(stderr, argv[0]); return 1; } state.target_argv = &argv[arg_idx]; if (init_capng() != 0) return 1; if (check_auditor_caps() != 0) return 1; if (set_memlock_rlimit() != 0) return 1; state.app.exe = strdup(state.target_argv[0]); state.app.prog_type = UNSUPPORTED; if (audit_machine < 0) audit_machine = audit_detect_machine(); if (audit_machine < 0) { fprintf(stderr, "Error: unable to determine hardware architecture for syscall lookup. Exiting.\n"); return 1; } state.app.execve_nr = audit_name_to_syscall("execve", audit_machine); state.app.mmap_nr = audit_name_to_syscall("mmap", audit_machine); state.app.brk_nr = audit_name_to_syscall("brk", audit_machine); state.app.mprotect_nr = audit_name_to_syscall("mprotect", audit_machine); state.app.mremap_nr = audit_name_to_syscall("mremap", audit_machine); state.app.capset_nr = audit_name_to_syscall("capset", audit_machine); state.skel = cap_audit_bpf__open_and_load(); if (!state.skel) { fprintf(stderr, "Error: Failed to load BPF program: %s\n", strerror(errno)); return 1; } err = cap_audit_bpf__attach(state.skel); if (err) { fprintf(stderr, "Error: Failed to attach BPF programs: %s\n", strerror(-err)); cap_audit_bpf__destroy(state.skel); return 1; } state.rb = ring_buffer__new(bpf_map__fd(state.skel->maps.cap_events), handle_cap_event, NULL, NULL); if (!state.rb) { fprintf(stderr, "Error: Failed to create ring buffer: %s\n", strerror(errno)); cap_audit_bpf__destroy(state.skel); return 1; } printf("[*] Capability auditor started\n"); if (pipe(state.sync_pipe) != 0) { fprintf(stderr, "Error: pipe failed: %s\n", strerror(errno)); ring_buffer__free(state.rb); cap_audit_bpf__destroy(state.skel); return 1; } child = fork(); if (child == 0) { char sync_byte; ssize_t bytes; close(state.sync_pipe[1]); bytes = read(state.sync_pipe[0], &sync_byte, 1); if (bytes != 1) { if (bytes < 0) perror("read"); else fprintf(stderr, "Error: failed to sync with parent\n"); close(state.sync_pipe[0]); exit(1); } close(state.sync_pipe[0]); execvp(state.target_argv[0], state.target_argv); perror("execvp"); exit(1); } else if (child < 0) { fprintf(stderr, "Error: fork failed: %s\n", strerror(errno)); close(state.sync_pipe[0]); close(state.sync_pipe[1]); ring_buffer__free(state.rb); cap_audit_bpf__destroy(state.skel); return 1; } state.app.pid = child; close(state.sync_pipe[0]); if (set_target_pid(child) != 0) { close(state.sync_pipe[1]); kill(child, SIGKILL); waitpid(child, NULL, 0); ring_buffer__free(state.rb); cap_audit_bpf__destroy(state.skel); free(state.app.exe); return 1; } if (write(state.sync_pipe[1], "1", 1) != 1) { fprintf(stderr, "Error: write failed: %s\n", strerror(errno)); close(state.sync_pipe[1]); kill(child, SIGKILL); waitpid(child, NULL, 0); ring_buffer__free(state.rb); cap_audit_bpf__destroy(state.skel); free(state.app.exe); return 1; } close(state.sync_pipe[1]); { char resolved[PATH_MAX]; if (resolve_target_exe(child, resolved, sizeof(resolved)) == 0) { char *resolved_dup = strdup(resolved); if (resolved_dup) { free(state.app.exe); state.app.exe = resolved_dup; } state.app.prog_type = classify_app(state.app.exe); } else { fprintf(stderr, "Warning: unable to resolve target path\n"); } } read_system_state(&state.app); inspect_target_file_caps(child); if (state.app.prog_type == PYTHON && state.verbose) printf("[*] Script interpreter: %s\n", state.app.exe); printf("[*] Tracing application: %s (PID %d)\n", state.app.exe, child); printf("[*] Press Ctrl-C to stop\n\n"); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); while (!state.stop) { err = ring_buffer__poll(state.rb, 100); if (err < 0 && err != -EINTR) { fprintf(stderr, "Error polling ring buffer: %s\n", strerror(-err)); break; } ret_pid = waitpid(child, &wstatus, WNOHANG); if (ret_pid == child) { if (WIFEXITED(wstatus)) printf( "\n[*] Application exited with status %d\n", WEXITSTATUS(wstatus)); else if (WIFSIGNALED(wstatus)) printf( "\n[*] Application terminated by signal %d\n", WTERMSIG(wstatus)); break; } } printf("[*] Analyzing results...\n"); state.shutting_down = 1; usleep(100000); ring_buffer__poll(state.rb, 0); if (state.json_output) output_json(); else if (state.yaml_output) output_yaml(); else analyze_capabilities(); ring_buffer__free(state.rb); cap_audit_bpf__destroy(state.skel); free(state.app.exe); for (int i = 0; i <= CAP_LAST_CAP; i++) { if (state.app.checks[i].reason) free(state.app.checks[i].reason); if (state.app.checks[i].op_reason) free(state.app.checks[i].op_reason); if (state.app.checks[i].denied_syscalls) free(state.app.checks[i].denied_syscalls); } return 0; } stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit.h000066400000000000000000000070461516575034500233320ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap-audit - Trace a target process to discover required capabilities. * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ #ifndef CAP_AUDIT_H #define CAP_AUDIT_H #include "config.h" #include #include #include #include #include #include #include "cap-ng.h" #include "cap_audit.skel.h" #ifndef CAP_OPT_NOAUDIT #define CAP_OPT_NOAUDIT 0x2 #endif #define ELFMAGIC "\177ELF" typedef enum { UNSUPPORTED, ELF, PYTHON } type_t; struct cap_event { __u64 timestamp_ns; __u32 pid; __u32 tid; __u32 uid; __u32 gid; int capability; int result; int syscall_nr; char comm[16]; __u64 stack_id; __u32 targ_ns_inum; __u32 cap_opts; }; struct cap_check { int capability; unsigned long count; unsigned long granted; unsigned long denied; int needed; char *reason; unsigned long op_count; unsigned long op_granted; unsigned long op_denied; int op_needed; char *op_reason; int *denied_syscalls; size_t denied_syscall_count; size_t denied_syscall_capacity; }; struct app_caps { pid_t pid; char *exe; int execve_nr; int mmap_nr; int brk_nr; int mprotect_nr; int mremap_nr; int capset_nr; type_t prog_type; struct cap_check checks[CAP_LAST_CAP + 1]; int yama_ptrace_scope; int kptr_restrict; int dmesg_restrict; int modules_disabled; int perf_event_paranoid; int unprivileged_bpf_disabled; int bpf_jit_enable; int bpf_jit_harden; int bpf_jit_kallsyms; int mmap_min_addr; int protected_hardlinks; int protected_symlinks; int suid_dumpable; char kernel_version[64]; int file_caps; int file_setpcap; }; struct audit_state { struct cap_audit_bpf *skel; struct ring_buffer *rb; struct app_caps app; int verbose; int json_output; int yaml_output; int sync_pipe[2]; char **target_argv; int capset_observed; volatile sig_atomic_t stop; int shutting_down; }; extern struct audit_state state; extern int audit_machine; int handle_cap_event(void *ctx, void *data, size_t data_sz); void analyze_capabilities(void); void output_json(void); void output_yaml(void); int include_cap_in_recommendations(int cap); const char *cap_name_safe(int cap); const char *syscall_name_from_nr(int nr); void read_sysctl(const char *path, int *value); void read_system_state(struct app_caps *app); int resolve_target_exe(pid_t pid, char *exepath, size_t exepath_len); int inspect_target_file_caps(pid_t pid); char *json_escape(const char *input); void update_reason_to(char **target, int syscall_nr); void update_reason(struct cap_check *check, int syscall_nr); void update_reason_op(struct cap_check *check, int syscall_nr); int cap_required_union(const struct cap_check *check); unsigned long cap_total_checks(const struct cap_check *check); unsigned long cap_total_granted(const struct cap_check *check); unsigned long cap_total_denied(const struct cap_check *check); const char *cap_union_reason(const struct cap_check *check); type_t classify_app(const char *exe); #endif stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit_analyze.c000066400000000000000000000475271516575034500250600ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap-audit - Trace a target process to discover required capabilities. * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ /* Human-readable analysis output: capability summaries, deployment * recommendations, and wrapped terminal formatting helpers. */ #include "cap_audit.h" #include #include #include #include #include #include #include #include int include_cap_in_recommendations(int cap) { if (cap == CAP_SETPCAP && state.app.file_caps && !state.app.file_setpcap) return 0; return 1; } static void cap_name_upper_buf(int cap, char *buf, size_t buf_len) { const char *name = cap_name_safe(cap); size_t i; if (buf_len == 0) return; for (i = 0; name[i] && i + 1 < buf_len; i++) buf[i] = toupper((unsigned char)name[i]); buf[i] = '\0'; } static int get_output_width(void) { struct winsize ws; const char *columns; long env_width; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) return ws.ws_col; columns = getenv("COLUMNS"); if (columns) { env_width = strtol(columns, NULL, 10); if (env_width >= 40 && env_width <= 400) return (int)env_width; } return 80; } static void print_rule(char ch) { int i; int width = get_output_width(); if (width < 40) width = 40; for (i = 0; i < width; i++) putchar(ch); putchar('\n'); } static void print_wrapped_text(const char *indent, const char *text) { size_t indent_len; char *cont_indent; int width; int content_width; const char *p; int line_len = 0; int first_line = 1; if (!text) { printf("%s\n", indent); return; } indent_len = strlen(indent); cont_indent = malloc(indent_len + 1); if (!cont_indent) { printf("%s%s\n", indent, text); return; } memset(cont_indent, ' ', indent_len); cont_indent[indent_len] = '\0'; width = get_output_width(); if (width < 40) width = 40; content_width = width - (int)indent_len; if (content_width < 16) content_width = 16; printf("%s", indent); p = text; while (*p) { size_t word_len; int need_space = line_len > 0; while (*p == ' ') p++; if (*p == '\n') { putchar('\n'); printf("%s", first_line ? cont_indent : cont_indent); line_len = 0; first_line = 0; p++; continue; } if (*p == '\0') break; word_len = strcspn(p, " \n"); if (need_space && line_len + 1 + (int)word_len > content_width) { putchar('\n'); printf("%s", cont_indent); line_len = 0; need_space = 0; first_line = 0; } if (need_space) { putchar(' '); line_len++; } fwrite(p, 1, word_len, stdout); line_len += word_len; p += word_len; } putchar('\n'); free(cont_indent); } static void print_wrappedf(const char *indent, const char *fmt, ...) { va_list ap; char *buf; va_start(ap, fmt); if (vasprintf(&buf, fmt, ap) < 0) buf = NULL; va_end(ap); if (buf) { print_wrapped_text(indent, buf); free(buf); } else { print_wrapped_text(indent, "(formatting error)"); } } static int cap_in_programmatic_set(int cap) { if (!include_cap_in_recommendations(cap)) return 0; if (state.capset_observed) return state.app.checks[cap].op_granted > 0; return state.app.checks[cap].granted > 0; } static void print_updatev_wrapped(const char *prefix, const char *cap_prefix, const char *suffix) { int width = get_output_width(); size_t prefix_len = strlen(prefix); size_t suffix_len = strlen(suffix); int cur_len = prefix_len; int cont_indent = 8; int i; if (width < 40) width = 40; for (i = 0; prefix[i] == ' '; i++) ; if (i > 0) cont_indent = i + 4; printf("%s", prefix); for (i = 0; i <= CAP_LAST_CAP; i++) { char cap_name[64]; char item[96]; size_t item_len; if (!cap_in_programmatic_set(i)) continue; cap_name_upper_buf(i, cap_name, sizeof(cap_name)); snprintf(item, sizeof(item), "%s%s", cap_prefix, cap_name); item_len = strlen(item); if (cur_len > (int)prefix_len && cur_len + 2 + (int)item_len + (int)suffix_len > width) { printf(",\n%*s%s", cont_indent, "", item); cur_len = cont_indent + item_len; } else { printf(", %s", item); cur_len += 2 + item_len; } } if (cur_len + 2 + (int)suffix_len > width && cur_len > cont_indent) { printf(",\n%*s%s", cont_indent, "", suffix); } else { printf(", %s", suffix); } } void analyze_capabilities(void) { int has_required = 0; int has_conditional = 0; int has_denied = 0; int required_count = 0; int conditional_count = 0; int denied_count = 0; unsigned long total_checks = 0; int i; int first; printf("\n"); print_rule('='); print_wrappedf("", "CAPABILITY ANALYSIS FOR: %s (PID %d)", state.app.exe, state.app.pid); print_rule('='); printf("\n"); printf("SYSTEM CONTEXT:\n"); print_rule('-'); printf(" Kernel version: %s\n", state.app.kernel_version); printf(" kernel.yama.ptrace_scope: %d\n", state.app.yama_ptrace_scope); printf(" kernel.kptr_restrict: %d\n", state.app.kptr_restrict); printf(" kernel.dmesg_restrict: %d\n", state.app.dmesg_restrict); printf(" kernel.modules_disabled: %d\n", state.app.modules_disabled); printf(" kernel.perf_event_paranoid: %d\n", state.app.perf_event_paranoid); printf(" kernel.unprivileged_bpf_disabled: %d\n", state.app.unprivileged_bpf_disabled); printf(" net.core.bpf_jit_enable: %d\n", state.app.bpf_jit_enable); printf(" net.core.bpf_jit_harden: %d\n", state.app.bpf_jit_harden); printf(" net.core.bpf_jit_kallsyms: %d\n", state.app.bpf_jit_kallsyms); printf(" vm.mmap_min_addr: %d\n", state.app.mmap_min_addr); printf(" fs.protected_hardlinks: %d\n", state.app.protected_hardlinks); printf(" fs.protected_symlinks: %d\n", state.app.protected_symlinks); printf(" fs.suid_dumpable: %d\n", state.app.suid_dumpable); printf("\n"); printf("REQUIRED CAPABILITIES:\n"); print_rule('-'); if (!state.capset_observed) { for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check; check = &state.app.checks[i]; if (check->granted > 0) { has_required = 1; printf(" %s (#%d)\n", cap_name_safe(i), i); printf(" Checks: %lu granted, %lu denied\n", check->granted, check->denied); if (check->reason) print_wrappedf(" Reason: ", "%s", check->reason); if (!include_cap_in_recommendations(i)) print_wrapped_text(" Note: ", "Internal to capability setup; excluded from recommendations."); printf("\n"); } } if (!has_required) print_wrapped_text(" ", "None - Application does not require elevated capabilities!\n"); } else { print_wrapped_text("", "INITIALIZATION CAPABILITIES (before capability drop):"); print_rule('-'); for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; if (check->granted > 0) { has_required = 1; printf(" %s (#%d)\n", cap_name_safe(i), i); printf(" Checks: %lu granted, %lu denied\n", check->granted, check->denied); if (check->reason) print_wrappedf(" Reason: ", "%s", check->reason); if (!include_cap_in_recommendations(i)) print_wrapped_text(" Note: ", "Internal to capability setup; excluded from recommendations."); printf("\n"); } } if (!has_required) printf(" None\n\n"); print_wrapped_text("", "OPERATIONAL CAPABILITIES (after capability drop):"); print_rule('-'); has_required = 0; for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; if (check->op_granted > 0) { has_required = 1; printf(" %s (#%d)\n", cap_name_safe(i), i); printf(" Checks: %lu granted, %lu denied\n", check->op_granted, check->op_denied); if (check->op_reason) print_wrappedf(" Reason: ", "%s", check->op_reason); if (!include_cap_in_recommendations(i)) print_wrapped_text(" Note: ", "Internal to capability setup; excluded from recommendations."); printf("\n"); } } if (!has_required) printf(" None\n\n"); } printf("CONDITIONAL CAPABILITIES:\n"); print_rule('-'); if (state.app.yama_ptrace_scope > 0) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && state.app.checks[i].granted == 0 && i == CAP_SYS_PTRACE) { has_conditional = 1; conditional_count++; printf(" CAP_SYS_PTRACE\n"); print_wrapped_text(" ", "Needed when kernel.yama.ptrace_scope > 0"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.yama_ptrace_scope); printf("\n"); } } } if (state.app.perf_event_paranoid >= 2) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_PERFMON) { has_conditional = 1; conditional_count++; printf(" CAP_PERFMON\n"); print_wrapped_text(" ", "Needed when kernel.perf_event_paranoid >= 2"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.perf_event_paranoid); print_wrapped_text(" Note: ", "CAP_SYS_ADMIN can substitute on kernels < 5.8"); printf("\n"); } } } if (state.app.unprivileged_bpf_disabled == 1) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_BPF) { has_conditional = 1; conditional_count++; printf(" CAP_BPF\n"); print_wrapped_text(" ", "Needed when kernel.unprivileged_bpf_disabled = 1"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.unprivileged_bpf_disabled); print_wrapped_text(" Note: ", "CAP_SYS_ADMIN can substitute on kernels < 5.8"); printf("\n"); } } } if (state.app.kptr_restrict >= 1) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_SYSLOG) { has_conditional = 1; conditional_count++; printf(" CAP_SYSLOG\n"); print_wrapped_text(" ", "Needed when kernel.kptr_restrict >= 1"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.kptr_restrict); printf("\n"); } } } if (state.app.dmesg_restrict >= 1) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_SYSLOG) { has_conditional = 1; conditional_count++; printf(" CAP_SYSLOG\n"); print_wrapped_text(" ", "Needed when kernel.dmesg_restrict >= 1"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.dmesg_restrict); printf("\n"); } } } if (state.app.modules_disabled == 1) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_SYS_MODULE) { has_conditional = 1; conditional_count++; printf(" NOTE: kernel.modules_disabled = 1\n"); print_wrapped_text(" ", "CAP_SYS_MODULE is ineffective!"); print_wrapped_text(" ", "Module loading is permanently disabled."); printf("\n"); } } } if (state.app.mmap_min_addr > 0) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_SYS_RAWIO) { has_conditional = 1; conditional_count++; printf(" CAP_SYS_RAWIO\n"); print_wrapped_text(" ", "Needed when vm.mmap_min_addr > 0 to map low addresses"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.mmap_min_addr); printf("\n"); } } } if (state.app.protected_hardlinks == 1) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_FOWNER) { has_conditional = 1; conditional_count++; printf(" CAP_FOWNER\n"); print_wrapped_text(" ", "Needed when fs.protected_hardlinks = 1 to link files not owned by the caller"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.protected_hardlinks); printf("\n"); } } } if (state.app.protected_symlinks == 1) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_DAC_OVERRIDE) { has_conditional = 1; conditional_count++; printf(" CAP_DAC_OVERRIDE\n"); print_wrapped_text(" ", "Needed when fs.protected_symlinks = 1 for symlinks in world-writable directories"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.protected_symlinks); printf("\n"); } } } if (state.app.suid_dumpable == 2) { for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_total_checks(&state.app.checks[i]) > 0 && i == CAP_SYS_PTRACE) { has_conditional = 1; conditional_count++; printf(" CAP_SYS_PTRACE\n"); print_wrapped_text(" ", "Needed when fs.suid_dumpable = 2 for core dumps and ptrace of setuid programs"); print_wrappedf(" ", "Current value: %d (capability needed)", state.app.suid_dumpable); printf("\n"); } } } if (!has_conditional) printf(" None\n\n"); printf("ATTEMPTED BUT DENIED:\n"); print_rule('-'); for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check; check = &state.app.checks[i]; if (cap_total_denied(check) > 0 && cap_total_granted(check) == 0) { size_t j; has_denied = 1; printf(" %s (#%d)\n", cap_name_safe(i), i); printf(" Attempts: %lu (all denied)\n", cap_total_denied(check)); printf(" Syscalls: "); if (check->denied_syscall_count == 0) printf("unknown\n"); for (j = 0; j < check->denied_syscall_count; j++) { const char *syscall_name; int syscall_nr; syscall_nr = check->denied_syscalls[j]; syscall_name = syscall_name_from_nr(syscall_nr); if (j > 0) printf(", "); if (syscall_name) printf("%s", syscall_name); else printf("unknown(#%d)", syscall_nr); } if (check->denied_syscall_count > 0) printf("\n"); print_wrapped_text(" Impact: ", "Application may have reduced functionality"); printf("\n"); } } if (!has_denied) printf(" None\n\n"); for (i = 0; i <= CAP_LAST_CAP; i++) { total_checks += cap_total_checks(&state.app.checks[i]); if (cap_required_union(&state.app.checks[i])) required_count++; if (cap_total_denied(&state.app.checks[i]) > 0 && cap_total_granted(&state.app.checks[i]) == 0) denied_count++; } printf("SUMMARY:\n"); print_rule('-'); printf(" Total capability checks: %lu\n", total_checks); printf(" Required capabilities: %d\n", required_count); printf(" Conditional capabilities: %d\n", conditional_count); printf(" Denied operations: %d\n", denied_count); printf("\n"); if (required_count > 0) { printf("RECOMMENDATIONS:\n"); print_rule('-'); if (state.app.prog_type != UNSUPPORTED) { printf(" Programmatic solution (%s):\n", state.app.prog_type == ELF ? "C with libcap-ng" : "Python with python3-libcap-ng"); if (state.capset_observed) print_wrapped_text(" Note: ", "The application drops capabilities after initialization. The programmatic snippet reflects the operational set only."); if (state.app.prog_type == ELF) { printf(" #include \n"); printf(" ...\n"); printf(" capng_clear(CAPNG_SELECT_BOTH);\n"); print_updatev_wrapped(" capng_updatev(CAPNG_ADD, " "CAPNG_EFFECTIVE|CAPNG_PERMITTED", "", "-1);\n"); printf(" if (capng_change_id(uid, gid, " "CAPNG_DROP_SUPP_GRP | " "CAPNG_CLEAR_BOUNDING))\n"); printf("\tperror(\"capng_change_id\");\n\n"); } else if (state.app.prog_type == PYTHON) { printf(" import sys\n"); printf(" import _capng as capng\n"); printf(" ...\n"); printf(" capng.capng_clear(capng.CAPNG_SELECT_BOTH)\n"); print_updatev_wrapped(" capng.capng_updatev(" "capng.CAPNG_ADD, " "capng.CAPNG_EFFECTIVE|" "capng.CAPNG_PERMITTED", "capng.", "-1)\n"); printf(" e = capng.capng_change_id(uid, gid, " "capng.CAPNG_DROP_SUPP_GRP | " "capng.CAPNG_CLEAR_BOUNDING)\n"); printf(" if e < 0:\n"); printf("\tprint(f\"Error: {e}\")\n"); printf("\tsys.exit(1)\n\n"); } } printf(" For systemd service:\n"); if (state.capset_observed) print_wrapped_text(" Note: ", "Ambient capabilities must include initialization requirements. The application drops to the operational set internally via capset."); printf(" [Service]\n"); printf(" User=\n"); printf(" Group=\n"); printf(" AmbientCapabilities="); first = 1; for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_required_union(&state.app.checks[i]) && include_cap_in_recommendations(i)) { if (!first) printf(" "); printf("%s", cap_name_safe(i)); first = 0; } } printf("\n"); printf(" CapabilityBoundingSet="); first = 1; for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_required_union(&state.app.checks[i]) && include_cap_in_recommendations(i)) { if (!first) printf(" "); printf("%s", cap_name_safe(i)); first = 0; } } printf("\n\n"); printf(" For file capabilities (via filecap):\n"); if (state.capset_observed) print_wrapped_text(" Note: ", "File capabilities must include initialization requirements. The application drops to the operational set internally via capset."); printf(" filecap /path/to/binary"); for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_required_union(&state.app.checks[i]) && include_cap_in_recommendations(i)) printf(" %s", cap_name_safe(i)); } printf("\n\n"); printf(" For Docker/Podman:\n"); if (state.capset_observed) print_wrapped_text(" Note: ", "Container capabilities must include initialization requirements. The application drops to the operational set internally via capset."); printf(" docker run --user $(id -u):$(id -g) \\\n"); printf(" --cap-drop=ALL \\\n"); for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_required_union(&state.app.checks[i]) && include_cap_in_recommendations(i)) printf(" --cap-add=%s \\\n", cap_name_safe(i)); } printf(" your-image:tag\n\n"); printf(" For Kubernetes:\n"); if (state.capset_observed) print_wrapped_text(" Note: ", "Container capabilities must include initialization requirements. The application drops to the operational set internally via capset."); printf(" securityContext:\n"); printf(" runAsUser: 1000\n"); printf(" runAsGroup: 1000\n"); printf(" capabilities:\n"); printf(" drop:\n"); printf(" - ALL\n"); printf(" add:\n"); for (i = 0; i <= CAP_LAST_CAP; i++) { if (cap_required_union(&state.app.checks[i]) && include_cap_in_recommendations(i)) printf(" - %s\n", cap_name_safe(i)); } printf("\n"); } else { printf("RECOMMENDATIONS:\n"); print_rule('-'); print_wrapped_text(" ", "This application does not require any elevated capabilities!"); print_wrapped_text(" ", "Run as an unprivileged user with no special capabilities."); printf("\n"); } print_wrapped_text("EXPERIMENTAL NOTICE: ", "cap-audit output is experimental, but very close."); } stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit_event.c000066400000000000000000000110451516575034500245200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap-audit - Trace a target process to discover required capabilities. * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ /* Ring buffer event processing: noise filtering, shutdown backstop, * and per-capability accounting for observed checks. */ #include "cap_audit.h" #include #include static int is_always_noise(const struct cap_event *e) { if (e->syscall_nr == state.app.execve_nr && (e->capability == CAP_SYS_ADMIN || e->capability == CAP_SETPCAP)) return 1; if ((e->cap_opts & CAP_OPT_NOAUDIT) && e->capability == CAP_SYS_ADMIN && (e->syscall_nr == state.app.brk_nr || e->syscall_nr == state.app.mmap_nr || e->syscall_nr == state.app.mprotect_nr || e->syscall_nr == state.app.mremap_nr)) return 1; return 0; } static int is_shutdown_noise(const struct cap_event *e) { if (!state.shutting_down) return 0; if (e->pid != (__u32)state.app.pid) return 0; if (e->capability == CAP_SYS_ADMIN || e->capability == CAP_SETPCAP) return 1; return 0; } static void add_denied_syscall(struct cap_check *check, int syscall_nr) { size_t i; int *tmp; size_t new_cap; for (i = 0; i < check->denied_syscall_count; i++) { if (check->denied_syscalls[i] == syscall_nr) return; } if (check->denied_syscall_count == check->denied_syscall_capacity) { new_cap = check->denied_syscall_capacity ? check->denied_syscall_capacity * 2 : 4; tmp = realloc(check->denied_syscalls, new_cap * sizeof(int)); if (!tmp) return; check->denied_syscalls = tmp; check->denied_syscall_capacity = new_cap; } check->denied_syscalls[check->denied_syscall_count++] = syscall_nr; } int handle_cap_event(void *ctx __attribute__((unused)), void *data, size_t data_sz __attribute__((unused))) { const struct cap_event *e = data; int op_phase = state.capset_observed; if (is_always_noise(e)) { if (state.verbose) { if (e->syscall_nr == state.app.execve_nr) printf("[CAP] Filtered exec noise: " "cap=%s syscall=%s\n", cap_name_safe(e->capability), syscall_name_from_nr(e->syscall_nr) ?: "unknown"); else printf("[CAP] Filtered advisory check: " "cap=%s syscall=%s (CAP_OPT_NOAUDIT)\n", cap_name_safe(e->capability), syscall_name_from_nr(e->syscall_nr) ?: "unknown"); } return 0; } if (is_shutdown_noise(e)) { if (state.verbose) printf("[CAP] Filtered shutdown noise: " "cap=%s syscall=%s\n", cap_name_safe(e->capability), syscall_name_from_nr(e->syscall_nr) ?: "unknown"); return 0; } if (state.verbose) { printf("[CAP] pid=%d cap=%s result=%s syscall=%s " "comm=%s\n", e->pid, cap_name_safe(e->capability), e->result ? "GRANTED" : "DENIED", syscall_name_from_nr(e->syscall_nr) ?: "unknown", e->comm); } if (!state.capset_observed && e->syscall_nr == state.app.capset_nr && e->pid == (__u32)state.app.pid) { state.capset_observed = 1; if (state.verbose) printf("[CAP] Capability drop detected (capset from " "initial PID); switching to operational " "phase\n"); } if (e->capability >= 0 && e->capability <= CAP_LAST_CAP) { struct cap_check *check; check = &state.app.checks[e->capability]; check->capability = e->capability; if (op_phase) { check->op_count++; if (e->result > 0) check->op_granted++; else if (e->result == 0) { check->op_denied++; add_denied_syscall(check, e->syscall_nr); } if (e->result > 0 && check->op_needed != 1) { check->op_needed = 1; update_reason_op(check, e->syscall_nr); } } else { check->count++; if (e->result > 0) check->granted++; else if (e->result == 0) { check->denied++; add_denied_syscall(check, e->syscall_nr); } if (e->result > 0 && check->needed != 1) { check->needed = 1; update_reason(check, e->syscall_nr); } } } return 0; } stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit_json.c000066400000000000000000000145351516575034500243570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap-audit - Trace a target process to discover required capabilities. * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ /* JSON output serialization: machine-readable capability summaries for * automated tooling and regression comparison. */ #include "cap_audit.h" #include #include void output_json(void) { int i; int first_cap; int first_denied; char *exe_json; char *kernel_json; exe_json = json_escape(state.app.exe); kernel_json = json_escape(state.app.kernel_version); printf("{\n"); printf(" \"application\": {\n"); printf(" \"pid\": %d,\n", state.app.pid); printf(" \"comm\": \"%s\"\n", exe_json ? exe_json : ""); printf(" },\n"); printf(" \"system_context\": {\n"); printf(" \"kernel_version\": \"%s\",\n", kernel_json ? kernel_json : ""); printf(" \"yama_ptrace_scope\": %d,\n", state.app.yama_ptrace_scope); printf(" \"kptr_restrict\": %d,\n", state.app.kptr_restrict); printf(" \"dmesg_restrict\": %d,\n", state.app.dmesg_restrict); printf(" \"modules_disabled\": %d,\n", state.app.modules_disabled); printf(" \"perf_event_paranoid\": %d,\n", state.app.perf_event_paranoid); printf(" \"unprivileged_bpf_disabled\": %d,\n", state.app.unprivileged_bpf_disabled); printf(" \"bpf_jit_enable\": %d,\n", state.app.bpf_jit_enable); printf(" \"bpf_jit_harden\": %d,\n", state.app.bpf_jit_harden); printf(" \"bpf_jit_kallsyms\": %d,\n", state.app.bpf_jit_kallsyms); printf(" \"vm_mmap_min_addr\": %d,\n", state.app.mmap_min_addr); printf(" \"fs_protected_hardlinks\": %d,\n", state.app.protected_hardlinks); printf(" \"fs_protected_symlinks\": %d,\n", state.app.protected_symlinks); printf(" \"fs_suid_dumpable\": %d\n", state.app.suid_dumpable); printf(" },\n"); free(exe_json); free(kernel_json); printf(" \"capability_drop_observed\": %s,\n", state.capset_observed ? "true" : "false"); printf(" \"required_capabilities\": [\n"); first_cap = 1; for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; char *name_json; char *reason_json; if (cap_required_union(check)) { name_json = json_escape(capng_capability_to_name(i)); reason_json = cap_union_reason(check) ? json_escape(cap_union_reason(check)) : NULL; if (!first_cap) printf(",\n"); printf(" {\n"); printf(" \"number\": %d,\n", i); printf(" \"name\": \"%s\",\n", name_json ? name_json : ""); printf(" \"checks\": {\n"); printf(" \"total\": %lu,\n", cap_total_checks(check)); printf(" \"granted\": %lu,\n", cap_total_granted(check)); printf(" \"denied\": %lu\n", cap_total_denied(check)); printf(" }"); if (cap_union_reason(check)) printf(",\n \"reason\": \"%s\"\n", reason_json ? reason_json : ""); else printf("\n"); printf(" }"); first_cap = 0; free(name_json); free(reason_json); } } printf("\n ]"); if (state.capset_observed) { printf(",\n \"initialization_capabilities\": [\n"); first_cap = 1; for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; char *name_json; char *reason_json; if (check->granted == 0) continue; name_json = json_escape(capng_capability_to_name(i)); reason_json = check->reason ? json_escape(check->reason) : NULL; if (!first_cap) printf(",\n"); printf(" {\n"); printf(" \"number\": %d,\n", i); printf(" \"name\": \"%s\",\n", name_json ? name_json : ""); printf(" \"checks\": {\n"); printf(" \"total\": %lu,\n", check->count); printf(" \"granted\": %lu,\n", check->granted); printf(" \"denied\": %lu\n", check->denied); printf(" }"); if (check->reason) printf(",\n \"reason\": \"%s\"\n", reason_json ? reason_json : ""); else printf("\n"); printf(" }"); first_cap = 0; free(name_json); free(reason_json); } printf("\n ],\n"); printf(" \"operational_capabilities\": [\n"); first_cap = 1; for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; char *name_json; char *reason_json; if (check->op_granted == 0) continue; name_json = json_escape(capng_capability_to_name(i)); reason_json = check->op_reason ? json_escape(check->op_reason) : NULL; if (!first_cap) printf(",\n"); printf(" {\n"); printf(" \"number\": %d,\n", i); printf(" \"name\": \"%s\",\n", name_json ? name_json : ""); printf(" \"checks\": {\n"); printf(" \"total\": %lu,\n", check->op_count); printf(" \"granted\": %lu,\n", check->op_granted); printf(" \"denied\": %lu\n", check->op_denied); printf(" }"); if (check->op_reason) printf(",\n \"reason\": \"%s\"\n", reason_json ? reason_json : ""); else printf("\n"); printf(" }"); first_cap = 0; free(name_json); free(reason_json); } printf("\n ]"); } printf(",\n"); printf(" \"denied_capabilities\": [\n"); first_denied = 1; for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; char *name_json; if (cap_total_denied(check) > 0 && cap_total_granted(check) == 0) { name_json = json_escape(capng_capability_to_name(i)); if (!first_denied) printf(",\n"); printf(" {\n"); printf(" \"number\": %d,\n", i); printf(" \"name\": \"%s\",\n", name_json ? name_json : ""); printf(" \"attempts\": %lu\n", cap_total_denied(check)); printf(" }"); first_denied = 0; free(name_json); } } printf("\n ]\n"); printf("}\n"); } stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit_util.c000066400000000000000000000177031516575034500243630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap-audit - Trace a target process to discover required capabilities. * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ /* Shared utility helpers: target inspection, syscall/cap name lookup, * output escaping, and common capability aggregation functions. */ #include "cap_audit.h" #include #include #include #include #include #include #include #include #include int resolve_target_exe(pid_t pid, char *exepath, size_t exepath_len) { char linkpath[64]; char selfpath[PATH_MAX]; ssize_t len; ssize_t self_len; int tries = 50; if (snprintf(linkpath, sizeof(linkpath), "/proc/%d/exe", pid) < 0) return -1; self_len = readlink("/proc/self/exe", selfpath, sizeof(selfpath) - 1); if (self_len >= 0) selfpath[self_len] = '\0'; while (tries--) { len = readlink(linkpath, exepath, exepath_len - 1); if (len < 0) { fprintf(stderr, "Warning: readlink(%s) failed: %s\n", linkpath, strerror(errno)); return -1; } exepath[len] = '\0'; if (self_len < 0 || strcmp(exepath, selfpath) != 0) break; if (tries == 0) fprintf(stderr, "Warning: %s still points to auditor binary (%s)\n", linkpath, exepath); usleep(10000); } return 0; } int inspect_target_file_caps(pid_t pid) { char exepath[PATH_MAX]; int fd; struct stat st; capng_results_t caps; state.app.file_caps = 0; state.app.file_setpcap = 0; if (resolve_target_exe(pid, exepath, sizeof(exepath)) < 0) return -1; fd = open(exepath, O_RDONLY | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "Warning: open(%s) failed: %s\n", exepath, strerror(errno)); return -1; } if (fstat(fd, &st) < 0) { fprintf(stderr, "Warning: fstat(%s) failed: %s\n", exepath, strerror(errno)); close(fd); return -1; } if (!S_ISREG(st.st_mode)) { fprintf(stderr, "Warning: %s is not a regular file\n", exepath); close(fd); return -1; } capng_clear(CAPNG_SELECT_BOTH); if (capng_get_caps_fd(fd)) { if (errno != ENODATA) fprintf(stderr, "Warning: capng_get_caps_fd(%s) failed: %s\n", exepath, strerror(errno)); close(fd); if (capng_get_caps_process()) fprintf(stderr, "Warning: failed to restore process capabilities\n"); return -1; } close(fd); caps = capng_have_capabilities(CAPNG_SELECT_CAPS); if (caps == CAPNG_NONE) caps = capng_have_permitted_capabilities(); if (caps > CAPNG_NONE) state.app.file_caps = 1; if (capng_have_capability(CAPNG_PERMITTED, CAP_SETPCAP) || capng_have_capability(CAPNG_INHERITABLE, CAP_SETPCAP)) state.app.file_setpcap = 1; if (state.verbose) printf("[*] File caps source: %s (has_caps=%d setpcap=%d)\n", exepath, state.app.file_caps, state.app.file_setpcap); if (capng_get_caps_process()) fprintf(stderr, "Warning: failed to restore process capabilities\n"); return 0; } const char *cap_name_safe(int cap) { const char *name = capng_capability_to_name(cap); return name ? name : "unknown"; } void read_sysctl(const char *path, int *value) { FILE *f; f = fopen(path, "r"); if (f) { if (fscanf(f, "%d", value) != 1) *value = -1; fclose(f); } else { *value = -1; } } void read_system_state(struct app_caps *app) { FILE *f; read_sysctl("/proc/sys/kernel/yama/ptrace_scope", &app->yama_ptrace_scope); read_sysctl("/proc/sys/kernel/kptr_restrict", &app->kptr_restrict); read_sysctl("/proc/sys/kernel/dmesg_restrict", &app->dmesg_restrict); read_sysctl("/proc/sys/kernel/modules_disabled", &app->modules_disabled); read_sysctl("/proc/sys/kernel/perf_event_paranoid", &app->perf_event_paranoid); read_sysctl("/proc/sys/kernel/unprivileged_bpf_disabled", &app->unprivileged_bpf_disabled); read_sysctl("/proc/sys/net/core/bpf_jit_enable", &app->bpf_jit_enable); read_sysctl("/proc/sys/net/core/bpf_jit_harden", &app->bpf_jit_harden); read_sysctl("/proc/sys/net/core/bpf_jit_kallsyms", &app->bpf_jit_kallsyms); read_sysctl("/proc/sys/vm/mmap_min_addr", &app->mmap_min_addr); read_sysctl("/proc/sys/fs/protected_hardlinks", &app->protected_hardlinks); read_sysctl("/proc/sys/fs/protected_symlinks", &app->protected_symlinks); read_sysctl("/proc/sys/fs/suid_dumpable", &app->suid_dumpable); f = fopen("/proc/sys/kernel/osrelease", "r"); if (f) { if (!fgets(app->kernel_version, sizeof(app->kernel_version), f)) app->kernel_version[0] = 0; app->kernel_version[strcspn(app->kernel_version, "\n")] = 0; fclose(f); } } const char *syscall_name_from_nr(int nr) { if (audit_machine < 0) return NULL; return audit_syscall_to_name(nr, audit_machine); } char *json_escape(const char *input) { size_t i; size_t needed = 0; char *out; char *pos; if (!input) return strdup(""); for (i = 0; input[i]; i++) { unsigned char c = input[i]; switch (c) { case '\"': case '\\': case '\b': case '\f': case '\n': case '\r': case '\t': needed += 2; break; default: if (c < 0x20) needed += 6; else needed++; break; } } out = malloc(needed + 1); if (!out) return NULL; pos = out; for (i = 0; input[i]; i++) { unsigned char c = input[i]; switch (c) { case '\"': *pos++ = '\\'; *pos++ = '\"'; break; case '\\': *pos++ = '\\'; *pos++ = '\\'; break; case '\b': *pos++ = '\\'; *pos++ = 'b'; break; case '\f': *pos++ = '\\'; *pos++ = 'f'; break; case '\n': *pos++ = '\\'; *pos++ = 'n'; break; case '\r': *pos++ = '\\'; *pos++ = 'r'; break; case '\t': *pos++ = '\\'; *pos++ = 't'; break; default: if (c < 0x20) { snprintf(pos, 7, "\\u%04x", c); pos += 6; } else { *pos++ = c; } break; } } *pos = '\0'; return out; } void update_reason_to(char **target, int syscall_nr) { const char *syscall_name; if (*target) free(*target); if (syscall_nr < 0) { if (asprintf(target, "Used during capability check (syscall unknown)") < 0) *target = NULL; return; } syscall_name = syscall_name_from_nr(syscall_nr); if (asprintf(target, "Used by %s", syscall_name ? syscall_name : "unknown") < 0) *target = NULL; } void update_reason(struct cap_check *check, int syscall_nr) { update_reason_to(&check->reason, syscall_nr); } void update_reason_op(struct cap_check *check, int syscall_nr) { update_reason_to(&check->op_reason, syscall_nr); } int cap_required_union(const struct cap_check *check) { return check->granted > 0 || check->op_granted > 0; } unsigned long cap_total_checks(const struct cap_check *check) { return check->count + check->op_count; } unsigned long cap_total_granted(const struct cap_check *check) { return check->granted + check->op_granted; } unsigned long cap_total_denied(const struct cap_check *check) { return check->denied + check->op_denied; } const char *cap_union_reason(const struct cap_check *check) { if (check->reason) return check->reason; return check->op_reason; } type_t classify_app(const char *exe) { int fd; ssize_t rc; char buf[257]; fd = open(exe, O_RDONLY); if (fd < 0) { fprintf(stderr, "Cannot open %s - %s\n", exe, strerror(errno)); exit(1); } rc = read(fd, buf, 256); close(fd); if (rc > 0) { buf[rc] = 0; if (buf[0] == '#' && buf[1] == '!') { char *ptr = strchr(buf, '\n'); if (ptr) *ptr = 0; if (strstr(buf, "python")) return PYTHON; } else if (strncmp(buf, ELFMAGIC, 4) == 0) return ELF; } return UNSUPPORTED; } stevegrubb-libcap-ng-0ab44af/utils/cap-audit/cap_audit_yaml.c000066400000000000000000000110501516575034500243350ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.1-or-later /* * cap-audit - Trace a target process to discover required capabilities. * Copyright (c) 2026 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Authors: * Steve Grubb * Portions of this code were made with codex 5.2 */ /* YAML output serialization: alternate machine-readable output mirroring * the JSON structure for downstream consumers. */ #include "cap_audit.h" #include #include void output_yaml(void) { int i; char *exe_yaml = json_escape(state.app.exe); char *kernel_yaml = json_escape(state.app.kernel_version); printf("application:\n"); printf(" pid: %d\n", state.app.pid); printf(" comm: \"%s\"\n", exe_yaml ? exe_yaml : ""); printf("system_context:\n"); printf(" kernel_version: \"%s\"\n", kernel_yaml ? kernel_yaml : ""); printf(" yama_ptrace_scope: %d\n", state.app.yama_ptrace_scope); printf(" kptr_restrict: %d\n", state.app.kptr_restrict); printf(" dmesg_restrict: %d\n", state.app.dmesg_restrict); printf(" modules_disabled: %d\n", state.app.modules_disabled); printf(" perf_event_paranoid: %d\n", state.app.perf_event_paranoid); printf(" unprivileged_bpf_disabled: %d\n", state.app.unprivileged_bpf_disabled); printf(" bpf_jit_enable: %d\n", state.app.bpf_jit_enable); printf(" bpf_jit_harden: %d\n", state.app.bpf_jit_harden); printf(" bpf_jit_kallsyms: %d\n", state.app.bpf_jit_kallsyms); printf(" vm_mmap_min_addr: %d\n", state.app.mmap_min_addr); printf(" fs_protected_hardlinks: %d\n", state.app.protected_hardlinks); printf(" fs_protected_symlinks: %d\n", state.app.protected_symlinks); printf(" fs_suid_dumpable: %d\n", state.app.suid_dumpable); free(exe_yaml); free(kernel_yaml); printf("capability_drop_observed: %s\n", state.capset_observed ? "true" : "false"); printf("required_capabilities:\n"); for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; if (cap_required_union(check)) { printf(" - number: %d\n", i); printf(" name: %s\n", cap_name_safe(i)); printf(" checks:\n"); printf(" total: %lu\n", cap_total_checks(check)); printf(" granted: %lu\n", cap_total_granted(check)); printf(" denied: %lu\n", cap_total_denied(check)); if (cap_union_reason(check)) { char *reason_yaml = json_escape(cap_union_reason(check)); printf(" reason: \"%s\"\n", reason_yaml ? reason_yaml : ""); free(reason_yaml); } } } if (state.capset_observed) { printf("initialization_capabilities:\n"); for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; if (check->granted == 0) continue; printf(" - number: %d\n", i); printf(" name: %s\n", cap_name_safe(i)); printf(" checks:\n"); printf(" total: %lu\n", check->count); printf(" granted: %lu\n", check->granted); printf(" denied: %lu\n", check->denied); if (check->reason) { char *reason_yaml = json_escape(check->reason); printf(" reason: \"%s\"\n", reason_yaml ? reason_yaml : ""); free(reason_yaml); } } printf("operational_capabilities:\n"); for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; if (check->op_granted == 0) continue; printf(" - number: %d\n", i); printf(" name: %s\n", cap_name_safe(i)); printf(" checks:\n"); printf(" total: %lu\n", check->op_count); printf(" granted: %lu\n", check->op_granted); printf(" denied: %lu\n", check->op_denied); if (check->op_reason) { char *reason_yaml = json_escape(check->op_reason); printf(" reason: \"%s\"\n", reason_yaml ? reason_yaml : ""); free(reason_yaml); } } } printf("denied_capabilities:\n"); for (i = 0; i <= CAP_LAST_CAP; i++) { struct cap_check *check = &state.app.checks[i]; if (cap_total_denied(check) > 0 && cap_total_granted(check) == 0) { printf(" - number: %d\n", i); printf(" name: %s\n", cap_name_safe(i)); printf(" attempts: %lu\n", cap_total_denied(check)); } } } stevegrubb-libcap-ng-0ab44af/utils/captest.8000066400000000000000000000040321516575034500211050ustar00rootroot00000000000000.TH CAPTEST: "8" "March 2026" "Red Hat" "System Administration Utilities" .SH NAME captest \- a program to demonstrate capabilities .SH SYNOPSIS .B captest [ \-\-ambient \-\-drop-all | \-\-drop-caps | \-\-id ] [ \-\-init-grp ] [ \-\-lock ] [ \-\-text ] .SH DESCRIPTION \fBcaptest\fP is a program that demonstrates and prints out the current process capabilities. Each option prints the same report. It will output current capabilities. then it will try to access /etc/shadow directly to show if that can be done. Then it creates a child process that attempts to read /etc/shadow and outputs the results of that. Then it outputs the capabilities that a child process would have. You can also apply file system capabilities to this program to study how they work. For example, filecap /usr/bin/captest chown. Then run captest as a normal user. Another interesting test is to make captest suid root so that you can see what the interaction is between root's credentials and capabilities. For example, chmod 4755 /usr/bin/captest. When run as a normal user, the program will see if privilege escalation is possible. But do not leave this app setuid root after you are done testing so that an attacker cannot take advantage of it. .SH OPTIONS .TP .B \-\-ambient This attempts to add CAP_CHOWN ambient capability. .TP .B \-\-drop-all This drops all capabilities including ambient and clears the bounding set. .TP .B \-\-drop-caps This drops just traditional capabilities. .TP .B \-\-id This changes to uid and gid 99, drops supplemental groups, and clears the bounding set. .TP .B \-\-init-grp This changes to uid and gid 99 and then adds any supplemental groups that comes with that account. You would have to add them prior to testing because by default there are no supplemental groups on account 99. .TP .B \-\-text This option outputs the effective capabilities in text rather than numerically. .TP .B \-\-lock This prevents the ability for child processes to regain privileges if the uid is 0. .SH "SEE ALSO" .BR filecap (8), .BR capabilities (7) .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/utils/captest.c000066400000000000000000000173661516575034500211760ustar00rootroot00000000000000/* * captest.c - A program that demonstrates and outputs capabilities * Copyright (c) 2009, 2013, 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_LINUX_SECUREBITS_H #include #endif /* children can't get caps back */ #ifndef SECURE_NOROOT #define SECURE_NOROOT 0 #endif #ifndef SECURE_NOROOT_LOCKED #define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ #endif /* Setuid apps run by uid 0 don't get caps back */ #ifndef SECURE_NO_SETUID_FIXUP #define SECURE_NO_SETUID_FIXUP 2 #endif #ifndef SECURE_NO_SETUID_FIXUP_LOCKED #define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ #endif static int text = 0, no_child = 0, lock = 0, ambient = 0; static void report(void) { int rc, escalated = 0, need_comma = 0; uid_t uid, euid, suid; gid_t gid, egid, sgid; // Refresh what we have for capabilities if (capng_get_caps_process()) { printf("Error getting capabilities\n"); exit(1); } // Check user credentials getresuid(&uid, &euid, &suid); getresgid(&gid, &egid, &sgid); if (no_child) { if ((uid != euid && uid != 0) || capng_have_capability(CAPNG_EFFECTIVE, CAP_SETUID)) { printf("Attempting to regain root..."); setuid(0); getresuid(&uid, &euid, &suid); if (uid == 0) { printf("SUCCESS - PRIVILEGE ESCALATION POSSIBLE\n"); setgid(0); getresgid(&gid, &egid, &sgid); escalated = 1; } else printf("FAILED\n"); } printf("Child "); } printf("User credentials uid:%u euid:%u suid:%u\n", uid, euid, suid); if (no_child) printf("Child "); printf("Group credentials gid:%u egid:%u sgid:%u\n", gid, egid, sgid); if (uid != euid || gid != egid) printf("Note: app has mismatching credentials!!\n"); // Check capabilities if (text) { if (capng_have_capabilities(CAPNG_SELECT_CAPS) == CAPNG_NONE) { if (no_child) printf("Child capabilities: none\n"); else printf("Current capabilities: none\n"); } else { if (no_child) printf("Child "); printf("Effective: "); capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_EFFECTIVE); printf("\n"); if (no_child) printf("Child "); printf("Permitted: "); capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_PERMITTED); printf("\n"); if (no_child) printf("Child "); printf("Inheritable: "); capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_INHERITABLE); printf("\n"); if (no_child) printf("Child "); printf("Bounding Set: "); capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_BOUNDING_SET); printf("\n"); if (no_child) printf("Child "); printf("Ambient: "); capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_AMBIENT); printf("\n"); } } else { if (capng_have_capabilities(CAPNG_SELECT_CAPS) == CAPNG_NONE) { if (no_child) printf("Child capabilities: none\n"); else printf("Current capabilities: none\n"); } else { if (no_child) printf("Child capabilities:\n"); capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_ALL); } } // Now check securebits flags #ifdef PR_SET_SECUREBITS if (no_child) printf("Child "); printf("securebits flags: "); rc = prctl(PR_GET_SECUREBITS, 1 << SECURE_NOROOT); if (rc & (1 << SECURE_NOROOT)) { printf("NOROOT"); need_comma = 1; } rc = prctl(PR_GET_SECUREBITS, 1 << SECURE_NOROOT_LOCKED); if (rc & (1 << SECURE_NOROOT_LOCKED)) { if (need_comma) printf(", "); printf("NOROOT_LOCKED"); need_comma = 1; } rc = prctl(PR_GET_SECUREBITS, 1 << SECURE_NO_SETUID_FIXUP); if (rc & (1 << SECURE_NO_SETUID_FIXUP)) { if (need_comma) printf(", "); printf("NO_SETUID_FIXUP"); need_comma = 1; } rc = prctl(PR_GET_SECUREBITS, 1 << SECURE_NO_SETUID_FIXUP_LOCKED); if (rc & (1 << SECURE_NO_SETUID_FIXUP_LOCKED)) { if (need_comma) printf(", "); printf("NO_SETUID_FIXUP_LOCKED"); need_comma = 1; } if (need_comma == 0) printf("none"); printf("\n"); #endif // Now do child process checks if (no_child == 0 || escalated) { printf("Attempting direct access to shadow..."); if (access("/etc/shadow", R_OK) == 0) printf("SUCCESS\n"); else printf("FAILED (%s)\n", strerror(errno)); } if (no_child == 0) { printf("Attempting to access shadow by child process..."); rc = system("cat /etc/shadow > /dev/null 2>&1"); if (rc == 0) printf("SUCCESS\n"); else printf("FAILED\n"); if (text) system("/usr/bin/captest --no-child --text"); else system("/usr/bin/captest --no-child"); } } static void usage(void) { printf("usage: captest [ --ambient | --drop-all | --drop-caps | --id | --init-grp ] [ --lock ] [ --text ]\n"); } int main(int argc, char *argv[]) { int which = 0, i; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--text") == 0) text = 1; else if (strcmp(argv[i], "--no-child") == 0) no_child = 1; else if (strcmp(argv[i], "--lock") == 0) lock = 1; else if (strcmp(argv[i], "--drop-all") == 0) which = 1; else if (strcmp(argv[i], "--drop-caps") == 0) which = 2; else if (strcmp(argv[i], "--id") == 0) which = 3; else if (strcmp(argv[i], "--init-grp") == 0) which = 4; else if (strcmp(argv[i], "--ambient") == 0) ambient = 1; else { usage(); return 0; } } switch (which) { case 1: capng_clear(CAPNG_SELECT_ALL); if (lock) { if (capng_lock()) { fprintf(stderr, "capng_lock failed\n"); return 1; } } if (capng_apply(CAPNG_SELECT_ALL)) { fprintf(stderr, "capng_apply failed\n"); return 1; } report(); break; case 2: capng_clear(CAPNG_SELECT_CAPS); if (ambient) capng_update(CAPNG_ADD, CAPNG_AMBIENT, CAP_CHOWN); if (lock) { if (capng_lock()) { fprintf(stderr, "capng_lock failed\n"); return 1; } } if (ambient) { if (capng_apply( CAPNG_SELECT_CAPS|CAPNG_SELECT_AMBIENT)) { fprintf(stderr, "capng_apply failed\n"); return 1; } } else { if (capng_apply(CAPNG_SELECT_CAPS)) { fprintf(stderr, "capng_apply failed\n"); return 1; } } report(); break; case 3: case 4: { int rc; capng_clear(CAPNG_SELECT_BOTH); capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_CHOWN); if (ambient) capng_update(CAPNG_ADD, CAPNG_INHERITABLE|CAPNG_AMBIENT, CAP_CHOWN); if (which == 4) rc = capng_change_id(99, 99, CAPNG_INIT_SUPP_GRP | CAPNG_CLEAR_BOUNDING); else rc = capng_change_id(99, 99, CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING); if (rc < 0) { printf("Error changing uid: %d\n", rc); capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_EFFECTIVE); printf("\n"); return 1; } printf("Keeping CAP_CHOWN to show capabilities across uid change.\n"); report(); } break; case 0: if (lock) { if (capng_lock()) { fprintf(stderr, "capng_lock failed\n"); return 1; } } report(); break; } return 0; } stevegrubb-libcap-ng-0ab44af/utils/filecap.8000066400000000000000000000031531516575034500210500ustar00rootroot00000000000000.TH FILECAP: "8" "Aug 2018" "Red Hat" "System Administration Utilities" .SH NAME filecap \- a program to see capabilities .SH SYNOPSIS .B filecap [ \-a | \-d | /dir | /dir/file [cap1 cap2 ...] ] .SH DESCRIPTION \fBfilecap\fP is a program that prints out a report of programs with file based capabilities. If a file is not in the report or there is no report at all, no capabilities were found. For expedience, the default is to check only the directories in the PATH environmental variable. If the \-a command line option is given, then all directories will be checked. If a directory is passed, it will recursively check that directory. If a path to a file is given, it will only check that file. If a file is given followed by capabilities, then the capabilities are written to the file. .SH OPTIONS .TP .B \-a This tells the program to show all capabilities starting from the / directory. Normally the PATH environmental variable is used to show you capabilities on files you are likely to execute. .TP .B \-d This dumps all capabilities for reference. .SH "EXAMPLES" .nf To check file capabilities in $PATH: .B filecap To check file capabilities of whole system: .B filecap -a To check file capabilities recursively in a directory: .B filecap /usr To check file capabilities of a specific program: .B filecap /bin/passwd To list all possible capabilities: .B filecap -d To set a file capability on a specific program: .B filecap /bin/ping net_raw net_admin To remove file capabilities on a specific program: .B filecap /bin/ping none .fi .SH "SEE ALSO" .BR pscap (8), .BR netcap (8), .BR capabilities (7). .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/utils/filecap.c000066400000000000000000000135461516575034500211320ustar00rootroot00000000000000/* * filecap.c - A program that lists running processes with capabilities * Copyright (c) 2009-10,2012,2017,2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include "cap-ng.h" #include #include #ifndef FTW_CONTINUE #define FTW_CONTINUE 0 #endif static int show_all = 0, header = 0, capabilities = 0, cremove = 0; static int single_file = 0; static void usage(void) { fprintf(stderr, "usage: filecap [-a | -d | /dir | /dir/file [cap1 cap2 ...] ]\n"); exit(1); } // Returns 1 on error and 0 otherwise static int check_file(const char *fpath, const struct stat *sb, int typeflag_unused __attribute__ ((unused)), struct FTW *s_unused __attribute__ ((unused))) { int ret = FTW_CONTINUE; if (S_ISREG(sb->st_mode) == 0) return ret; int fd = open(fpath, O_RDONLY|O_CLOEXEC); if (fd >= 0) { capng_results_t rc; int permitted = 0; capng_clear(CAPNG_SELECT_BOTH); if (capng_get_caps_fd(fd) < 0 && errno != ENODATA) { fprintf(stderr, "Unable to get capabilities of %s: %s\n", fpath, strerror(errno)); if (single_file) ret = 1; } rc = capng_have_capabilities(CAPNG_SELECT_CAPS); if (rc == CAPNG_NONE) { permitted = 1; rc = capng_have_permitted_capabilities(); } if (rc > CAPNG_NONE) { if (header == 0) { header = 1; printf("%-9s %-22s capabilities rootid\n", "set", "file"); } int rootid = capng_get_rootid(); printf("%s %-22s ", permitted ? "permitted" : "effective", fpath); if (rc == CAPNG_FULL) printf("full"); else capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_PERMITTED); if (rootid != CAPNG_UNSET_ROOTID) printf(" %d", rootid); printf("\n"); } close(fd); } return ret; } // Use cases: // filecap // filecap -a // filecap /path/dir // filecap /path/file // filecap /path/file capability1 capability2 capability 3 ... // int main(int argc, char *argv[]) { #if CAP_LAST_CAP < 31 || !defined (VFS_CAP_U32) || \ (!defined (HAVE_ATTR_XATTR_H) && !defined(HAVE_SYS_XATTR_H)) fprintf(stderr, "File based capabilities are not supported\n"); #else char *path_env, *path = NULL, *dir = NULL; struct stat sbuf; struct stat path_sbuf; int nftw_flags = FTW_PHYS; int i, rc = 0; int have_path_stat = 0; if (argc >1) { for (i=1; i= 0) { if (path == NULL) usage(); capng_update(CAPNG_ADD, CAPNG_PERMITTED|CAPNG_EFFECTIVE, cap); capabilities = 1; } else if (strcmp("none", argv[i]) == 0) { capng_clear(CAPNG_SELECT_BOTH); capabilities = 1; cremove = 1; } else { fprintf(stderr, "Unrecognized capability.\n"); usage(); } } } } if (path == NULL && dir == NULL && show_all == 0) { path_env = getenv("PATH"); if (path_env != NULL) { path = strdup(path_env); if (!path) return 1; for (dir=strtok(path,":"); dir!=NULL; dir=strtok(NULL,":")) { nftw(dir, check_file, 1024, nftw_flags); } free(path); } } else if (path == NULL && dir == NULL && show_all == 1) { // Find files nftw("/", check_file, 1024, nftw_flags); } else if (dir) { // Print out the dir nftw(dir, check_file, 1024, nftw_flags); }else if (path && capabilities == 0) { // Print out specific file single_file = 1; rc = check_file(path, &sbuf, 0, NULL); } else if (path && capabilities == 1) { // Write capabilities to file struct stat fbuf; int fd = open(path, O_WRONLY|O_NOFOLLOW|O_CLOEXEC); if (fd < 0) { fprintf(stderr, "Could not open %s for writing (%s)\n", path, strerror(errno)); return 1; } if (have_path_stat && fstat(fd, &fbuf) == 0 && (fbuf.st_dev != path_sbuf.st_dev || fbuf.st_ino != path_sbuf.st_ino)) { fprintf(stderr, "File changed during operation, refusing " "to continue\n"); close(fd); return 1; } if (capng_apply_caps_fd(fd) < 0) { fprintf(stderr, "Could not set capabilities on %s: %s\n", path, strerror(errno)); rc = 1; } close(fd); } #endif return rc; } stevegrubb-libcap-ng-0ab44af/utils/libcap-ng.bash_completion000066400000000000000000000031451516575034500243010ustar00rootroot00000000000000_pscap() { local cur prev local opts="-a -p --tree" COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" case "$prev" in -p) COMPREPLY=( $(compgen -W "$(ps -eo pid= 2>/dev/null)" \ -- "$cur") ) return 0 ;; esac if [[ "$cur" == -* ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) fi } _netcap() { local cur prev local opts="--advanced --interface --list-interfaces --json --no-color" COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" case "$prev" in --interface) COMPREPLY=( $(compgen -W "$(command ls /sys/class/net \ 2>/dev/null)" -- "$cur") ) return 0 ;; esac if [[ "$cur" == -* ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) fi } _filecap() { local cur prev local opts="-a -d" COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ "$cur" == -* ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) return 0 fi case "$prev" in -a|-d) return 0 ;; esac if [[ "$cur" == /* ]]; then COMPREPLY=( $(compgen -d -- "$cur") $(compgen -f -- "$cur") ) fi } _cap_audit() { local cur prev local opts="-h --help -v --verbose -j --json -y --yaml --" COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ "$prev" == "--" ]]; then COMPREPLY=( $(compgen -c -- "$cur") ) return 0 fi if [[ "$cur" == -* ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) else COMPREPLY=( $(compgen -c -- "$cur") ) fi } complete -F _pscap pscap complete -F _netcap netcap complete -F _filecap filecap complete -F _cap_audit cap-audit stevegrubb-libcap-ng-0ab44af/utils/netcap-advanced.c000066400000000000000000002722761516575034500225530ustar00rootroot00000000000000/* * netcap-advanced.c - Advanced capability analysis * Copyright (c) 2026 Steve Grubb * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LINUX_VM_SOCKETS_DIAG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cap-ng.h" #include "netcap-advanced.h" #include "proc-sanitize.h" /* * Overview: * netcap --advanced builds a process/socket ownership model from procfs and * sock_diag, then renders it as a tree or JSON without changing system state. * * The flow is: discover interface addresses, map socket inode->process * ownership from /proc//fd, parse protocol-specific listener tables, * and project each endpoint onto interface/plane groupings for reporting. * * For internet sockets, wildcard binds are expanded onto concrete interface * addresses so the rendered tree/JSON can be consumed as an exposure map. * VSOCK listeners are collected via sock_diag when available and fall back to * /proc parsing when not, with ownership stitched back through socket inodes. * Tree output supports colorized capability/flag severity, with --no-color * forcing plain text. SO_REUSEPORT is detected per socket via pidfd_open and * pidfd_getfd while scanning /proc//fd so the flag can be propagated into * endpoint rendering. * * Process metadata includes ambient capability enumeration with per-capability * detail and cgroup-unit extraction limited to system.slice services to keep * ownership context focused on service units. Line wrapping for tree output is * ANSI-escape-aware so colorized text wraps at display width without breaking * SGR sequences. * * Results depend on the current network namespace and procfs visibility; * restricted privileges can hide processes/sockets and yield partial output. */ #ifdef NETCAP_DIAG_DEBUG #define diag_dbg(fmt, ...) \ fprintf(stderr, "netcap-diag: " fmt "\n", ##__VA_ARGS__) #else #define diag_dbg(fmt, ...) do { } while (0) #endif enum plane_kind { PLANE_INET_EXTERNAL, PLANE_INET_LOOPBACK, PLANE_PACKET, PLANE_VSOCK, PLANE_COUNT, }; #define PLANE_PACKET_NAME "LINK-LAYER" /* Keep user-facing key name centralized to avoid legacy regressions. */ #define DEFENSES_RUNS_AS_KEY "runs_as_nonroot" enum endpoint_flags { FLAG_WILDCARD_BIND = 1U << 0, FLAG_PRIVILEGED_CAPS = 1U << 2, FLAG_HYPERVISOR_PLANE = 1U << 4, FLAG_SSH_VSOCK_22 = 1U << 5, FLAG_REUSEPORT = 1U << 6, }; struct strset { const char **slots; size_t slots_cap; size_t used; }; struct iface_addr { int af; char *addr; }; struct iface_info { char *name; struct iface_addr *addrs; size_t addrs_n; size_t addrs_cap; }; struct defense_info { char *runs_as_nonroot; char *no_new_privs; char *seccomp; char *lsm_label; }; struct process_info { int pid; int uid; char *comm; char *exe; char *unit; char *caps; char *ambient_caps; int ambient_present; int open_ended_bounding; int has_privileged_caps; struct defense_info defenses; }; struct inode_proc { unsigned long inode; struct process_info **procs; size_t n; size_t cap; int reuseport; }; struct endpoint { char *proto; char *bind; char *label; unsigned int port; unsigned int vsock_cid; int has_vsock; enum plane_kind plane; char *ifname; char *ifaddr; struct process_info **procs; size_t procs_n; size_t procs_cap; int wildcard_bind; int reuseport; }; struct model { struct iface_info *ifaces; size_t ifaces_n; size_t ifaces_cap; struct process_info **procs; size_t procs_n; size_t procs_cap; struct inode_proc *inode_map; size_t inode_n; size_t inode_cap; size_t *inode_slots; size_t inode_slots_cap; struct endpoint *eps; size_t eps_n; size_t eps_cap; }; #define INODE_SLOT_EMPTY SIZE_MAX #define PIDSET_EMPTY INT_MIN struct pidset { int *slots; size_t cap; size_t used; }; struct status_fields { unsigned long no_new_privs; unsigned long seccomp; int seen_no_new_privs; int seen_seccomp; }; struct endpoint_attrs { int wildcard; int reuseport; }; static void free_process(struct process_info *p); static void free_endpoint(struct endpoint *e); static int use_color; #define COLOR_ORANGE "\033[38;5;208m" #define COLOR_YELLOW "\033[38;5;226m" #define COLOR_GREEN "\033[38;5;82m" #define COLOR_RESET "\033[0m" enum cap_severity { CAP_SEV_NEUTRAL, CAP_SEV_YELLOW, CAP_SEV_ORANGE, }; static const char *orange_caps[] = { "sys_ptrace", "sys_module", "sys_rawio", "setuid", "setgid", "setpcap", "audit_control", }; static const char *yellow_caps[] = { "sys_admin", "dac_override", "dac_read_search", "net_admin", "net_raw", "chown", "fowner", "mknod", "sys_chroot", }; /* * cap_name_severity - classify one capability name into severity tiers. * @name: capability token without "cap_" prefix. * * Returns severity bucket used for tree color selection. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static enum cap_severity cap_name_severity(const char *name) { size_t i; for (i = 0; i < sizeof(orange_caps) / sizeof(orange_caps[0]); i++) { if (strcmp(name, orange_caps[i]) == 0) return CAP_SEV_ORANGE; } for (i = 0; i < sizeof(yellow_caps) / sizeof(yellow_caps[0]); i++) { if (strcmp(name, yellow_caps[i]) == 0) return CAP_SEV_YELLOW; } return CAP_SEV_NEUTRAL; } /* * caps_contains_token - test whether @token appears as a capability list item. * @caps: comma/space separated capability summary text. * @token: capability token to locate. * * Returns non-zero when @token is present as a whole list element, else 0. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int caps_contains_token(const char *caps, const char *token) { size_t len; const char *p; if (!caps || !token) return 0; len = strlen(token); for (p = caps; (p = strstr(p, token)) != NULL; p++) { int left_ok = (p == caps) || (p > caps + 1 && p[-1] == ' ' && p[-2] == ','); char right = p[len]; int right_ok = right == 0 || right == ',' || right == ' ' || right == '['; if (left_ok && right_ok) return 1; } return 0; } /* * sev_color - map severity level to ANSI color sequence. * @sev: severity class to map. * * Returns static color code pointer, or NULL when uncolored. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static const char *sev_color(enum cap_severity sev) { if (sev == CAP_SEV_ORANGE) return COLOR_ORANGE; if (sev == CAP_SEV_YELLOW) return COLOR_YELLOW; return NULL; } /* * caps_worst_severity - find highest severity capability in @caps text. * @caps: capability list text from caps_summary_for_pid(). * * Returns highest matched severity, or CAP_SEV_NEUTRAL. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static enum cap_severity caps_worst_severity(const char *caps) { size_t i; if (!caps) return CAP_SEV_NEUTRAL; if (strcmp(caps, "(full)") == 0) return CAP_SEV_ORANGE; for (i = 0; i < sizeof(orange_caps) / sizeof(orange_caps[0]); i++) { if (caps_contains_token(caps, orange_caps[i])) return CAP_SEV_ORANGE; } for (i = 0; i < sizeof(yellow_caps) / sizeof(yellow_caps[0]); i++) { if (caps_contains_token(caps, yellow_caps[i])) return CAP_SEV_YELLOW; } return CAP_SEV_NEUTRAL; } static void free_model(struct model *m); static void json_escape(const char *s); static void print_tree_node(const char *prefix, int is_last, const char *txt, int width); static int bind_sort_cmp(const char *a, const char *b); static struct inode_proc *lookup_inode(struct model *m, unsigned long inode); static void render_interfaces_text(struct model *m); static void render_interfaces_json(struct model *m); /* * iface_addr_cmp - order interface addresses by family then text. * @av/@bv: qsort pointers to struct iface_addr elements. * * Returns strcmp-style ordering suitable for qsort(). */ static int iface_addr_cmp(const void *av, const void *bv) { const struct iface_addr *a = av; const struct iface_addr *b = bv; if (a->af != b->af) return a->af - b->af; return strcmp(a->addr, b->addr); } /* * iface_info_cmp - order interfaces by name for stable output. * @av/@bv: qsort pointers to struct iface_info elements. * * Returns strcmp-style ordering suitable for qsort(). */ static int iface_info_cmp(const void *av, const void *bv) { const struct iface_info *a = av; const struct iface_info *b = bv; return strcmp(a->name, b->name); } /* * sort_interfaces - sort interfaces and per-interface addresses in place. * @m: model containing interface inventory from getifaddrs(). * * Returns no value. */ static void sort_interfaces(struct model *m) { size_t i; if (m->ifaces_n > 1) qsort(m->ifaces, m->ifaces_n, sizeof(struct iface_info), iface_info_cmp); for (i = 0; i < m->ifaces_n; i++) { if (m->ifaces[i].addrs_n > 1) qsort(m->ifaces[i].addrs, m->ifaces[i].addrs_n, sizeof(struct iface_addr), iface_addr_cmp); } } /* * str_hash - hash a string key for open-addressed set placement. * @s: NUL-terminated key string. * @slots_cap: destination hash table capacity. * * Returns index in [0, @slots_cap). * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static size_t str_hash(const char *s, size_t slots_cap) { uint64_t x = 1469598103934665603ULL; for (; *s; s++) { x ^= (unsigned char)*s; x *= 1099511628211ULL; } return (size_t)(x % slots_cap); } /* * strset_rebuild - resize and rehash string set storage. * @set: set object whose slots array is replaced. * @new_cap: requested slot capacity before minimum clamping. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int strset_rebuild(struct strset *set, size_t new_cap) { const char **slots; size_t i; if (new_cap < 16) new_cap = 16; slots = calloc(new_cap, sizeof(*slots)); if (!slots) { fprintf(stderr, "Out of memory\n"); return -1; } for (i = 0; i < set->slots_cap; i++) { size_t pos; if (!set->slots[i]) continue; pos = str_hash(set->slots[i], new_cap); while (slots[pos]) pos = (pos + 1) % new_cap; slots[pos] = set->slots[i]; } free(set->slots); set->slots = slots; set->slots_cap = new_cap; return 0; } /* * strset_add - insert @s into the string set if absent. * @set: destination hash set. * @s: caller-owned string pointer stored by reference. * * Returns 1 when inserted, 0 when already present, -1 on failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int strset_add(struct strset *set, const char *s) { size_t pos; if (set->slots_cap == 0) { if (strset_rebuild(set, 16) != 0) return -1; } if ((set->used + 1) * 4 >= set->slots_cap * 3) { size_t new_cap = set->slots_cap * 2; if (new_cap < set->slots_cap) return -1; if (strset_rebuild(set, new_cap) != 0) return -1; } pos = str_hash(s, set->slots_cap); while (set->slots[pos]) { if (strcmp(set->slots[pos], s) == 0) return 0; pos = (pos + 1) % set->slots_cap; } set->slots[pos] = s; set->used++; return 1; } /* * strset_free - release dynamic storage owned by @set. * @set: hash set to reset. * * Returns no value. * Side effects/assumptions: Frees heap memory referenced by @set. */ static void strset_free(struct strset *set) { free(set->slots); set->slots = NULL; set->slots_cap = 0; set->used = 0; } /* * inode_hash - hash inode key for inode ownership map slot selection. * @inode: socket inode value. * @slots_cap: destination hash table capacity. * * Returns index in [0, @slots_cap). * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static size_t inode_hash(unsigned long inode, size_t slots_cap) { uint64_t x = inode; x ^= x >> 33; x *= 0xff51afd7ed558ccdULL; x ^= x >> 33; x *= 0xc4ceb9fe1a85ec53ULL; x ^= x >> 33; return (size_t)(x % slots_cap); } /* * inode_hash_rebuild - resize and repopulate inode hash slots. * @m: model containing inode_map entries and slot metadata. * @new_cap: requested new slot capacity. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int inode_hash_rebuild(struct model *m, size_t new_cap) { size_t i; size_t *slots; if (new_cap < 16) new_cap = 16; slots = malloc(new_cap * sizeof(*slots)); if (!slots) { fprintf(stderr, "Out of memory\n"); return -1; } for (i = 0; i < new_cap; i++) slots[i] = INODE_SLOT_EMPTY; for (i = 0; i < m->inode_n; i++) { size_t pos = inode_hash(m->inode_map[i].inode, new_cap); while (slots[pos] != INODE_SLOT_EMPTY) pos = (pos + 1) % new_cap; slots[pos] = i; } free(m->inode_slots); m->inode_slots = slots; m->inode_slots_cap = new_cap; return 0; } /* * inode_hash_ensure_capacity - grow inode hash table when load is high. * @m: model whose inode slot table may be resized. * * Returns 0 when capacity is sufficient or grown, -1 on failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int inode_hash_ensure_capacity(struct model *m) { size_t new_cap; if (m->inode_slots_cap == 0) return inode_hash_rebuild(m, 16); if ((m->inode_n + 1) * 4 < m->inode_slots_cap * 3) return 0; new_cap = m->inode_slots_cap * 2; if (new_cap < m->inode_slots_cap) return -1; return inode_hash_rebuild(m, new_cap); } /* * inode_hash_find - find inode_map index for @inode. * @m: model containing inode hash slots. * @inode: inode key to search. * * Returns non-negative inode_map index on hit, -1 on miss. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static ssize_t inode_hash_find(struct model *m, unsigned long inode) { size_t pos; size_t start; if (m->inode_slots_cap == 0) return -1; pos = inode_hash(inode, m->inode_slots_cap); start = pos; while (m->inode_slots[pos] != INODE_SLOT_EMPTY) { size_t idx = m->inode_slots[pos]; if (m->inode_map[idx].inode == inode) return idx; pos = (pos + 1) % m->inode_slots_cap; if (pos == start) break; } return -1; } /* * inode_hash_insert - place one inode_map index into hash slots. * @m: model containing destination hash slots. * @idx: inode_map entry index to insert. * * Returns no value. * Side effects/assumptions: Mutates @m->inode_slots insertion state. */ static void inode_hash_insert(struct model *m, size_t idx) { size_t pos = inode_hash(m->inode_map[idx].inode, m->inode_slots_cap); while (m->inode_slots[pos] != INODE_SLOT_EMPTY) pos = (pos + 1) % m->inode_slots_cap; m->inode_slots[pos] = idx; } /* * get_width - choose terminal width for wrapped tree output. * @none: function takes no parameters. * * Returns terminal columns from TIOCGWINSZ or $COLUMNS, else 80. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int get_width(void) { struct winsize ws; const int fds[] = { STDOUT_FILENO, STDERR_FILENO, STDIN_FILENO }; const char *env; char *end = NULL; unsigned long v; size_t i; for (i = 0; i < sizeof(fds) / sizeof(fds[0]); i++) { if (ioctl(fds[i], TIOCGWINSZ, &ws) == 0 && ws.ws_col) return ws.ws_col; } env = getenv("COLUMNS"); if (env) { v = strtoul(env, &end, 10); if (end != env && *end == '\0' && v > 0 && v < 400) return (int)v; } return 80; } /* * xstrdup - duplicate @s into a newly allocated string. * @s: source string, or NULL. * * Returns caller-owned copy, or NULL for NULL input or allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static char *xstrdup(const char *s) { char *p; if (!s) return NULL; p = strdup(s); if (!p) fprintf(stderr, "Out of memory\n"); return p; } /* * vec_grow - grow vector storage for dynamic arrays. * @v: pointer to heap buffer pointer updated on success. * @cap: current/new capacity element count. * @item: size in bytes of each element. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int vec_grow(void **v, size_t *cap, size_t item) { void *p; size_t ncap; if (*cap) ncap = *cap * 2; else ncap = 8; p = realloc(*v, ncap * item); if (!p) { fprintf(stderr, "Out of memory\n"); return -1; } *v = p; *cap = ncap; return 0; } /* * pid_hash - hash process ID for pidset probing. * @pid: process id key. * @cap: pidset slot capacity. * * Returns slot index in [0, @cap). * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static size_t pid_hash(int pid, size_t cap) { uint32_t v = (uint32_t)pid; v ^= v >> 16; v *= 0x7feb352dU; v ^= v >> 15; v *= 0x846ca68bU; v ^= v >> 16; return v % cap; } /* * pidset_rehash - resize/reinsert pidset contents. * @ps: pidset to grow. * @new_cap: requested slot capacity. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int pidset_rehash(struct pidset *ps, size_t new_cap) { int *new_slots; size_t i; new_slots = malloc(new_cap * sizeof(*new_slots)); if (!new_slots) return -1; for (i = 0; i < new_cap; i++) new_slots[i] = PIDSET_EMPTY; for (i = 0; i < ps->cap; i++) { size_t pos; if (ps->slots[i] == PIDSET_EMPTY) continue; pos = pid_hash(ps->slots[i], new_cap); while (new_slots[pos] != PIDSET_EMPTY) pos = (pos + 1) % new_cap; new_slots[pos] = ps->slots[i]; } free(ps->slots); ps->slots = new_slots; ps->cap = new_cap; return 0; } /* * pidset_init - initialize pidset with empty hash table state. * @ps: pidset object to initialize. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Allocates heap storage for @ps slots. */ static int pidset_init(struct pidset *ps) { ps->cap = 16; ps->used = 0; ps->slots = malloc(ps->cap * sizeof(*ps->slots)); if (!ps->slots) return -1; for (size_t i = 0; i < ps->cap; i++) ps->slots[i] = PIDSET_EMPTY; return 0; } /* * pidset_free - release pidset storage and reset fields. * @ps: pidset object to clear. * * Returns no value. * Side effects/assumptions: Frees heap memory referenced by @ps. */ static void pidset_free(struct pidset *ps) { free(ps->slots); ps->slots = NULL; ps->cap = 0; ps->used = 0; } /* * pidset_test_and_add - query/insert PID in dedup set. * @ps: pidset tracking seen process IDs. * @pid: process id to test and insert. * * Returns 1 if already present, 0 if newly inserted, -1 on failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int pidset_test_and_add(struct pidset *ps, int pid) { size_t pos; if ((ps->used + 1) * 10 >= ps->cap * 7) { if (pidset_rehash(ps, ps->cap * 2)) return -1; } pos = pid_hash(pid, ps->cap); while (ps->slots[pos] != PIDSET_EMPTY) { if (ps->slots[pos] == pid) return 1; pos = (pos + 1) % ps->cap; } ps->slots[pos] = pid; ps->used++; return 0; } /* * print_flag_nodes - render endpoint/process flag leaves under "flags" node. * @pfx_flags: tree prefix for flag child lines. * @width: wrap width for tree nodes. * @flags: bitmask of endpoint/process flags to print. * @priv_sev: privileged capability severity for colorizing that flag. * * Returns no value. * Side effects/assumptions: Writes formatted output to stdout. */ static void print_flag_nodes(const char *pfx_flags, int width, unsigned int flags, enum cap_severity priv_sev) { static const struct { unsigned int bit; const char *name; const char *color; } map[] = { { FLAG_HYPERVISOR_PLANE, "hypervisor-plane", COLOR_YELLOW }, { FLAG_SSH_VSOCK_22, "ssh-on-vsock-port-22", NULL }, { FLAG_WILDCARD_BIND, "wildcard-bind", COLOR_YELLOW }, { FLAG_REUSEPORT, "reuseport", COLOR_YELLOW }, { FLAG_PRIVILEGED_CAPS, "privileged-caps", NULL }, }; size_t i; size_t n = 0; size_t printed = 0; char node[256]; for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) if (flags & map[i].bit) n++; if (!n) { print_tree_node(pfx_flags, 1, "(none)", width); return; } for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) { const char *color = map[i].color; if (!(flags & map[i].bit)) continue; if (map[i].bit == FLAG_PRIVILEGED_CAPS) color = sev_color(priv_sev); if (use_color && color) snprintf(node, sizeof(node), "%s%s%s", color, map[i].name, COLOR_RESET); else snprintf(node, sizeof(node), "%s", map[i].name); printed++; print_tree_node(pfx_flags, printed == n, node, width); } } /* * str_is_loopback - check whether textual @addr is loopback for @af. * @af: address family used to interpret @addr. * @addr: textual IPv4/IPv6 address to classify. * * Returns non-zero for loopback addresses, else 0. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int str_is_loopback(int af, const char *addr) { struct in_addr a4; struct in6_addr a6; if (af == AF_INET) { if (inet_pton(AF_INET, addr, &a4) != 1) return 0; return (ntohl(a4.s_addr) & 0xff000000U) == 0x7f000000U; } if (af == AF_INET6) { if (inet_pton(AF_INET6, addr, &a6) != 1) return 0; return IN6_IS_ADDR_LOOPBACK(&a6); } return 0; } /* * str_is_wildcard - check whether textual @addr is wildcard-any for @af. * @af: address family used to interpret @addr. * @addr: textual IPv4/IPv6 bind address to classify. * * Returns non-zero for 0.0.0.0/:: wildcard binds, else 0. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int str_is_wildcard(int af, const char *addr) { if (af == AF_INET) return strcmp(addr, "0.0.0.0") == 0; if (af == AF_INET6) return strcmp(addr, "::") == 0; return 0; } /* * str_is_multicast - check whether textual @addr is multicast for @af. * @af: address family used to interpret @addr. * @addr: textual IPv4/IPv6 bind address to classify. * * Returns non-zero for multicast addresses, else 0. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int str_is_multicast(int af, const char *addr) { struct in_addr a4; struct in6_addr a6; if (af == AF_INET) { if (inet_pton(AF_INET, addr, &a4) != 1) return 0; return IN_MULTICAST(ntohl(a4.s_addr)); } if (af == AF_INET6) { if (inet_pton(AF_INET6, addr, &a6) != 1) return 0; return IN6_IS_ADDR_MULTICAST(&a6); } return 0; } /* * find_iface - locate interface record by name in @m. * @m: model containing interface inventory. * @name: interface name key (borrowed, not owned). * * Returns a mutable iface pointer on match, or NULL if not found. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static struct iface_info *find_iface(struct model *m, const char *name) { size_t i; for (i = 0; i < m->ifaces_n; i++) if (strcmp(m->ifaces[i].name, name) == 0) return &m->ifaces[i]; return NULL; } /* * add_iface_addr - append one interface address if not already present. * @ifc: interface record to update (takes ownership of duplicated @addr). * @af: address family for @addr. * @addr: textual address to copy into @ifc. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int add_iface_addr(struct iface_info *ifc, int af, const char *addr) { size_t i; for (i = 0; i < ifc->addrs_n; i++) { if (ifc->addrs[i].af == af && strcmp(ifc->addrs[i].addr, addr) == 0) return 0; } if (ifc->addrs_n == ifc->addrs_cap && vec_grow((void **)&ifc->addrs, &ifc->addrs_cap, sizeof(struct iface_addr))) return -1; ifc->addrs[ifc->addrs_n].af = af; ifc->addrs[ifc->addrs_n].addr = xstrdup(addr); if (!ifc->addrs[ifc->addrs_n].addr) return -1; ifc->addrs_n++; return 0; } /* * collect_interfaces - snapshot AF_INET/AF_INET6 iface addresses in @m. * @m: model populated from getifaddrs() in the current network namespace. * * Returns 0 on success, -1 on getifaddrs/allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int collect_interfaces(struct model *m) { struct ifaddrs *ifa, *cur; char buf[INET6_ADDRSTRLEN]; struct iface_info *ifc; if (getifaddrs(&ifa) != 0) return -1; for (cur = ifa; cur; cur = cur->ifa_next) { if (!cur->ifa_name || !cur->ifa_addr) continue; if (cur->ifa_addr->sa_family != AF_INET && cur->ifa_addr->sa_family != AF_INET6) continue; ifc = find_iface(m, cur->ifa_name); if (!ifc) { if (m->ifaces_n == m->ifaces_cap && vec_grow((void **)&m->ifaces, &m->ifaces_cap, sizeof(struct iface_info))) goto fail; ifc = &m->ifaces[m->ifaces_n++]; memset(ifc, 0, sizeof(*ifc)); ifc->name = xstrdup(cur->ifa_name); if (!ifc->name) goto fail; } if (cur->ifa_addr->sa_family == AF_INET) { if (!inet_ntop(AF_INET, &((struct sockaddr_in *)cur->ifa_addr)->sin_addr, buf, sizeof(buf))) continue; } else { if (!inet_ntop(AF_INET6, &((struct sockaddr_in6 *)cur->ifa_addr)->sin6_addr, buf, sizeof(buf))) continue; } if (add_iface_addr(ifc, cur->ifa_addr->sa_family, buf)) goto fail; } freeifaddrs(ifa); return 0; fail: freeifaddrs(ifa); return -1; } /* * read_first_line - read and trim the first line from @path. * @path: procfs/sysfs-style file path to read. * * Returns a caller-owned string, or NULL on open/read/allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static char *read_first_line(const char *path) { int fd; char *buf; ssize_t len; fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) return NULL; buf = malloc(1024); if (!buf) { close(fd); return NULL; } len = read(fd, buf, 1023); close(fd); if (len <= 0) { free(buf); return NULL; } buf[len] = 0; while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) buf[--len] = 0; return buf; } /* * extract_unit_from_cgroup - best-effort unit name lookup for @pid. * @pid: process ID whose /proc//cgroup is inspected. * * Returns a caller-owned unit/scope name, or NULL if not found/readable. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static char *extract_unit_from_cgroup(int pid) { char path[64], line[512]; FILE *f; snprintf(path, sizeof(path), "/proc/%d/cgroup", pid); f = fopen(path, "rte"); if (!f) return NULL; __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(line, sizeof(line), f)) { char *s; s = strstr(line, ".service"); if (!s) s = strstr(line, ".scope"); if (s && !strstr(line, "system.slice")) continue; if (s) { while (s > line && *s != '/') s--; if (*s == '/') s++; char *e = s; while (*e && *e != '\n' && *e != '/') e++; *e = 0; fclose(f); return xstrdup(s); } } fclose(f); return NULL; } /* * caps_summary_for_pid - format capability summary for one process. * @pid: process ID inspected through libcap-ng APIs. * @privileged: out flag set when notable privileged effective caps exist. * @has_amb: out flag set when ambient capabilities are present. * @has_bnd: out flag set when bounding set entries are present. * * Returns caller-owned summary text; errors degrade to "(none)" style text. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static char *caps_summary_for_pid(int pid, int *privileged, int *has_amb, int *has_bnd, char **amb_list) { char out[4096]; char amb_out[1024]; char *amb_dst = amb_out; char *dst = out; size_t amb_left = sizeof(amb_out); size_t left = sizeof(out); int i, first = 1, amb_first = 1; capng_results_t c; *privileged = 0; *has_amb = 0; *has_bnd = 0; *amb_list = NULL; capng_clear(CAPNG_SELECT_ALL); capng_setpid(pid); if (capng_get_caps_process()) return xstrdup("(none)"); if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SYS_ADMIN) || capng_have_capability(CAPNG_EFFECTIVE, CAP_SYS_PTRACE) || capng_have_capability(CAPNG_EFFECTIVE, CAP_DAC_READ_SEARCH) || capng_have_capability(CAPNG_EFFECTIVE, CAP_NET_ADMIN) || capng_have_capability(CAPNG_EFFECTIVE, CAP_NET_RAW)) *privileged = 1; c = capng_have_capabilities(CAPNG_SELECT_CAPS); if (c == CAPNG_FULL) { strncpy(out, "(full)", sizeof(out)); out[sizeof(out) - 1] = 0; } else if (c <= CAPNG_NONE) { strncpy(out, "(none)", sizeof(out)); out[sizeof(out) - 1] = 0; } else { *dst = 0; for (i = 0; i <= CAP_LAST_CAP; i++) { size_t n; size_t sep = first ? 0 : 2; if (!capng_have_capability(CAPNG_PERMITTED, i)) continue; const char *name = capng_capability_to_name(i); if (!name) continue; if (strncmp(name, "cap_", 4) == 0) name += 4; n = strlen(name); if (left <= sep + n) break; if (!first) { *dst++ = ','; *dst++ = ' '; left -= 2; } memcpy(dst, name, n); dst += n; left -= n; *dst = 0; first = 0; } if (out[0] == 0) { strncpy(out, "(none)", sizeof(out)); out[sizeof(out) - 1] = 0; } } if (capng_have_capabilities(CAPNG_SELECT_AMBIENT) > CAPNG_NONE) *has_amb = 1; if (*has_amb) { *amb_dst = 0; for (i = 0; i <= CAP_LAST_CAP; i++) { size_t n; size_t sep = amb_first ? 0 : 2; const char *name; if (!capng_have_capability(CAPNG_AMBIENT, i)) continue; name = capng_capability_to_name(i); if (!name) continue; if (strncmp(name, "cap_", 4) == 0) name += 4; n = strlen(name); if (amb_left <= sep + n) break; if (!amb_first) { *amb_dst++ = ','; *amb_dst++ = ' '; amb_left -= 2; } memcpy(amb_dst, name, n); amb_dst += n; amb_left -= n; *amb_dst = 0; amb_first = 0; } if (amb_out[0]) *amb_list = xstrdup(amb_out); } if (capng_have_capabilities(CAPNG_SELECT_BOUNDS) > CAPNG_NONE) *has_bnd = 1; return xstrdup(out); } /* * parse_status_defenses - read process hardening metadata into @d. * @pid: process ID whose procfs status/attr files are parsed. * @uid: process real UID, used for root/non-root interpretation. * @d: destination struct receiving caller-freed string fields. * @sf: parsed /proc//status fields consumed for hardening decode. * * Missing fields are tolerated; function leaves best-effort defaults. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void parse_status_defenses(int pid, int uid, struct defense_info *d, const struct status_fields *sf) { char path[64]; d->runs_as_nonroot = xstrdup(uid != 0 ? "yes" : "no"); d->no_new_privs = xstrdup("unknown"); d->seccomp = xstrdup("disabled"); d->lsm_label = NULL; if (sf->seen_no_new_privs) { free(d->no_new_privs); d->no_new_privs = xstrdup(sf->no_new_privs ? "yes" : "no"); } if (sf->seen_seccomp) { free(d->seccomp); if (sf->seccomp == 0) d->seccomp = xstrdup("disabled"); else if (sf->seccomp == 1) d->seccomp = xstrdup("strict"); else d->seccomp = xstrdup("filter"); } snprintf(path, sizeof(path), "/proc/%d/attr/current", pid); d->lsm_label = read_first_line(path); if (sanitize_untrusted_owned(&d->lsm_label) < 0) { free(d->lsm_label); d->lsm_label = NULL; } } /* * add_process - collect process metadata and append it to @m. * @m: model taking ownership of the created process_info on success. * @pid: numeric process ID to read from /proc. * * Returns stored process pointer on success, or NULL on parse/allocation error. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static struct process_info *add_process(struct model *m, int pid) { char path[64], line[256], comm[64] = ""; char exepath[PATH_MAX]; FILE *f; ssize_t exelen; int uid = -1; int has_amb = 0, has_bnd = 0; char *amb_list = NULL; struct status_fields sf = { 0 }; struct process_info *p; snprintf(path, sizeof(path), "/proc/%d/status", pid); f = fopen(path, "rte"); if (!f) return NULL; __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(line, sizeof(line), f)) { if (sscanf(line, "Name:\t%63s", comm) == 1) continue; if (sscanf(line, "Uid:\t%d", &uid) == 1) continue; if (sscanf(line, "NoNewPrivs:\t%lu", &sf.no_new_privs) == 1) { sf.seen_no_new_privs = 1; continue; } if (sscanf(line, "Seccomp:\t%lu", &sf.seccomp) == 1) { sf.seen_seccomp = 1; continue; } if (uid >= 0 && comm[0] && sf.seen_no_new_privs && sf.seen_seccomp) break; } fclose(f); if (uid < 0) return NULL; p = calloc(1, sizeof(*p)); if (!p) { fprintf(stderr, "Out of memory\n"); return NULL; } p->pid = pid; p->uid = uid; p->comm = xstrdup(comm[0] ? comm : "?"); if (sanitize_untrusted_owned(&p->comm) < 0) goto fail; snprintf(path, sizeof(path), "/proc/%d/exe", pid); exelen = readlink(path, exepath, sizeof(exepath) - 1); if (exelen >= 0) { size_t deleted_len = strlen(" (deleted)"); exepath[exelen] = '\0'; if ((size_t)exelen > deleted_len && strcmp(exepath + exelen - deleted_len, " (deleted)") == 0) exepath[exelen - deleted_len] = '\0'; p->exe = xstrdup(exepath); if (sanitize_untrusted_owned(&p->exe) < 0) goto fail; } p->unit = extract_unit_from_cgroup(pid); if (sanitize_untrusted_owned(&p->unit) < 0) goto fail; p->caps = caps_summary_for_pid(pid, &p->has_privileged_caps, &has_amb, &has_bnd, &amb_list); p->ambient_caps = amb_list; p->ambient_present = has_amb; p->open_ended_bounding = has_bnd; parse_status_defenses(pid, uid, &p->defenses, &sf); if (!p->comm || (exelen >= 0 && !p->exe) || !p->caps || (has_amb && !p->ambient_caps) || !p->defenses.runs_as_nonroot || !p->defenses.no_new_privs || !p->defenses.seccomp) goto fail; if (m->procs_n == m->procs_cap && vec_grow((void **)&m->procs, &m->procs_cap, sizeof(struct process_info *))) goto fail; m->procs[m->procs_n++] = p; return p; fail: free_process(p); return NULL; } /* * add_inode_proc - add inode->process ownership mapping into @m. * @m: model containing inode map storage. * @inode: socket inode key from procfs/netlink tables. * @p: process entry pointer that must remain valid for @m lifetime. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int add_inode_proc(struct model *m, unsigned long inode, struct process_info *p) { size_t j; ssize_t idx; struct inode_proc *ip = NULL; idx = inode_hash_find(m, inode); if (idx >= 0) ip = &m->inode_map[idx]; if (!ip) { if (inode_hash_ensure_capacity(m) != 0) return -1; if (m->inode_n == m->inode_cap && vec_grow((void **)&m->inode_map, &m->inode_cap, sizeof(struct inode_proc))) return -1; ip = &m->inode_map[m->inode_n++]; memset(ip, 0, sizeof(*ip)); ip->inode = inode; inode_hash_insert(m, m->inode_n - 1); } for (j = 0; j < ip->n; j++) if (ip->procs[j]->pid == p->pid) return 0; if (ip->n == ip->cap && vec_grow((void **)&ip->procs, &ip->cap, sizeof(struct process_info *))) return -1; ip->procs[ip->n++] = p; return 0; } /* * probe_reuseport - probe SO_REUSEPORT on a target process socket fd. * @pid: process id owning @fdnum. * @fdnum: socket fd number in target process. * * Uses pidfd_open + pidfd_getfd when supported by the running kernel. * Returns 1/0 on successful getsockopt, or -1 when unsupported/inaccessible. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int probe_reuseport(int pid, int fdnum) { #if defined(__NR_pidfd_open) && defined(__NR_pidfd_getfd) static int warned_unavail; int pidfd; int dupfd; int val = 0; socklen_t len = sizeof(val); int rc; pidfd = syscall(__NR_pidfd_open, pid, 0); if (pidfd < 0) { if (errno == ENOSYS && !warned_unavail) { diag_dbg("pidfd_getfd unavailable; SO_REUSEPORT detection disabled"); warned_unavail = 1; } return -1; } dupfd = syscall(__NR_pidfd_getfd, pidfd, fdnum, 0); if (dupfd < 0) { if (errno == ENOSYS && !warned_unavail) { diag_dbg("pidfd_getfd unavailable; SO_REUSEPORT detection disabled"); warned_unavail = 1; } close(pidfd); return -1; } rc = getsockopt(dupfd, SOL_SOCKET, SO_REUSEPORT, &val, &len); close(dupfd); close(pidfd); if (rc < 0) return -1; return val ? 1 : 0; #else (void)pid; (void)fdnum; return -1; #endif } /* * collect_proc_inodes - build inode ownership map from /proc//fd links. * @m: model receiving process entries and inode->process associations. * * This is best-effort and skips tasks/fds hidden by permissions or races. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void collect_proc_inodes(struct model *m) { DIR *d; struct dirent *ent; d = opendir("/proc"); if (!d) return; while ((ent = readdir(d))) { int pid; DIR *fds; struct dirent *fdent; char fdpath[64]; struct process_info *p; if (ent->d_name[0] < '0' || ent->d_name[0] > '9') continue; pid = atoi(ent->d_name); if (pid <= 0) continue; p = add_process(m, pid); if (!p) continue; snprintf(fdpath, sizeof(fdpath), "/proc/%d/fd", pid); fds = opendir(fdpath); if (!fds) continue; while ((fdent = readdir(fds))) { char lpath[128], link[256], *s; ssize_t l; unsigned long inode; int fdnum; int reuseport; struct inode_proc *ip; if (fdent->d_name[0] == '.') continue; snprintf(lpath, sizeof(lpath), "%s/%s", fdpath, fdent->d_name); l = readlink(lpath, link, sizeof(link) - 1); if (l < 0) continue; link[l] = 0; /* * procfs may expose socket links in kernel-dependent formats. * Handle both common "socket:[inode]" and "[0000]:inode" forms. */ if (strncmp(link, "socket:[", 8) == 0) { s = link + 8; } else if (strncmp(link, "[0000]:", 7) == 0) { s = link + 7; } else { continue; } inode = strtoul(s, NULL, 10); if (!inode) continue; add_inode_proc(m, inode, p); fdnum = atoi(fdent->d_name); if (fdnum < 0) continue; /* * Probe SO_REUSEPORT here because this is the only stage with both * target pid and fd number for pidfd_getfd; store on inode_proc so * endpoint projection can reuse the result later. */ reuseport = probe_reuseport(pid, fdnum); if (reuseport != 1) continue; ip = lookup_inode(m, inode); if (ip) ip->reuseport |= 1; } closedir(fds); } closedir(d); } /* * lookup_inode - locate inode ownership entry in @m. * @m: model containing inode ownership map. * @inode: socket inode key to resolve. * * Returns mutable map entry pointer, or NULL if inode is unknown. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static struct inode_proc *lookup_inode(struct model *m, unsigned long inode) { ssize_t idx; idx = inode_hash_find(m, inode); if (idx < 0) return NULL; return &m->inode_map[idx]; } /* * add_endpoint - add/merge one inet or packet endpoint in @m. * @m: model receiving endpoint data. * @proto/@bind/@ifname/@ifaddr: copied strings for endpoint identity. * @port/@plane: endpoint attributes for rendering/grouping. * @attrs: wildcard/reuseport flags carried from parser/projection stages. * @ip: inode-owner mapping whose process pointers are attached to endpoint. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int add_endpoint(struct model *m, const char *proto, const char *bind, unsigned int port, enum plane_kind plane, const char *ifname, const char *ifaddr, const struct endpoint_attrs *attrs, struct inode_proc *ip) { size_t i, j; struct endpoint *e; char label[256]; if (strchr(bind, ':')) snprintf(label, sizeof(label), "%s:[%s]:%u", proto, bind, port); else snprintf(label, sizeof(label), "%s:%s:%u", proto, bind, port); for (i = 0; i < m->eps_n; i++) { e = &m->eps[i]; if (strcmp(e->label, label) == 0 && strcmp(e->ifname, ifname) == 0 && strcmp(e->ifaddr, ifaddr) == 0) { e->reuseport |= attrs->reuseport; goto add_procs; } } if (m->eps_n == m->eps_cap && vec_grow((void **)&m->eps, &m->eps_cap, sizeof(struct endpoint))) return -1; e = &m->eps[m->eps_n]; memset(e, 0, sizeof(*e)); e->proto = xstrdup(proto); e->bind = xstrdup(bind); e->label = xstrdup(label); e->port = port; e->vsock_cid = 0; e->has_vsock = 0; e->plane = plane; e->ifname = xstrdup(ifname); e->ifaddr = xstrdup(ifaddr); e->wildcard_bind = attrs->wildcard; e->reuseport = attrs->reuseport; if (!e->proto || !e->bind || !e->label || !e->ifname || !e->ifaddr) { free(e->proto); free(e->bind); free(e->label); free(e->ifname); free(e->ifaddr); memset(e, 0, sizeof(*e)); return -1; } m->eps_n++; add_procs: for (j = 0; j < ip->n; j++) { size_t k; for (k = 0; k < e->procs_n; k++) if (e->procs[k]->pid == ip->procs[j]->pid) goto next; if (e->procs_n == e->procs_cap && vec_grow((void **)&e->procs, &e->procs_cap, sizeof(struct process_info *))) return -1; e->procs[e->procs_n++] = ip->procs[j]; next: ; } return 0; } /* * add_vsock_endpoint - add/merge one VSOCK endpoint in @m. * @m: model receiving endpoint data. * @type: socket type label (stream/dgram/seqpacket) copied into model. * @cid/@port: source CID/port; @cid may be VMADDR_CID_ANY. * @ip: inode-owner mapping whose process pointers are attached to endpoint. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int add_vsock_endpoint(struct model *m, const char *type, unsigned int cid, unsigned int port, struct inode_proc *ip) { size_t i, j; struct endpoint *e; char label[128]; char cidbuf[32]; if (cid == VMADDR_CID_ANY) strcpy(cidbuf, "ANY"); else snprintf(cidbuf, sizeof(cidbuf), "%u", cid); snprintf(label, sizeof(label), "%s:cid=%s:%u", type, cidbuf, port); for (i = 0; i < m->eps_n; i++) { e = &m->eps[i]; if (e->plane == PLANE_VSOCK && strcmp(e->label, label) == 0) goto add_procs; } if (m->eps_n == m->eps_cap && vec_grow((void **)&m->eps, &m->eps_cap, sizeof(struct endpoint))) return -1; e = &m->eps[m->eps_n]; memset(e, 0, sizeof(*e)); e->proto = xstrdup(type); e->bind = xstrdup(cidbuf); e->label = xstrdup(label); e->port = port; e->vsock_cid = cid; e->has_vsock = 1; e->plane = PLANE_VSOCK; e->ifname = xstrdup(""); e->ifaddr = xstrdup(""); e->reuseport = 0; if (!e->proto || !e->bind || !e->label || !e->ifname || !e->ifaddr) { free(e->proto); free(e->bind); free(e->label); free(e->ifname); free(e->ifaddr); memset(e, 0, sizeof(*e)); return -1; } m->eps_n++; add_procs: for (j = 0; j < ip->n; j++) { size_t k; for (k = 0; k < e->procs_n; k++) if (e->procs[k]->pid == ip->procs[j]->pid) goto next; if (e->procs_n == e->procs_cap && vec_grow((void **)&e->procs, &e->procs_cap, sizeof(struct process_info *))) return -1; e->procs[e->procs_n++] = ip->procs[j]; next: ; } return 0; } /* * endpoint_to_ifaces - project one inet bind onto iface/address groupings. * @m: model containing iface inventory and endpoint lists. * @proto/@af/@bind/@port: socket identity from procfs/sock_diag. * @ip: inode-owner mapping used to attach owning processes. * * Wildcard binds expand across non-loopback ifaces in current netns. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void endpoint_to_ifaces(struct model *m, const char *proto, int af, const char *bind, unsigned int port, int reuseport, struct inode_proc *ip) { size_t i, j; int wildcard = str_is_wildcard(af, bind); int multicast = str_is_multicast(af, bind); int matched = 0; struct endpoint_attrs attrs; attrs.wildcard = wildcard; attrs.reuseport = reuseport; for (i = 0; i < m->ifaces_n; i++) { struct iface_info *ifc = &m->ifaces[i]; for (j = 0; j < ifc->addrs_n; j++) { if (ifc->addrs[j].af != af) continue; if (wildcard) { /* * 0.0.0.0/:: listeners are treated as externally reachable; keep * loopback out of this expansion to avoid duplicate exposure rows. */ if (strcmp(ifc->name, "lo") == 0) continue; add_endpoint(m, proto, bind, port, PLANE_INET_EXTERNAL, ifc->name, ifc->addrs[j].addr, &attrs, ip); matched = 1; } else if (strcmp(ifc->addrs[j].addr, bind) == 0) { enum plane_kind plane = str_is_loopback(af, bind) ? PLANE_INET_LOOPBACK : PLANE_INET_EXTERNAL; add_endpoint(m, proto, bind, port, plane, ifc->name, ifc->addrs[j].addr, &attrs, ip); matched = 1; } } } if (!matched) add_endpoint(m, proto, bind, port, str_is_loopback(af, bind) ? PLANE_INET_LOOPBACK : PLANE_INET_EXTERNAL, str_is_loopback(af, bind) ? "lo" : (multicast ? "multicast/group" : "unknown"), bind, &attrs, ip); } /* * parse_inet_file - parse one procfs inet socket table into endpoints. * @m: model receiving endpoint mappings. * @path: procfs table path (tcp/udp/raw variants). * @proto: protocol label used in rendered endpoint names. * @af: address family used to decode local bind addresses. * * Non-listeners/unowned sockets are skipped; output is best-effort. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void parse_inet_file(struct model *m, const char *path, const char *proto, int af) { FILE *f; char line[512]; int row = 0; f = fopen(path, "rte"); if (!f) return; __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(line, sizeof(line), f)) { unsigned int lport, rport, state; unsigned long txq, rxq, tmr, when, retr, inode; int d, uid, timeout; char laddrh[96], raddrh[96], more[128]; char addr[INET6_ADDRSTRLEN]; struct inode_proc *ip; more[0] = 0; if (!row++) continue; if (sscanf(line, "%d: %95[0-9A-Fa-f]:%X %95[0-9A-Fa-f]:%X %X " "%lX:%lX %lX:%lX %lX %d %d %lu %127s", &d, laddrh, &lport, raddrh, &rport, &state, &txq, &rxq, &tmr, &when, &retr, &uid, &timeout, &inode, more) < 14) continue; if ((strcmp(proto, "tcp") == 0 || strcmp(proto, "tcp6") == 0) && state != 0x0A) continue; if ((strcmp(proto, "udp") == 0 || strcmp(proto, "udp6") == 0 || strcmp(proto, "udplite") == 0 || strcmp(proto, "udplite6") == 0) && lport == 0) continue; ip = lookup_inode(m, inode); if (!ip) continue; if (af == AF_INET) { struct in_addr v4; unsigned int host; if (sscanf(laddrh, "%8x", &host) != 1) continue; /* * procfs inet tables print IPv4 addresses as host- * order hex. Assigning directly to s_addr keeps the * bytes correct on both little- and big-endian systems. */ v4.s_addr = host; if (!inet_ntop(AF_INET, &v4, addr, sizeof(addr))) continue; } else { unsigned char bytes[16] = { 0 }; int i; int ok = 1; if (strlen(laddrh) != 32) continue; for (i = 0; i < 4; i++) { uint32_t host; uint32_t net; if (sscanf(laddrh + (i * 8), "%8x", &host) != 1) { ok = 0; break; } net = htonl(host); memcpy(bytes + (i * 4), &net, sizeof(net)); } if (!ok) continue; if (!inet_ntop(AF_INET6, bytes, addr, sizeof(addr))) continue; } endpoint_to_ifaces(m, proto, af, addr, lport, ip->reuseport, ip); } fclose(f); } /* * parse_packet_file - parse /proc/net/packet and add packet endpoints. * @m: model receiving packet-plane endpoint/process mappings. * * Visibility depends on current netns and procfs access permissions. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void parse_packet_file(struct model *m) { FILE *f; char line[512], ifn[IF_NAMESIZE]; int row = 0; f = fopen("/proc/net/packet", "rte"); if (!f) return; __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(line, sizeof(line), f)) { unsigned long sk, inode; unsigned int ref, type, proto, iface, r, rmem, uid; struct inode_proc *ip; struct endpoint_attrs attrs; char bind[64], addr[64], name[64]; if (!row++) continue; if (sscanf(line, "%lX %u %u %X %u %u %u %u %lu", &sk, &ref, &type, &proto, &iface, &r, &rmem, &uid, &inode) < 9) continue; ip = lookup_inode(m, inode); if (!ip) continue; if (!if_indextoname(iface, ifn)) strcpy(ifn, "unknown"); snprintf(bind, sizeof(bind), "::"); snprintf(addr, sizeof(addr), "ifindex:%u", iface); snprintf(name, sizeof(name), "packet"); attrs.wildcard = 0; attrs.reuseport = 0; add_endpoint(m, name, bind, proto, PLANE_PACKET, ifn, addr, &attrs, ip); } fclose(f); } /* * parse_u32_hex_or_dec - parse @s as decimal or hexadecimal u32. * @s: numeric token from procfs/netlink text fields. * @out: destination value on successful parse. * * Returns 0 on success, -1 if @s is not a valid integer token. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int parse_u32_hex_or_dec(const char *s, unsigned int *out) { char *end; unsigned long v; int base = 10; const char *p; for (p = s; *p; p++) { if ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')) { base = 16; break; } } if (strncmp(s, "0x", 2) == 0 || strncmp(s, "0X", 2) == 0) base = 16; if (base == 10 && strlen(s) > 3 && s[0] == '0') base = 16; v = strtoul(s, &end, base); if (end == s || *end) return -1; *out = (unsigned int)v; return 0; } /* * parse_vsock_file - fallback VSOCK parser using /proc/net/vsock. * @m: model receiving parsed VSOCK endpoint/process mappings. * * Used when sock_diag support is unavailable or denied. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void parse_vsock_file(struct model *m) { FILE *f; char line[512]; f = fopen("/proc/net/vsock", "rte"); if (!f) return; __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(line, sizeof(line), f)) { char work[512]; char *tok[24]; char *save = NULL; char *local, *s, *sep; int tcnt = 0; unsigned long inode; unsigned int st, type, cid, port; struct inode_proc *ip; const char *kind; if (strstr(line, "Local") || strstr(line, "local") || strstr(line, "Num")) continue; snprintf(work, sizeof(work), "%s", line); s = strtok_r(work, " \t\n", &save); while (s && tcnt < (int)(sizeof(tok) / sizeof(tok[0]))) { tok[tcnt++] = s; s = strtok_r(NULL, " \t\n", &save); } if (tcnt < 5) continue; local = NULL; for (int i = 0; i < tcnt; i++) { if (strchr(tok[i], ':')) { local = tok[i]; break; } } if (!local) continue; sep = strchr(local, ':'); if (!sep) continue; *sep = '\0'; if (parse_u32_hex_or_dec(local, &cid) || parse_u32_hex_or_dec(sep + 1, &port)) continue; if (parse_u32_hex_or_dec(tok[tcnt - 2], &st)) continue; if (parse_u32_hex_or_dec(tok[tcnt - 3], &type)) continue; inode = strtoul(tok[tcnt - 1], NULL, 10); if (!inode) continue; if (type == SOCK_STREAM) { if (st != 0x0A) continue; kind = "stream"; } else if (type == SOCK_SEQPACKET) { if (st != 0x0A) continue; kind = "seqpacket"; } else if (type == SOCK_DGRAM) { if (port == 0) continue; kind = "dgram"; } else { continue; } ip = lookup_inode(m, inode); if (!ip) continue; add_vsock_endpoint(m, kind, cid, port, ip); } fclose(f); } /* * vsock_type_to_name - map VSOCK socket type to display label. * @type: SOCK_* type value from kernel socket metadata. * * Returns static string label, or NULL for unknown/unsupported type. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static const char *vsock_type_to_name(unsigned int type) { if (type == SOCK_STREAM) return "stream"; if (type == SOCK_DGRAM) return "dgram"; if (type == SOCK_SEQPACKET) return "seqpacket"; return NULL; } #ifdef HAVE_LINUX_VM_SOCKETS_DIAG_H /* * parse_vsock_diag_messages - consume VSOCK sock_diag dump replies. * @m: model receiving VSOCK endpoint/process mappings. * @fd: open NETLINK_SOCK_DIAG socket with pending VSOCK responses. * * Returns 0 on NLMSG_DONE, or -1 on malformed/error netlink messages. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int parse_vsock_diag_messages(struct model *m, int fd) { char buf[8192]; ssize_t len; while (1) { len = recv(fd, buf, sizeof(buf), 0); if (len < 0) { if (errno == EINTR) continue; return -1; } if (len == 0) return -1; struct nlmsghdr *nlh; unsigned int rem; if (len > UINT_MAX) return -1; rem = (unsigned int)len; for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, rem); nlh = NLMSG_NEXT(nlh, rem)) { struct vsock_diag_msg *r; struct inode_proc *ip; const char *kind; if (nlh->nlmsg_type == NLMSG_DONE) return 0; if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *e; if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*e))) return -1; e = NLMSG_DATA(nlh); if (e->error == 0) continue; errno = -e->error; return -1; } if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*r))) continue; r = NLMSG_DATA(nlh); if (r->vdiag_family != AF_VSOCK) continue; kind = vsock_type_to_name(r->vdiag_type); if (!kind) continue; if ((r->vdiag_type == SOCK_STREAM || r->vdiag_type == SOCK_SEQPACKET) && r->vdiag_state != TCP_LISTEN) continue; if (r->vdiag_type == SOCK_DGRAM && r->vdiag_src_port == 0) continue; ip = lookup_inode(m, r->vdiag_ino); if (!ip) continue; diag_dbg("vsock type=%u state=%u src=%u:%u dst=%u:%u ino=%u", r->vdiag_type, r->vdiag_state, r->vdiag_src_cid, r->vdiag_src_port, r->vdiag_dst_cid, r->vdiag_dst_port, r->vdiag_ino); add_vsock_endpoint(m, kind, r->vdiag_src_cid, r->vdiag_src_port, ip); } } } /* * parse_vsock_diag - request VSOCK listener dump via sock_diag netlink. * @m: model receiving parsed VSOCK endpoint/process mappings. * * Returns 0 on success, -1 with errno on permission/support/socket errors. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int parse_vsock_diag(struct model *m) { struct { struct nlmsghdr nlh; struct vsock_diag_req req; } req; struct sockaddr_nl sa; int fd; int rc = -1; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG); if (fd < 0) return -1; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.req)); req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.req.sdiag_family = AF_VSOCK; req.req.sdiag_protocol = 0; req.req.vdiag_states = ~0U; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_pid = 0; if (sendto(fd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) goto out; rc = parse_vsock_diag_messages(m, fd); out: close(fd); return rc; } #else /* * parse_vsock_diag - unsupported-build stub for VSOCK sock_diag path. * @m: unused model pointer. * * Returns -1 and sets errno=EOPNOTSUPP. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int parse_vsock_diag(struct model *m) { (void)m; errno = EOPNOTSUPP; return -1; } #endif /* * parse_diag_messages - consume inet sock_diag replies for @proto/@af. * @m: model receiving parsed endpoint mappings. * @fd: open NETLINK_SOCK_DIAG socket with pending responses. * @proto: requested protocol (SCTP/DCCP). * @af: requested address family (AF_INET/AF_INET6). * * Returns 0 on NLMSG_DONE, or -1 on malformed/error netlink messages. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int parse_diag_messages(struct model *m, int fd, int proto, int af) { char buf[8192]; ssize_t len; while (1) { len = recv(fd, buf, sizeof(buf), 0); if (len < 0) { if (errno == EINTR) continue; return -1; } if (len == 0) return -1; diag_dbg("recv proto=%d af=%d len=%zd", proto, af, len); struct nlmsghdr *nlh; unsigned int rem; if (len > UINT_MAX) return -1; rem = (unsigned int)len; for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, rem); nlh = NLMSG_NEXT(nlh, rem)) { struct inet_diag_msg *r; char addr[INET6_ADDRSTRLEN]; unsigned int port; struct inode_proc *ip; if (nlh->nlmsg_type == NLMSG_DONE) return 0; if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *e; if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*e))) return -1; e = NLMSG_DATA(nlh); if (e->error == 0) continue; errno = -e->error; diag_dbg("error proto=%d af=%d err=%d", proto, af, -e->error); return -1; } if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*r))) continue; r = NLMSG_DATA(nlh); ip = lookup_inode(m, r->idiag_inode); if (!ip) continue; port = ntohs(r->id.idiag_sport); if (!port) continue; if (af == AF_INET) { if (!inet_ntop(AF_INET, r->id.idiag_src, addr, sizeof(addr))) continue; } else { if (!inet_ntop(AF_INET6, r->id.idiag_src, addr, sizeof(addr))) continue; } endpoint_to_ifaces(m, proto == IPPROTO_SCTP ? "sctp" : "dccp", af, addr, port, ip->reuseport, ip); } } } /* * parse_diag_for_proto_af - issue one inet sock_diag listener dump request. * @m: model receiving parsed endpoint mappings. * @proto: protocol selector for the request. * @af: address family selector for the request. * * Best-effort helper; failures are tolerated by callers. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void parse_diag_for_proto_af(struct model *m, int proto, int af) { struct { struct nlmsghdr nlh; struct inet_diag_req_v2 req; } req; struct sockaddr_nl sa; int fd; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG); if (fd < 0) return; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.req)); req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.req.sdiag_family = af; req.req.sdiag_protocol = proto; req.req.idiag_states = 1U << TCP_LISTEN; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_pid = 0; if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) goto out; diag_dbg("send proto=%d af=%d len=%u", proto, af, req.nlh.nlmsg_len); if (send(fd, &req, req.nlh.nlmsg_len, 0) < 0) goto out; parse_diag_messages(m, fd, proto, af); out: close(fd); } /* * parse_diag_listeners - collect SCTP/DCCP listeners via sock_diag. * @m: model receiving discovered listener endpoints. * * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void parse_diag_listeners(struct model *m) { parse_diag_for_proto_af(m, IPPROTO_SCTP, AF_INET); parse_diag_for_proto_af(m, IPPROTO_SCTP, AF_INET6); parse_diag_for_proto_af(m, IPPROTO_DCCP, AF_INET); parse_diag_for_proto_af(m, IPPROTO_DCCP, AF_INET6); } /* * collect_endpoints - gather all endpoint classes into @m. * @m: model receiving inet, diag, packet, and vsock endpoint mappings. * * Data source is current netns procfs/netlink visibility. * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void collect_endpoints(struct model *m) { parse_inet_file(m, "/proc/net/tcp", "tcp", AF_INET); parse_inet_file(m, "/proc/net/tcp6", "tcp6", AF_INET6); parse_inet_file(m, "/proc/net/udp", "udp", AF_INET); parse_inet_file(m, "/proc/net/udp6", "udp6", AF_INET6); parse_inet_file(m, "/proc/net/udplite", "udplite", AF_INET); parse_inet_file(m, "/proc/net/udplite6", "udplite6", AF_INET6); parse_inet_file(m, "/proc/net/raw", "raw", AF_INET); parse_inet_file(m, "/proc/net/raw6", "raw6", AF_INET6); parse_diag_listeners(m); parse_packet_file(m); if (parse_vsock_diag(m) < 0) { diag_dbg("vsock diag unavailable (%s), falling back to /proc", strerror(errno)); parse_vsock_file(m); } } /* * wrap_to - choose a safe wrap index for one output line. * @text: source string being wrapped. * @from: starting offset in @text. * @limit: maximum columns to consume from @from. * * Returns next index to continue rendering from. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ enum color_state { COLOR_STATE_NONE, COLOR_STATE_ORANGE, COLOR_STATE_YELLOW, COLOR_STATE_GREEN, }; /* * color_state_code - map parser color state to ANSI start sequence. * @st: tracked color state carried across wrapped lines. * * Returns SGR color code for @st, or NULL for default color. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static const char *color_state_code(enum color_state st) { switch (st) { case COLOR_STATE_ORANGE: return COLOR_ORANGE; case COLOR_STATE_YELLOW: return COLOR_YELLOW; case COLOR_STATE_GREEN: return COLOR_GREEN; default: return NULL; } } /* * skip_ansi_sgr - advance index past one ANSI SGR escape sequence. * @text: source string potentially containing SGR escapes. * @i: current index, expected at ESC byte when sequence starts. * * Returns new index after sequence (or unchanged when not at SGR). * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int skip_ansi_sgr(const char *text, int i) { if (text[i] != '\033' || text[i + 1] != '[') return i; i += 2; while (text[i] && text[i] != 'm') i++; if (text[i] == 'm') i++; return i; } /* * scan_color_state - track active color state across wrapped text spans. * @text: source string segment being scanned. * @from: start index (inclusive) of rendered segment. * @to: end index (exclusive) of rendered segment. * @st: incoming color state before scanning @text[@from:@to]. * * Returns resulting color state after processing embedded SGR escapes. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static enum color_state scan_color_state(const char *text, int from, int to, enum color_state st) { int i; for (i = from; i < to && text[i]; ) { if (text[i] == '\033' && text[i + 1] == '[') { if (strncmp(text + i, COLOR_ORANGE, strlen(COLOR_ORANGE)) == 0) { st = COLOR_STATE_ORANGE; i += strlen(COLOR_ORANGE); continue; } if (strncmp(text + i, COLOR_YELLOW, strlen(COLOR_YELLOW)) == 0) { st = COLOR_STATE_YELLOW; i += strlen(COLOR_YELLOW); continue; } if (strncmp(text + i, COLOR_GREEN, strlen(COLOR_GREEN)) == 0) { st = COLOR_STATE_GREEN; i += strlen(COLOR_GREEN); continue; } if (strncmp(text + i, COLOR_RESET, strlen(COLOR_RESET)) == 0) { st = COLOR_STATE_NONE; i += strlen(COLOR_RESET); continue; } i = skip_ansi_sgr(text, i); continue; } i++; } return st; } static int wrap_to(const char *text, int from, int limit) { int i; int vis = 0; int space = -1; for (i = from; text[i] && vis < limit; ) { if (text[i] == '\033' && text[i + 1] == '[') { i = skip_ansi_sgr(text, i); continue; } if (text[i] == ' ') space = i; i++; vis++; } if (!text[i]) return i; if (space > from) return space; if (i == from) return from + 1; return i; } /* * print_tree_node - render one tree node line (with wrapping) to stdout. * @prefix: precomputed branch prefix glyphs. * @is_last: non-zero when this node is the last child. * @txt: node text to render. * @width: target display width used for wrapping. * * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void print_tree_node(const char *prefix, int is_last, const char *txt, int width) { char head[512]; char cont[512]; int pos = 0; int first = 1; enum color_state st = COLOR_STATE_NONE; snprintf(head, sizeof(head), "%s%s", prefix, is_last ? "└─ " : "├─ "); snprintf(cont, sizeof(cont), "%s%s", prefix, is_last ? " " : "│ "); while (1) { const char *lead = first ? head : cont; int lead_len = strlen(lead); int avail = width - lead_len; int to; const char *code; if (avail < 10) avail = 10; if (!txt[pos]) { printf("%s\n", lead); return; } to = wrap_to(txt, pos, avail); printf("%s", lead); code = color_state_code(st); if (!first && code) fputs(code, stdout); printf("%.*s", to - pos, txt + pos); st = scan_color_state(txt, pos, to, st); if (st != COLOR_STATE_NONE) fputs(COLOR_RESET, stdout); putchar('\n'); while (txt[to] == ' ') to++; pos = to; first = 0; if (!txt[pos]) return; } } /* * build_child_prefix - extend tree prefix glyphs for child nodes. * @dst: output buffer receiving generated prefix text. * @dst_sz: size of @dst in bytes. * @prefix: parent prefix string. * @parent_is_last: non-zero when parent is the last sibling. * * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void build_child_prefix(char *dst, size_t dst_sz, const char *prefix, int parent_is_last) { snprintf(dst, dst_sz, "%s%s", prefix, parent_is_last ? " " : "│ "); } /* * endpoint_cmp - qsort comparator for stable endpoint grouping. * @a: pointer to first endpoint element. * @b: pointer to second endpoint element. * * Sort order is plane, interface name, interface address, then label. * Returns negative/zero/positive qsort ordering result. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int endpoint_cmp(const void *a, const void *b) { const struct endpoint *ea = a, *eb = b; /* * Sort by plane for single-pass render grouping, then interface/protocol, * then bind (wildcards first), then port, then ifaddr/label for stability. */ if (ea->plane != eb->plane) return ea->plane - eb->plane; if (strcmp(ea->ifname, eb->ifname) != 0) return strcmp(ea->ifname, eb->ifname); if (strcmp(ea->proto, eb->proto) != 0) return strcmp(ea->proto, eb->proto); if (bind_sort_cmp(ea->bind, eb->bind) != 0) return bind_sort_cmp(ea->bind, eb->bind); if (ea->port != eb->port) return ea->port < eb->port ? -1 : 1; if (strcmp(ea->ifaddr, eb->ifaddr) != 0) return strcmp(ea->ifaddr, eb->ifaddr); return strcmp(ea->label, eb->label); } /* * format_bind_node - normalize bind text for tree display. * @dst: output buffer receiving display text. * @dst_sz: size of @dst in bytes. * @bind: endpoint bind address to format. * * Returns no value. * Side effects/assumptions: Writes formatted bind label into @dst. */ static void format_bind_node(char *dst, size_t dst_sz, const char *bind) { if (strcmp(bind, "0.0.0.0") == 0 || strcmp(bind, "::") == 0) snprintf(dst, dst_sz, "*"); else if (strchr(bind, ':')) snprintf(dst, dst_sz, "[%s]", bind); else snprintf(dst, dst_sz, "%s", bind); } /* * bind_sort_cmp - compare bind addresses with wildcard-first ordering. * @a: first bind address. * @b: second bind address. * * Returns negative/zero/positive ordering suitable for qsort tie-breaks. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static int bind_sort_cmp(const char *a, const char *b) { int a_star = strcmp(a, "0.0.0.0") == 0 || strcmp(a, "::") == 0; int b_star = strcmp(b, "0.0.0.0") == 0 || strcmp(b, "::") == 0; if (a_star != b_star) return a_star ? -1 : 1; return strcmp(a, b); } /* * render_tree_process_details - emit one process subtree under an endpoint. * @prefix: parent tree prefix for process node. * @is_last: non-zero when process node is final sibling. * @p: process metadata to render. * @e: endpoint context supplying endpoint-derived flags. * @width: display width used for wrapping rendered nodes. * * Returns no value. * Side effects/assumptions: Writes formatted tree output to stdout. */ static void render_tree_process_details(const char *prefix, int is_last, struct process_info *p, const struct endpoint *e, int width) { char pfx_child[256], pfx_def[256], pfx_flags[256]; char line[4096]; const char *def_nodes[8]; char def_buf[8][512]; size_t def_n = 0; size_t ai; unsigned int flags = 0; enum cap_severity priv_sev = caps_worst_severity(p->caps); snprintf(line, sizeof(line), "%s (pid=%d uid=%d%s%s%s%s)", p->comm, p->pid, p->uid, p->exe ? " exe=" : "", p->exe ? p->exe : "", p->unit ? " unit=" : "", p->unit ? p->unit : ""); print_tree_node(prefix, is_last, line, width); build_child_prefix(pfx_child, sizeof(pfx_child), prefix, is_last); if (strcmp(p->caps, "(full)") == 0 || strcmp(p->caps, "(none)") == 0) { const char *c = strcmp(p->caps, "(full)") == 0 ? COLOR_ORANGE : COLOR_GREEN; if (use_color) snprintf(line, sizeof(line), "caps: %s%s%s", c, p->caps, COLOR_RESET); else snprintf(line, sizeof(line), "caps: %s", p->caps); } else { char capsbuf[3072] = ""; char *tmp = xstrdup(p->caps); char *save = NULL; char *tok; int first = 1; if (!tmp) { snprintf(line, sizeof(line), "caps: %s", p->caps); } else { for (tok = strtok_r(tmp, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) { char part[256]; char *t = tok; enum cap_severity sev; while (*t == ' ') t++; sev = cap_name_severity(t); if (use_color && sev_color(sev)) snprintf(part, sizeof(part), "%s%s%s", sev_color(sev), t, COLOR_RESET); else snprintf(part, sizeof(part), "%s", t); if (!first) strncat(capsbuf, ", ", sizeof(capsbuf)-strlen(capsbuf)-1); strncat(capsbuf, part, sizeof(capsbuf)-strlen(capsbuf)-1); first = 0; } free(tmp); if (p->ambient_present) { if (use_color) strncat(capsbuf, " [\033[38;5;208mambient-present\033[0m]", sizeof(capsbuf)-strlen(capsbuf)-1); else strncat(capsbuf, " [ambient-present]", sizeof(capsbuf)-strlen(capsbuf)-1); } if (p->open_ended_bounding) { if (use_color && strcmp(p->caps, "(full)") != 0 && caps_contains_token(p->caps, "setpcap")) strncat(capsbuf, " [\033[38;5;208mopen-ended-bounding\033[0m]", sizeof(capsbuf) - strlen(capsbuf) - 1); else strncat(capsbuf, " [open-ended-bounding]", sizeof(capsbuf) - strlen(capsbuf) - 1); } snprintf(line, sizeof(line), "caps: %s", capsbuf); } } print_tree_node(pfx_child, 0, line, width); if (p->ambient_caps) { if (use_color) snprintf(line, sizeof(line), "ambient: %s%s%s", COLOR_ORANGE, p->ambient_caps, COLOR_RESET); else snprintf(line, sizeof(line), "ambient: %s", p->ambient_caps); print_tree_node(pfx_child, 0, line, width); } snprintf(def_buf[def_n], sizeof(def_buf[def_n]), DEFENSES_RUNS_AS_KEY ": %s%s%s", strcmp(p->defenses.runs_as_nonroot, "yes") == 0 && use_color ? COLOR_GREEN : (strcmp(p->defenses.runs_as_nonroot, "no") == 0 && use_color ? COLOR_YELLOW : ""), p->defenses.runs_as_nonroot, use_color && (strcmp(p->defenses.runs_as_nonroot, "yes") == 0 || strcmp(p->defenses.runs_as_nonroot, "no") == 0) ? COLOR_RESET : ""); def_nodes[def_n] = def_buf[def_n]; def_n++; if (strcmp(p->defenses.no_new_privs, "yes") == 0 && use_color) snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "no_new_privs: %syes%s", COLOR_GREEN, COLOR_RESET); else if (strcmp(p->defenses.no_new_privs, "no") == 0 && use_color) snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "no_new_privs: %sno%s", COLOR_YELLOW, COLOR_RESET); else snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "no_new_privs: %s", p->defenses.no_new_privs); def_nodes[def_n] = def_buf[def_n]; def_n++; if ((strcmp(p->defenses.seccomp, "filter") == 0 || strcmp(p->defenses.seccomp, "strict") == 0) && use_color) snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "seccomp: %s%s%s", COLOR_GREEN, p->defenses.seccomp, COLOR_RESET); else if (strcmp(p->defenses.seccomp, "disabled") == 0 && use_color) snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "seccomp: %sdisabled%s", COLOR_YELLOW, COLOR_RESET); else snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "seccomp: %s", p->defenses.seccomp); def_nodes[def_n] = def_buf[def_n]; def_n++; if (p->defenses.lsm_label) { if (use_color && strstr(p->defenses.lsm_label, "unconfined_t")) snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "lsm: %s%s%s", COLOR_ORANGE, p->defenses.lsm_label, COLOR_RESET); else if (use_color && p->defenses.lsm_label[0]) snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "lsm: %s%s%s", COLOR_GREEN, p->defenses.lsm_label, COLOR_RESET); else snprintf(def_buf[def_n], sizeof(def_buf[def_n]), "lsm: %s", p->defenses.lsm_label); def_nodes[def_n] = def_buf[def_n]; def_n++; } print_tree_node(pfx_child, 0, "defenses", width); build_child_prefix(pfx_def, sizeof(pfx_def), pfx_child, 0); for (ai = 0; ai < def_n; ai++) print_tree_node(pfx_def, ai + 1 == def_n, def_nodes[ai], width); if (e->plane == PLANE_VSOCK) { flags |= FLAG_HYPERVISOR_PLANE; if (e->port == 22) flags |= FLAG_SSH_VSOCK_22; } else { if (e->wildcard_bind) flags |= FLAG_WILDCARD_BIND; if (e->reuseport) flags |= FLAG_REUSEPORT; } if (p->has_privileged_caps) flags |= FLAG_PRIVILEGED_CAPS; print_tree_node(pfx_child, 1, "flags", width); build_child_prefix(pfx_flags, sizeof(pfx_flags), pfx_child, 1); print_flag_nodes(pfx_flags, width, flags, priv_sev); } /* * render_json_process - emit one process object inside endpoint JSON arrays. * @p: process metadata record to serialize. * @ep: endpoint context contributing endpoint-related flags. * @indent: indentation prefix already prepared by caller. * * Returns no value. * Side effects/assumptions: Writes JSON fragments to stdout. */ static void render_json_process(struct process_info *p, const struct endpoint *ep, const char *indent) { int firstf = 1; printf("%s{\"comm\": ", indent); json_escape(p->comm); if (p->exe) { printf(", \"exe\": "); json_escape(p->exe); } printf(", \"pid\": %d, \"uid\": %d", p->pid, p->uid); if (p->unit) { printf(", \"unit\": "); json_escape(p->unit); } printf(", \"caps\": "); json_escape(p->caps); printf(", \"ambient_present\": %s", p->ambient_present ? "true" : "false"); if (p->ambient_caps) { printf(", \"ambient_caps\": "); json_escape(p->ambient_caps); } printf(", \"open_ended_bounding\": %s", p->open_ended_bounding ? "true" : "false"); printf(", \"defenses\": {\"" DEFENSES_RUNS_AS_KEY "\": "); json_escape(p->defenses.runs_as_nonroot); printf(", \"no_new_privs\": "); json_escape(p->defenses.no_new_privs); printf(", \"seccomp\": "); json_escape(p->defenses.seccomp); if (p->defenses.lsm_label) { printf(", \"lsm\": "); json_escape(p->defenses.lsm_label); } printf("}, \"flags\": ["); if (ep->plane == PLANE_VSOCK) { json_escape("hypervisor-plane"); firstf = 0; if (ep->port == 22) { printf(", "); json_escape("ssh-on-vsock-port-22"); } } else { if (ep->wildcard_bind) { if (!firstf) printf(", "); json_escape("wildcard-bind"); firstf = 0; } if (ep->reuseport) { if (!firstf) printf(", "); json_escape("reuseport"); firstf = 0; } } if (p->has_privileged_caps) { if (!firstf) printf(", "); json_escape("privileged-caps"); } printf("]}"); } /* * render_tree - print human-readable advanced report as a tree. * @m: model to render; endpoint array is sorted in place before printing. * * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void render_tree(struct model *m) { size_t i; int planes[PLANE_COUNT]; size_t plane_n = 0; int width = get_width(); if (m->eps_n > 1) qsort(m->eps, m->eps_n, sizeof(struct endpoint), endpoint_cmp); puts("netcap --advanced"); for (i = 0; i < PLANE_COUNT; i++) { size_t j; for (j = 0; j < m->eps_n; j++) { if (m->eps[j].plane == (enum plane_kind)i) { planes[plane_n++] = i; break; } } } for (i = 0; i < plane_n; i++) { /* Tree level: plane (INET external/loopback, packet, vsock). */ int plane = planes[i]; int plane_last = (i + 1 == plane_n); char pfx_plane[256] = ""; char pfx_iface[256]; const char *plane_name = plane == PLANE_INET_EXTERNAL ? "INET (external)" : plane == PLANE_INET_LOOPBACK ? "INET (loopback)" : plane == PLANE_PACKET ? PLANE_PACKET_NAME : "VSOCK"; size_t j = 0; print_tree_node(pfx_plane, plane_last, plane_name, width); build_child_prefix(pfx_iface, sizeof(pfx_iface), pfx_plane, plane_last); if (plane == PLANE_VSOCK) { /* VSOCK has no iface/address hierarchy, so print endpoint-first. */ for (j = 0; j < m->eps_n; j++) { struct endpoint *e = &m->eps[j]; char pfx_proc[256]; int ep_last; size_t k; if (e->plane != PLANE_VSOCK) continue; ep_last = 1; for (size_t n = j + 1; n < m->eps_n; n++) { if (m->eps[n].plane == PLANE_VSOCK) { ep_last = 0; break; } } print_tree_node(pfx_iface, ep_last, e->label, width); build_child_prefix(pfx_proc, sizeof(pfx_proc), pfx_iface, ep_last); for (k = 0; k < e->procs_n; k++) { struct process_info *p = e->procs[k]; int proc_last = (k + 1 == e->procs_n); render_tree_process_details(pfx_proc, proc_last, p, e, width); } } continue; } while (j < m->eps_n) { /* Tree level: interface grouping within the current plane. */ size_t iface_start, iface_end; char iface_line[160]; char pfx_iface_child[256]; int iface_last; if (m->eps[j].plane != (enum plane_kind)plane) { j++; continue; } iface_start = j; iface_end = j + 1; while (iface_end < m->eps_n && m->eps[iface_end].plane == (enum plane_kind)plane && strcmp(m->eps[iface_end].ifname, m->eps[iface_start].ifname) == 0) iface_end++; iface_last = 1; if (iface_end < m->eps_n && m->eps[iface_end].plane == (enum plane_kind)plane) iface_last = 0; snprintf(iface_line, sizeof(iface_line), "%s", m->eps[iface_start].ifname); print_tree_node(pfx_iface, iface_last, iface_line, width); build_child_prefix(pfx_iface_child, sizeof(pfx_iface_child), pfx_iface, iface_last); { char pfx_proto_root[256]; snprintf(pfx_proto_root, sizeof(pfx_proto_root), "%s", pfx_iface_child); for (j = iface_start; j < iface_end; ) { /* Tree level: protocol grouping on this interface. */ size_t proto_start = j, proto_end; char pfx_bind[256]; int proto_last; proto_end = j + 1; while (proto_end < iface_end && strcmp(m->eps[proto_end].proto, m->eps[proto_start].proto) == 0) proto_end++; proto_last = (proto_end == iface_end); /* Highlight higher-risk raw/packet protocol families. */ if (use_color && (strcmp(m->eps[proto_start].proto, "raw") == 0 || strcmp(m->eps[proto_start].proto, "raw6") == 0 || strcmp(m->eps[proto_start].proto, "packet") == 0)) { char pbuf[64]; snprintf(pbuf, sizeof(pbuf), "%s%s%s", COLOR_YELLOW, m->eps[proto_start].proto, COLOR_RESET); print_tree_node(pfx_proto_root, proto_last, pbuf, width); } else { print_tree_node(pfx_proto_root, proto_last, m->eps[proto_start].proto, width); } build_child_prefix(pfx_bind, sizeof(pfx_bind), pfx_proto_root, proto_last); { size_t bi = proto_start; while (bi < proto_end) { /* Tree level: bind address (wildcard/specific). */ size_t bind_start = bi; size_t bind_end; char bind_line[128], pfx_port[256]; int bind_last; bind_end = bi + 1; while (bind_end < proto_end && strcmp(m->eps[bind_end].bind, m->eps[bind_start].bind) == 0) bind_end++; bind_last = (bind_end == proto_end); format_bind_node(bind_line, sizeof(bind_line), m->eps[bind_start].bind); print_tree_node(pfx_bind, bind_last, bind_line, width); build_child_prefix(pfx_port, sizeof(pfx_port), pfx_bind, bind_last); for (bi = bind_start; bi < bind_end; ) { /* Tree level: port number under each bind. */ size_t port_start = bi; size_t port_end; char pfx_proc[256], port_line[64]; int port_last; size_t k; struct pidset seen; port_end = bi + 1; while (port_end < bind_end && m->eps[port_end].port == m->eps[port_start].port) port_end++; port_last = (port_end == bind_end); snprintf(port_line, sizeof(port_line), "%u", m->eps[port_start].port); print_tree_node(pfx_port, port_last, port_line, width); build_child_prefix(pfx_proc, sizeof(pfx_proc), pfx_port, port_last); if (pidset_init(&seen)) { bi = port_end; continue; } for (k = port_start; k < port_end; k++) { /* Tree level: process details under the current port. */ struct endpoint *e = &m->eps[k]; size_t pi; for (pi = 0; pi < e->procs_n; pi++) { int seen_rc; struct process_info *p = e->procs[pi]; int proc_last; /* * Deduplicate processes that appear under multiple * endpoints sharing this grouped port. */ seen_rc = pidset_test_and_add(&seen, p->pid); if (seen_rc) continue; proc_last = (k + 1 == port_end) && (pi + 1 == e->procs_n); render_tree_process_details(pfx_proc, proc_last, p, e, width); } } pidset_free(&seen); bi = port_end; } } } j = proto_end; } } j = iface_end; } } } /* * render_interfaces_text - print interface inventory in human-readable form. * @m: model whose interface inventory is rendered. * * Returns no value. */ static void render_interfaces_text(struct model *m) { size_t i; sort_interfaces(m); puts("netcap --advanced --list-interfaces"); for (i = 0; i < m->ifaces_n; i++) printf("%s\n", m->ifaces[i].name); } /* * json_escape - write @s as a quoted JSON string to stdout. * @s: UTF-8/text string to emit as one JSON string literal. * * Control characters and quotes are escaped; caller handles separators. * Returns no value. * Side effects/assumptions: Writes to stdout and may read procfs/netns * state indirectly via caller-supplied model-derived strings. */ static void json_escape(const char *s) { const unsigned char *p = (const unsigned char *)s; putchar('"'); for (; *p; p++) { if (*p == '"') fputs("\\\"", stdout); else if (*p == '\\') fputs("\\\\", stdout); else if (*p < 0x20) printf("\\u%04x", *p); else putchar(*p); } putchar('"'); } /* * render_json - print machine-readable advanced report JSON to stdout. * @m: model to render; endpoint array is sorted in place before printing. * * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void render_json(struct model *m) { size_t i, j, k, l; if (m->eps_n > 1) qsort(m->eps, m->eps_n, sizeof(struct endpoint), endpoint_cmp); puts("{"); puts(" \"schema_version\": 1,"); puts(" \"planes\": ["); for (i = 0; i < PLANE_COUNT; i++) { const char *pname = i == PLANE_INET_EXTERNAL ? "INET" : i == PLANE_INET_LOOPBACK ? "INET" : i == PLANE_PACKET ? PLANE_PACKET_NAME : "VSOCK"; const char *scope = i == PLANE_INET_EXTERNAL ? "external" : i == PLANE_INET_LOOPBACK ? "loopback" : NULL; struct strset seen_ifaces = { 0 }; int first_vsock = 1; int first_if = 1; printf(" {\"name\": "); json_escape(pname); if (scope) { printf(", \"scope\": "); json_escape(scope); } if (i == PLANE_VSOCK) puts(", \"endpoints\": ["); else puts(", \"ifaces\": ["); if (i == PLANE_VSOCK) { for (j = 0; j < m->eps_n; j++) { struct endpoint *ep = &m->eps[j]; if (ep->plane != PLANE_VSOCK) continue; if (!first_vsock) puts(","); first_vsock = 0; printf(" {\"label\": "); json_escape(ep->label); printf(", \"vsock_type\": "); json_escape(ep->proto); printf(", \"cid\": "); if (ep->vsock_cid == VMADDR_CID_ANY) json_escape("ANY"); else printf("%u", ep->vsock_cid); printf(", \"port\": %u", ep->port); puts(", \"processes\": ["); for (size_t pi = 0; pi < ep->procs_n; pi++) { struct process_info *p = ep->procs[pi]; render_json_process(p, ep, " "); if (pi + 1 != ep->procs_n) puts(","); else putchar('\n'); } puts(" ]}"); } puts(" ]}"); if (i + 1 != PLANE_COUNT) puts(","); strset_free(&seen_ifaces); continue; } for (j = 0; j < m->eps_n; j++) { struct strset seen_addrs = { 0 }; const char *ifn = m->eps[j].ifname; int first_addr = 1; int seen; if (m->eps[j].plane != (enum plane_kind)i) continue; seen = strset_add(&seen_ifaces, ifn); if (seen < 0) { strset_free(&seen_addrs); continue; } if (seen == 0) continue; if (!first_if) puts(","); first_if = 0; printf(" {\"name\": "); json_escape(ifn); puts(", \"addrs\": ["); for (k = 0; k < m->eps_n; k++) { const char *ifa = m->eps[k].ifaddr; int first_ep = 1; int addr_seen; if (m->eps[k].plane != (enum plane_kind)i || strcmp(m->eps[k].ifname, ifn) != 0) continue; addr_seen = strset_add(&seen_addrs, ifa); if (addr_seen < 0) break; if (addr_seen == 0) continue; if (!first_addr) puts(","); first_addr = 0; printf(" {\"addr\": "); json_escape(ifa); puts(", \"endpoints\": ["); for (l = 0; l < m->eps_n; l++) { struct endpoint *ep = &m->eps[l]; if (ep->plane != (enum plane_kind)i || strcmp(ep->ifname, ifn) != 0 || strcmp(ep->ifaddr, ifa) != 0) continue; if (!first_ep) puts(","); first_ep = 0; printf(" {\"label\": "); json_escape(ep->label); printf(", \"proto\": "); json_escape(ep->proto); printf(", \"bind\": "); json_escape(ep->bind); printf(", \"port\": %u", ep->port); puts(", \"processes\": ["); for (size_t pi = 0; pi < ep->procs_n; pi++) { struct process_info *p = ep->procs[pi]; render_json_process(p, ep, " "); if (pi + 1 != ep->procs_n) puts(","); else putchar('\n'); } puts(" ]}"); } puts(" ]}"); } puts(" ]}"); strset_free(&seen_addrs); } puts(" ]}"); strset_free(&seen_ifaces); if (i + 1 != PLANE_COUNT) puts(","); } puts(" ]"); puts("}"); } /* * render_interfaces_json - print interface inventory as JSON. * @m: model whose interface inventory is rendered. * * Returns no value. */ static void render_interfaces_json(struct model *m) { size_t i; sort_interfaces(m); puts("{"); puts(" \"schema_version\": 1,"); puts(" \"interfaces\": ["); for (i = 0; i < m->ifaces_n; i++) { printf(" "); json_escape(m->ifaces[i].name); printf("%s\n", i + 1 == m->ifaces_n ? "" : ","); } puts(" ]"); puts("}"); } /* * free_endpoint - free one endpoint and all owned dynamic fields. * @e: endpoint entry pointer, or NULL. * * Returns no value. */ static void free_endpoint(struct endpoint *e) { if (!e) return; free(e->proto); free(e->bind); free(e->label); free(e->ifname); free(e->ifaddr); free(e->procs); } /* * filter_model_interface - keep only one named interface in @m. * @m: model container to prune in place. * @ifname: interface name to retain. * * Returns no value. */ static void filter_model_interface(struct model *m, const char *ifname) { size_t i, out; if (!m || !ifname) return; for (i = 0, out = 0; i < m->ifaces_n; i++) { if (strcmp(m->ifaces[i].name, ifname) == 0) { if (out != i) m->ifaces[out] = m->ifaces[i]; out++; continue; } free(m->ifaces[i].name); for (size_t j = 0; j < m->ifaces[i].addrs_n; j++) free(m->ifaces[i].addrs[j].addr); free(m->ifaces[i].addrs); } m->ifaces_n = out; for (i = 0, out = 0; i < m->eps_n; i++) { if (strcmp(m->eps[i].ifname, ifname) == 0) { if (out != i) m->eps[out] = m->eps[i]; out++; continue; } free_endpoint(&m->eps[i]); } m->eps_n = out; } /* * free_process - free one process_info and all owned dynamic fields. * @p: process entry pointer, or NULL. * * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void free_process(struct process_info *p) { if (!p) return; free(p->comm); free(p->exe); free(p->unit); free(p->caps); free(p->ambient_caps); free(p->defenses.runs_as_nonroot); free(p->defenses.no_new_privs); free(p->defenses.seccomp); free(p->defenses.lsm_label); free(p); } /* * free_model - free all heap allocations referenced by @m. * @m: model container whose internal arrays/strings are released. * * Returns no value. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ static void free_model(struct model *m) { size_t i, j; for (i = 0; i < m->ifaces_n; i++) { free(m->ifaces[i].name); for (j = 0; j < m->ifaces[i].addrs_n; j++) free(m->ifaces[i].addrs[j].addr); free(m->ifaces[i].addrs); } free(m->ifaces); for (i = 0; i < m->procs_n; i++) free_process(m->procs[i]); free(m->procs); for (i = 0; i < m->inode_n; i++) free(m->inode_map[i].procs); free(m->inode_map); free(m->inode_slots); for (i = 0; i < m->eps_n; i++) free_endpoint(&m->eps[i]); free(m->eps); } /* * netcap_advanced_main - entry point for "netcap --advanced" mode. * @opts: parsed options; must be non-NULL and have @advanced set. * * Returns 0 after rendering advanced output, or 1 when advanced mode * is not requested. * Side effects/assumptions: Reads procfs/netlink in the current network * namespace, prints to stdout/stderr, and root is typically needed for a * fuller process-to-socket ownership mapping. */ int netcap_advanced_main(const struct netcap_opts *opts) { struct model m; if (!opts || !opts->advanced) return 1; memset(&m, 0, sizeof(m)); if (collect_interfaces(&m) != 0) { fprintf(stderr, "warning: failed to enumerate interfaces\n"); } if (opts->interface) filter_model_interface(&m, opts->interface); if (opts->list_interfaces) { if (opts->json) render_interfaces_json(&m); else render_interfaces_text(&m); free_model(&m); return 0; } collect_proc_inodes(&m); collect_endpoints(&m); if (opts->interface) filter_model_interface(&m, opts->interface); use_color = !opts->json && !opts->no_color && isatty(STDOUT_FILENO); if (opts->json) render_json(&m); else render_tree(&m); free_model(&m); return 0; } stevegrubb-libcap-ng-0ab44af/utils/netcap-advanced.h000066400000000000000000000021511516575034500225370ustar00rootroot00000000000000/* * netcap-advanced.h - Advanced capability analysis header file * Copyright (c) 2026 Steve Grubb * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef NETCAP_ADVANCED_H #define NETCAP_ADVANCED_H struct netcap_opts { int advanced; int list_interfaces; int json; int no_color; const char *interface; }; int netcap_advanced_main(const struct netcap_opts *opts); #endif stevegrubb-libcap-ng-0ab44af/utils/netcap.8.in000066400000000000000000000260341516575034500213270ustar00rootroot00000000000000.TH NETCAP: "8" "March 2026" "Red Hat" "System Administration Utilities" .SH NAME netcap \- a program to review network capability posture .SH SYNOPSIS .B netcap .\" BEGIN_ADVANCED .RI [ --advanced .RI [ --interface " IFACE ] .RI [ --list-interfaces ] .RI [ --json ] .RI [ --no-color ]] .\" END_ADVANCED .SH DESCRIPTION \fBnetcap\fP is a program that prints out a report of process capabilities. By default, it runs in its historical filtered mode: if an application is using tcp, udp, raw, or packet family sockets AND has any capabilities, it will be in the report. If the process has partial capabilities, it is further examined to see if it has an open-ended bounding set. If this is found to be true, a '+' symbol is added. If the process has ambient capabilities, a '@' symbol is added. .\" BEGIN_ADVANCED With \fB\-\-advanced\fP, \fBnetcap\fP switches to bind/listener inventory mode for the current network namespace. In this mode, reachable binds/listeners are reported regardless of whether the owning process has capabilities. .\" END_ADVANCED Some directories in the /proc file system are readonly by root. The program will try to access and report what it can. .\" BEGIN_ADVANCED Full output normally requires root, or a combination of \fBCAP_DAC_READ_SEARCH\fP (to read other processes' \fB/proc//fd\fP) and \fBCAP_NET_ADMIN\fP (for \fBNETLINK_SOCK_DIAG\fP queries that cover SCTP, DCCP, and VSOCK). Without these permissions, the report is partial; missing sock_diag-derived protocols are omitted without an explicit warning. .\" END_ADVANCED .\" BEGIN_ADVANCED .SH OPTIONS .TP .B \-\-advanced Enable the posture/tree view. This mode inventories reachable binds in the current network namespace and shows per-process capabilities, defenses, and flags. .TP .BI \-\-interface " IFACE" With \fB\-\-advanced\fP, limit the report to the named interface. Output format and wrapping are unchanged; only interface-scoped rows for that interface are kept. This also filters \fB\-\-list\-interfaces\fP output to the named interface. .TP .B \-\-list\-interfaces With \fB\-\-advanced\fP, list the current network namespace interface names instead of the exposure tree. .TP .B \-\-json With \fB\-\-advanced\fP, emit machine-readable JSON instead of the tree view. When combined with \fB\-\-list\-interfaces\fP, emit machine-readable interface inventory JSON instead of text. .TP .B \-\-no\-color Suppress colored output. By default, when stdout is a terminal, keywords are colorized by severity to highlight problems, cautions, and positive hardening state. .\" END_ADVANCED .\" BEGIN_ADVANCED .SH "ADVANCED MODE" Advanced mode is an attack-surface and posture inventory for the current network namespace. It is intended for administrators who need a structured view of bind ownership and process hardening state. With \fB\-\-advanced --list\-interfaces\fP, the advanced-mode interface snapshot is rendered directly without enumerating listeners. This is intended as a simple interface enumeration step before later per-interface analysis. With \fB\-\-advanced --interface IFACE\fP, the same inventory and tree/JSON renderers are used, but only endpoints projected onto that interface are retained. The tree layout is: .RS Planes \-> interface \-> protocol \-> bind \-> port \-> process \-> caps/defenses/flags .RE For VSOCK planes, the interface/address hierarchy is skipped and rendered as: .RS Planes \-> endpoint \-> process \-> caps/defenses/flags .RE Planes are grouped as INET (external), INET (loopback), VSOCK, and LINK-LAYER. These names match the tree output exactly. The console tree is a causal exposure chain: protocol, then bind address, then port, then owning process. Wildcard binds are printed as \fB*\fP. In tree view, a single \fB0.0.0.0\fP or \fB::\fP listener is expanded onto every non-loopback interface in the current namespace. The same daemon may therefore appear under multiple interfaces, reflecting wildcard-bind reachability. Some endpoints may appear under interface \fBmulticast/group\fP when bound to multicast addresses that do not map to a single interface; these represent group-joined sockets rather than point-to-point listeners. Under each protocol, \fBbind\fP nodes are socket bind addresses (exposure), and \fBport\fP nodes are children of binds. Each process entry may include a \fBunit\fP field showing the systemd service or scope name, extracted from the process cgroup hierarchy; it is omitted when the process is not managed by systemd. Each process line also includes an optional \fBexe\fP field showing the full executable path read from \fB/proc//exe\fP; it is omitted for kernel threads or when the path is unreadable. The kernel \fBName:\fP field shown as \fBcomm\fP is truncated to 15 characters, so \fBexe\fP provides the complete binary path. Socket selection is protocol-specific: .IP \[bu] 2 TCP: LISTEN sockets only. .IP \[bu] 2 UDP/UDPLITE: bound sockets. .IP \[bu] 2 SCTP/DCCP: listener sockets only; established flows are not shown. These protocols are discovered via \fBNETLINK_SOCK_DIAG\fP, which requires \fBCAP_NET_ADMIN\fP; without that capability, SCTP and DCCP listeners are silently absent from the report. .IP \[bu] 2 VSOCK: stream LISTEN sockets, plus bound datagram/seqpacket sockets. .IP \[bu] 2 RAW/PACKET: shown as binds; semantics differ from port listeners. The defenses section is best-effort: \fBno_new_privs\fP and \fBseccomp\fP mode are read from \fB/proc//status\fP when available, and the LSM label is read from \fB/proc//attr/current\fP when available. \fBruns_as_nonroot\fP is always reported. Flags annotate notable conditions: .IP \[bu] 2 \fBwildcard-bind\fP: bind is bound to a wildcard address (for example 0.0.0.0 or ::). .IP \[bu] 2 \fBhypervisor-plane\fP: bind is in the VSOCK communication plane. .IP \[bu] 2 \fBssh-on-vsock-port-22\fP: VSOCK bind uses port 22. .IP \[bu] 2 \fBprivileged-caps\fP: process has capabilities considered privileged for attack-surface review (CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_DAC_READ_SEARCH, CAP_NET_ADMIN, and CAP_NET_RAW). The \fBcaps\fP field may include bracketed annotations: \fB[ambient-present]\fP indicates ambient capabilities are set, and \fB[open-ended-bounding]\fP indicates the bounding set is non-empty (the process or its children can potentially gain additional capabilities). .SH "JSON OUTPUT" When \fB\-\-advanced --json\fP is used, the top-level object contains: .IP \[bu] 2 \fBschema_version\fP (integer). .IP \[bu] 2 \fBplanes\fP (array). Each plane object contains \fBname\fP, optional \fBscope\fP (\fBexternal\fP or \fBloopback\fP), and either: .IP \[bu] 2 \fBifaces\fP (INET/LINK-LAYER planes), or .IP \[bu] 2 \fBendpoints\fP (VSOCK planes). Under \fBifaces\fP: each interface has \fBname\fP and \fBaddrs\fP (array). Each addr object has \fBaddr\fP and \fBendpoints\fP (array). For INET and LINK-LAYER planes, each endpoint object has \fBlabel\fP, \fBproto\fP, \fBbind\fP, \fBport\fP, and \fBprocesses\fP (array). For VSOCK planes, endpoint objects use \fBlabel\fP, \fBvsock_type\fP (socket type string: \fBstream\fP/\fBdgram\fP/\fBseqpacket\fP), \fBcid\fP (source CID or the string \fBANY\fP), \fBport\fP, and \fBprocesses\fP; they do not include \fBproto\fP or \fBbind\fP. Each process object has \fBcomm\fP, optional \fBexe\fP, \fBpid\fP, \fBuid\fP, optional \fBunit\fP, \fBcaps\fP, \fBambient_present\fP (boolean), \fBopen_ended_bounding\fP (boolean), \fBdefenses\fP object, and \fBflags\fP array. \fBambient_present\fP and \fBopen_ended_bounding\fP are the structured equivalents of the \fB[ambient-present]\fP and \fB[open-ended-bounding]\fP annotations that may appear in the caps text string; both fields are always present in JSON output. Fields under endpoints/processes/defenses may be omitted when unavailable. When process defenses are present, they include \fBruns_as_nonroot\fP, derived from the real UID (first field of \fBUid:\fP in \fB/proc//status\fP): \fByes\fP when real uid != 0, \fBno\fP when real uid == 0. A process that started as root and later changed only effective UID via setuid still reports \fBno\fP. If a plane has no discovered binds, it appears with an empty list. If a consumer does not receive a given plane, treat it as none found or omitted by the producer. When \fB\-\-advanced --list\-interfaces --json\fP is used, the top-level object contains \fBschema_version\fP (integer) and \fBinterfaces\fP (array of interface-name strings). .\" END_ADVANCED .SH INTERPRETATION \fBnetcap\fP reports processes that are both network-facing and running with capabilities. The capability set shown is the permitted set. A '+' annotation means the process has an open-ended bounding set, and an '@' annotation means the process has ambient capabilities. .\" BEGIN_ADVANCED Colorized tree output is intended to prioritize remediation quickly: orange highlights problems to fix, yellow highlights cautionary conditions to review, green highlights good hardening state, and uncolored output is neutral context. The \fBcaps\fP line shows permitted capabilities. Orange-tier capabilities represent severe privilege that is rarely necessary for network-facing daemons: \fBsys_ptrace\fP, \fBsys_module\fP, \fBsys_rawio\fP, \fBsetuid\fP, \fBsetgid\fP, \fBsetpcap\fP, and \fBaudit_control\fP. Yellow-tier capabilities are common but still materially increase attack impact: \fBsys_admin\fP, \fBdac_override\fP, \fBdac_read_search\fP, \fBnet_admin\fP, \fBnet_raw\fP, \fBchown\fP, \fBfowner\fP, \fBmknod\fP, and \fBsys_chroot\fP. \fBcaps: (full)\fP means all capabilities are present (effectively full root privilege). \fBcaps: (none)\fP is ideal. Ambient capabilities are inherited across \fBexecve\fP by child processes (unless constrained by \fBno_new_privs\fP), so their presence on a network daemon is typically a problem. The \fBambient:\fP line lists active ambient capabilities. \fB[open-ended-bounding]\fP means the process has not trimmed its bounding set ceiling. This is not active privilege by itself, but it leaves future privilege-acquisition paths open (for example via file capabilities or ambient inheritance). For libcap-ng users, trimming is typically a one-flag change (\fBCAPNG_SELECT_BOUNDS\fP) in existing capability-set calls; with libcap, bounding capabilities are dropped individually. Defense fields summarize exploit-resistance posture: \fBno_new_privs: yes\fP blocks privilege gain through setuid/file-capability exec paths and is often the highest-value single toggle. \fBseccomp: filter\fP restricts available syscalls (\fBdisabled\fP means no syscall filtering). \fBruns_as_nonroot: yes\fP indicates non-UID-0 execution. Flags provide reachability and interception context: \fBwildcard-bind\fP means the daemon listens on every interface, \fBhypervisor-plane\fP indicates host/ guest boundary exposure (VSOCK), and \fBreuseport\fP means another process with the same UID could bind and potentially intercept traffic. \fBraw\fP/\fBraw6\fP and \fBpacket\fP protocol nodes denote sockets with IP- or link-layer packet craft/sniff capabilities, significantly expanding what an attacker can do post-compromise. .\" END_ADVANCED .SH "SEE ALSO" .BR pscap (8), .BR filecap (8), .BR capabilities (7), .BR netstat (8). .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/utils/netcap.c000066400000000000000000000456721516575034500210060ustar00rootroot00000000000000/* * netcap.c - A program that lists network apps with capabilities * Copyright (c) 2009-10,2012,2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * * The /proc/net/tcp|udp|raw parsing code was borrowed from netstat.c */ #include "config.h" #include #ifdef HAVE_NETCAP_ADVANCED #include #include #include #include #ifdef HAVE_LINUX_VM_SOCKETS_DIAG_H #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include "cap-ng.h" #include "proc-llist.h" #include "netcap-advanced.h" #include "proc-sanitize.h" static llist l; static int perm_warn = 0, header = 0, last_uid = -1; static char *tacct = NULL; static void usage(void) { fprintf(stderr, "usage: netcap [--advanced " "[--interface IFACE] [--list-interfaces] [--json] " "[--no-color]]\n"); exit(1); } static int collect_process_info(void) { DIR *d, *f; struct dirent *ent; d = opendir("/proc"); if (d == NULL) { fprintf(stderr, "Can't open /proc: %s\n", strerror(errno)); return 1; } while (( ent = readdir(d) )) { FILE *sf; int pid, ppid; capng_results_t caps; char buf[100]; char *safe_cmd; char *tmp, cmd[16], state; char *text = NULL, *bounds = NULL, *ambient = NULL; int fd, len, euid = -1; // Skip non-process dir entries if(*ent->d_name<'0' || *ent->d_name>'9') continue; errno = 0; pid = strtol(ent->d_name, NULL, 10); if (errno) continue; // Parse up the stat file for the proc snprintf(buf, sizeof(buf), "/proc/%d/stat", pid); fd = open(buf, O_RDONLY|O_CLOEXEC, 0); if (fd < 0) continue; len = read(fd, buf, sizeof buf - 1); close(fd); if (len < 40) continue; buf[len] = 0; tmp = strrchr(buf, ')'); if (tmp) *tmp = 0; else continue; memset(cmd, 0, sizeof(cmd)); if (sscanf(buf, "%d (%15c", &ppid, cmd) != 2) continue; if (sscanf(tmp+2, "%c %d", &state, &ppid) != 2) continue; // Skip kthreads if (pid == 2 || ppid == 2) continue; // now get the capabilities capng_clear(CAPNG_SELECT_ALL); capng_setpid(pid); if (capng_get_caps_process()) continue; caps = capng_have_capabilities(CAPNG_SELECT_CAPS); if (caps <= CAPNG_NONE) continue; if (caps == CAPNG_FULL) { text = strdup("full"); if (!text) { fprintf(stderr, "Out of memory\n"); continue; } } else { text = capng_print_caps_text(CAPNG_PRINT_BUFFER, CAPNG_PERMITTED); if (text == NULL) { fprintf(stderr, "Out of memory doing pid %d\n", pid); continue; } } // Get the effective uid snprintf(buf, sizeof(buf), "/proc/%d/status", pid); sf = fopen(buf, "rte"); if (sf == NULL) euid = 0; else { int line = 0; __fsetlocking(sf, FSETLOCKING_BYCALLER); while (fgets(buf, sizeof(buf), sf)) { if (line == 0) { line++; continue; } if (memcmp(buf, "Uid:", 4) == 0) { int id; if (sscanf(buf, "Uid: %d %d", &id, &euid) == 2) break; } } fclose(sf); if (euid == -1) euid = 0; } caps = capng_have_capabilities(CAPNG_SELECT_AMBIENT); if (caps > CAPNG_NONE) ambient = strdup("@"); else ambient = strdup(""); if (!ambient) { fprintf(stderr, "Out of memory\n"); free(text); continue; } // Now record the bounding set information caps = capng_have_capabilities(CAPNG_SELECT_BOUNDS); if (caps > CAPNG_NONE) bounds = strdup("+"); else bounds = strdup(""); if (!bounds) { fprintf(stderr, "Out of memory\n"); free(text); free(ambient); continue; } // Now lets get the inodes each process has open snprintf(buf, sizeof(buf), "/proc/%d/fd", pid); f = opendir(buf); if (f == NULL) { if (errno == EACCES) { if (perm_warn == 0) { fprintf(stderr, "You may need to be root to " "get a full report\n"); perm_warn = 1; } } else fprintf(stderr, "Can't open %s: %s\n", buf, strerror(errno)); free(text); free(bounds); free(ambient); continue; } // For each file in the fd dir... struct dirent *fd_ent; while (( fd_ent = readdir(f) )) { char line[256], ln[256], *s, *e; unsigned long inode; lnode node; int llen; if (fd_ent->d_name[0] == '.') continue; snprintf(ln, 256, "%s/%s", buf, fd_ent->d_name); if ((llen = readlink(ln, line, sizeof(line)-1)) < 0) continue; line[llen] = 0; // Only look at the socket entries if (memcmp(line, "socket:", 7) == 0) { // Type 1 sockets s = strchr(line+7, '['); if (s == NULL) continue; s++; e = strchr(s, ']'); if (e == NULL) continue; *e = 0; } else if (memcmp(line, "[0000]:", 7) == 0) { // Type 2 sockets s = line + 8; } else continue; errno = 0; inode = strtoul(s, NULL, 10); if (errno) continue; safe_cmd = sanitize_untrusted_field(cmd); if (!safe_cmd) continue; node.ppid = ppid; node.pid = pid; node.uid = euid; node.cmd = safe_cmd; node.inode = inode; node.capabilities = strdup(text); node.bounds = strdup(bounds); node.ambient = strdup(ambient); if (node.cmd && node.capabilities && node.bounds && node.ambient) // We make one entry for each socket inode list_append(&l, &node); else { free(node.cmd); free(node.capabilities); free(node.bounds); free(node.ambient); } } closedir(f); free(text); free(bounds); free(ambient); } closedir(d); return 0; } static void report_finding(unsigned int port, const char *type, const char *ifc) { struct passwd *p; lnode *n = list_get_cur(&l); // And print out anything with capabilities if (header == 0) { printf("%-7s %-7s %-16s %-15s %-8s %-15s %s\n", "ppid", "pid", "acct", "command", "type", "port", "capabilities"); header = 1; } if (n->uid == 0) { // Take short cut for this one tacct = "root"; last_uid = 0; } else if (last_uid != (int)n->uid) { // Only look up if name changed p = getpwuid(n->uid); last_uid = n->uid; if (p) tacct = p->pw_name; // If not taking this branch, use last val } if (tacct) { printf("%-7d %-7d %-16s", n->ppid, n->pid, tacct); } else printf("%-7d %-7d %-16d", n->ppid, n->pid, last_uid); printf(" %-15s %-8s", n->cmd, type); if (ifc) printf(" %-15s", ifc); else printf(" %-15u", port); printf(" %s %s%s\n", n->capabilities, n->ambient, n->bounds); } static void read_net(const char *proc, const char *type, int use_local_port) { int line = 0; FILE *f; char buf[256]; unsigned long rxq, txq, time_len, retr, inode; unsigned int local_port, rem_port, state, timer_run; int d, uid, timeout; char rem_addr[128], local_addr[128], more[512]; f = fopen(proc, "rte"); if (f == NULL) { if (errno != ENOENT) fprintf(stderr, "Can't open %s: %s\n", proc, strerror(errno)); return; } __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, sizeof(buf), f)) { if (line == 0) { line++; continue; } more[0] = 0; if (sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X " "%lX:%lX %X:%lX %lX %d %d %lu %511s\n", &d, local_addr, &local_port, rem_addr, &rem_port, &state, &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more) < 14) continue; if (list_find_inode(&l, inode)) report_finding(use_local_port ? local_port : 0, type, NULL); } fclose(f); } // Caller must have buffer >= 65 bytes static void get_interface(unsigned int iface, char *ifc) { unsigned int line = 0; FILE *f; char buf[256], more[256]; // Terminate the interface in case of error *ifc = 0; // Offset the interface number since header is 2 lines long iface += 2; f = fopen("/proc/net/dev", "rte"); if (f == NULL) { if (errno != ENOENT) fprintf(stderr, "Can't open /proc/net/dev: %s\n", strerror(errno)); return; } __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, sizeof(buf), f)) { if (line == iface) { char *c; char *safe_ifc; sscanf(buf, "%16s: %255s\n", ifc, more); c = strchr(ifc, ':'); if (c) *c = 0; safe_ifc = sanitize_untrusted_field(ifc); if (safe_ifc) { strncpy(ifc, safe_ifc, 64); ifc[64] = '\0'; free(safe_ifc); } else *ifc = 0; fclose(f); return; } line++; } fclose(f); } static void read_packet(void) { int line = 0; FILE *f; char buf[256]; unsigned long sk, inode; unsigned int ref_cnt, type, proto, iface, r, rmem, uid; char more[256], ifc[65]; f = fopen("/proc/net/packet", "rte"); if (f == NULL) { if (errno != ENOENT) fprintf(stderr, "Can't open /proc/net/packet: %s\n", strerror(errno)); return; } __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, sizeof(buf), f)) { if (line == 0) { line++; continue; } more[0] = 0; if (sscanf(buf, "%lX %u %u %X %u %u %u %u %lu %255s\n", &sk, &ref_cnt, &type, &proto, &iface, &r, &rmem, &uid, &inode, more) < 9) continue; get_interface(iface, ifc); if (list_find_inode(&l, inode)) report_finding(0, "pkt", ifc); } fclose(f); } #ifdef HAVE_NETCAP_ADVANCED static int parse_u32_hex_or_dec(const char *s, unsigned int *out) { char *end; unsigned long v; int base = 10; const char *p; for (p = s; *p; p++) { if ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')) { base = 16; break; } } if (strncmp(s, "0x", 2) == 0 || strncmp(s, "0X", 2) == 0) base = 16; if (base == 10 && strlen(s) > 3 && s[0] == '0') base = 16; v = strtoul(s, &end, base); if (end == s || *end) return -1; *out = (unsigned int)v; return 0; } static int read_diag_messages(int fd, int proto, const char *type) { char buf[8192]; ssize_t len; struct sockaddr_nl nladdr; socklen_t nladdr_len; while (1) { nladdr_len = sizeof(nladdr); memset(&nladdr, 0, sizeof(nladdr)); len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&nladdr, &nladdr_len); if (len < 0) { if (errno == EINTR) continue; return -1; } if (len == 0) return -1; if (nladdr_len < sizeof(nladdr) || nladdr.nl_pid != 0) continue; struct nlmsghdr *nlh; unsigned int rem; if (len > UINT_MAX) return -1; rem = (unsigned int)len; for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, rem); nlh = NLMSG_NEXT(nlh, rem)) { struct inet_diag_msg *r; unsigned int port; if (nlh->nlmsg_type == NLMSG_DONE) return 0; if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *e; if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*e))) return -1; e = NLMSG_DATA(nlh); if (e->error == 0) continue; errno = -e->error; return -1; } if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*r))) continue; r = NLMSG_DATA(nlh); if (!list_find_inode(&l, r->idiag_inode)) continue; port = ntohs(r->id.idiag_sport); if (!port) continue; if (proto == IPPROTO_SCTP || proto == IPPROTO_DCCP) report_finding(port, type, NULL); } } } static int read_diag_for_proto_af(int proto, int af, const char *type) { struct { struct nlmsghdr nlh; struct inet_diag_req_v2 req; } req; struct sockaddr_nl sa; int fd; int rc = -1; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG); if (fd < 0) return -1; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.req)); req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.req.sdiag_family = af; req.req.sdiag_protocol = proto; req.req.idiag_states = 1U << TCP_LISTEN; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_pid = 0; if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) goto out; if (send(fd, &req, req.nlh.nlmsg_len, 0) < 0) goto out; rc = read_diag_messages(fd, proto, type); out: close(fd); return rc; } static void read_diag_listeners(void) { int sctp_ok = 0; int dccp_ok = 0; if (read_diag_for_proto_af(IPPROTO_SCTP, AF_INET, "sctp") == 0) sctp_ok = 1; if (read_diag_for_proto_af(IPPROTO_SCTP, AF_INET6, "sctp") == 0) sctp_ok = 1; if (read_diag_for_proto_af(IPPROTO_DCCP, AF_INET, "dccp") == 0) dccp_ok = 1; if (read_diag_for_proto_af(IPPROTO_DCCP, AF_INET6, "dccp") == 0) dccp_ok = 1; if (!dccp_ok) { read_net("/proc/net/dccp", "dccp", 1); read_net("/proc/net/dccp6", "dccp", 1); } (void)sctp_ok; } #ifdef HAVE_LINUX_VM_SOCKETS_DIAG_H static int read_vsock_diag_messages(int fd) { char buf[8192]; ssize_t len; struct sockaddr_nl nladdr; socklen_t nladdr_len; while (1) { nladdr_len = sizeof(nladdr); memset(&nladdr, 0, sizeof(nladdr)); len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&nladdr, &nladdr_len); if (len < 0) { if (errno == EINTR) continue; return -1; } if (len == 0) return -1; if (nladdr_len < sizeof(nladdr) || nladdr.nl_pid != 0) continue; struct nlmsghdr *nlh; unsigned int rem; if (len > UINT_MAX) return -1; rem = (unsigned int)len; for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, rem); nlh = NLMSG_NEXT(nlh, rem)) { struct vsock_diag_msg *r; if (nlh->nlmsg_type == NLMSG_DONE) return 0; if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *e; if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*e))) return -1; e = NLMSG_DATA(nlh); if (e->error == 0) continue; errno = -e->error; return -1; } if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*r))) continue; r = NLMSG_DATA(nlh); if (r->vdiag_family != AF_VSOCK) continue; if (r->vdiag_type != SOCK_STREAM || r->vdiag_state != TCP_LISTEN) continue; if (!list_find_inode(&l, r->vdiag_ino)) continue; if (r->vdiag_src_port == 0) continue; report_finding(r->vdiag_src_port, "vsock", NULL); } } } static int read_vsock_diag(void) { struct { struct nlmsghdr nlh; struct vsock_diag_req req; } req; struct sockaddr_nl sa; int fd; int rc = -1; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG); if (fd < 0) return -1; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.req)); req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.req.sdiag_family = AF_VSOCK; req.req.sdiag_protocol = 0; req.req.vdiag_states = ~0U; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_pid = 0; if (sendto(fd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) goto out; rc = read_vsock_diag_messages(fd); out: close(fd); return rc; } #else static int read_vsock_diag(void) { errno = EOPNOTSUPP; return -1; } #endif static void read_vsock_proc(void) { FILE *f; char line[512]; f = fopen("/proc/net/vsock", "rte"); if (f == NULL) { if (errno != ENOENT) fprintf(stderr, "Can't open /proc/net/vsock: %s\n", strerror(errno)); return; } __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(line, sizeof(line), f)) { char work[512]; char *tok[24]; char *save = NULL; char *local, *sep, *s; int tcnt = 0; unsigned long inode; unsigned int st, type, cid, port; if (strstr(line, "Local") || strstr(line, "local") || strstr(line, "Num")) continue; snprintf(work, sizeof(work), "%s", line); s = strtok_r(work, " \t\n", &save); while (s && tcnt < (int)(sizeof(tok) / sizeof(tok[0]))) { tok[tcnt++] = s; s = strtok_r(NULL, " \t\n", &save); } if (tcnt < 5) continue; local = NULL; int i; for (i = 0; i < tcnt; i++) { if (strchr(tok[i], ':')) { local = tok[i]; break; } } if (!local) continue; sep = strchr(local, ':'); if (!sep) continue; *sep = '\0'; if (parse_u32_hex_or_dec(local, &cid) || parse_u32_hex_or_dec(sep + 1, &port)) continue; if (parse_u32_hex_or_dec(tok[tcnt - 2], &st)) continue; if (parse_u32_hex_or_dec(tok[tcnt - 3], &type)) continue; inode = strtoul(tok[tcnt - 1], NULL, 10); if (!inode) continue; if (type != SOCK_STREAM || st != 0x0A || port == 0) continue; if (!list_find_inode(&l, inode)) continue; (void)cid; report_finding(port, "vsock", NULL); } fclose(f); } static void read_vsock(void) { if (read_vsock_diag() < 0) read_vsock_proc(); } #endif int main(int argc, char **argv) { struct netcap_opts opts = { 0, 0, 0, 0, NULL }; int i; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--advanced") == 0) opts.advanced = 1; else if (strcmp(argv[i], "--interface") == 0) { if (i + 1 >= argc) { fputs("--interface requires an argument\n", stderr); usage(); } opts.interface = argv[++i]; } else if (strncmp(argv[i], "--interface=", 12) == 0) { if (argv[i][12] == 0) { fputs("--interface requires an argument\n", stderr); usage(); } opts.interface = argv[i] + 12; } else if (strcmp(argv[i], "--list-interfaces") == 0) opts.list_interfaces = 1; else if (strcmp(argv[i], "--json") == 0) opts.json = 1; else if (strcmp(argv[i], "--no-color") == 0) opts.no_color = 1; else { fprintf(stderr, "Unknown option: %s\n", argv[i]); usage(); } } if (opts.json && !opts.advanced) { fputs("--json is only valid with --advanced\n", stderr); usage(); } if (opts.list_interfaces && !opts.advanced) { fputs("--list-interfaces is only valid with --advanced\n", stderr); usage(); } if (opts.interface && !opts.advanced) { fputs("--interface is only valid with --advanced\n", stderr); usage(); } if (opts.advanced) { #ifdef HAVE_NETCAP_ADVANCED return netcap_advanced_main(&opts); #else fputs("netcap --advanced was disabled at configure time\n", stderr); fputs("because required kernel headers were not available\n", stderr); return 1; #endif } if (argc > 1) { fputs("Too many arguments\n", stderr); usage(); } list_create(&l); collect_process_info(); // Now we check the tcp socket list... read_net("/proc/net/tcp", "tcp", 1); read_net("/proc/net/tcp6", "tcp6", 1); // Next udp sockets... read_net("/proc/net/udp", "udp", 1); read_net("/proc/net/udp6", "udp6", 1); read_net("/proc/net/udplite", "udplite", 1); read_net("/proc/net/udplite6", "udplite6", 1); // Next, raw sockets... read_net("/proc/net/raw", "raw", 0); read_net("/proc/net/raw6", "raw6", 0); // And last, read packet sockets read_packet(); // Add listeners from protocols supported in advanced mode #ifdef HAVE_NETCAP_ADVANCED read_diag_listeners(); read_vsock(); #endif // Could also do icmp,netlink,unix list_clear(&l); return 0; } stevegrubb-libcap-ng-0ab44af/utils/proc-llist.c000066400000000000000000000043121516575034500216060ustar00rootroot00000000000000/* * proc-llist.c - Minimal linked list library * Copyright (c) 2009, 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include "proc-llist.h" void list_create(llist *l) { l->head = NULL; l->cur = NULL; l->cnt = 0; } void list_append(llist *l, lnode *node) { lnode* newnode; if (node == NULL || l == NULL) return; newnode = malloc(sizeof(lnode)); if (newnode == NULL) return; newnode->ppid = node->ppid; newnode->pid = node->pid; newnode->uid = node->uid; newnode->inode = node->inode; // Take custody of the memory newnode->cmd = node->cmd; newnode->capabilities = node->capabilities; newnode->bounds = node->bounds; newnode->ambient = node->ambient; newnode->next = NULL; // if we are at top, fix this up if (l->head == NULL) l->head = newnode; else // Otherwise add pointer to newnode l->cur->next = newnode; // make newnode current l->cur = newnode; l->cnt++; } void list_clear(llist* l) { lnode* nextnode; register lnode* cur; cur = l->head; while (cur) { nextnode=cur->next; free(cur->cmd); free(cur->capabilities); free(cur->bounds); free(cur->ambient); free(cur); cur=nextnode; } l->head = NULL; l->cur = NULL; l->cnt = 0; } lnode *list_find_inode(llist *l, unsigned long i) { register lnode* cur; cur = l->head; /* start at the beginning */ while (cur) { if (cur->inode == i) { l->cur = cur; return cur; } else cur = cur->next; } return NULL; } stevegrubb-libcap-ng-0ab44af/utils/proc-llist.h000066400000000000000000000037121516575034500216160ustar00rootroot00000000000000/* * proc-llist.h * Copyright (c) 2009, 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef PROC_HEADER #define PROC_HEADER #include "config.h" #include /* Ensure types in _lnode are defined on all systems */ /* This is the node of the linked list. Any data elements that are per * record goes here. */ typedef struct _lnode{ pid_t ppid; // parent process ID pid_t pid; // process ID uid_t uid; // user ID char *cmd; // command run by user unsigned long inode; // inode of socket char *capabilities; // Text of partial capabilities char *bounds; // Text for bounding set char *ambient; // Text for ambient set struct _lnode* next; // Next node pointer } lnode; /* This is the linked list head. Only data elements that are 1 per * event goes here. */ typedef struct { lnode *head; // List head lnode *cur; // Pointer to current node unsigned int cnt; // How many items in this list } llist; void list_create(llist *l); static inline lnode *list_get_cur(llist *l) { return l->cur; } void list_append(llist *l, lnode *node); void list_clear(llist* l); /* Given a message type, find the matching node */ lnode *list_find_inode(llist *l, unsigned long i); #endif stevegrubb-libcap-ng-0ab44af/utils/proc-sanitize.c000066400000000000000000000044331516575034500223110ustar00rootroot00000000000000/* * proc-sanitize.c - Shared terminal sanitization helpers for proc text * Copyright (c) 2026 Steve Grubb * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include "proc-sanitize.h" /* * sanitize_untrusted_field - escape terminal control bytes in untrusted text. * @src: source text gathered from procfs/cgroup metadata. * * Returns caller-owned sanitized text, or NULL on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ char *sanitize_untrusted_field(const char *src) { size_t in_len; char *dst; char *out; size_t i; if (!src) return NULL; in_len = strlen(src); dst = malloc(in_len * 4 + 1); if (!dst) return NULL; out = dst; for (i = 0; i < in_len; i++) { unsigned char c = (unsigned char)src[i]; if (c < 0x20 || c == 0x7f) { snprintf(out, 5, "\\x%02X", c); out += 4; } else { *out++ = (char)c; } } *out = '\0'; return dst; } /* * sanitize_untrusted_owned - replace owned string with sanitized version. * @s: pointer to owned string pointer that will be replaced in place. * * Returns 0 on success, -1 on allocation failure. * Side effects/assumptions: Operates on in-memory data and may read * procfs/netns state; it does not change kernel configuration. */ int sanitize_untrusted_owned(char **s) { char *safe; if (!s || !*s) return 0; safe = sanitize_untrusted_field(*s); if (!safe) return -1; free(*s); *s = safe; return 0; } stevegrubb-libcap-ng-0ab44af/utils/proc-sanitize.h000066400000000000000000000020341516575034500223110ustar00rootroot00000000000000/* * proc-sanitize.h - Shared terminal sanitization helpers for proc text * Copyright (c) 2026 Steve Grubb * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef PROC_SANITIZE_H #define PROC_SANITIZE_H char *sanitize_untrusted_field(const char *src); int sanitize_untrusted_owned(char **s); #endif stevegrubb-libcap-ng-0ab44af/utils/pscap.8000066400000000000000000000024311516575034500205510ustar00rootroot00000000000000.TH PSCAP: "8" "March 2026" "Red Hat" "System Administration Utilities" .SH NAME pscap \- a program to see capabilities .SH SYNOPSIS .B pscap [ \-a ] [ \-p pid ] [ \-\-tree ] .SH DESCRIPTION \fBpscap\fP is a program that prints out a report of process capabilities. If the application has any capabilities, it will be in the report. By giving a pid with the \-p command line option, only the process specified with the pid is reported. If a process is not in the report, it has dropped all capabilities. If the process has partial capabilities, it is further examined to see if it has an open-ended bounding set. If this is found to be true, a '+' symbol is added. If the process has ambient capabilities, an '@' symbol is added. The command name in the output may be followed by an asterisk mark (*). This mark denotes processes which run in child user namespaces (relative to the user namespace of pscap itself). The \fB\-\-tree\fP option causes the output of the program to be mapped into a tree structure so that the relationships between processes can be studied to see what capabilities might be leaked to child processes. In tree mode, each process label is shown as command(pid:account). .SH "SEE ALSO" .BR netcap (8), .BR filecap (8), .BR capabilities (7), .BR ps (8). .SH AUTHOR Steve Grubb stevegrubb-libcap-ng-0ab44af/utils/pscap.c000066400000000000000000000341021516575034500206240ustar00rootroot00000000000000/* * pscap.c - A program that lists running processes with capabilities * Copyright (c) 2009,2012,2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "cap-ng.h" #include "proc-sanitize.h" #define CMD_LEN 16 #define ACCOUNT_LEN 32 #define USERNS_MARK_LEN 3 // two characters plus '\0'. static void usage(void) { fprintf(stderr, "usage: pscap [-a] [-p pid] [--tree]\n"); exit(1); } struct proc_info { pid_t pid; pid_t ppid; char *cmd; char account[ACCOUNT_LEN]; char *caps_text; }; static int get_euid(int pid) { char path[32], buf[128]; FILE *f; int euid = -1; snprintf(path, sizeof(path), "/proc/%d/status", pid); f = fopen(path, "rte"); if (f == NULL) return 0; __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, sizeof(buf), f)) { if (memcmp(buf, "Uid:", 4) == 0) { int uid; sscanf(buf, "Uid: %d %d", &uid, &euid); break; } } fclose(f); if (euid < 0) return 0; return euid; } static void get_account_name(int pid, char *account, size_t account_len) { struct passwd *p; int euid; euid = get_euid(pid); if (euid == 0) { strncpy(account, "root", account_len - 1); account[account_len - 1] = '\0'; return; } p = getpwuid(euid); if (p && p->pw_name) { strncpy(account, p->pw_name, account_len - 1); account[account_len - 1] = '\0'; return; } snprintf(account, account_len, "%d", euid); } static int get_width(void) { struct winsize ws; char *e; long c; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) return ws.ws_col; e = getenv("COLUMNS"); if (e) { char *endptr; errno = 0; c = strtol(e, &endptr, 10); if (errno == 0 && endptr != e && *endptr == '\0' && c > 0 && c < 400) return (int)c; } return 80; } static size_t wrap_to(const char *text, size_t max) { size_t len = strlen(text); size_t i; if (len <= max) return len; for (i = max; i > 0; i--) { if (text[i - 1] == ',') { if (i < len && text[i] == ' ') return i + 1; return i; } if (text[i - 1] == ' ') return i; } return max; } /* * compare_pid - order processes by pid for sorting/bsearch * @a: pointer to left struct proc_info * @b: pointer to right struct proc_info * * Returns -1, 0, or 1 for ordering. */ static int compare_pid(const void *a, const void *b) { const struct proc_info *left = a; const struct proc_info *right = b; if (left->pid < right->pid) return -1; if (left->pid > right->pid) return 1; return 0; } /* * find_proc - locate a process record by pid * @procs: process array sorted by pid * @count: number of entries in @procs * @pid: process id to locate * * Returns pointer to the matching entry or NULL if not found. */ static void *find_proc(struct proc_info *procs, size_t count, pid_t pid) { struct proc_info key; key.pid = pid; return bsearch(&key, procs, count, sizeof(*procs), compare_pid); } /* * append_marker - append a marker string to the capability text * @text: capability text buffer pointer to append to * @marker: marker string to append (e.g. " @" or " +") * * Returns 0 on success, -1 on allocation failure. */ static int append_marker(char **text, const char *marker) { size_t len = strlen(*text); size_t marker_len = strlen(marker); char *tmp = realloc(*text, len + marker_len + 1); if (!tmp) return -1; memcpy(tmp + len, marker, marker_len + 1); *text = tmp; return 0; } /* * format_caps - format capability text with optional markers * @caps: capability summary state from capng_have_capabilities() * @ambient: true if ambient capabilities are present * @bounds: true if bounding set differs from full * * Returns allocated capability string or NULL on allocation failure. */ static char *format_caps(int caps, bool ambient, bool bounds) { char *text; if (caps == CAPNG_PARTIAL) text = capng_print_caps_text(CAPNG_PRINT_BUFFER, CAPNG_PERMITTED); else if (caps == CAPNG_FULL) text = strdup("full"); else text = strdup("none"); if (!text) return NULL; if (ambient) append_marker(&text, " @"); if (bounds) append_marker(&text, " +"); return text; } /* * print_tree_node - render a node and its children in tree mode * @procs: process array * @count: number of entries in @procs * @index: index of current node in @procs * @prefix: current line prefix * @is_last: true if this node is the last child of its parent * @is_root: true if this node is a tree root * * Returns nothing. Recurses through children to emit full subtree. */ static void print_tree_node(struct proc_info *procs, size_t count, size_t index, const char *prefix, bool is_last, bool is_root, int width) { struct proc_info *proc = &procs[index]; size_t child_total = 0; size_t child_seen = 0; size_t i; size_t prefix_len; size_t cont_len; size_t avail; size_t n; const char *caps; char head[64]; const char *branch = ""; char *line_prefix; char *cont_prefix; char *child_prefix; if (!is_root) branch = is_last ? " " : "│ "; line_prefix = malloc(strlen(prefix) + strlen(is_root ? "" : (is_last ? "└─ " : "├─ ")) + 1); if (!line_prefix) return; strcpy(line_prefix, prefix); if (!is_root) strcat(line_prefix, is_last ? "└─ " : "├─ "); cont_prefix = malloc(strlen(prefix) + strlen(branch) + 1); if (!cont_prefix) { free(line_prefix); return; } strcpy(cont_prefix, prefix); strcat(cont_prefix, branch); snprintf(head, sizeof(head), "%s(%d:%s) [", proc->cmd, proc->pid, proc->account); prefix_len = strlen(line_prefix); cont_len = strlen(cont_prefix); caps = proc->caps_text; if ((int)(prefix_len + strlen(head) + strlen(caps) + 1) <= width) { printf("%s%s%s]\n", line_prefix, head, caps); goto children; } avail = width > (int)(prefix_len + strlen(head)) ? (size_t)(width - (int)(prefix_len + strlen(head))) : 10; if (avail < 10) avail = 10; n = wrap_to(caps, avail); printf("%s%s%.*s\n", line_prefix, head, (int)n, caps); caps += n; while (*caps) { avail = width > (int)cont_len ? (size_t)(width - (int)cont_len) : 10; if (avail < 10) avail = 10; if (strlen(caps) + 1 <= avail) { printf("%s%s]\n", cont_prefix, caps); break; } n = wrap_to(caps, avail); printf("%s%.*s\n", cont_prefix, (int)n, caps); caps += n; } children: free(line_prefix); free(cont_prefix); for (i = 0; i < count; i++) { if (procs[i].ppid == proc->pid) child_total++; } if (child_total == 0) return; child_prefix = malloc(strlen(prefix) + strlen(branch) + 1); if (!child_prefix) return; strcpy(child_prefix, prefix); strcat(child_prefix, branch); for (i = 0; i < count; i++) { if (procs[i].ppid != proc->pid) continue; child_seen++; print_tree_node(procs, count, i, child_prefix, child_seen == child_total, false, width); } free(child_prefix); } /* * print_tree - render all process trees in pid order * @procs: process array * @count: number of entries in @procs * * Returns nothing. Each tree starts at a pid whose parent isn't present. */ static void print_tree(struct proc_info *procs, size_t count) { size_t i; int width = get_width(); if (count > 1) qsort(procs, count, sizeof(*procs), compare_pid); for (i = 0; i < count; i++) { if (!find_proc(procs, count, procs[i].ppid)) print_tree_node(procs, count, i, "", true, true, width); } } /* * Precise recursive checks for parent-child relation between namespaces * using ioctl() were avoided, because there didn't seem to be any case when * we may dereference the namespace symlink in /proc/PID/ns for processes in * user namespaces other than the current or child ones. Thus, the check just * tries to dereference the link and checks that it does not point to the * current NS. */ static bool in_child_userns(int pid) { char ns_file_path[32]; struct stat statbuf; ino_t own_ns_inode; dev_t own_ns_dev; if (stat("/proc/self/ns/user", &statbuf) < 0) return false; own_ns_inode = statbuf.st_ino; own_ns_dev = statbuf.st_dev; snprintf(ns_file_path, sizeof(ns_file_path), "/proc/%d/ns/user", pid); if (stat(ns_file_path, &statbuf) < 0) return false; return statbuf.st_ino != own_ns_inode || statbuf.st_dev != own_ns_dev; } int main(int argc, char *argv[]) { char *endptr = NULL; DIR *d; struct dirent *ent; int header = 0, show_all = 0, caps; pid_t our_pid = getpid(); pid_t target_pid = 0; int uid = -1; char *name = NULL; int tree_mode = 0; struct proc_info *procs = NULL; size_t proc_count = 0; size_t proc_capacity = 0; size_t i; for (i = 1; i < (size_t)argc; i++) { if (strcmp(argv[i], "-a") == 0) { show_all = 1; continue; } if (strcmp(argv[i], "--tree") == 0) { tree_mode = 1; continue; } if (strcmp(argv[i], "-p") == 0) { if (i + 1 >= (size_t)argc) usage(); errno = 0; target_pid = strtol(argv[++i], &endptr, 10); if (errno) { fprintf(stderr, "Can't read pid: %s\n", argv[i]); return 1; } if ((endptr == argv[i]) || (*endptr != '\0') || !target_pid) { fprintf(stderr, "Invalid pid argument: %s\n", argv[i]); return 1; } if (target_pid == 1) show_all = 1; continue; } usage(); } d = opendir("/proc"); if (d == NULL) { fprintf(stderr, "Can't open /proc: %s\n", strerror(errno)); return 1; } while (( ent = readdir(d) )) { int pid, ppid; char buf[100]; char *safe_cmd = NULL; char *tmp, cmd[CMD_LEN + USERNS_MARK_LEN], state; int fd, len; struct passwd *p; // Skip non-process dir entries if(*ent->d_name<'0' || *ent->d_name>'9') continue; errno = 0; pid = strtol(ent->d_name, NULL, 10); if (errno) continue; if (target_pid && (pid != target_pid)) continue; /* Skip our pid so we aren't listed */ if (pid == our_pid) continue; // Parse up the stat file for the proc snprintf(buf, sizeof(buf), "/proc/%d/stat", pid); fd = open(buf, O_RDONLY|O_CLOEXEC, 0); if (fd < 0) continue; len = read(fd, buf, sizeof(buf) - 1); close(fd); if (len < 40) continue; buf[len] = 0; tmp = strrchr(buf, ')'); if (tmp) *tmp = 0; else continue; memset(cmd, 0, sizeof(cmd)); sscanf(buf, "%d (%15c", &ppid, cmd); // ppid is throwaway sscanf(tmp+2, "%c %d", &state, &ppid); // Skip kthreads if (pid == 2 || ppid == 2) continue; // now get the capabilities capng_clear(CAPNG_SELECT_ALL); capng_setpid(pid); if (capng_get_caps_process()) continue; // And print out anything with capabilities caps = capng_have_capabilities(CAPNG_SELECT_CAPS); safe_cmd = sanitize_untrusted_field(cmd); if (!safe_cmd) continue; if (in_child_userns(pid)) { char *marked = malloc(strlen(safe_cmd) + 3); if (!marked) { free(safe_cmd); continue; } snprintf(marked, strlen(safe_cmd) + 3, "%s *", safe_cmd); free(safe_cmd); safe_cmd = marked; } if (tree_mode) { char *caps_text; bool has_ambient; bool has_bounds; if (!show_all && caps <= CAPNG_NONE) { free(safe_cmd); continue; } has_ambient = capng_have_capabilities( CAPNG_SELECT_AMBIENT) > CAPNG_NONE; has_bounds = capng_have_capabilities( CAPNG_SELECT_BOUNDS) > CAPNG_NONE; caps_text = format_caps(caps, has_ambient, has_bounds); if (!caps_text) { free(safe_cmd); continue; } if (proc_count == proc_capacity) { size_t new_capacity = proc_capacity ? proc_capacity * 2 : 256; struct proc_info *pi_tmp; pi_tmp = realloc(procs, new_capacity * sizeof(*procs)); if (!pi_tmp) { free(caps_text); free(safe_cmd); continue; } procs = pi_tmp; proc_capacity = new_capacity; } procs[proc_count].pid = pid; procs[proc_count].ppid = ppid; procs[proc_count].cmd = safe_cmd; get_account_name(pid, procs[proc_count].account, sizeof(procs[proc_count].account)); procs[proc_count].caps_text = caps_text; proc_count++; safe_cmd = NULL; } else if (caps > CAPNG_NONE) { int euid = get_euid(pid); if (header == 0) { printf("%-7s %-7s %-16s %-15s %s\n", "ppid", "pid", "uid", "command", "capabilities"); header = 1; } if (euid == 0) { // Take short cut for this one name = "root"; uid = 0; } else if (euid != uid) { // Only look up if name changed p = getpwuid(euid); uid = euid; if (p) name = p->pw_name; else name = NULL; } if (name) { printf("%-7d %-7d %-16s %-15s ", ppid, pid, name, safe_cmd); } else printf("%-7d %-7d %-16d %-15s ", ppid, pid, uid, safe_cmd); if (caps == CAPNG_PARTIAL) { capng_print_caps_text(CAPNG_PRINT_STDOUT, CAPNG_PERMITTED); if (capng_have_capabilities( CAPNG_SELECT_AMBIENT) > CAPNG_NONE) printf(" @"); if (capng_have_capabilities(CAPNG_SELECT_BOUNDS) > CAPNG_NONE) printf(" +"); printf("\n"); } else { printf("full"); if (capng_have_capabilities( CAPNG_SELECT_AMBIENT) > CAPNG_NONE) printf(" @"); if (capng_have_capabilities(CAPNG_SELECT_BOUNDS) > CAPNG_NONE) printf(" +"); printf("\n"); } free(safe_cmd); safe_cmd = NULL; } if (safe_cmd) free(safe_cmd); } closedir(d); if (tree_mode) { print_tree(procs, proc_count); for (i = 0; i < proc_count; i++) { free(procs[i].cmd); free(procs[i].caps_text); } free(procs); } return 0; }