From 20acad8b9e2554c7d0dc3026aff1842590285fc6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 18:18:15 +0000 Subject: [PATCH 1/8] docs: Initialize documentation pipeline [skip ci] --- .doc-pipeline-status.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .doc-pipeline-status.md diff --git a/.doc-pipeline-status.md b/.doc-pipeline-status.md new file mode 100644 index 00000000000..a314c2847a0 --- /dev/null +++ b/.doc-pipeline-status.md @@ -0,0 +1,5 @@ +# Documentation Pipeline Started + +Run ID: doc-orchestrator-1779992279215 +Status: In Progress +Started: 2026-05-28 18:18:15 UTC From abeb5e0adad4abc5a1a502e375597ba8f6db32f6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 18:18:24 +0000 Subject: [PATCH 2/8] chore(docs): Clean slate - remove all documentation (46 files) [skip ci] --- docs/.gitignore | 8 - docs/README.md | 38 - docs/doxygen/DOXYGEN-LICENSE.txt | 674 --------- docs/doxygen/DoxygenLayout.xml | 194 --- docs/doxygen/documentation.h | 18 - docs/doxygen/mainpage.md | 4 - docs/doxygen/osquery.css | 1291 ----------------- docs/img/logo-2x-dark.png | Bin 3533 -> 0 bytes docs/img/logo-2x.png | Bin 3467 -> 0 bytes docs/requirements.txt | 2 - .../docs/security_assessment_2016_01_25.pdf | Bin 806575 -> 0 bytes docs/wiki/deployment/anomaly-detection.md | 67 - docs/wiki/deployment/aws-logging.md | 79 - docs/wiki/deployment/configuration.md | 791 ---------- docs/wiki/deployment/debugging.md | 239 --- docs/wiki/deployment/dependency-security.md | 132 -- docs/wiki/deployment/extensions.md | 97 -- docs/wiki/deployment/file-carving.md | 28 - .../deployment/file-integrity-monitoring.md | 188 --- docs/wiki/deployment/log-aggregation.md | 96 -- docs/wiki/deployment/logging.md | 291 ---- docs/wiki/deployment/performance-safety.md | 145 -- docs/wiki/deployment/process-auditing.md | 418 ------ docs/wiki/deployment/remote.md | 354 ----- docs/wiki/deployment/syslog.md | 118 -- docs/wiki/deployment/yara.md | 285 ---- docs/wiki/development/building.md | 483 ------ docs/wiki/development/config-plugins.md | 67 - docs/wiki/development/creating-tables.md | 255 ---- docs/wiki/development/cve-scan.md | 97 -- docs/wiki/development/logger-plugins.md | 37 - docs/wiki/development/options-arguments.md | 27 - docs/wiki/development/osquery-sdk.md | 225 --- docs/wiki/development/pubsub-framework.md | 105 -- docs/wiki/development/reading-files.md | 47 - docs/wiki/development/unit-tests.md | 55 - docs/wiki/index.md | 40 - docs/wiki/installation/cli-flags.md | 714 --------- docs/wiki/installation/custom-packages.md | 79 - docs/wiki/installation/install-linux.md | 44 - docs/wiki/installation/install-macos.md | 76 - docs/wiki/installation/install-windows.md | 111 -- docs/wiki/introduction/sql.md | 615 -------- docs/wiki/introduction/using-osqueryd.md | 45 - docs/wiki/introduction/using-osqueryi.md | 113 -- docs/wiki/theme/osquery.css | 5 - 46 files changed, 8797 deletions(-) delete mode 100644 docs/.gitignore delete mode 100644 docs/README.md delete mode 100644 docs/doxygen/DOXYGEN-LICENSE.txt delete mode 100644 docs/doxygen/DoxygenLayout.xml delete mode 100644 docs/doxygen/documentation.h delete mode 100644 docs/doxygen/mainpage.md delete mode 100644 docs/doxygen/osquery.css delete mode 100644 docs/img/logo-2x-dark.png delete mode 100644 docs/img/logo-2x.png delete mode 100644 docs/requirements.txt delete mode 100644 docs/static/docs/security_assessment_2016_01_25.pdf delete mode 100644 docs/wiki/deployment/anomaly-detection.md delete mode 100644 docs/wiki/deployment/aws-logging.md delete mode 100644 docs/wiki/deployment/configuration.md delete mode 100644 docs/wiki/deployment/debugging.md delete mode 100644 docs/wiki/deployment/dependency-security.md delete mode 100644 docs/wiki/deployment/extensions.md delete mode 100644 docs/wiki/deployment/file-carving.md delete mode 100644 docs/wiki/deployment/file-integrity-monitoring.md delete mode 100644 docs/wiki/deployment/log-aggregation.md delete mode 100644 docs/wiki/deployment/logging.md delete mode 100644 docs/wiki/deployment/performance-safety.md delete mode 100644 docs/wiki/deployment/process-auditing.md delete mode 100644 docs/wiki/deployment/remote.md delete mode 100644 docs/wiki/deployment/syslog.md delete mode 100644 docs/wiki/deployment/yara.md delete mode 100644 docs/wiki/development/building.md delete mode 100644 docs/wiki/development/config-plugins.md delete mode 100644 docs/wiki/development/creating-tables.md delete mode 100644 docs/wiki/development/cve-scan.md delete mode 100644 docs/wiki/development/logger-plugins.md delete mode 100644 docs/wiki/development/options-arguments.md delete mode 100644 docs/wiki/development/osquery-sdk.md delete mode 100644 docs/wiki/development/pubsub-framework.md delete mode 100644 docs/wiki/development/reading-files.md delete mode 100644 docs/wiki/development/unit-tests.md delete mode 100644 docs/wiki/index.md delete mode 100644 docs/wiki/installation/cli-flags.md delete mode 100644 docs/wiki/installation/custom-packages.md delete mode 100644 docs/wiki/installation/install-linux.md delete mode 100644 docs/wiki/installation/install-macos.md delete mode 100644 docs/wiki/installation/install-windows.md delete mode 100644 docs/wiki/introduction/sql.md delete mode 100644 docs/wiki/introduction/using-osqueryd.md delete mode 100644 docs/wiki/introduction/using-osqueryi.md delete mode 100644 docs/wiki/theme/osquery.css diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 0d9b0c1da1a..00000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -Gemfile.lock -_site/ -*.swp -pkg/ -test/ -.idea/ -.bundle/ -.jekyll-metadata diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 0d91104d013..00000000000 --- a/docs/README.md +++ /dev/null @@ -1,38 +0,0 @@ -## ReadTheDocs Wiki - -The ReadTheDocs wiki (https://osquery.readthedocs.org/en/stable) is generated using a RTD-configured osquery project and associated GitHub Service. This Service is documented by RTD and more-or-less setup automatically with the project. RTD generates documentation for every version (git tag). It calls the most recent tag 'stable', the most recent commit to master 'devel', and includes links to every past version. The project settings and sidebar for RTD is kept in the root as [mkdocs.yml](https://github.com/osquery/osquery/blob/master/mkdocs.yml). - -### Adding a new page - -New wiki pages should be organized into one of the following categories: - -- **Introduction**: Overview of the project or a tool. -- **Installation**: Deep dives into OS-specifics, packaging, and switches that control starting tools. -- **Deployment**: Tool concepts and all the wonderful goodies of making osquery useful. -- **Development**: Help and guides for starting with osquery development and build. - -Make a new "filename.md" within the category folder within `/docs/wiki/CATEGORY/`. Then add the friendly page title and path to [mkdocs.yml](https://github.com/osquery/osquery/blob/master/mkdocs.yml), in the order the page should appear within the wiki sidebar. - -### Wiki style tips - -- Inline code highlighting (`$ echo 'this is inline'`) does not look the best in RTD, try to have as little inline syntax highlighting as possible. -- **osqueryd**, **osqueryi** and other tool names should be in bold. Use `inline highlight` when a tool or script is mentioned for the first time. -- Filesystem paths and non-clickable URI examples should also be bold. -- Flag names are usually in quotes, `inline highlight` when introduced for the first time or used as an example. - -## Doxygen - -The Doxygen documentation is not hosted anywhere, each developer must build and view-locally. To build the docs use `make docs`. - -The output HTML documentation is written to `./build/docs/html/`. Use `index.html` to begin exploring. - -## Tables and Packs - -Table schema, the osquery user API, is created using the Python-based ".spec" files in [`./specs`](https://github.com/osquery/osquery/tree/master/specs). More documentation on how specs work can be found in the [Creating New Tables](http://osquery.readthedocs.org/en/stable/development/creating-tables/) developer documentation. These files are used to build osquery, but can be parsed to create JSON-based API schema. This JSON is published to the homepage at [https://osquery.io/schema/]. - -Use: `./tools/codegen/genapi.py` to generate the amalgamated schema. To generate a "change log" between tags, use the same script but use `--diff` and supply the two JSON inputs. - -```python -./tools/codegen/genapi.py > ./build/docs/CURRENT.json -./tools/codegen/genapi.py --diff ./build/docs/OLD.json ./build/docs/CURRENT.json -``` diff --git a/docs/doxygen/DOXYGEN-LICENSE.txt b/docs/doxygen/DOXYGEN-LICENSE.txt deleted file mode 100644 index 7e0f595dc2e..00000000000 --- a/docs/doxygen/DOXYGEN-LICENSE.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. 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 -them 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 prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. 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. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey 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; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If 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 convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU 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 that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - 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. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -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. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - 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 -state 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 3 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, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program 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, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU 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 Lesser General -Public License instead of this License. But first, please read -. diff --git a/docs/doxygen/DoxygenLayout.xml b/docs/doxygen/DoxygenLayout.xml deleted file mode 100644 index 6c040acce11..00000000000 --- a/docs/doxygen/DoxygenLayout.xml +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/doxygen/documentation.h b/docs/doxygen/documentation.h deleted file mode 100644 index 5ef861af051..00000000000 --- a/docs/doxygen/documentation.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2014-present, The osquery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -#pragma once - -/** - * @brief The main osquery namespace - * - * @namespace osquery If you're uncertain about where code should go, it - * should probably be in the global osquery namespace. - */ -namespace osquery {} diff --git a/docs/doxygen/mainpage.md b/docs/doxygen/mainpage.md deleted file mode 100644 index bec257c086e..00000000000 --- a/docs/doxygen/mainpage.md +++ /dev/null @@ -1,4 +0,0 @@ -@mainpage osquery api documentation - -This is the generated public api documentation for osquery. For more free-form -documentation, see the GitHub wiki: https://github.com/osquery/osquery/wiki. diff --git a/docs/doxygen/osquery.css b/docs/doxygen/osquery.css deleted file mode 100644 index 6599f84ce55..00000000000 --- a/docs/doxygen/osquery.css +++ /dev/null @@ -1,1291 +0,0 @@ -/* The standard CSS for doxygen 1.8.4 */ - -body, table, div, p, dl { - font: 13px "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; -} - -/* @group Heading Levels */ - -h1.groupheader { - font-size: 28px; -} - -.title { - font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; - font-size: 24px; - font-weight: normal; - margin: 10px 2px; -} - -h2.groupheader { - border-bottom: none; - color: rgb(60, 76, 108); - font-size: 24px; - font-weight: normal; - margin: 42px 0px 20px 0px; - margin-bottom: 20px; - padding: 0px; - width: 100%; -} - -h3.groupheader { - font-size: 100%; -} - -h1, h2, h3, h4, h5, h6 { - /*-webkit-transition: text-shadow 0.5s linear; - -moz-transition: text-shadow 0.5s linear; - -ms-transition: text-shadow 0.5s linear; - -o-transition: text-shadow 0.5s linear; - transition: text-shadow 0.5s linear;*/ -} - -h1 { - font-size: 28px; -} - -h2 { - color: rgb(60, 76, 108); - font-size: 24px; - font-weight: normal; - margin: 42px 0px 20px 0px; -} - -h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { - text-shadow: 0 0 15px cyan; -} - -dt { - font-weight: bold; -} - -div.multicol { - -moz-column-gap: 1em; - -webkit-column-gap: 1em; - -moz-column-count: 3; - -webkit-column-count: 3; -} - -p.startli, p.startdd, p.starttd { - margin-top: 2px; -} - -p.endli { - margin-bottom: 0px; -} - -p.enddd { - margin-bottom: 4px; -} - -p.endtd { - margin-bottom: 2px; -} - -/* @end */ - -caption { - font-weight: bold; -} - -span.legend { - font-size: 70%; - text-align: center; -} - -h3.version { - font-size: 90%; - text-align: center; -} - -div.qindex, div.navtab{ - background-color: #EBEFF6; - border: 1px solid #A3B4D7; - text-align: center; -} - -div.qindex, div.navpath { - width: 100%; - line-height: 140%; -} - -div.navtab { - margin-right: 15px; -} - -/* @group Link Styling */ - -a { - color: #45109B; - font-weight: normal; - text-decoration: none; -} - -.contents a:visited { - color: #45109B; -} - -a:hover { - text-decoration: underline; -} - -a.qindex { - font-weight: bold; -} - -a.qindexHL { - font-weight: bold; - background-color: #9CAFD4; - color: #ffffff; - border: 1px double #869DCA; -} - -.contents a.qindexHL:visited { - color: #ffffff; -} - -a.el { - font-family: Courier, Consolas, monospace; - font-weight: normal; -} - -a.elRef { -} - -a.code, a.code:visited { - color: rgb(51, 102, 204); -} - -a.codeRef, a.codeRef:visited { - color: rgb(51, 102, 204); -} - -/* @end */ - -dl.el { - margin-left: -1cm; -} - -pre.fragment { - r: solid 1px rgb(221, 221, 221); - border-radius: 3px; - round-color: rgb(248, 248, 248); - ng: 6px 10px; - n: 15px 0px; - overflow: auto; - word-wrap: break-word; - font-size: 9pt; - line-height: 125%; - family: Consolas, "Liberation Mono", Courier, monospace; - font-size: 105%; -} - -div.fragment { - - padding: 6px 10px; - margin: 15px 0px; - border: solid 1px rgb(221, 221, 221); - border-radius: 3px; - - background-color: rgb(248, 248, 248); -} - -div.line { - font-family: Consolas, "Liberation Mono", Courier, monospace; - font-size: 13px; - min-height: 13px; - line-height: 1.0; - text-wrap: unrestricted; - white-space: -moz-pre-wrap; /* Moz */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - white-space: pre-wrap; /* CSS3 */ - word-wrap: break-word; /* IE 5.5+ */ - text-indent: -53px; - padding-left: 53px; - padding-bottom: 0px; - margin: 0px; - -webkit-transition-property: background-color, box-shadow; - -webkit-transition-duration: 0.5s; - -moz-transition-property: background-color, box-shadow; - -moz-transition-duration: 0.5s; - -ms-transition-property: background-color, box-shadow; - -ms-transition-duration: 0.5s; - -o-transition-property: background-color, box-shadow; - -o-transition-duration: 0.5s; - transition-property: background-color, box-shadow; - transition-duration: 0.5s; -} - -div.line.glow { - background-color: cyan; - box-shadow: 0 0 10px cyan; -} - - -span.lineno { - padding-right: 4px; - text-align: right; - border-right: 2px solid #0F0; - background-color: #E8E8E8; - white-space: pre; -} -span.lineno a { - background-color: #D8D8D8; -} - -span.lineno a:hover { - background-color: #C8C8C8; -} - -div.ah { - background-color: black; - font-weight: bold; - color: #ffffff; - margin-bottom: 3px; - margin-top: 3px; - padding: 0.2em; - border: solid thin #333; - border-radius: 0.5em; - -webkit-border-radius: .5em; - -moz-border-radius: .5em; - box-shadow: 2px 2px 3px #999; - -webkit-box-shadow: 2px 2px 3px #999; - -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; - background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444)); - background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000); -} - -div.groupHeader { - margin-left: 0px; - margin-top: 9px; - margin-bottom: 4.7px; - - font-size: 19px; - font-weight: normal; -} - -div.groupText { - margin-left: 16px; - font-style: italic; -} - -body { - background-color: white; - color: black; - margin: 0; -} - -div.contents { - margin-top: 10px; - margin-left: 12px; - margin-right: 8px; -} - -td.indexkey { - background-color: #EBEFF6; - font-weight: bold; - border: 1px solid #C4CFE5; - margin: 2px 0px 2px 0; - padding: 2px 10px; - white-space: nowrap; - vertical-align: top; -} - -td.indexvalue { - background-color: #EBEFF6; - border: 1px solid #C4CFE5; - padding: 2px 10px; - margin: 2px 0px; -} - -tr.memlist { - background-color: #EEF1F7; -} - -p.formulaDsp { - text-align: center; -} - -img.formulaDsp { - -} - -img.formulaInl { - vertical-align: middle; -} - -div.center { - text-align: center; - margin-top: 0px; - margin-bottom: 0px; - padding: 0px; -} - -div.center img { - border: 0px; -} - -address.footer { - text-align: right; - padding-right: 12px; -} - -img.footer { - border: 0px; - vertical-align: middle; -} - -/* @group Code Colorization */ - -span.keyword { - color: #008000 -} - -span.keywordtype { - color: #604020 -} - -span.keywordflow { - color: #e08000 -} - -span.comment { - color: #800000 -} - -span.preprocessor { - color: #806020 -} - -span.stringliteral { - color: #002080 -} - -span.charliteral { - color: #008080 -} - -span.vhdldigit { - color: #ff00ff -} - -span.vhdlchar { - color: #000000 -} - -span.vhdlkeyword { - color: #700070 -} - -span.vhdllogic { - color: #ff0000 -} - -blockquote { - background-color: #F7F8FB; - border-left: 2px solid #9CAFD4; - margin: 0 24px 0 4px; - padding: 0 12px 0 16px; -} - -/* @end */ - -/* -.search { - color: #003399; - font-weight: bold; -} - -form.search { - margin-bottom: 0px; - margin-top: 0px; -} - -input.search { - font-size: 75%; - color: #000080; - font-weight: normal; - background-color: #e8eef2; -} -*/ - -td.tiny { - font-size: 75%; -} - -.dirtab { - padding: 4px; - border-collapse: collapse; - border: 1px solid #A3B4D7; -} - -th.dirtab { - background: #EBEFF6; - font-weight: bold; -} - -hr { - height: 0px; - border: none; - border-top: 1px solid #444; -} - -hr.footer { - height: 0px; - border-top: 3px solid #444; -} - -/* @group Member Descriptions */ - -table.memberdecls { - border-spacing: 0px; - padding: 0px; -} - -.memberdecls td, .fieldtable tr { - -webkit-transition-property: background-color, box-shadow; - -webkit-transition-duration: 0.5s; - -moz-transition-property: background-color, box-shadow; - -moz-transition-duration: 0.5s; - -ms-transition-property: background-color, box-shadow; - -ms-transition-duration: 0.5s; - -o-transition-property: background-color, box-shadow; - -o-transition-duration: 0.5s; - transition-property: background-color, box-shadow; - transition-duration: 0.5s; -} - -.memberdecls td.glow, .fieldtable tr.glow { - background-color: cyan; - box-shadow: 0 0 15px cyan; -} - -.mdescLeft, .mdescRight, -.memItemLeft, .memItemRight, -.memTemplItemLeft, .memTemplItemRight, .memTemplParams { - background-color: white; - border: none; - margin: 4px; - padding: 1px 0 0 8px; - - font-family: Courier, Consolas, monospace; -} - -.mdescLeft, .mdescRight { - padding: 0px 8px 4px 24px; - color: black; - - font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; - font-style: normal; -} - -.memSeparator { - rder-bottom: none; - line-height: 8px; - margin: 0px; - padding: 0px; -} - -.memItemLeft, .memTemplItemLeft { - white-space: nowrap; -} - -.memItemRight { - width: 100%; -} - -.memTemplParams { - color: #4665A2; - white-space: nowrap; - font-size: 80%; -} - -/* @end */ - -/* @group Member Details */ - -/* Styles for detailed member documentation */ - -.memtemplate { - font-family: Courier, Consolas, monospace; - font-size: 100%; - color: black; - font-weight: normal; - margin-left: 1px; -} - -.memnav { - background-color: #EBEFF6; - border: 1px solid #A3B4D7; - text-align: center; - margin: 2px; - margin-right: 15px; - padding: 2px; -} - -.mempage { - width: 100%; -} - -.memitem { - padding: 0; - margin-bottom: 10px; - margin-right: 5px; - -webkit-transition: box-shadow 0.5s linear; - -moz-transition: box-shadow 0.5s linear; - -ms-transition: box-shadow 0.5s linear; - -o-transition: box-shadow 0.5s linear; - transition: box-shadow 0.5s linear; - display: table !important; - width: 100%; -} - -.memitem.glow { - box-shadow: 0 0 15px cyan; -} - -.memname { - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - font-weight: 400; - font-size: 19px; - rgin-left: 0px; -} - -.memname td { - vertical-align: bottom; -} - -.memproto, dl.reflist dt { - margin-top: 1.5em; - border: none; - border-bottom: 1px solid rgb(172, 172, 172); - padding: 0px; - color: black; - font-weight: bold; - text-shadow: none; - background-image: none; - background-color: rgb(235, 238, 241); - /* opera specific markup */ - box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); - border-top-right-radius: 0px; - border-top-left-radius: 0px; - /* firefox specific markup */ - -moz-box-shadow: none; - -moz-border-radius-topright: 0px; - -moz-border-radius-topleft: 0px; - /* webkit specific markup */ - -webkit-box-shadow: none; - -webkit-border-top-right-radius: 0px; - -webkit-border-top-left-radius: 0px; -} - -.memdoc, dl.reflist dd { - border: none; - padding: 6px; - background-color: #FBFCFD; - border-top-width: 0; - ckground-image: none; - background-color: #FFFFFF; - /* opera specific markup */ - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - box-shadow: none; - /* firefox specific markup */ - -moz-border-radius-bottomleft: 0px; - -moz-border-radius-bottomright: 0px; - -moz-box-shadow: none; - /* webkit specific markup */ - -webkit-border-bottom-left-radius: 0px; - -webkit-border-bottom-right-radius: 0px; - -webkit-box-shadow: none; -} - -dl.reflist dt { - padding: 5px; -} - -dl.reflist dd { - margin: 0px 0px 10px 0px; - padding: 5px; -} - -.paramkey { - text-align: right; -} - -.paramtype { - white-space: nowrap; -} - -.paramname { - color: rgb(37, 53, 85); - white-space: nowrap; -} -.paramname em { - font-style: italic; - font-weight: normal; -} -.paramname code { - line-height: 14px; -} - -.params, .retval, .exception, .tparams { - margin-left: 0px; - padding-left: 0px; -} - -.params .paramname, .retval .paramname { - font-family: Courier, Consolas, monospace; - font-style: italic; - font-weight: normal; - //text-shadow: rgba(0, 0, 0, 0.3) 0px 1px 1px; -} - -.params .paramtype { - font-style: italic; - vertical-align: top; -} - -.params .paramdir { - font-family: "courier new",courier,monospace; - vertical-align: top; -} - -table.mlabels { - border-spacing: 0px; -} - -td.mlabels-left { - width: 100%; - padding: 0px; -} - -td.mlabels-right { - vertical-align: middle; - padding: 0px; - white-space: nowrap; -} - -span.mlabels { - margin-left: 8px; -} - -span.mlabel { - background-color: rgb(172, 172, 172);; - border: none; - text-shadow: none; - color: white; - margin-right: 4px; - padding: 2px 3px; - border-radius: 4px; - font-size: 9pt; - white-space: nowrap; - vertical-align: middle; -} - - - -/* @end */ - -/* these are for tree view when not used as main index */ - -div.directory { - margin: 10px 0px; - border-top: 1px solid #A8B8D9; - border-bottom: 1px solid #A8B8D9; - width: 100%; -} - -.directory table { - border-collapse:collapse; -} - -.directory td { - margin: 0px; - padding: 0px; - vertical-align: top; -} - -.directory td.entry { - white-space: nowrap; - padding-right: 6px; - padding-top: 3px; -} - -.directory td.entry a { - outline:none; -} - -.directory td.entry a img { - border: none; -} - -.directory td.desc { - width: 100%; - padding-left: 6px; - padding-right: 6px; - padding-top: 3px; - border-left: 1px solid rgba(0,0,0,0.05); -} - -.directory tr.even { - padding-left: 6px; - background-color: #F7F8FB; -} - -.directory img { - vertical-align: -30%; -} - -.directory .levels { - white-space: nowrap; - width: 100%; - text-align: right; - font-size: 9pt; -} - -.directory .levels span { - cursor: pointer; - padding-left: 2px; - padding-right: 2px; - color: #3D578C; -} - -div.dynheader { - margin-top: 8px; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -address { - font-style: normal; - color: #2A3D61; -} - -table.doxtable { - border-collapse:collapse; - margin-top: 4px; - margin-bottom: 4px; -} - -table.doxtable td, table.doxtable th { - border: 1px solid #2D4068; - padding: 3px 7px 2px; -} - -table.doxtable th { - background-color: #374F7F; - color: #FFFFFF; - font-size: 110%; - padding-bottom: 4px; - padding-top: 5px; -} - -table.fieldtable { - /*width: 100%;*/ - margin-bottom: 10px; - border: 1px solid #A8B8D9; - border-spacing: 0px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; - -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); - box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); -} - -.fieldtable td, .fieldtable th { - padding: 3px 7px 2px; -} - -.fieldtable td.fieldtype, .fieldtable td.fieldname { - white-space: nowrap; - border-right: 1px solid #A8B8D9; - border-bottom: 1px solid #A8B8D9; - vertical-align: top; -} - -.fieldtable td.fieldname { - padding-top: 3px; -} - -.fieldtable td.fielddoc { - border-bottom: 1px solid #A8B8D9; - /*width: 100%;*/ -} - -.fieldtable td.fielddoc p:first-child { - margin-top: 0px; -} - -.fieldtable td.fielddoc p:last-child { - margin-bottom: 2px; -} - -.fieldtable tr:last-child td { - border-bottom: none; -} - -.fieldtable th { - background-image:url('nav_f.png'); - background-repeat:repeat-x; - background-color: #E2E8F2; - font-size: 90%; - color: #253555; - padding-bottom: 4px; - padding-top: 5px; - text-align:left; - -moz-border-radius-topleft: 4px; - -moz-border-radius-topright: 4px; - -webkit-border-top-left-radius: 4px; - -webkit-border-top-right-radius: 4px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom: 1px solid #A8B8D9; -} - - -.tabsearch { - top: 0px; - left: 10px; - height: 36px; - background-image: url('tab_b.png'); - z-index: 101; - overflow: hidden; - font-size: 13px; -} - -.navpath ul -{ - font-size: 11px; - background-image:url('tab_b.png'); - background-repeat:repeat-x; - background-position: 0 -5px; - height:30px; - line-height:30px; - color:#8AA0CC; - border:solid 1px #C2CDE4; - overflow:hidden; - margin:0px; - padding:0px; -} - -.navpath li -{ - list-style-type:none; - float:left; - padding-left:10px; - padding-right:15px; - background-image:url('bc_s.png'); - background-repeat:no-repeat; - background-position:right; - color:#364D7C; -} - -.navpath li.navelem a -{ - height:32px; - display:block; - text-decoration: none; - outline: none; - color: #283A5D; - font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; - //text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); - text-decoration: none; -} - -.navpath li.navelem a:hover -{ - color:#6884BD; -} - -.navpath li.footer -{ - list-style-type:none; - float:right; - padding-left:10px; - padding-right:15px; - background-image:none; - background-repeat:no-repeat; - background-position:right; - color:#364D7C; - font-size: 8pt; -} - - -div.summary -{ - float: right; - font-size: 8pt; - padding-right: 5px; - width: 50%; - text-align: right; -} - -div.summary a -{ - white-space: nowrap; -} - -div.ingroups -{ - font-size: 8pt; - width: 50%; - text-align: left; -} - -div.ingroups a -{ - white-space: nowrap; -} - -div.header -{ - background-image: none; - background-color: white; - margin: 0px; - border: none; -} - -div.headertitle -{ - padding: 5px 5px 5px 10px; -} - -dl -{ - padding: 0 0 0 10px; -} - -/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */ -dl.section -{ - margin-left: 0px; - padding-left: 0px; -} - -dl.note -{ - rgin-left: 0px; - padding: 6px 0px 3px 8px; - border-left: 6px solid; - border-color: #D0C000; - background-color: #fff799 -} - -dl.warning, dl.attention -{ - margin-left: 0px; - padding: 6px 0px 3px 8px; - - border-left: 6px solid; - border-color: #FF0000; -} - -dl.pre, dl.post, dl.invariant -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #00D000; -} - -dl.deprecated -{ - rgin-left: 0px; - padding: 6px 0px 3px 8px; - border-left: 6px solid; - border-color: #505050; -} - -dl.deprecated dt a.el -{ - font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; -} - -dl.todo -{ - rgin-left: 0px; - padding: 6px 0px 3px 8px; - border-left:4px solid; - border-color: #00C0E0; -} - -dl.test -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #3030E0; -} - -dl.bug -{ - margin-left:-7px; - padding-left: 3px; - border-left:4px solid; - border-color: #C08050; -} - -dl.section dd { - margin-bottom: 6px; -} - - -#projectlogo -{ - text-align: center; - vertical-align: bottom; - border-collapse: separate; -} - -#projectlogo img -{ - border: 0px none; -} - -#projectname -{ - font: 300% Tahoma, Arial,sans-serif; - margin: 0px; - padding: 2px 0px; -} - -#projectbrief -{ - font: 120% Tahoma, Arial,sans-serif; - margin: 0px; - padding: 0px; -} - -#projectnumber -{ - font: 50% Tahoma, Arial,sans-serif; - margin: 0px; - padding: 0px; -} - -#titlearea -{ - padding: 0px; - margin: 0px; - width: 100%; - border-bottom: 1px solid #5373B4; -} - -.image -{ - text-align: center; -} - -.dotgraph -{ - text-align: center; -} - -.mscgraph -{ - text-align: center; -} - -.caption -{ - font-weight: bold; -} - -div.zoom -{ - border: 1px solid #90A5CE; -} - -dl.citelist { - margin-bottom:50px; -} - -dl.citelist dt { - color:#334975; - float:left; - font-weight:bold; - margin-right:10px; - padding:5px; -} - -dl.citelist dd { - margin:2px 0; - padding:5px 0; -} - -div.toc { - padding: 14px 25px; - background-color: #F4F6FA; - border: 1px solid #D8DFEE; - border-radius: 7px 7px 7px 7px; - float: right; - height: auto; - margin: 0 20px 10px 10px; - width: 200px; -} - -div.toc li { - background: url("bdwn.png") no-repeat scroll 0 5px transparent; - font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif; - margin-top: 5px; - padding-left: 10px; - padding-top: 2px; -} - -div.toc h3 { - font: bold 12px/1.2 Arial,FreeSans,sans-serif; - color: #4665A2; - border-bottom: 0 none; - margin: 0; -} - -div.toc ul { - list-style: none outside none; - border: medium none; - padding: 0px; -} - -div.toc li.level1 { - margin-left: 0px; -} - -div.toc li.level2 { - margin-left: 15px; -} - -div.toc li.level3 { - margin-left: 30px; -} - -div.toc li.level4 { - margin-left: 45px; -} - -.inherit_header { - font-weight: bold; - color: gray; - cursor: pointer; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.inherit_header td { - padding: 6px 0px 2px 5px; -} - -.inherit { - display: none; -} - -tr.heading h2 { - margin-top: 42px; - margin-bottom: 20px; -} - -@media print -{ - #top { display: none; } - #side-nav { display: none; } - #nav-path { display: none; } - body { overflow:visible; } - h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } - .summary { display: none; } - .memitem { page-break-inside: avoid; } - #doc-content - { - margin-left:0 !important; - height:auto !important; - width:auto !important; - overflow:inherit; - display:inline; - } -} - - -.tabs, .tabs2, .tabs3 { - background-image: none; - background-color: #292540; - color: white; - -} - - -.tabs2 { - background-color: #494083; -} - - -.tablist li { - background-image: none; -} - - -.tablist a { - background-image: none; - color: white; - text-shadow: none; -} - - -.tablist a:hover { - background-image: none; - text-shadow: none; -} - - -.tablist li.current a { - background-image: none; - text-shadow: none; -} - - -.tabs li.current { - background-color: #51637b; -} - -.tabs2 li.current { - background-color: #888; -} - - -.navpath { - border: none; -} - - -.navpath ul { - background-image: none; - background-color: #888; - border: none; -} - - -.navpath li { - background-image: none; -} - - -.navpath li.navelem a { - background-image: none; - color: white; - text-shadow: none; -} - - -.navpath li.navelem a:hover { - background-image: none; - color: white; - text-shadow: none; -} - diff --git a/docs/img/logo-2x-dark.png b/docs/img/logo-2x-dark.png deleted file mode 100644 index 4b5850c88b16cc82fb74bec8c06af37dd098be29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3533 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 diff --git a/docs/img/logo-2x.png b/docs/img/logo-2x.png deleted file mode 100644 index f66e3537715ac4502afbd28c5ad4908d6d1b7b8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3467 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�H(XGJGL_6>R$L#VmuQhNLJl=y~}-EJ|e(1A=K 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
    }Jy&S8QT>yg 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

    7;Xe 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%0AEnyC}Y0$xuH?eI37ryDDu_b<%C z1YWmSQ{jlCk11@sugUdr)?|u2=xHf#g5y~eXQmE5DP^jzhI$WvkxlmO0C^;~J%~Oh zvzEgwbJ~@_7$c-OXEUW8zSO2K)2?palrP^^jSJYvXf3&CX>}rm({;S zjM{=1t-0DL*NNI(S~u8+y0qLGIqIup)dg)5N>wA#whvL+mc@^>-w|&GS3ai2HD+7^|H#gLZ%vf>y1U=>Y0C8*STSOM^KbNT{vDJfMiN|q znfLlCM{S7iWO>2b`*?6pnxX6Ncv5_1==!r`TXs{28|DYQOv>G;b-kdo&xj-B%c{G! zRD*)#v7O?tUiWE})brh4PGXw=Qsa9$uTDqY2jOm?!sM|f_$$LEV>#>jm+4ARhtn@b z_l52TbN%>7X5(|`Y%3S+5Ga+Psz;npd?n0DoHiXhFvaNi{czVz{}{E6PR{WzpW)KGUL((JzRo8qkl` zAizfc2V~5qPsnMJ@Q34#9$Si`Gw1DVhAYtOcr@_(B6Hr(eucAaE9O81T$HH{}a4IAE-$d46(~HH0q|=T?JT-e;@&;v}!x* zJv+tVvYkg_vfC~qw0i6k2V-^HF0wpPK9QzDfaS2Ap9^~=b56oVpMmm?x5Lw@vl(GZ zhV9!unZrXe-Kn!iTU~}QUv0_xty`*pt0$|A-vq+{TaXUQfyJ7+?IiYTSU9!JIL(D%U9r!ww?582Fw{oyFN<{6Ln8OL||anT_*M@f6DOJKYe$N14Q zUj4MX?~^O9XwY`Sa98-I(mi6PQa6QknDS=GKawI-E|Db5$Cf77RqaP&H%BpEH+P6+ zW_Od=&QbVhnH>9|b4~kpDnXO&&O^IH`a+)>?k?q4qgXhd3;Pdcu;P)=y~l3Zc=stO zW4lD(@?B!2rWAgG@9E66X{$haWA=!mQjr3|@Z8DE-k8ncoa5}+VSt?YF1et__Rl`K zYH7lz42(H6l*05YUpU|v3(RXyBK)&?ABI|%SGL2&kPtUX>sL=EiShvy^l;Qng50H{ zL<9;F;@UiCwpocz_-C>rWaX!2?rnLIW#+G8n3QxaZPEsN`|>KOy%IQrUZ_h0RB^){ z4i*mN5l4VVrIzRttE@VRByP)WvA!g$V#nlXwFGfFwKbIV8SogL@|jNOq5rxnx$s%L zgfF8zBQ@1akILC(g;T0CVdFE(`hp~bb?)aBi)>CEx`LqW`MN<;lT#!)Ho0+{8pEbJ zXMAPbvj(xfl|t~*W2DNz`TTGSX|sBIA)wMuqeLa?7P3qYSQY`!jn>bq;=8nc&zOd? z{$ge2fQcdDHA#OpGZ^mf#9fs+iTNbydHIMoa{KcP2>1Z=og#S}^8sPvPG=2fhf{J0 z(~OPu`PvTSH601<6fN&W5@qRr!Q6~$A`vI)5eZepBIG z%Vh?}J7b!W2#ybD`hTn0;^d{TgmSO=n3cCWR%}VH*y%*(++LDtXsm#&hB86 zJ(m+tMVyiY@>{0+)Q33IMif@5`PbFXOpN2G!vwvh%oIx^*!l^`FPkEG?G2vBykag^ z>GavocqC}Z&dZatcmGgES5V?k&>(SlQvQB$A!Hk zZjihe<+WBDWIhhfT-MILW)a*p~zq_UtQh)xvBn%Nx@m{26m}&dIt`NxHA?>km_HCWZ#s za1BhdFyI@lFFG)143BZcCr`ipa>m)b2`c%!LpgC~b(%?5fcl+x6%GAx2w=kuH(g38 zrRXJr&I}~Yqwp1zwBF8*W1E_OMHjz)uXy-8|CjzJ^M6j6G1Alhcm2;LEy-ByUyZR3 z)y2PQ4cl;eb9}BF)<(ACUYeKW{0`v=M5&)&Wkd-DN){s!|RNBU2M&dNZ*N%P@`$d7_oN$n*ml1=CFe)p8*)%{010f|oByKBpl6D^U;63yzG);QH{ zdN%KvD1xPFMs>qjEwr#5ro%O97l6Pw?B;Rtpoqad8}jz1m~6V7?fdZ!B~EpYG2LHDjx%DF-7jqkH>sN%Y^RO{^)@wylhV=G0ELByZH`lS6%vKd4L7>4M}!+b zd?NJkrE7PU>&4RBBZcDct@?ZyitFSZ8vQ&{>WR>Aapm(Tg?emQDC=Br4J!)pOvOPx zB_eF+JgW*}df6$XdQ*Ku@jJ+QvDj)Pmw^~!!ibqv;)K+^RGk=-Q5@e`-k&t;^kIwi zDz0%!W7xl^4bQ|>Tsvf778VXwi3EF2?BueGE?BaUo}7)Odh0yx93#1vMM=8JJ%+Z! zpO-z2xNk@q#Y!Fsd(9Gy%Q-3pBod;DE7)PLFj|yj_OsJxV z5kU>C6?g8VDF^hJ8Wd+#X<1X2)LGV+Y7NjstojaRF!gwHEl9C(#ja&C(9-jp zC2A&7{i;BuyF-oHQxdicp<5agV>+C=pwjl(T7p*u9x85>$Z zs-_babY5g^1`gMhbu8c(W%>TA6h>yWB4wwtR?a6js%Q+R@+0(T)}+kAFdGzq-vgf9 z>a}%um2o84hEtcjO?s%KN)Bx-My69G_n3Hd?nsg_TKQnf{3#n)zPLHlMvesxiLcKZ zqL9J3Br5)cyk~I8#iZ^G9UOs^7>{C%;V#}^0I(nkGCrJCXC<=6HlVThRCnvH`1B~Rf1OhX*+N<>1GJ7APUCQYV@$zIZi z7vaXy(9A3YWl*P-f<#4`4Re#Gb7TK966Iy3?1_f(iNx{PEo9?_>=!sBy$#e(WXQ^t zuYkbELg+@zT2mCa95mJu6#PpM-HTYMS^n*@#cJqReLBSoF3;QbhH#Lu3{cA8UE*uU za^qT?094&aMqN(Ck$Rbh2sQXaU~|sTV%#%~sRV6=vhP}YO;3!2xceZ6qGD~8 zZY`+WxC{lUHADuQ>gLW*fx6F1I^A+Pdobr&O*MneLLuv>hjD+F@hiN0DUl5ey^Kocp&CK>kwzr` zv4>P2PB9qZG7H@wLC6l#J~ro2COy26;`@!1Q5tOQI5C;!fFqwClKBLew(7tq@aI!K z{O`jWC=)GF26Bt7Ldwf3qsl`n1HLsAqBTbOg>yK$mEB{qOQYb7!ph_#8C9$J6Z~px z@(K`F;MPh&K1@F`g4Y#5Xv7^t@%b96aeh} zCaBO=8aW$Mj>}9m(oIk?TM&-Ug)_QMu~0?6?BF7SlfZ9Tq%ifM*|9c6L_DBT_?W2~te}Lig;j zh9dj`FF*!oN(%^|64YInp-BbZjXhg4J?+Brhyr&BOb_)wa=T?vY;pV@&e$Q5@ZQ@H6!wD3ChyG01BFz{C0L|83u=Vv=R4P^2>;T#0`Y z80W#AyvCDwx?P^#X?qV6Je813*t}JNkZ4))xcIQc<@Y4McR}^%<6>rNYq@2u0#nXa z!TUZQh_z7i>Rhzf6WzkYIwGWc-lQ1YQN<~Y%xq|d613Aa)ZyLduZO%*|BEu-0XQlL z`B!6VR%J$F&6UjLF-b~AK}>ii;evs-UxUreP=*<6Qkcpx1E^YE4RnBOc|fiWw!)lj z-)3P)8oi^=Bz^&JtsujhfVxiqp*X;#ose=WB;#kkI)2V6R~cpbR|s~w1#2351|H^K z^eM{?wJvev`PGdU5l1C3N;F^Rf{xNQgMu^3?qS5rP_U)DXUM^3xa14!QB@#m4Ryubs@Uj(@1C=&NlWdT;Flf z>1*8?!AsxTVB0!P*H~mVI#}IOjUfPJ+BezcuS97yx&P zE_;|OyH0n9gQ?@v0Y|FtVkC#x2eJy)Obner7-u+`If6G1pYqBCWeW?<2>ekVv^%)> zJ+lgOssj<()5G}tO(HMBAYO?pa#LvjedzM2WuW9h6BW1w7Gqnk$(%r=jPRz|sOi z;a{q_-hDl|E}QEX^XRh9M~BTBMuI1ec;1Jibqoqpy4d__M=rq$tHKQ~wqO26?KBkZB zLHkTD9F^)=lDm5KPQKQhB1Z_UwGs{ol`MGoRtv?0_K88g3)ayEZU?kQ4aG!;Y|F== zZW2uTzZc;>hM0@#1W~*0?Pj%lDy%s2cbbSA`l8p20kQKJYat~^X+10cb&II>+v%)t z9wVrOv6Zl5mZplGp4<5xv+`UOjS~J{s;uHiFphagN&P($jR*NNJ8(Tj-qqNI#*kgjAg3@# zX*u(QMOJFfYOPyV`|_+oe6A`g7%kO20@>u@-hj@i8O7IluFqr1@h~cVzzwsjXB2u@ zP&a|-UN`3ey8HFSLv&cB?%9pnZD%4U#k$+Un=Ha}smCmN*XxaaVo69L{H5c5H8Pd=srS)NP;2eD(ZE4{HwZx4tg+j~nK< z?nqH4(X=Yw`3DO7xO!Lw?5EH#Qm@*4-R{%A;WFPs@cx$*2g`p>T+lNz(*1Y57WHu( zLRR=+ABI<;l6F*aK6pKIDndue{S7@Ch=fV>z{7+c*?4U&&FW%n7*K)Awdvv%RjjoD z&fC27-2sTc%tsyd=bN2XuC<4aNfEkBV_K~6p8#=_sJ#iXW2M~*z>fp#hkqfQd)k&T&!MTeKep5^vig3 zVKpWK-Yn~H96SYC2d55S)P20ax6^&OErx*5WTPApz3+0vK{EWICMB>Sc+QKCtZwVt z8ef$$iYr>GM{5!CT8>0JL|~D!okqBEFkqjF6G!ffvvy2-Kj02`&a*d8W||mcC2E@d zB(7lKd8VcxOUC&Tl|Z zmt%P*iXXF5ltpEfQkyO7YQa4bIW8?_uNX?DJz&hHBcraN@W36iXOU!Oggr4SimyJL zEz1S4SblLv9!SY@7B&)(A0!l;=f7C(cd%pzs%%Rt5rP%8;%_Nasb4#S(yFOo?2)wa ztK5$O{+EqCZpn!-Mm7bQM%h6mN~n!7(Be$#Ileui5&tBYwJ24~WPay96w<~*h!W~rOVOKfQHG-ukmL!p#2C6y+=3Lfk$-PyL@87H^^==nl z_cIK}2N$HX4(D(K*GU`Mqj}KNE?T5=Pg0bnDo%I24|{f z6BrxeB(2r{Fxy0S)AUsVny>QXiGPv5X9a``h6SzOEMT9cRw&#)`bw%)eixRx*ID>k z4w0v{Y6+T-aRLue6-{8K4}3QDd_ZC>vw;A!LL0VxzT;g_SE2P*k284{K&O|E+}{I~5G+H|Cu`*<70C-`as&D5S*s?3x=p+!~U` zZHx)&PVZ_ltj*i%t2!K#?I6$beHtsCHUd^Wm>gF=Xq2-wu@@j7HZvRCF|b5%>-0q< z+GU)bdtHV-C-Vj4bRo}^J}wLnP%=PJyhq3Ln;p~F>|gwviqr?#CRO`>jEy2V=1hN$ zY8*@T=bGt~rw`Qfwa{)lsttHr!!NjKv)f^tB|IapOrDfyp-yzm?{S2G{w{hCF8wdz_t&}aKWK$4>}>y4`0d6XkD+S4`tB+4 z-5p;?%cJj;`WJ=pk#x8PotwZ0c7h*9`}QRVMQl-ieh%V)$F-%6C>)PG-28f$OX~A} z7wPx&kjQ3UoxZq#Xrwp4y0FvZ`}Pm*xXsm^?;{C!x9WZn&i9>0=H}=7-t;w=(X?Bc zbf)b9^&9-AnEmU|WOeT6ktpD8<$VA-)-KQkMgHAn?dPMb+bh!4Y=7=|W{3~3x$PfC zpxhtlcT26d%d+oop!lDatPX9W4^@Ge*59GGSEX|zWGEFf-}mPY2vW7-HfPdOy^MuY z+a1~7EcVn4$HIHwtG!ykUf6H#-QOIcs&9>L%!YPzml?Tp83}*8BKA5uxd}d+|V{-m?{h22vWEDMyE#{fQgRE+q=Im!K5PIhuGtQ!N8b21+2jFL!dX$JfbD`KS3iigHNqEy3xRZ=X>$}+ zfr1J-6+R`^KaSxEtuWAv*Lzl@KM{b#;JHP0hSsK5z}3vVZ+A#?PS=zUL1cqBq}FjZqZG<=Lc<@1g_^@$kNM75Q8LWfGaxdBtAz!+WlZ!>CBvY6RSL%Lcd#CM@McQ4F|D zjir*eae1YW_p3EccG*2iBWMj}lx;O66`yEGKG(MeTA8^%)1A!8EDlVNcM(B}V`3x| zV)dYU{9&z|6tWy@EU-3umJC@| z`M;`kKK5(nA6#T<8fU0G*Lbo*)ad};uZSmnb+~QyRv7vR>C$y6mTQGhAUjg!ky1(h zH;Hm2Op0eU6Nj4yMOIV|FjBEVjDuzYMKf)-MBCd%^{=3j3SW`CZMGo?UzIhmY%riP z0j$ed%F~+tE!Lf~+qd`oC0+338ypir%D&R4PA|)TZG)LL(Tlf2B*yZXimAUj%-cRG zE!qUdjLgh9Obm0g+#*lksNW`KTMlWtrAuLw*NY8MLz>wP2o}3i>t?$@1ddpbdZR3JI8#k$ZmK+s5sczJ!^UPT1#KwxEQZw1!!A zSSOD4kB$dNc>#9+%I7Me|E06DU|%TRkv*X0J<(&>tje)J;{!?fpc9jev{xT(tFAvZ z6Fe=7i0)cl zlMFhJ&SF{cq*y>r0xfPyYsRfE+_D;(+dnv}Y@U-vp>In5wXkFcNIPxf?8tJ=BOp1H z^N%g;HWc`$=?+Fx(IxhF8~*o_;~45W#mUFu;>5!V8L)hGP9%16glXuy0rJq<1OzU) z2phndKsRo4&TjJG@?i=|iP9&(gJhiGbxC%Sb2N`C=TqOb`Ch)q`Scpzwx^@U?eGh} zRwsL{Ae)`zcC5%k0_%Li0xnk0pHyf$ASP}pEAwXA1V~59Lbar%426gowCwQc96!>T zEh~*1;9FIEJjgFGwcH>rmVpEL*kt(t6Gf89?)NKCn@qr~Wa#spqW5m{qc9K19oQP| z)$_a6t9Azs^*B;i{+sOZ@jdj>B*#GCHKViQFQXVI@?|hw} z>|FZT^|3Iih?TVW{@%x@+eQVBk&i~r5;|bctY4M=p)w`quyz;X(An_Wp9qOGpn4F9 zDzv=~^O72`D4YbDkxGl(xDDI2F_{0S*}g@HnD<>6%P6)UB1%+<#w$564U6EGX>vRy z2pb#@FHeV!pp*6B3Dazbdv(bd(yyM`{I}sta;>I6vXEAg)W|4)lvO$t>VF~)pGdR z*zuhX6qAUPOH+wi5B|o>Q>C6IWm+6pCvE)+M};a+DOe9BGO^{pt0zBRkcvTnnajN* zNIJs^qmGAs3d%ncTcNjX5(8Csn3N1ji`vAwBm$YeOYiB?5^#X}jfk?esw`LvawC6h zUB05|h z8O!pq$qMwi${UqID-9k>n_w{cDRS6jEQghj(2(Okt>d}TA`6nE1T~qBL8NOSMm$s& z?6k3O8gQv2v0WNqkX!?piL&|BH{noG7cq?_RcqpD75<@!-GTqZly+|<(G+AHGC1C7 z<)ODD#;5S(S2|$Gl!R~$A~0V}AQ-?c6r1;q*eQ7-a-)F64%258mF!3Y1hUHDKEu{8 zql4`Yv8^?%-$I(yC~jcT`tZd>)1eDpa{s z3|8qk5SPIZ&Bs%O@j1=WHaqquk6|FO&~OZ-U6wIJajsAnU9#QsX)4v2pVu8d?=pYB zzJf$8Og0(1%S(cwGv{v$8Bf$}nqIq_j&^1uHWaH4P|PR8ysFYUlqRNW1k2o{4UhPhG| z*IBzH1=UEs7US0TI%U;vkV*6|z`~rUUDS|zGU)0}_XtJWa|ZdoLeu z;M4AL#5yO*q0H_uy46C-GfdxwWfwSE2&oTJTwF60u7_%RZSY3Iftcc(H~#B)A7f6= zp>HnLJhmtdV{9x+Pu}Qbd3?mc6K8r)61>FIHLKht{E(o;*?NeFlAFRl+%o3Xl=!ZL zMKGaZ!ZIQCGUF5R_3p#b!p_~?JYLj5(Gg>ZsfmA(vt{$+ccZY+*7F4ZIyZ1APdG3d2@^)CR{1~+FrI;(VwE?d#hOS*#KRPqtnm}Ec zHn&7M8mKGGWwt)vY2hm}4D+hKL@pl0)dgFC4VoTyhh9)NWhX^ z-UoA*Ae_wdv;cz1XYpDZXZ#(4)AqOMh+NNX%QzpM4@&-M)6BR*s?y-3bJNMbTmr-q*R46x_07mvR07Q(x6;bG9~0eFXf@em&Lc! zB=UY5S7X~5;GteDhehH5Mwoj&O4k&c<&%X23X3JtEGU|Xo547tkEUzjjb9R9R&zqj zUM7gBK>NTai$wwc{3Tcw%wS2PL+S8~uWp?IRx-*@YsJW+t+lE%6Rm!>N36=(#%6oH z{#Sl*(@n`ekNd;y;*Jgk5|h&Cm_?=17(A;0W}e$is3RkhOKu&UXP$(Y73zfohe#l( zgB}+(-OmXP3{!&2)w8(*!c&#o#*<2CELN%=yRO?fgw z71893%A|cMc+L6Ol5weFVd=b&TE^ppmBFcq(^pAcL>uB|YxUE|*f6tt(c%nwVrEaC zQwsj19>If1!jqog##Z$t-{DOm*B4gdr2~aMX}N#L!%OKR=Yk^xD8tNy_MBWqwL0oA z(7x6$XJ83e%NpEJeXk;2tor-j+V#?=Bke-EP*FFM+wojA%GJ$V7q%uUey{6?S#@%O zdYp(0FF~AMdm?y_3jF8tg#o&>F7_$-;)aTh)zJp?G~zRqMibGJ>e&;CN}5U#rv>w3 z5GH$Q%cMNDxD%UJx&Q&?a9k7F$+Y4P_1R^Sb2Ei@30jk&8AV~^0IYisaX0M_>BpulFO}tN%CIv@ zg3gk(XG>mymi;#2!c@l)X1{v{osaGVzDC<{1s}U@@*t;kJ3G8aSglSTh*Nofc*zQ&6PfAmA`E=Vti7~jdrA37^ zdf^sgI6IslyQvN95i`u%$?dBx98M=~8KQ{S1|=b`-K)J#I~IA^NrKuggukUo1Vs`u zf4K*yixj$+>bul@diPkU${r_`Z$q-^$OY;R9_Pm6ZOtB4#&SmH&V$Q8VD_I~Bmdc^ zV)*~Iss4XWZrJ``lN&l#hX1OCwWV%tyUBw1eXYyC+w-WGM!=5UgDmW~XtNGLO%vwG zR~v#*PA||^#g-}scWmRWBYHSCal|R%HHfs;lR;Vnwj8B)r&5(Qb zUBnCw@{u+>kE>%nEQj7#5ku0$aeaa;a zn#2G^za5Pdf$LceP8{S}eCjDeU5VJvL>|R}EsRGm0!&5?`2-Ui^&SuWUDH$R7dX?!ZGX)DW{h6Lp+D%YYYwse9U4B6&JK z0g+|+XBj3pfm?@Sc;BbVnlG;IRAOz)DSB!%)z_Q^rtI$& z?>bgTfvVU;WEP&sLh=fSHbJwqQzHuGZ384Z)OfbZVOL12Z$I31yvcNI+GRJ~wNXNc zd3SnJc34lWJGHzukxd_-y}S!bpM>or(6bkxLJM@hdT26vBNBzZ6<*WRXS~>+Y*IcK zPZ+XN7ML>I=Yz;|lf)!hq>fycs=BN$JnAV@rm=ObAD!AiIkoOcb6s0}O3t=IHoo&2 zu{#AMsp{mJC1Ev}SCQ<1FVN50+{*jD>t5@9RMEiBGshj&RT|$!yW#d&jp07lM3*tv zZq_U!6~?TZf_uH~*5y5FV!k*2`QJD^zYYm-FQ?4Ok(rTeZ<|J4U(lf&lk21w`&Ij2 zp~+L$QqD_bt}Lk47MnxQxuZ2R<}0J0C7FyIb~GUFRR|CP7O~6Yv2x0XjKUFUA4nUCwL(pb!W{zX}&%{V+S& zSiHlac}4<4>=u)lYf76|qk(7_O1r!KE0x*XQkP7_9UaH@DDBLvK0DrL9R#vX-puJ! zmYc!Cb%xun8NgZ=>ov}0fEaDvm}~XNBXjNSnSU&Tx*UIGa`G5~HVX`ktWBSJ%=t$H zv)@8FIUWS%|9oD?m)|mXpuuwSA*tBmqVvP%j(M;fFed!KcHW?H1E%+NvlXlw z&5{X%$)OKWw_wBG!EI=)s;_dJdepad%{n`(uo+tGx3zKPHhCTqvrKF|*{&^`P9N?U zSUszpJbBP>e8?@S-uNTTY(x$d-YtzlLbKemXtCWF@^Jym>S$}9DYI*{9yR4mQ(m4n zm~$o?h0-n?GgC*dc{OWE)xAXO!oE=7TwQDhdZ4{u&q#!i+h||aRI0mk)K08ji@Wo* zXRS=@0>!aa9ngVGGEM-*%75u2!`D;W_ZFM;5cm)&hq%C+dysbpmu z8wQGPg1WPPkPY#ZwZzo^0jn@f;VeD@VpHj*jhJ*_s3DDfxjHnG=UI{lh@ocJ(FMjM z7Zk(U1mDALv%ej?4%`mG9gVTWi~EqO6we7Se)-bn#57%v(!K;y8h2*XclA8$Lr2;Q zw7p+mDuas&xwyzRzpkupJzJeX?~!^bpP-*0+2Xq177rU5DECU6HT9EqAp}>P<+%eN zKxf|I3ZMfA$0WqOjZ;Jy40~n_$rojyi^d}Y$Uzo`H!u}86@@1t28@P?;wQHwjLMIi zo+bngGyydf0S+lx+(Ed`kDD7FL>JiIrcc1gv`jLJ#Tg_76kI*I*EdX?eXD_hK`Ib2 z#YNy}aHwP;pkuooOyg1`p^9S3wLbsBAt2XLCq%0_hS-DH>0sNtLnGDXy#qg;bmh}s zzNbGeKLwm8-3%COJ?nB@&twuK72So;f%Ha;q5uTZpmRFC3?W`3X13kw z&{Da8nvMm`fu+{zfhzR0oPmJhFn^nwQMe=H1>0TopH`MnzWVhz>_g}#+c^7p@K~yO zhZ2e4hW>)P)a(QO&jN&zjqyMJld&-}{dfP#mbfQtj@s-$-Xb1=s2{!nqCjn4t}KUi zhSRh6lo3t$paa|zcVtn7BMjsb$>Uu~{ONlva4OWOP%9&hxK}TDbuOPmbJ;$5J?~N# zED$1kt8OD_kDqFLKOc8^sZ-doFb_*_1>Y%pUblF^26S(Jo)&lqe^w2m8SjTtP15~& zH`()bfBddveBU1ax&ZWwjiQ-zaZ$;DGJW5xKEJcR-o@Sq-(NrR=AY5h9zp9>?7i{o z{siERBG5B5(3+d+x)^>uKgj+%U!EVK*)S{C=KefOTt%y8q0mkuK6O=O3;@ue~#|Gd47I?`cALV3NV`e;jO9eDP@J!A#eSn^eLtmc%Q)WA>8Hu-ssm2 ztiId%xM5`al<|(V+kbhb_A}BTf$nuc*touZRqKVkHHc)&x!vAl1DO9<>HYo`O%u0` zPkB9o*0bi(k;PUd-ltp`gf~A0xD>*+;+b zd*3R2nDfUWlJ(f5;aq&eZw5j0PpYINU=h;P{9>}8)Pj?3oH^*|cru9tV7CZAh`;F@ zl==ju4mcNt5a7E1VY3E_f9cu^?ZkfIZ%Yq1po!t`x3{NJlDn-k$8S`}8fh5e(!Zu} zIFpym+~j;Z1)Puix8-_C^j*9Z%o$pvLD+F8GHek~fr4v0A1)fs_H2)k2Y4U|%oiHo z7#Tcx<{HKfPxFP=%vM4%$`3Xap1(k>hFs{`eiL)3y^KT}pA(pSXwSbEPBa(F1ntU= zdDV8bx?MY{l0>^NOr=|MU(K85ZQo*|;;*L){h9PGhPW>vNzdiLsc>Zm^_7ou+Yl^G z`~-1}lX-xahIS3E>bVp{tC>pliKfxTFqu`GTeQgB#SQKIu`C8+s5%qMWT>zalEe|_ z^4WAt&-{eyO)60WrpcZnNVroSk!1;RiN@MtP-oE!RBcZ}L_{>5oF+J;BbB@voJ8Aj z`oH7?$fm4{6*1;l$xiEL;NPJdmk{E*KbS4R*8&#mfWz=$)c*Po*5{sCI)!;ORDZw< z0LjdT4f@NZ2++}09>(hS{yasa%DYtY7PAsG^S?E)u$13{SyQ=#sF#e~oK0(b1wxs5 zACManw6T5}FBNnb9+;0qUh8$5QGHQS$)qe(^P+8Sa_*YFvp>|UrZBCu;sw^Vrd01i zave z(XDrK$g+5@eE=tR1FY7qPR`t*U#ci8eWS7mk*e(Ra1(tz=S^#VpViUUA*Y`tId5(6 zQoS-tsp{97FmB+u{&CnOlZQL0t5=N#Og#5<;;AOjZGq!KSRhsM7INk`@szYZ|5xiu zub{5i9N}ryY_Z@&fK;Wn28I=>sR>Hg&|N`7sy?ajWkSrQKIWcT{P(4XjSTHLHU(bN z`>vmc2wX!PO}X2DA>w*tAZ}86`#HI18;`9%OoIb;Q)|jPM?v%?Q7H}q;K~-Q;NP5y~*L#8F4~uQ#u>@vU=OwjV8>Q4Y1l{wDo57lcn|M_wQR3q4C~;=b-VS?V9@H837rogZbT!~#tROeA2Yy3sz-!aUuY z+je#+jjO!ichUi9{>w5-(6cl*4*1jXsr;&4YhA#`hSe2^+P1jxJ?JKNmSN8hs|Nrd zv&6Re5rN;K25j|t9%s)4_>2HcUt5<9*wn48Oxxpx!4D(|JSZ`+ftRz%;kvI`C+OyY zzIRauPH6^r@$VK%rHvpEs4J-$Ab~MzKDAJAe5i6ea3|_l?AmLDQQ3pGGyHp8oALS)W#^9LZBTWhB$o$;Wk(4wn_x!U*uV07d;9yO~J>?RwX|Z*q{Z8 zE3l>)?*Kk__(cVk%@n=fE4MP+Im_@89}s%Ian{C28htqlR3({n9`>O2)6XM@IjpI} zrGfSW09Kv#X^q#HC&W=CP1rAZGyhV+U=TUAw#UD-+4$@8UQ6S{FrOojV?2Lir|)FEpTw(rbZbFv{6#G? zCd%k~YPUdj-ca8fq&3a;O1zk#<{G$%huefRAyYFSDEuY$0O8(4%ITO}b82vH`gCgQ ztbrXQ^GAoqDUwg!}CcnEZ=l zYQ{xH6xp3^GZA;Bk4aPuDru5gE3jkpU7jb#?WJs4aa@?y_AFeL7h=l;0TsZu-%t`NK?yUve;>OC<2J<_r2$-sy`LS-;K^RKR(iNT?(ECc%uwCSoH_I+d3l1J8ETHK)Z+nM#rgt(b4o@bYuZW$EZM<2V987T9%s!p&Nn9u5pRF3FZNg zCCyG!a*(!;cGAKwTOINBz|HNlj38MnM+@SeQ)ig;qFXB@p6Jky2RnmY5KA|m#Ic{6 zG|xlHNUsZ^{(W?Es6~19-M-MKy1(8XvWQ;2z*zqfU48^P=YU36zpcmNo_FK#t+W$S zs%liCojjr|8FtbB;B8e4avTcjjSC(42vUpv9G{kz@>uZ-y5Ft84L^G{*loAqSS{LH&rPCI!2in%!+}@O=Ob5otbPLx^#>D` ztRpd;-^%`oz?P~*X9?PeG*bg?1uvk+0F!m_{*N~HaCVuRlHBTGvR6IxkdC~IurFR_ z4Y!^1fn+;6e*RVF@4IU3HqayWx5v;U3_;=^2ufWIUOL1cnP0d^PX{4ip3U*Nmq zt+gchzrObS<^3saqj0cdxCH|HZ}|!>cf~^h*V z45QQ|;=Z`Jxu(3nNh7BOCbd=Y?$@%MHqT=tvFfOsMqWF16NNUL{AONzs&U_IL{w3t z)lI_XT{J=h3VK&sUC9wKK5Sn;GoT1|03|Y`CaR@WKV3nyJ1p7B{3(jUCDTD&)A~5bbS-qsqCPI6$1iA7q z?FeOkGKa_`E9RM6;ZVSjWh%8~L<|0%j|L$1$2knmu*2P`CECDdj7p%E-u{u54SH6) z)})zK!W7HVUv9Xe22;Xf73JiE!=}=lh0QAZ<2nk5>|_H0hnD|38k?+i3J$yCy8)dJ zojA5}0Nz7u)Tp0m(S)@)0;eLPz0_P(B5imW%!3E)na0)+wV@wc^b1$yk7b?IppUTl zb6q`~-U*@|k1YLWQ$8*4T05g$H}{((!X8#F9@;$+{*3H<8nS1ROVO-3IpQSfuVSs>T*%3=7?g zJfFRJRnE?_lN=YJV{D-JqclQTNM%l+F zf<{MV0^N|b1b;8}4-Tbj0vg&08|;}-Wo|r`{QJFL;bCswB7$7!2-N6YMj|b>4dxF^ zD&po%>+l0#zs~aoi&hccfhY8lW8^5(FTJnbMo zC)eLqWEdc)xg{!1Mt9BKAp4AISm!Ix2AKre$ES;Mj@WnDREhPM=po)Zs}WA@_baNo z4}6gx*pdQ4YOe8)5h#m`tg34=e!^iN3HqCz$dW)}Fo zGPgZxtQyR8lM&DRGHuO?Xr0+xR2vE{e<$f>9KLnRMe*A))aS zgI9>sG&l>GQF>qtkXhzuG`8jx$$F?|WQz-2fLo~Dvgm?W$VZWe`x%k|Vu#di1_uDMG*?GfDL5esQ$4EL=ULUO^76wNgz_dvqg#WjwAqGPLa93Wu@--BQ3-&J zi;r2@G@mi&d$?85%q&>T7(k&{`e>4Hw}4AaeJCKE0HD7OX0wJKoKp#*CA)gC$OVbg zrQa7|_;#*y{h7aHl2+C-Ry{d5Q1dfU0Hq$$;+UGe7b>Gv7T*BHT?%oe zB~Kthv=~swKs5#8jD}sAtBwRM`I^J?CC*3|G$d*J-a7vzogjrcUdR2Mn@l`f&DpTnrS9 z$F)+VIWU#G8PW4oNcOXc7n!5DB|0rtr3u2Qst6Ue5{-KcB`}Ptn>ne52r$`KsB;2K zQ-%B~I`b55$qr%Bu z8rp-RZGN1aSgP{pkr#cibnnL*5nPWkirVhk(`e4;Q<|1b-obwAfLgJzWaf)5uG&}m zP1o$!d&7L&b7}*k5}9WSrF-X*n=v0bvkJv-e8!VtK3ty_6}UmF8rEV3#j(G+2M1DRinMa5ND=9 zMQGew{NcA|HZuQ1w?&w_Dv)^|G%gxfVr3X`IDDvR#sz2K6b4E+_ zq!n~m^Wf+N#zac=a(wp7^i%rJPt@oQ_%dTIQx{cx_d2{B>XWw(v_@xqo6 zlh46b+4eR|O!iB!9|#SPj>}$FxUW`x9?nk7Uf|a#SBS^^OXfH zfy2$2L^8^;!>YCRP>>Z=_X5Y7UC`Wpn#NFOO2^wF3o|dktnKs(%-Z?c|FSk@jVRp& z8nm%gx7An$Usmex_P9*&W_j`*Skl+K<^XMA>o+`P$mV6c1s`9d?O`6O@2n90(0z27w zH+9|R!GeXeu3F?2MjI0&LCEKuAJ{2aCwTmJ*c+gE-q<{TqL@FQ#O=SDg#=_%{YY+K zTeJZZu%;RX<^dQDDXmkt?8}E$Z6Vf;h2BG->Ou zpb_XRgL_>z!1_ik)1Pw$1c9Ai{?^W2vA0u##TSAPo>FcyuU*7&(eK`)P-wMwF#q{o zxi4nB(%<+^UYSFQDYgD2`FcFZ_fWI$;W|y#1>KAGIjBDSq0fXnB7ZD-g*vU}^@7?! zBJJZ6yX{)RQ;im`a=un1&$|XT^Y|P<=Sd{0h#Bl!b>0{#Q2n|-(Ayp9acHGml0`}| zHn>iT&7dTG-8GMH--va6ceLEs<#r)1dqJiasKOZMm`cx0b z+TI7GK6QOmp@8N-Y)aMiKmSpm%AYar;4^R*ji{Xd^XlQ5e?MzcIUyXi!!sLglt?M7 z_=`~8DMeeib5lP^eUQ(K=ls@>YNNKaxz z-PYC0Q%|PGLaMlUIBW{R$%HWzuY8!Sm=|jJZV`D}%5$QTvOGLf57vH1op;~J1n65@ zTOkX`zk$9rcnI{Zs0;xhD_b)jYa8Lsb=N5V_l3ootTy4}`L~qem7+gR4#iJM$YH@v9*winAynnrmx4(U6HmFl;8Lw zqx*5Vb+)K>7S5Mj@RCu1Sn>23vyp611-B)Og&mZF*w1QCtO$5|_DmqWq|L|nuMx(j zWDO$&ijgP{bksoTOxC%{6!ZvlB5`I_h=bHnA7*{h?GEle~WJY;u0+oupY-=!j6LI86z=Ycytg!k$P~?o#v= zLGmW->i+{}gB-J#Up_mMRG)Uu@PoG~)?{DyzsLw{|?! z9}(PH2jkL#8~Rz$krfY7fe0GI*BQt6JV&VGWYCL+RPD|9MCU+QNP1= z*>t$ywIo@nOJQH7l+6D8p@b?~MxEQdGf2x?H!83GynM#myK(%b`m-m4z&ETy$om*^Dj?CbZ6zKPnyhd zm)RUiT8cvp+q^730uSe2b8b$7&9{Be#}r91G{=M% z0aY+wv2qzH3S7nX`K8{Ox^kr+ON8XfAz2hUPRev#Dau^XhTR^Gv2go9zKadaoXEpK z?+&^JdN+91f8OmGiCkL*#XL))uJ!FKd8qKsQJoIM`dzPs__==;rU-libF7v|B=|x+ z=F2HcCS)6ZES0=~^ie7;it=cwv?Dc7IFCIvbrNmV^5Of?1*wPPvZISG`H06)wh|fS zQFuvf8R*P)P+W{rAhP)B0F0RNKsrb+_KyyhR*cmdBwgLJ{;@E91EU&$Qe}AT&K;=j*`C^$DFyz z{6v~|nc1T_;SDd$3{_5e0jF+x!k`x>7%EdmRK7e;d-N zs%QKbq3qgTK_r>mUqzNAT06nefB@d!z-pHHt!ccmmg7lU8q3$lQJdc5G3l@oF6|&v zN^p;K6zev{N1|@GKXaJ^4z2;j`;0hpBqY5V^B3bUb>7Ik9A=8qO z29je?Xuv4qo=>JE)UVpMneP2ZLAh86%NUd^C>wSj_BJ*pd?L-{4gmO37P{u@E4h~r z!~7ky4hg+4$s5PwC9z${&?pxoA(;jKd>daEww0~B=*-;wznmK{#hTFGXYIbiDC8vp zlp}xgxVnlD2mvC9O`J((Y1gl%D*csj130NJVfgzLwxpO1s|3XC=kS~q4YVC`+l-E@ z1UvH>egS0{bcakY`&|C$!@Fil$Hpb2X!hK9v-Kp>&`K zUBgNOOjVOhm(eM%!`{RffcI%~0Iyjoi8XOgj|nF!Wo0re^`8`mz&70 z41RZ59MS0ASw^$V%C2m@50gP7^fV>C*attcrL_5z&ZZm@IistLrlWODsm-F>ro#b3 z8Z0u_X>CJWU9B<*6U>u#97`a`;mnnkr_x?DAXP5~=XoFt?e!5UX*9IwJ&kIllt=Dn zc~4n8iFp(|EEjaJ8b2v8MP)qDL9TZ++jz4|cr1!hA=Gj^_JRCegO)le?bndIQ>8DN z%2l|s<|MR6cP%V7DoDZ`?%cQ9+Hjku2mJ{@V1@ayuc}!!;BuB~zAU-Ia` z8*L^Ppv;F5NN_pEH#26%VB@Ie?xr!NX>o2I7uT)ey6(b#4Qis#vzU7+U zTribRkfOhByE=T`Pj7#ouHW5&qN&`p@PF+itmC!)_%Z=wEVQ@v{d~OK{?v=|et&-A z|Dt$UrZQ`GLr8f2n`8UP=sW6Gr5%;XMqK3cUhU*IL&sZc`%cj_M_WH};8U2HCW0qf z8v&y6%`6ZJmq^W@4lKcHHuJ8JuyHApx!tCV#C-%Rn=r{7Y|ckMb7s0{ttwi#m6QjP zd@dNud>~LZXi(P=x#=8hg5zLkF^3N6`5 z#dkz=OydvhIi(qJ6}Ddu;FVEqI-ZbtfvAlsF}M*X z7i6L}Ve(|PBy!=;;Pjr2an#8CphiVV1SPYCYcbl*2#QJUV=b;-ftgrMDIFZd{RAt?i)kan3~^y_{9%KRl}r+FSmqkMzB zJvHL+VA6`2_-hGGF#o}cUPly7qsv||=?3sQ^LU5Ur2Ev&!D0IWZ%x&R2RC0#v7_qHoJC=86|Ea$ zyw0Hfi|s8}5#U58WO6clJDgteSG^6oYo-v3&kJ!JhGbs&fsoe)d702M!%xYy(8rnV zyvA$Ym|dUEq_h{mX(>`gI!5d>Vr@V6;!kCZc$_9gO?dxXaPXo2q^iqKX@%Z8e%_y@ z$k|RAUbSq0@4w)*Z>u(RfjWwX&RTG=T5#DUitIGKxa)leKi4S$FpP)o4}$PUjPu@c z-{vttNUxBK4GDm7zJBp-X%&!sMiM>QGC5>kMp35v}R_z3aVMvoM}FI>M%`q z$9$qAHDq;mJvb7MWW?{i>c)`gz7eBg9k!dEVtv42v>qOUe;I(Wp6PCcIPK~tY2)8B z4zC8w^nv^ELOSm{j{OJ>P9sLc%au=SMK~4uKE@C;Itb{nBc{Chr${(H8YBuXWuekO z#YCCme{}6s?^FVpiXnXC%C11#E7O7sls@Hz=7J-92j43EOdExjaIcGT3WAbd$Ii_t zh#c==Yw|;W3;LC|8#OiB;m1&HG}}{$!#P2dDYD<`0c$?x6vZt&&`=j7)PJC1{psZC zQAgZY9Kgbg)vEn~USkZ#%7tem+@6YPj5TsJ0O*j50=A2~G{#IEnRWMoq59V7o^>kY zo1D`KP6Q7>JGXv60NPZ^34a9|Gt*72ExGF6YK#{185_yKZ2ty-sjJD{-l_OYdkZ_>sgsix)N?E5hU2xpYstE<|Yr zjtym@J1`hGe&T*o(Hv8_d+Zi3PvIx!Sb=QKlJ4K#oJXwiT_XSJUjip)=gTkHt(7=$ z{sYh?z0TA3`@|qdO(_qHEDE1|ZEA_<(bxPXrwL)lhqvJcXQI zASP#+Uc#ONGvan@hMW+FIcat;J-KN)4J#IoBiwYksW%nf$;_H8)7YLl)1Zg^OY}N} z(aVWjCl6R1nBZHSc9^8tI^vASk8PO>vb z@^(bSq+B!=RIbVKCU{;V-iy)0lM^x(qhK0Vnsv(y+8Zu)4I;~z1u7_W3-utAkk4ik zVhRR4^_!?#t)lkh^ABr>%k*^dXVDTugV9NT?T>a|R_bgiB|eWIyF^_(Q8wXLQcC>z z)1%6gqzB1b34Sfqt`H!C1(m$>qoe)^_juV+u67Wrs2O|4?~VVf-tl@$Lxf^9P;Qst zRYF3?b;X&6X(>2o^yH^CWn>mk6K`iks@vLTMSALojvN7};mOFnDz0wVHnAJ`CSf6a zD-j0uB2IK!*0P{QmX*tv9#TV;#siU0^1|NktA<>6*ut3056ACJ$Sw{`XgUZhw`B*^ zb!K9RYCGBaBpp$xL|eLI$%p#R*XbT3B`B5n9bMe1%Y_wKRJFI<_N0$T(px!hcI{A@ z@sERV)iP(=9`cB!R5k%vO`y1s`9O!b$p>1CyZruz(6Z^b9#O$8I;{B=)70$9JsYd( zc}rEWvG`Ju$fshy8Trl!)ZG5ynpsgK9?<`Z5o?psY zKjt^Kx&u02Ex7R+Myc&r9RXVTZgB4+o80(YtI?F91GF)zTqpZ((e**Z}cwcCoF?+@`E5|$ZI&| z4=GKzJ7Y37xUN}~xL00QEQb!9gK`2Q2$Q0am8RSsD2uFNI|XCaO5F4UErd&#MwDnV zO_+^|-Y(dj1Pjz_D6!iSW=iuq5KjANlC|+!oPc9+&g!o#E0{%OJI^d-X=w0D9Py}H z12As_Fp-qm0P9DYz!5_(sgE?=W1V71@`ri%fIoYm=|G$c$=H*$^%FG)a$>kNst&++ zwEc^I|IFT+<6Ip)mC$?A9bW2|lgW#Zh}w26xMlDLH-1NudB))Ja~(X`Kr#x^m-H#? zH0xivFGKOJMV1E5RGA;1gNLnfQ-$_HG<8IP&~=O#QjE}9)fS?|OVQ-zOPt;`v1yn- zrmxwV%Zsv73|%E9a0-i@>eg^cQvY*Af7*wOaxJl(O1fP(qJp1Qi%z2I^=9L6 zUc!HU2u0T2)%ZCEUtAis(}!MQMkTog3Z@pV#j&Q8SBIF6Ei$!FP=I!zC`SM)0kez4 zl3gPY+U`l*QoPTjTVU}4d=$R|AEN8OYvlI<)61nA+f&OYgDH1x7FNeExNKH}>`9w0 z@tSY-Ja`~+w;lEuY!r?UkEn==X(4g^fLn+bSt)Us^_{4*DPRgvGM(*VngphR1s7ln zfIel8792?b6=lklWOsEw5fE|A36FsztB4Jq+5jI?fBOL*!e>P7nOiaRmb(j>@WY|80b?!S2hOyd129=MZUB=7C$LwBasyCr6s;np zsp|CC+W;>ommWyBuPYd*E(rno@j>(3pXwp;Pq}oodpcefEZLt{ddY4KDk>glH`xZ} zmI4=)GzCw>#^87xhpLV(iOK4zg;B;McpE7{jNst+P`btCy;EwkA~1Mt$I{5&*~0__ z$`7YiopX9pzYDzOKdTr$#N$Fid|uR5gX>wM+VsNS54P zbuX;BMLLFd03b4SHMwL9(32hb%66(C#c(@WBmG-pVzHDWdoZCDgE&Gv`z~r3?#XfY zPca*tJJTjx37DP6s2&YM?UhQv%#HLn{MV+8W(^eFI~#bDBx~IG_=BWn1T?yhB4WBZ z_f#Ilc{p=FZktB)`ajp#fEc0z3y>Dp(^Dh=|IR zLN#Lptc>{^C6med6c(YoZ6UUYb+u_u^lz)?%Hp8khq|PU1+~ySw}W*zTbsCs+UY_^ zJND#VR8a?4`D<$-Q_xohb$aCSs@hJ%?$rXwun0%D7Y@0WbT6ylN7tZ|%_>imEFYu9 z&#TLaUybjSIKKi@gu_?4&N)2`IaN=^=@*f{Mq&>6>h`!jYTS?n^>VEf@YC+&Y4UD( z@w5wA#jEG)*BUi+r0iBFUF80T&9{NT#|J$kqy*oJnB!!L+x;q5S+qj6_pJ6Q3}CQO z4Ld5J;wiWpho@Wua59pSxt|bFc6rH(e!q!?V5|=qbWbd+>jtjHXGy5F&@dk?_jb`~ z;jzfcX`?RuIk*N-!&r11w5Z{5X`=o5S!cunJ46NDQ=1lS;DVf^0Rw~cU07ugS12^u zO-8gC)z#hJ-bY>VSzAWjY!g`4Ki&#;>Yl1<2Q*G)^)lf9fqBP;aQ0ph6;xAd{Se&w zTgRP%W)@Y`GsS^sErP)pTmOn@YK9LXPD@>+I8A-S@u3IFOOqx&FFzw97!&mg|^jpi)~EO-P02HYW{uwh)NZ7_Oiu|^2FP`0#pyMGv0CH; zBgGFnV5ERw0CdchOa04(j$jn5*sdtC<6T0(i^(K0BET?ErO_#?S`@koC11KrbV0fv zMh?|)!A&9Jy$PsUZ1WW<;=C{Ozz5D+MWTd-gP8J+0o+w4*gfC;LR75gMBurC{oO~- zvwLX7VJ7|iZsnRirB7=dH67v6?#OuOx&1p|*F?v#uU$W}TepS+%18J>j60UrHPIez z6xzrzA>N3pi|68?g;Zs-jx3v)!tTKq|K(WLs=~JV4YX5%>7|0H6%3hBt7xKf_t}Y? zjPz=P^42e*ae2AU0gq4&Mi!PzS4dW?E+FQr-vVN;swW`k8YMUe4o|~d0y>U|!DrVs5b{pXygVC+L|08#cMd`Uopt6O}06c9fV5e8U@*s+Bc{GWVuA zruzQROw^#~&h$}=3ax#CozuJOpwtJn3jslLEB~Xf7nS#y-*+jg05Ul2D6>IAwbL-| z$?Z2vg6Z4MLRiEtcj;%Ddy7RKM2TM?-C>#&T?(P7@NRwV#+K!w>Tc%`g!-(6mHRx4-4N(O6DB2y|QDne$=}5F~(}T ze9VH6;9J*~3t=^qW=Kuf9h&$v#afeO;AXk~5uyW#J(v~6-M&%lKWysqH)V3=y5`Le3Ybrvr3wHv=?A%b}`)BuH8{ z=IHBHJ{|K8#xx%cTX?y%r`~vTLk|oc0QN2*gLZWI){`B; zrE4_ge)wQ8nerI#JvD+fJgf;afQF^As~L!t@~+TxmSkFiIrZI00l2C%b`OLCcUms? zQrW=&7Xtia;MKxkJHLRURGiluIjx{i%Yb3wFO0#+8Q*AMo`>z#FSZ9=wru3rlFb~V z*P1dPk&~X-XzW+p8~Yy*+u{7dpU1l+Grqhr$9Z44U+yP8SL?q{h(7*YV&T62Cdm5= zsq{^Z+xxi9?JvtDdReR080>y4t69J?`CG7(Y zQs8g&K7ymj08TYW7g}2(7yUeGRXq;-Cu`v|-*KZ2=iQ*doMG5+yiKm~ z*8%UeIR!>y^QUfqkex(yhU^|_b%t+CpkIGJ4%YY&nj)3!bPT2CqmpdV072cu&Cfi? zKNR)dqKdjZp1ei5G9yWG%Rs)#(t~hnkp8b0FbSOT1YpfYR&a9VPZz)v49*zPt58J% z0o(1?-U4T8pVY|a^f@>XsR}%RCO=3y2bQ>w8PPPnJoc|<*=d**--*eeioK(wy+!4i zuv@!}+@mU6E&gp8r?w^NIeH=K)-WR={w4$R+dLE?V=)i1ciCml?)^_2friRNSRrIN z8i^zC(PdQQVCh(gd;H@kc(7cDVX?b%TT19tE8hgmxns|r(W79?b7Ze>DR*}de=F(! z$v1NNZ%I|U4b%9HgXrk1zkX) z#;wn&#f+oE-K7=z=n?(pX^)Vpeyl~g>~PR3X!Z75^(=v5{FBYSNEWVyAkM|wNw3M8 z5?uzkM#RR|LZXeG1*8w6rkUDi)ZhPAAm){m)OTj2)DE97eZW?`;u*i`9GQeELbB4y zxRPjxE6xrH+T)t2ruf_YL-lNl9FOGLZ+wn(I^!1X2F!I5lPJSH zvQRPWlpBBZLn~(JhQ4?RaLbvXDPT-jcdZWsVg|&y+}a&F1KeuS=!Iw*v=tFO=JNIl zj1wd_r^(6ii$V&pZENI~5M2Tci`$8FuWbbeD2ycDAfv@#&ID zjC@gCWWQymFj(09&{^k!HYtU1KL^qQ_G=*O%uofQ&Lbe|L|6p>*J?o0s6G?T=cneZ zp2ZGVMMUoMmB@4;Uq$zt}BrtRX$)Za4a2AC_bmqa`DS|ZkypZMtz^|trI1# z8m^bGC5}eQUxc?~4XG*%Gt#JHLIx+*66MUXQ!u4N9>2^+@LUTN2Sa7x&GJ_aq7-uP z&`Vdq5yJ)I(Ws*L?7tMgtwz;_pBg9JoDp#(DnYG};_H81M5~sPkkiTMK5^)zg^l=+ zY&HQoWQPlo33sRf`>c&;iuJ07_6(FRGp@M>>s2z3E!=WDjcI#Fa`W+)u z6uTa)lB#OXgzkz}X(7B)i93 z8!W_rrtDjv_O6jAh!mwbpKri70h=LCBr-r`G-#fwO$CPb43q$SM9K>s!Wrtc4XNd| z{=uk*!JWpaCe}HCU49;M4Ca38If7MAmm`P1YGXRozr^n`u>Bi*N0#e^7i;sj6|4ikpt$ zL%<+!bpZ3sxt@T5>oWUIDasUecelX<1-RnHqPP8L6z|!9VY9L9pFMuoC z@&d}ibauL-w55}|b(FO1pq?ewgMZD&kuKP@$Tb9T;fWq{5ll29%fZf2XccJKPkRMd z@}S6n-uJ%o@7jch%;;~Hf8VgE1(Hd!e`FH%ADR4D5p|ILH^U9{6_%CQs5`+*jO$hO zhLjY{pYB>^zzr9;?eeIbrSy#I)-{ONhsMPrr7`2x;)XCjJWCXdc{OJNE6${#x{C=* z-#&=L!ASU8LL2g*vdng`HcN{o8c)*T9=0gBxW9QjQ`aGQh(e+JarO?g$BhZ#gD44@m+~b?q_t}+r&`4zPB>|*jFP!H5ygATn+7$^)_;yKMIPgLc zzqe(?3xT-42?7|tRsw+Gt648oyR`Se0xB9Fv34PLH4N79782vwj}WinGea;|8X4fL zVT!<_fIY4$)>!X6LP0Kpp5l`nX+mCx;9C*!Y32E?^5eHY^b-YKB$X?Yc2Ciaz)Ucb}v-+6wlCFy5jPo7NO}) zEx7-W>=vmV1-Tb66k5(Jpb?%!<4&_nNq0;E(ZxK9;Smz1a2;u`ky|DVk>V+ zii2ocDB6C$##&B@=AyI5Li4Q2Xr*?+S@Rk6g6BikCv68Qk!vI96|6Dd$g~}JmOhG< zvDbbHLp9F}GZAuP-rNu`r{kD|bLPe~?_A+R;}dx=UAN6h@Z~Tual~dGshR1JC(>U< zOUIM6bWK|vxzucRqOn`4gGp=ek-g4RTktFF@YT&w{T?#sGX1&Vv-S7v&JJHFwm2%y zhjEx`kz^89c0d6>JdazR%fdXBmM=jUe}Vfs3fzJ5bC#|HV8RMS5h*tRaA*69LGb+} zez|4%+phC>EN)}>E}+PwWIc{yumd-)ThSeE9=5Ad-?J;y}pwp3c z7)f-3mucV8`p>;TWOpCyZ=-IwPCw!L&oLGmVQO6Km_nDSiNprZy`Pyn`4D^&YgCaL z`_B=Wd@yycg(G(#Lx_3cYE%_GQFLl%vO||E{0n!TyK<1LH;0`=mTAX;zk*rx{+lCw zDalQ;>;h#r^SK@BWb;+QfVlbvb0Hz>2?=bMcIa|jOCa>%k};lS=A?oP$PIY#&K zuCt|u+O*0!ui&O$McZDG>U92@!EVsQi=cBiG|91CWq?9$gxwn!N+Z~7kZ9ac;3z~11D$LC#}vcD$$xQ`O91IihJ}++Ko?Z$*mY>>@U*La6uB*lR%JiwnDMsA9p{kIuU@-S0J=I?2Ie(H-<>(n8xPEqBdMvHWdh|9(+_%dfuzTx?nVt14M zoIRT*wR4RUkEBd}kMiBYMqPBWgqYxZL5+9qVa{8R*;_|0#;2`g+aS^9@4&Vz`2gFV z%nxk){C}xZ-HlV5XnX7dt*olo>Gwz zQ8e+bwnXoa^#WV68YDY30k8j!Dq$m(6(Rxe+YMEW*65i#cAJm~s)!3)Fq4qg6SFR4 zthIcCfDT0LV?O~-F;v}tt`m;B@gv01?u)Jw~}E?NN~n)CRN% zcLEUsPbpLtO`*)yPn7Kf$fIzZfo2{;V2V{f^smfpQTdH};|P20)^AH(avWl{m=?s9 zKY&ryCwO-XPegidQGm^3vGii)IC1ZhA7e0)8FhUQ)~oQYLH&@o`I(lb+GI{5Jh2LK1|4bzVV-V< zL3dIJSld3{b~H#tDotj*XH`VHw}Id|)1MyA>+`&PH`=>ehv#@p_ii-sq?I+;pAI_P zjoC3jmSDj;pKK2O0NJiyA=IOOA=0DT;;O}kCwsZnI3}t~U;FRCJyaJeF;1sss^>JH zE?OLwYpY2^+mL2T*}#+;_pj1^2duQC2LOiv_Kc@;+{>@jpb-4g%F`0QIksRtp!bAJu?K2;j+`k{rEwf%%XS+KvBLS>1i4c$((btr6-8u7fX zHX_d6-;IW0#L+O&jc0*2z_~PoRnX`Ty#JM#4B3uPTez1~RR19SAv22ZudBN}oN6Yd zLKj2?9s13>Y~X@2=9Y1mh3^d&8^59(@6}|vtzBe++^ursE`6&>>t%<|dQ;QPH*f~; z)7lutpvO87(sSLl?^;9^1(eZa*2q5nlY%L5++B8YdiBK ztZv{5cd3xT6J~|$hs1xk`!J&0~6cf>Z*jn4_zk@_y(4?Lzpfn|?xooOR zWC^?Y6uX2o;T`-be-Qnch*GMX#G&j*()Tx_EmzK;trSju-z&(jsBNvCW7WIqjNl%; zUg+P&9{tp;QX!e=cNBPrZb6&^RYIoi@!cM@*bjRzI09h)U73cm{l=?VhtLvk5cD_O zBUx0k(&pY1*Sup-!*|laO3^cuovY<}%e$p}mq#Aw#fCY=!fXD?V*7g~+5X?8unO|( z8%C8F$mXS$`k|^SsM?xTGdpNQ%3uT8N2ICGl}9YQs^*ddE+Hh+V*Web=VaMS?Y+KX zV|h8SyRZ18c<$d!#+YTwyp0xk2>@ghgrhTO`{ymn*DZ#rKd`J92ohxpLQUMkGz!rb ztXHK_-LZwUqrq?ViQkk14WjeB~iQd-|(I&W5 zT{x*ymN|L-f;wKuUHacTC>Fqv|99Za|Nq3fABXRMqcGOMDLG z;ht?>l9S(mBURFIq6<;s-OJVmB%-r~Gz`bOtWY_Ab02pyjQVby{!NI33{(;qUpZ#) z@h;55`PEQ41Tm6^57VKAe;AQ-0a#PUr|kGXvRHiG%zZXt{s*vbx#PJuLv6~2i8;nS&2;^Ka_Z6 z0m>iwfE^tTCD`D{(cr9JCwpQS7C=;*9$enAo><=Ttbyxe*~{&xnv&C_za#o?&b(dSnlF!^mp2Y=gsN{J8T!6& zS2jFe*edHE7ID&Qwm)@Yts^RnVqP7zeouxB=bks>#xqtD67usqzu9oVJ9?*HrL6Z9 zA6js~K2A=$PE!>V$;ex>(jxnwLz>I6YV;%>X1G>Fg1I7 zEPGv&!&rRCns0Z6SMs(Qgd(&0cj^VlY@P40wBa(i`8p>j6Q0pW^CMaT?CYWGvioKK zqhY|un@Q&Pz-r?B?#r{!E8x1Vdn% zrvXJ+xN$L`pXS)@bWL{j5!WJj{rTZp8 z`Dn{_K*a0Vr4=%TQ9v9)iQoGtrWb;H~i z-Ep?UX|uD9ip6`%_bnFKSJtQ@ieBOBaIcqKkoqs>hP92K+cACJIvNB6zNxa(6f*TS z!k$PanCo%etp|ou)(mB_xTwYO#8hnL%N7x~$|)&o9Vi zJi2SFnWMquaTS|tm0#Y3PsRd&%+MWqgMF2&x_n`Josqh11U@S-pMg)U^;==bpy3kj zsVK;Xhv&S$$_*V+K&Pjf$vq zo|E}xp32J1maxrhq(?druQQrirA*FV;_}f2+7ZfT;UWex z+q*ZS)go*8CfcGh{~SlHuTxnYcQ_qTDn z?61dAj;l>VJ1rW%9>w*@?T=-;y~i)#>@Tjb|FpTG1GKqe{X*E1ynkasb^RR2^K}LO z5O?oQfW7s1Z&ccs^*yTY9>Vj*%bw}&{^|_*x|`b?N=tM5?5_S62eZYz^i{VzW&Hdt zY<83v#wR}6@mw155Dph+|EM4(EbaTbetvB_0Rw$SC}p^Taj#|TT*kH?re^7_PS>ji ztM8qqOJFr*NualmgYHiTws9}<^J@Q{Wk0E;i!RZsJWm!K_FA%Tr+>U&&-&9@yR!)7 ztjmGQ=}Jqn=E@SHE1Lp}&QfgglP4~+savjXazw7Z)UV2i^LFvrHrwZZSI8?yl5FsN zeOnjb7A5}9Snz{lWVu;a$M=oqcuDu`t|r9rO6semuPF!qLf0JJYGj=K)|tY)lmnVTo+ zYZTBY?8-=nZOl*a>%TNdmPS@H`4ExB%L=Q`%?&P7=op6jeU8o+8#+?h2+Ar5k;A^Dc6F)u-(|Vs!CDw<_Dlm z7DmRq%y-;M(bvL&lZwA=)Ctm&J3MKk*R@^sUNi<|$x_t*gqh&5~T5!Ai2dz75M!8NN$qXXhGpi z!Ix0lBer}HcX^=@g)u%1EosJuEol05_^R`76#`x4MWIpDX}5ALaKD|9+@Ol{E5plh zLiW6B`9X4_9kr-uE}Jf~=k3u2Y}X;+8?Ap4v8=gN5ms8yPlCZZ_;+Rru&gyi<%(Xs z8p3BsdQi8uU^5S5rtaOQ+e>2WLz30pNI!Qu3XpQUE+BIB782;H1_ka-aB81$8ZH3$ z7k7mf?8e^*nP)-Z_Pw8|6*Ygb55-NP9wG(P5PAgD>O5`MkVMrAy%Li5qgtoJ3%|Pd zqY~|*PwQ|3Gu+f=rLwQ&#NPu6ksRw6fO8yE&W)tn(CRHfK6yYXlbHfKxDxN_Hntz4 zg{XH~MWNDJ9AxnMFj0d$Oa);bWI+4kIb4;^$V`bXrygvmBH)`Er9lEtcw?9-eStZM z=2=JF)4sSk@>OQ=kD*2J;~YOMmz1dpIq>+E9B@S}VjQaoBQs8qfJHL(=qQx}3armo z<{C1<{>INvCuq}EeJ}^#@Jc@($QIgZmRbiEKd>G&hLXN=ZG`SioF5E*Q~EV*7i`jA zs-9YQF-sNiTIp7f&)Qib6}fHn?3*u!=;~^8{|?yKKodsCDMk9zmC3%1aZTYXFUPnfX}N-Ie8$ti@;=ewU0pev*qT zDQ>bDL3{oqFK&TeA)vqk#hh4}4UK*r)RQOJVAj>GEL~hYxZ1btCX;XB=R_gP>dJv( zPeq<)_Ods!lvRmiJ7Wi~gF`JT&w=0YK2-B`x=F2U%mM%2xpj}N5+HKrbcpK|OsMn; z)#u6l5oPZId`J=|%_r_1WpKzUThDuFSK!GcOO$9_#B7DTOKVte(-sr9dV=EA#_m_* z#1OHN6RstUsXa3ZX6^M)O`Ibh3XR9Y=2JdR{JIa~4*Dc};ydKQTKt4JKgl5Dlh|rw zb|CBgy3V76LMD54l#R?$%VG^f5-KaIsDcXdVlV^QOID(>PJ09-fCQ3OKq%qRP>;!# z3~+3(7orrXNmS~Izou26rumEzVjhFCy=X}NJ!-t1bhR;`W}R$A-Q&1SI?R^6(=nqW zsYxOTEP>{W)Js@|Bu8On)zt>rlg=Tjy?)56yP=o%_O*sFqZXw@mpJ|#_@a8sFe#mu zmsa-DfCX^|v@FRkcAIdYv2dFhI>kEvu2vdP{;p&rggEp@_wn>k6^3 z#RzIDLVn*Am8G2SF9)pp)EP|+nAHke?k}-H=;MP9E8oGa=H?DUUG)NcuhHf{ANnpa z!=z*u{l9&XA!BQRDK1SZ7u2!>rr7wOflYS}{gJm4(8r&qwEL5-aaVESu5;EBf(zK= zb@peH?2ctJ*(P^#0MouarJ$`Fp3AbOs;<#jc`mk$W;vuSuepB}-|=UBWcE?8as6EUBqdP^Y$4jfl1yZ2au-=MkXFr>9>hUZOegGE(4NL26xp~b8N_%X34jI1d(wA=pDRg<{tkDlg~pNe6bPmQxa*(xGkc)YJ5e&Lzc@kfLu<{?9v zo%$U@-{(PA!YvEtvbrdHHJ;@8`!hPGC|1e}zwxWZ5L(ECL)>hj+$E?G@tl1DXDlI# zxDaQr7S{V@4H`Q@r*PJdwK#jM_^K2H z<}x<2wy)h_R~=g;+6yVpm8uPrMZ?@b9s-y-uTvLPoSIB2LqMXAd0^G=-6yS&|0}87Sf}{OC z_&qA}z7iEF+Q()|a#|iLfj07P>OS$>m#wc<4{Pi;e1<`#LCd@0Kx`7Dn4sETXX92@UEz`rlyLZVE zHW+nK5p_dbRHo>SPS^29g%n$jhb|1K<5b+`G3;~GR{1H`(-xu(=G_)wz+>09)PGOg zb4wG^t;u-^FkV1~3R3{C=`g?^i3Qux?gam5LQ;)0YYbINqH*aofy4KSbGKX)1{v)( zSVJ2n3rnt3g9g7okO@}gg2l@{EHho=K{dsY{9&=s+0bK5&V+V+8SpN`4~vB~ z>2v(4A~Gt#zAi_u$45BrGq-qe<9I1xTMwu$IN~VK4qydXOZ3v&I)KP8E|?BUiaVxjsYdNbDR_5O`cLc#~T=VdYgHiyNA+xzGJ}k0{xJ zuOVY1Uyq%cl)!bKUry}U5S@fJO#K8YGV%0wg+MGK7M3Spe;e9!5Yf{ZX(eA0s72Lj z8dqWCI~&iaaR=inXy-W(^Hf)RGA!>7jts=q+19o(o}ur2*C+o-_>&l*Gp*A9xMfK<%&d^P4-g$XFMe>XMQHIuuzlz17qVa!OJgM=i5Lz! z#IN#{kO=CDNTGM(0zz1yVpr(A#77agHuZ01{hO^{T`afAT5n^zq{TQotqC+*oLAD5 zeBEZyEt;R_8DLUxb|{^Br$0-0b|RlY|cKC*3OJ|<$klQq zjZ?bHO8sd0umI!?kX-HvNI~n3ZXXb+^Y5l7OvUwkS78XmBE5)`tN<_DuSAL^VnMGA;AtxYY{~=UKbvxv-n3BX=^~;8 z@MpaZ&`Wob0RC)tPCB&C!9Uw~77`X2zA2hx+C-QmN^vS-m;&L+9;i={@VL8}NJlN5 z61qMmD;Sb+L0>3+@kvS?mgQF>?Fx`lC&21SCj(^E_CFxuxLHgyUrQki_eCQKBghcR zBPXxLlDEC6d@Znpm5j)(J*lW*B>B@4OQnYEPQ!?XK!a4+XOuvszB;=0?mhh>)skQ< zEqM`n3{02@;G+b`GzG zWaBf4lc#(xXP$mnuNNL&V}ms2Y2O5G^fW%|Jk4J!rJ+#spgDtP+A94PDQJnRanxU3#h!P9H_ zeB-LnLq^&0#lFIjp`IbeG7@8zEWeUJricZ+65!EG4q!$qssBc7?a!FSffDT6IOeog z@ITI^$2`E9Ob1w%Eg?3vmY#nP1uP;;5=;f8Ms~S@{+k!%bJMaF5;;X6i?6OuiOjd?iqp8~^?O{7@|O9{MX=YH*5oPqEm=Nn4!Iniq8(36UV)m`sY=PnFZ=?+kG7RNW5X{Uym3+m>QGHa27SoELA8Pf`b1wa<6v!scIFMF-VZjT&=4Z-uiJE zT_Sw7jTPl>U&~)XRpq(BPkX7Dg*_}PZn_Y(Um_Ot!M?g`wQ6&I>V;=V(mze*Y3arm zI>#nB;@oOq-@UO_vbDvNay4#DDW~-L4dfgX;``sh02WrZ{|W|haIpPPECB!iCm3+4 zK52tZ0Mm7?cmqs;Zsk*8RyaB>a#?w~RhFgEm@J~~d3wG~Z-P3fU702pkf1bc0&ewk zF^${PS7JL>@(z3Bo6k>>*YuNerGUo$@m2rcF+U(QmmGU5EGH^1YMkv?j*YL?)@}A| zujqBcEGhc*uaMcew?VsMlI!bqNIEUhORqgN9BqdEeX{(nnj4zv9^}RAytf>BkEVo| z&-S;NKZE&)ub_fij3x}73U0G@Zk^j&SHR)F;~iN`KUI|>+{z7WDA8S5N9eg z41c`*U6*}to@OMElL3uBa-Burdkt-ZZ;fodd}}#3+4xpb~`g%7=0T|2qma8RogHgfjO=Zgl+JFwo}fUvn*cqlY=EMGNsVQAwI?# zbBslsB$iE^9V^jZq}D6y9j&m}8$NdUu8vwh88j1UD+dbh4XD#mO$ zjxk!1$gU2&tTdK_lKARKH`Rf&B}{QriaRq^d0#_If@RRb)_z!JPI5YOqUpiNG%UP! z&QOE#RNmp06!ar-{c7E9Dp^aMa1NV~0FwOa2Xu`1(4Cz1@u^ox2-A%G==;Ym_|7jh z9QkG;u<}^d23$88B4pGKx2vO{zlr)a%^}2%RSo;CWAkH`CalT4dC8`B9mn~0$o&(y zo%(KlO(ENf!|yg)Lngxo47X0m{2_C!_4n!YhTxSbZZvowo*?wWACi7ugw+@tT+MKo7 zQ3111K}%*t_AII8*lqO8vj0o31%!9H<|6uXEz>yg?<4B zc`sZ^w#SaD65nm9)ivERP*D4ITnMq#&RRmmazX4*{vQHlRht zUmyPEWTgB^F#oPl)#Awo!VqmHWNIXB519*Ji26la)c77Eu5F`7l?ULnA z?%Q_zylu;N%rI)ZcqxguhScYaHF(KgidMGuA%*9hBS6o4~|h8odAQ^8rC9!<6=|gMZ+; z$t0V+@+CO*+^oy{`9g3QkUpwuv$3D-%Y6b)L}qf)FVQktHcZ#e`*oMfkc&Rg=#vo8 zjs2zZ($Rmro{ZPh$Ua6t)V;?Bv2BD|ER9{9b;|qqd5-)xOJvpQFqQrUW9mLUCc7WQ zW~W-Bp-1`_eQKYbW`}A1=@8J?$UF&oLWSpJpwgr)r6MH*<<~fJNnq~i6#^XREr+ys z&<(o)ACdhB`>b6t;fl!+=zd~oik;QV7XsfKj%aEi%}i_+s8lTLm9Z;0XQsO(^J}2` zL4C-5fo)!nMeZ8$ZN2Sn|1Mmq|H}nh)EGid3u+XzE^xgST%8f3k13uL$n#HJUd~Tv zCzM%GSY?uZWyHV{(A=$%)eoKxya!{0EXWzSna`T62xp1G#HG%=d)dOMRB^~$<0?Z9 zdpYi35+-EVpcEJooGl#?=WQlf(`17z1dGAwpF#E0;a6uZcK0Oxl=qhxM*65jd65a> z+AntjNq?qEDc~fN`X)=b^$9ok?xh=oy*dz?*XWKHkwo)W9;VWy@`X(D?h=y|pq3W| zY&saX({sQ+%yCTRj_V^d6Q_8(#gxR9c>CSD@_u^k(VTe)7B0Xj7dyueoiw;Kl3;>N zZ!w}a4HQS`lpBx|{;WsMYKm8>hngB6Cy71Wo-{Zy1!OBpBSAHA->yp)r%zW*RVGv_ zefU`+O~HCnB}zG+xX$_Q@y_Q<#qzLlRPiH7zL_zt4Gwv^r-5lY;m?u0Ib6h%nyuYG zn#$ZHpL~=hmbd$Z7gv8$o2IY6=#8qggu9WtMQvR0GC{7r@^-uB^k6OXXP9RYq+@z2 zD74-ClhWGd{koVg4Q?n39sdjm2ct`JOpYjviDzK)obBasusdi8IA3?Ju~)MHRk|hM z#sD|^$V^C~g#$M*Thzf=Ph+qOZ4VW(2~XB{>bBI=kK(mJ106eQBdTDhaU;ui%WOy&hQH6?QJEk-pDKQ> znr<4hTI2D3R5q0RS$))CL<+A*sj(gO=Ho`5U%`G8BAsfdsYwk>JtnNw;g*AC;kxtr zYr*lspyeeLEtCdkch0hTR-({Xs8#+2x?%BsG&UB2RGrgHa%DsVZHNHqvOg6) zp4~Ax$c#}!XZ&`CvEx> z@I`zi*`BPcjVuuBTH~EqqzUF1fx2SbBk;SRbrWXe?LL4F6CRO3jd+H36v88V3zH^? zNYbRsXkzt~gkMrcO$$c*q{k+|RE$}w2-Vq*!Jyvv>zub(EYn?WJ^by+WFF7!up2rz z1?<2CWidlO03CGE5|w=NiS|U7u;IWrdFJm z!NWHJ1wzG)*l00eDFp9HI_GzYfZ1h+W1@SWyLWRN$Vq}FDKSH3LQnQq5_dnI+Q!4SI&9OTDH6! z$P9~7=3$eMuI1IWfNh*Ul1bPqEXHYhXxU$#3va$u4ne%BywVPI%!N!kDGmQ&z>ww+ zL>NWtn^Ln@hnqZgR9DJOKPif`AD*Paa%2I_1_7-jE3+;UB3WJ)=1XWZDtJz9$K<4m zk!JAXr&icwg6XBiJ#GAUqSc2HeK3ckxj<4-e8^bRIJFXrm8sf7&P8?q;SNP=`Rmog zNgl+Ea=5qPU@^;;kxQB-S96l49@xqdqRfhKB2K-MS~ug(GM?(qMqI}gL!Of*wwM^K zccUQc$gzsM7SNp<fxzFD^}69&^OX8up&P9CCsfj^=sHi0me zgT$>I)8TQ$V5Zo2sDyf^Vrm9=Q+yi-k*i6{RQVWFoazWRt**w{Gf>*Z%?3Wid5%oP zr?B;5M(k7N;W2$8SRtsJ1Nb@nkGxoTtiq?O2@Xrm?X+h09H^ZMP(xtzMS7dY&o&SR zmfkS&_3;U1nBh+pth~G@KjguYo>?*>7Xl^IXj5syY4vzG93!x4dxR-9=ZS!Xf10Z~ z=A7_91NmF17sDAWqkD{Fa!IStCNvgP@Hu`3g4EKNh4*ocgkvF7J@^@;i7W&lBok}J zrQk28;817NAm;r(6Mto=z`Ne2bzd=7W%!_}4I-LMd3O|t7^pp*z`|i~ z8%UPaE?9yvl&3*oJvkEv4zc0MN+o%ztR7;a9NjEX@5dFiAWsKr$~c{+ML9s9YlaE$ z^^<3}xx;LxB96N_5X{Ug53`F;rw`tMa$LSyt*P`G3 z8zgErbxjD{o!~y{367x9c2*NCm`D{h#2{p$l&|hp?wLY{FT@=iySG5)6bA>r*b$nO zU^69)+_2`u7D{J8Edh%0MYe?(Tf&v67r2?m>DQo%r1Tg0&}nc!s`V% z!n0^`h$Up3j-ODc$ILzOsUb5dh0#MS06vkS{K5fcek zaF;0*VWcwNbhn0So?YaZEfEOSIhr|vvw?JIWebIOuUIA~OoLhcoVQt<4{TI~MB zH0s5jWGD3~1R?9Bw7gs1@!uaSX0aW;+d_nAt;o=Ae$=RHaI)(HQiSW~mXv*Ig$HTE z2EjiW&IwZWT;`(ih(WO!U(`%0z0(vD$Z)*!LFzY!f38AXqSqnZjNd3CSjqeX>GIqfNn2AAaB*e0#oa_jR-9FrxG<7<2T8t1r zX+sr}HpwjMBIC!Pr#OR~%+jhsFZBR%3P_;*Ew)Tor<9%|=w;fmrfZtNho_p4Dz#^nm@u9o*gA3*|#E zajwpL1tZ7S-X-ArEKWGqeD|OXP?zM~8h}uyONz!9toeog*5?smB-4sgr+UE?cJLV{ zXv$AFHvT|Kqw8BTCv(aPq>h#c?`_+HKxLoFSITT}_{G88Ia#}?dG&Z{{tRnv>Gh%m zlumy(7vnl=&)7?O#Wh7+-M|851zB_hf$g#k=;0} zS>(OUiW_>YIGjYyg(2$Q6mm=aRYJO-e1MRklF%FpUN5cOflxj67QE!Q2J19}wyV56 zdOfJ2S4Z${Xvphrk-X$|VU&oBm!y!SYC?}sRDm<85)n-E?H5GE$eEovW-iE_DbCna zJ7Jol4O6xGm$Lea6F$dI-)+)CLj{|3`5AfH39~o&gex$!2HWrq$G>8G4I5Ky)+4Ty zVKpTI#*opW=*HAjUZ-f^-Q#!hM1UQ$>C9-dnrSqsV~W~Q!*~ZC(Deh8f zCJ!CzmE=`Z&dY@t$Klpr)7<+HSloB;22`#wBE5QlZbUL|%=aaDF^HlNfiXPA!nw*U z(na)gdF#U_BdI>ME)yMKzN6)hoZTLsq+=~KR2><5n85;CMao_34B&YCnH4m}BTjl- zan0LCX2_KloWwhAO%F*15+{DvJD9IE`YV+pZvw<=|=gi_C0l>yD0)P$w z4{YxLfPGn!pozv7%>F*d1F3a0YCx~F=P{WDYIAcb-X!93+PZwxfC(DS5^r-s1|2Hf z$kxHJCJVPIby2QV@u|u)6H<*R^t@oP80%7ZQ1ernsJ(rCG=s9C@bnY`Edm?`bwi<=3#f!s;>{+<&+V|l&yDU& zbx%ju@bK%h8TSEef4qZdic3WJqFrEGQXg<03o)k5j9gG>Wb{-fY^iCnOi%_D_B${G zo%E7HUw+@NNh5*z-wKI?q+7+b56Nm_%0n65843|>x$WIhG1k>wQSy^w@`(^2K|XP<6lu1~ovG1a zh~yRu22Z|Fmi>KAX|YEOc7cMI4*_HArdCpLOhO_}^B72HUn$fRB|`Fo9MifxDG5-H zmIJ_g^%MZs%ZPD`7K;2o>y;UXV8I2VQ;J2ojcsPMwFIw9xt+0(YbPJgtMz&WoX4`I z2YqNQsBEpc(7JucWImxW8oG~q8P;&l#hsGY(s{qxf-z9u4tg_eQi-ujQuAxKhxX68 z5w+z$F`dlS4ZMu3Kr2>Z^6w6Ep548p{PQ`kANsl;AVx6{GWQ0i@qd4ENx!xeOZ#B(QY_X%~xOy8$!<@}Z=y=*PpN^FU4d$g&1*x;tcd;PR7dbqg}EmIdi2 zm0;rh;Qv+V7uV+@E{%M;%p|{2sISg6n5dK>$%;XQ_0i$-E?JyN=%0AHPYc`sGW*)U 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

    `6UETE}?{UaB(Z8{=>pu z`p1y5e0rSYLMWL2ZZsrL{>?s_5RsYmJdWVpW@oxlM2whnC@K2kB@^leqd{D8Ff>6> zabpw@Ym#YA$wWGGexa>C-@X~NfSR6ow1?-d5tt7DC+9q-wSf68 zcNbrnPS3Sn|G`FMN%^U`N6H1CkM2IP&rVxEUGG=UUWT$3O*K0JLW$T$y}3O&$ngO? zl510zFVk(^pX}--2c7YITaJ7xM}HfRq4eq{!3f-8a}t|;9eF2C;o+n~lQ+W1r(M^v^s;#~%?e@n!o+k?`$>%w!Dl?8S|E1B)17 zdH$!`-JohobQ&OMV()dFkVo6+q#66Xm zL=eCaaThDZM~nsG+y>p!9WuI{gC2BHrpKv+U`de4p}#9Tz|WWvubG=_stv;>||tsZ6r0?GPZYg6UxB>HAc-etck_L{n; zf^9F-b}!WQ&H?&|WHd~I*yf2W(%CmCAO0bZZs9;j#>B&EcFO;FB2~KE1J4>)mXtCD zJ@1#|xD2g)rJ{}Onc{kCBjD6(zT#Jd^lE4lBPC2w?8BcUsAiy=Gxjq^3~|w8L1e*T z!9_uRfxGafFqrUwFjR0HL@pvPR%jEU8x8aYi$vVSN^J|;zb%)^c791$*Ei^Ii-&@XzJ|w^Kd%&u&QKTVBu{mZ>`IcZM&6| zjGB~?o@7Oup56r8CH~PVcr|gI0}Ypiiq{{7Fknce1y`cNT_E2#BYqR+o zNv;wT6Db!n8Al(jhqXq?gUo~PO?;}^Ry1#G;=w7v}E2C3$7$>_`ULbc-Q94oywt`_b)K>JQ}S2A(4 z2DYDg3}et;oCRJ;m=Iei$$!-Z9!)va3k)2;W+;@;Ih0#Dhk+5T7Of>3YH(l=Y|nIW zVb9fMGWP|QIP?}7qQx8a$Qx24NFZ#vF6tq%rf(eTO!eu)+M`1toH^UV`Jw^ zA7!K123-EeMpW3?V7MMEciB0!rv`zqUdOc`=xXo0-|WjJIr zyKAuDDWA?P1ME>0c@>4g-6!fb4Fj(S8wiUo;dKqqG~1L*WmG8H>1G1E&dhx9MdqhV z%JXHcDDoc)N0^Z)K_@3a$S%tJyR&7hx0I+D8}}5WLgf^=D0o%_)Kxc1l2U=Cb#s_# zFmtrowxb~cVP~Tjd%*<%DrMi2?^oOuzgT0VSmTQ4qU8SR_w6Bf6qwplL2=+4!|OO3 z?7g5g*PBiF?G2OD16E$OZ!9 wapXhJfA|0RL^Cu-&vG6K~rTdvYhx6XN=<--#J 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 diff --git a/docs/wiki/deployment/anomaly-detection.md b/docs/wiki/deployment/anomaly-detection.md deleted file mode 100644 index a43ac79c680..00000000000 --- a/docs/wiki/deployment/anomaly-detection.md +++ /dev/null @@ -1,67 +0,0 @@ -# Anomaly detection with osquery - -An osquery deployment can help you establish an infrastructural baseline, allowing you to detect malicious activity using scheduled queries. - -This approach will help you catch known malware ([WireLurker](https://bits.blogs.nytimes.com/2014/11/05/malicious-software-campaign-targets-apple-users-in-china/), IceFog, Imuler, etc.), and more importantly, unknown malware. Let's look at macOS startup items for a given laptop using [osqueryi](../introduction/using-osqueryi.md): - -```sh -$ osqueryi -osqueryi> SELECT * FROM startup_items; -+--------------+----------------------------------------------------------+ -| name | path | -+--------------+----------------------------------------------------------+ -| Quicksilver | /Applications/Quicksilver.app | -| iTunesHelper | /Applications/iTunes.app/Contents/MacOS/iTunesHelper.app | -| Dropbox.app | /Applications/Dropbox.app | -+--------------+----------------------------------------------------------+ -``` - -We see some pretty standard applications that run at boot, like iTunes and Dropbox. - -Now imagine this same system is compromised at a later date. - -We can use osquery's log aggregation capabilities to easily pinpoint when the attack occurred and what was installed. - -## Looking at the logs - -Using the [log aggregation guide](log-aggregation.md), you will receive log lines like the following in your datastore (ElasticSearch, Splunk, etc.): - -```json -{ - "name": "startup_items", - "action": "added", - "columns": { - "name": "Phone.app", - "path": "/Applications/Phone.app" - }, - "hostname": "ted-osx.local", - "calendarTime": "Fri Nov 7 09:42:42 2014", - "unixTime": "1415382685", - "epoch": "314159265", - "counter": "1" -} -``` - -It's clear that a suspicious application called "Phone" was added to this host's set of startup items on Nov 7th at 09:42 AM. - -### Case-study: WireLurker - -In November 2015, Palo Alto Networks [discovered](https://unit42.paloaltonetworks.com/wirelurker-new-era-os-x-ios-malware/) a new piece of macOS malware called Wirelurker. - -If you have osquery deployed, you can search for their static IOCs (indicators of compromise): - -```SQL -SELECT * - FROM launchd - WHERE path = '/Library/LaunchDaemons/com.apple.machook_damon.plist' - OR path = '/Library/LaunchDaemons/com.apple.globalupdate.plist'; -``` - -Better yet, you can generically detect WireLurker or other persistent malware using launchd and the following scheduled query, which will keep track of new, unique additions to your infrastructure: - -```SQL -SELECT path, label, program_arguments, inetd_compatibility, root_directory - FROM launchd; -``` - -This method has the distinct advantage of detecting malicious applications like WireLurker based on their behaviors rather than specific IOCs. diff --git a/docs/wiki/deployment/aws-logging.md b/docs/wiki/deployment/aws-logging.md deleted file mode 100644 index a07170cd16e..00000000000 --- a/docs/wiki/deployment/aws-logging.md +++ /dev/null @@ -1,79 +0,0 @@ -# Logging osquery to AWS - -As of osquery version 1.7.4, osquery can log results directly to Amazon AWS [Kinesis Streams](https://aws.amazon.com/kinesis/data-streams/) and [Kinesis Firehose](https://aws.amazon.com/kinesis/data-firehose/). For users of these services, `osqueryd` can eliminate the need for a separate log forwarding daemon running in your deployments. - -## Configuration - -The Kinesis Streams and Kinesis Firehose logger plugins are named `aws_kinesis` and `aws_firehose` respectively. They can be enabled as with other logger plugins using the [CLI flag](https://github.com/osquery/osquery/blob/master/docs/wiki/installation/cli-flags.md) `logger_plugin`. For this example, both logger plugins must be enabled via `--logger-plugin=aws_kinesis,aws_firehose`. - -Some configuration is shared between the two plugins: - -``` ---aws_access_key_id VALUE AWS access key ID override ---aws_profile_name VALUE AWS config profile to use for auth and region config ---aws_region VALUE AWS region override ---aws_secret_access_key VALUE AWS secret access key override ---aws_sts_arn_role VALUE AWS STS assume role ARN ---aws_sts_region VALUE AWS STS assume role region ---aws_sts_session_name VALUE AWS STS session name ---aws_sts_timeout VALUE AWS STS temporary credential timeout period in seconds (900-3600) ---aws_enable_proxy VALUE Enable proxying of HTTP/HTTPS requests in AWS client config (true or false) ---aws_proxy_scheme VALUE Proxy HTTP scheme for use in AWS client config (http or https) ---aws_proxy_host VALUE Proxy host for use in AWS client config ---aws_proxy_port VALUE Proxy port for use in AWS client config ---aws_proxy_username VALUE Proxy username for use in AWS client config ---aws_proxy_password VALUE Proxy password for use in AWS client config -``` - -When working with AWS, osquery will look for credentials and region configuration in the following order: - -1. Configuration flags; for the region, the service specific flags first (sts, kinesis, firehose) and then `aws_region` as a fallback -2. Profile from the [AWS config files](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) (only if `--aws_profile_name` is specified) -3. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) -4. `default` profile in the AWS config files -5. Profile from the EC2 Instance Metadata Service - -All of the STS configuration flags are optional. However, if `aws_sts_arn_role` is set, you can utilize temporary credentials via assume role with the [AWS Security Token Service](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html). - -### Kinesis Streams - -When logging to Kinesis Streams, the stream name must be specified with `aws_kinesis_stream`, and the log flushing period can be configured with `aws_kinesis_period`. - -Setting `aws_kinesis_random_partition_key` to `true` will use random partition keys when sending data to Kinesis. Using random values will load balance over stream shards if you are using multiple shards in a stream. Note that using this setting will result in the logs of each host distributed across shards, so do not use it if you need logs from each host to be processed by a consistent shard. The default for this setting is `false`. - -Custom endpoint for non-AWS Kinesis implementations can be specified with `aws_kinesis_endpoint`. - -If the region to be used is different from the default one present in `aws_region`, or the one in the profile file, then `aws_kinesis_region` can be used. - -### Kinesis Firehose - -Similarly for Kinesis Firehose delivery streams, the stream name must be specified with `aws_firehose_stream`, and the period can be configured with `aws_firehose_period`. - -Custom endpoint for non-AWS Firehose implementations can be specified with `aws_firehose_endpoint`. - -If the region to be used is different from the default one present in `aws_region`, or the one in the profile file, then `aws_firehose_region` can be used. - -### Sample Config File - -```JSON -{ - "options": { - "host_identifier": "hostname", - "schedule_splay_percent": 10, - "aws_kinesis_stream": "foo_stream", - "aws_firehose_stream": "bar_delivery_stream", - "aws_access_key_id": "ACCESS_KEY", - "aws_secret_access_key": "SECRET_KEY", - "aws_region": "us-east-1" - }, - "schedule": { - "time": { - "query": "SELECT * FROM time;", - "interval": 2, - "removed": false - } - } -} -``` - -**Note**: Kinesis services have a maximum 1MB record size. Result logs bigger than this will not be forwarded by `osqueryd` as they will be rejected by the Kinesis services. diff --git a/docs/wiki/deployment/configuration.md b/docs/wiki/deployment/configuration.md deleted file mode 100644 index 86f82d57128..00000000000 --- a/docs/wiki/deployment/configuration.md +++ /dev/null @@ -1,791 +0,0 @@ -# Configuring an osquery deployment - -An osquery deployment consists of: - -* Installing the tools for [Windows](../installation/install-windows.md), [macOS](../installation/install-macos.md), or [Linux](../installation/install-linux.md) -* Reviewing the [osqueryd](../introduction/using-osqueryd.md) introduction -* Configuring and starting the `osqueryd` service (this page) -* Managing and [collecting](log-aggregation.md) the query results - -## Configuration components - -The osquery "configuration" is read from a config plugin. This plugin is a data -retrieval method and is set to **filesystem** by default. Other retrieval and -run-time updating methods may include an HTTP/TLS request using the **tls** -config plugin. In all cases the response data must be JSON-formatted. - -There are several components contributing to a configuration: - -* Daemon options and feature settings -* Query Schedule: the set of SQL queries and intervals -* File Change Monitoring: categories and paths of monitored files and directories -* (insert new feature that requires a configuration here!) - -There are also "initialization" parameters that control how `osqueryd` is -launched. These parameters only make sense as command-line arguments since -they are used before a configuration plugin is selected. See the [command line -flags](../installation/cli-flags.md) overview for a complete list of these -parameters. - -The default config plugin, **filesystem**, reads from a file and optional -directory ".d" based on the filename. The included initscripts set the default -config path as follows: - -* Windows: **C:\Program Files\osquery\osquery.conf** -* Linux: **/etc/osquery/osquery.conf** and **/etc/osquery/osquery.conf.d/** -* macOS: **/var/osquery/osquery.conf** and **/var/osquery/osquery.conf.d/** - -You may override the **filesystem** plugin's path using -`--config_path=/path/to/osquery.conf`. You may also use the ".d/" directory -search path based on that custom location. - -Here is an example config that includes options and the query schedule: - -```json -{ - "options": { - "host_identifier": "hostname", - "schedule_splay_percent": 10 - }, - "schedule": { - "macos_kextstat": { - "query": "SELECT * FROM kernel_extensions;", - "interval": 10 - }, - "foobar": { - "query": "SELECT foo, bar, pid FROM foobar_table;", - "interval": 600 - } - } -} -``` - -This config tells osqueryd to schedule two queries, **macos_kextstat** and -**foobar**: - -* the schedule keys must be unique -* the `interval` specifies query frequency, in seconds. It has a - maximum value of 604,800 (1 week) - -The first query will log changes to the macOS host's kernel extensions, -with a query interval of 10 seconds. Consider using osquery's [performance -tooling](performance-safety.md) to understand the performance impact for each -query. - -The results of your query are cached on disk using -[RocksDB](https://rocksdb.org). On the first query run, all of the results are -stored in RocksDB. On subsequent runs, only result-set-difference (changes) are logged to RocksDB. - -Scheduled queries can also set: `"removed":false` and `"snapshot":true`. See -the next section on [logging](../deployment/logging.md), and the below configuration specification to learn how query options affect the output. - -> NOTICE: that the `interval` time in seconds is how many seconds the _daemon_ -itself has been running before the scheduled query will be executed. If the -system is suspended or put to sleep the progression of time "freezes" and -resumes when the system comes back online. For example a scheduled query with -an interval of `86400`, or 24 hours, running on a laptop system could take -a few days before the query executes if the system is suspended at night. - -## Query Packs - -Configuration supports sets, called packs, of queries that help define your -schedule. Packs are distributed with osquery and labeled based on broad -categories of information and visibility. For example, a "compliance" pack will -include queries that check for changes in locked down operating system features -and user settings. A "vulnerability management" pack may perform general asset -management queries that build event logs around package and software install -changes. - -In an osquery configuration JSON, packs are defined as a top-level-key and -consist of pack name to pack content JSON data structures. - -```json -{ - "schedule": {...}, - "packs": { - "internal_stuff": { - "discovery": [ - "SELECT pid FROM processes WHERE name = 'ldap';" - ], - "platform": "linux", - "version": "1.5.2", - "queries": { - "active_directory": { - "query": "SELECT * FROM ad_config;", - "interval": "1200", - "description": "Check each user's active directory cached settings." - } - } - }, - "testing": { - "shard": "10", - "queries": { - "suid_bins": { - "query": "SELECT * FROM suid_bins;", - "interval": "3600" - } - } - } - } -} -``` - -The pack value may also be a string, such as: - -```json -{ - "packs": { - "external_pack": "/path/to/external_pack.conf", - "internal_stuff": { - [...] - } - } -} -``` - -If using a string instead of an inline JSON dictionary the configuration plugin will be asked to "generate" that resource. In the case of the default **filesystem** plugin, these strings are considered paths. - -The **filesystem** plugin supports another convention for adding a directory of packs: - -```json -{ - "packs": { - "*": "/path/to/*", - } -} -``` - -Here the name `*` asks the plugin to *glob* the value and construct a multi-pack. The name of each pack will correspond to the filename *leaf* without the final extension, e.g. `/path/to/external_pack.conf` will be named `external_pack`. - -Queries added to the schedule from packs inherit the pack name as part of the scheduled query name identifier. For example, consider the embedded `active_directory` query above, it is in the `internal_stuff` pack so the scheduled query name becomes: `pack_internal_stuff_active_directory`. The delimiter can be changed using the `--pack_delimiter=_`, see the [CLI Options](../installation/cli-flags.md) for more details. - -### Discovery queries - -Discovery queries are a feature of query packs that make it much easier to monitor services at scale. Consider that there are some groups of scheduled -queries which should only be run on a host when a condition is true. For -example, perhaps you want to write some queries to monitor MySQL. You've made a -pack called "mysql" and now you only want the queries in that pack to execute -if the `mysqld` program is running on the host. - -Without discovery queries, you could have your configuration management write a -different configuration file for your MySQL tier. Unfortunately, however, this -requires you to know the complete set of hosts in your environment which are -running MySQL. This is problematic, especially if engineers in your environment -can install arbitrary software on arbitrary hosts. If MySQL is installed on a -non-standard host, you have no way to know. Therefore, you cannot schedule your MySQL pack on those hosts through configuration management logic. - -One solution to this problem is discovery queries. - -Query packs allow you to define a set of osquery queries which control whether -or not the pack will execute. Discovery queries are represented by the -top-level "discovery" key-word in a pack. The value should be a list of osquery -queries. If all of the queries return more than zero rows, then the queries are -added to the query schedule. This allows you to distribute configurations for -many services and programs, while ensuring that only relevant queries will be -executing on your host. - -You don't need to define any discovery queries for a pack. If no discovery -queries are defined, then the pack will always execute. - -Discovery queries look like: - -```json -{ - "discovery": [ - "SELECT pid FROM processes WHERE name = 'foobar';", - "SELECT 1 FROM users WHERE username like 'www%';" - ], - "queries": {} -} -``` - -In the above example, the pack will only execute on hosts which are running -processes called "foobar" and has users that start with "www". - -Discovery queries are refreshed for all packs every 60 minutes. You can -change this value via the `pack_refresh_interval` configuration option. - -Finally, if you have multiple discovery queries they will short-circuit -(stop after the first query with no results). This is useful if you are selecting -from a table provided by an extension because you can verify it is loaded before -running further queries. - -### Packs FAQs - -**Where do packs go?** - -The default way to define a query pack is in the main configuration file. -Consider the following example: - -```json -{ - "packs": { - "foo": { - "queries": {} - }, - "bar": { - "queries": {} - } - } -} -``` - -Alternatively, however, you can also define the value of a pack as a raw -string. Consider the following example: - -```json -{ - "packs": { - "foo": "/tmp/foo.json", - "bar": "/tmp/bar.json" - } -} -``` - -In the above example, the packs are defined using a local filesystem path. -When osquery's config parser is provided a string instead of inline dictionary the active config plugin is called to resolve what should be done to go from `/tmp/foo.json` to the actual content of the pack. See [configuration plugin](../development/config-plugins.md) development for more information on packs. - -**Where can I get more packs?** - -We release (and bundle alongside RPMs/DEBs/PKGs/etc.) query packs that emit high signal events as well as event data that is worth storing in the case of future incidents and security events. The queries within each pack will be performance tested and well-formed (JOIN, select-limited, etc.). But it is always an exercise for the user to make sure queries are useful and are not impacting performance critical hosts. You can find the query packs that are released by the osquery team in [**/packs**](https://github.com/osquery/osquery/blob/master/packs) within the osquery repository. - -**How do I modify the default options in the provided packs?** - -We don't offer a built-in way to modify the default intervals / options in the -supplied query packs. Fortunately, however, packs are just JSON. Therefore, it -would be rather trivial to write a tool which reads in pack JSON, modifies it -in some way, then re-writes the JSON. - -## Configuration specification - -This section details all (read: most) of the default configuration keys, called the default specification. We mention 'default' as the configuration can be extended using `ConfigParser` plugins. - -### Options - -The `options` key defines a map of option name to option value pairs. The names must be a CLI flag in the "osquery configuration options" set; running `osqueryd --help` will enumerate the list. - -Example: - -```json -{ - "options": { - "read_max": 100000, - "events_max": 100000, - "host_identifier": "uuid" - } -} -``` - -If a flag value is specified on the CLI as a switch, or specified in the Gflags `--flagfile` file it will be overridden if the equivalent "options" key exists in the config. - -There are LOTs of CLI flags that CANNOT be set with the `options` key. These flags determine the start and initialization of osquery and configuration loading usually depends on these CLI-only flags. Refer to the `--help` list to determine the appropriateness of options. - -It is possible to set "custom" options that do not exist as flags. These will not do anything without adding appropriate code. Options using the prefix `custom_` can be accessed via `osquery::Flag::updateValue("custom_NAME", value)` and `osquery::Flag::getValue("custom_NAME");`. - -### Schedule - -The `schedule` key defines a map of scheduled query names to the query details. You will see mention of the schedule throughout osquery's documentation. It is the focal point of osqueryd's capabilities. - -Example: - -```json -{ - "schedule": { - "users_browser_plugins": { - "query": "SELECT * FROM users JOIN browser_plugins USING (uid);", - "interval": 60 - }, - "hashes_of_bin": { - "query": "SELECT path, hash.sha256 FROM file JOIN hash USING (path) WHERE file.directory = '/bin/';", - "interval": 3600, - "removed": false, - "platform": "darwin", - "version": "1.4.5", - "shard": 1 - } - } -} -``` - -Each of `schedule`'s value's is also a map, we call these scheduled queries and their key is the `name` which shows up in your results log. In the example above the schedule includes two queries: **users_browser_plugins** and **hashes_of_bin**. While it is common to schedule a `SELECT * FROM your_favorite_table`, one of the powers of osquery is SQL expression and the combination of several table concepts please use `JOIN`s liberally. - -The basic scheduled query specification includes: - -- `query`: the SQL query to run -- `interval`: an interval in seconds to run the query (subject to splay/smoothing) -- `removed`: a boolean to determine if "removed" actions should be logged, default true -- `snapshot`: a boolean to set 'snapshot' mode, default false -- `platform`: restrict this query to a given platform, default is 'all' platforms; you may use commas to set multiple platforms -- `version`: only run on osquery versions greater than or equal-to this version string -- `shard`: restrict this query to a percentage (1-100) of target hosts -- `denylist`: a boolean to determine if this query may be denylisted (when stopped by the Watchdog for excessive resource consumption), default true -- `startup_priority`: an unsigned integer to set the order of executing scheduled queries on startup, default is UINT64_MAX - -The `platform` key can be: - -- `darwin` for macOS hosts -- `linux` for any RedHat or Debian-based hosts -- `posix` for `darwin` and `linux` hosts -- `windows` for any Windows desktop or server hosts -- `any` or `all` for all, alternatively no platform key selects all - -The `shard` key works by hashing the hostname then taking the quotient 255 of the first byte. This allows us to select a deterministic 'preview' for the query, this helps when slow-rolling or testing new queries. - -Note that queries are still constrained by the Watchdog when the `denylist` key is set to false. This means that setting `denylist` to false is _not_ sufficient to ensure a query will be run without resource constraints. Queries stopped by the Watchdog should be addressed by modifying the query SQL and/or Watchdog configuration until the limits are not exceeded. - -The schedule and associated queries generate a timeline of events through the defined intervals. There are several tables `*_events` which natively yield a time series, all other tables are subjected to execution on an interval. When the results from a table differ from the results when the query was last executed, logs are emitted with `{"action": "removed"}` or `{"action": "added"}` for the appropriate action. - -Snapshot queries, those with `snapshot: true` will not store differentials and will not emulate an event stream. Snapshots always return the entire results from the query on the given interval. See -the next section on [logging](../deployment/logging.md) for examples of each log output. - -Queries may be "denylisted" if they cause osquery to use excessive system resources. A denylisted query returns to the schedule after a cool-down period of 1 day. Some queries may be very important and you may request that they continue to run even if they are latent. Set the `denylist: false` to prevent a query from being denylisted. - -Note that queries which leave `startup_priority` as the default value will not execute at startup. When using watchdog, scheduled queries running on startup will generally run before watchdog starts. The watchdog start delay can be adjusted with `watchdog_delay` (default: 60), but running many queries on startup may be resource demanding, and thus could cause the watchdog to deny the startup queries if the delay is too short. - -### Packs - -The above section on packs almost covers all you need to know about query packs. The specification contains a few caveats since packs are designed for distribution. Packs use the `packs` key, a map where the key is a pack name and the value can be either a string or a dictionary (object). When a string is used the value is passed back into the config plugin and acts as a "resource" request. - -```json -{ - "packs": { - "pack_name_1": "/path/to/pack.json", - "pack_name_2": { - "queries": {}, - "shard": 10, - "version": "1.7.0", - "platform": "linux", - "discovery": [ - "SELECT * FROM processes WHERE name = 'osqueryi';" - ] - } - } -} -``` - -As with scheduled queries, described above, each pack borrows the `platform`, `version`, and `shard` selectors and restrictions. These work the exact same way, but apply to the entire pack. This is a short-hand for applying selectors and restrictions to large sets of queries. - -The `queries` key mimics the configuration's `schedule` key. - -The `discovery` query set feature is described in detail in the above packs section. This array should include queries to be executed in an `AND` manner. - -### File Paths - -The `file_paths` key defines a map of file integrity monitoring (FIM) categories to sets of filesystem globbing lines. Please refer to the [FIM](../deployment/file-integrity-monitoring.md) guide for details on how to use osquery as a FIM tool. - -Example: - -```json -{ - "file_paths": { - "custom_category": [ - "/etc/**", - "/tmp/.*" - ], - "device_nodes": [ - "/dev/*" - ] - }, - "file_accesses": [ - "custom_category" - ] -} -``` - -The file paths set has a sister key: `file_accesses` which contains a set of categories names that opt-in for filesystem access monitoring. - -### YARA - -The `yara` key uses two subkeys to configure YARA signatures: `signatures`, and to define a mapping for signature sets to categories of `file_paths` defined in the "file paths" configuration. Please refer to the much more detailed [YARA](../deployment/yara.md) deployment guide. - -Example: - -```json -{ - "yara": { - "signatures": { - "signature_group_1": [ - "/path/to/signature.sig" - ] - }, - "file_paths": { - "custom_category": [ - "signature_group_1" - ] - } - } -} -``` - -There is a strict relationship between the top-level `file_paths` key, and `yara`'s equivalent subkey. - -### Prometheus - -The `prometheus_targets` key can be used to configure Prometheus targets to be queried. The metric timestamp of millisecond precision is taken when the target response is received. The `prometheus_targets` parent key consists of a child key `urls`, which contains a list target urls to be scraped, and an optional child key `timeout` which contains the request timeout duration in seconds (defaults to 1 second if not provided). - -Example: - -```json -{ - "prometheus_targets": { - "timeout": 5, - "urls": [ - "http://localhost:9100/metrics", - "http://localhost:9101/metrics" - ] - } -} -``` - -### Views - -Views are saved queries expressed as tables. Large subqueries or complex joining logic can often be moved into views allowing you to make your queries more concise. - -Example: - -```json -{ - "views": { - "kernel_hashes" : "SELECT hash.path AS kernel_binary, version, hash.sha256 AS sha256, hash.sha1 AS sha1, hash.md5 AS md5 FROM (SELECT path || '/Contents/MacOS/' AS directory, name, version FROM kernel_extensions) JOIN hash USING (directory);" - } -} -``` - -```SQL -SELECT * FROM kernel_hashes WHERE kernel_binary NOT LIKE "%apple%"; -``` - -### EC2 - -There are two tables that provide EC2 instance related information. On non-EC2 instances these tables return empty results. `ec2_instance_metadata` table contains instance meta data information. `ec2_instance_tags` returns tags for the EC2 instance osquery is running on. Retrieving tags for EC2 instance requires authentication and appropriate permission. There are multiple ways credentials can be provided to osquery. See [AWS logging configuration](../deployment/aws-logging.md#configuration) for configuring credentials. AWS region (`--aws_region`) argument is not required and will be ignored by `ec2_instance_tags` implementation. The credentials configured should have permission to perform `ec2:DescribeTags` action. - -### Azure - -Like EC2, there are two tables that provide Azure instance related information. These tables query a REST endpoint that may or may not exist outside of Azure, so querying them outside of Azure is not recommended. The `azure_instance_metadata` table contains general metadata for the instance. The `azure_instance_tags` table contains tags for the Azure instance that osquery is running on. These tables don't require any special Azure permissions or credentials. - -### Decorator queries - -Decorator queries exist in osquery versions 1.7.3+ and are used to add additional "decorations" to results, snapshot and status logs. There are three types of decorator queries based on when and how you want the decoration data. - -```json -{ - "decorators": { - "load": [ - "SELECT version FROM osquery_info;", - "SELECT uuid AS host_uuid FROM system_info;" - ], - "always": [ - "SELECT user AS username FROM logged_in_users WHERE user <> '' ORDER BY time LIMIT 1;" - ], - "interval": { - "3600": [ - "SELECT total_seconds AS uptime FROM uptime;" - ] - } - } -} -``` - -The types of decorators are: - -* `load`: run these decorators when the configuration loads (or is reloaded) -* `always`: run these decorators before each query in the schedule -* `interval`: a special key that defines a map of interval times, see below - -Each decorator query should return at most 1 row. A warning will be generated if more than 1 row is returned as they will be forcefully ignored and constitute undefined behavior. Each decorator query should be careful not to emit column collisions, this is also undefined behavior. - -The columns, and their values, will be appended to each log line as follows. Assuming the above set of decorators is used, and the schedule is execution for over an hour (3600 seconds): - -```json -{"decorations": {"user": "you", "uptime": "10000", "version": "1.7.3"}} -``` - -Expect the normal set of log keys to be included and note that `decorations` is a top-level key in the log line whose value is an embedded map. - -The configuration flag `decorations_top_level` can be set to `true` to make decorator data populate as top level key/value objects instead of being contained as a child of `decorations`. When using this feature, you must be weary of key collisions in existing, reserved, top-level keys. When collisions do occur, existing key/value data will likely be overwritten by the decorator key/value. The following example shows the results of collisions on various top-level keys: - -Example configuration: - -````json -{ - "decorators": { - "load": [ - "SELECT 'collision' AS name;", - "SELECT 'collision' AS hostIdentifier;", - "SELECT 'collision' AS calendarTime;", - "SELECT 'collision' AS unixTime;", - "SELECT 'collision' AS columns;", - "SELECT 'collision' AS action;" - ] - } -} -```` - -Example output: - -````json -{ - "name": "collision", - "hostIdentifier": "collision", - "calendarTime": "collision", - "unixTime": "collision", - "action": "added", - "columns": { - "cpu_brand": "Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz", - "hostname": "osquery.example.com", - "physical_memory": "1234567890" - } -} -```` - -The `interval` type uses a map of interval 'periods' as keys, and the set of decorator queries for each value. Each of these intervals MUST be minute-intervals. Anything not divisible by 60 will generate a warning, and will not run. - -### Automatic Table Construction - -Osquery can be configured to expose local SQLite databases as tables without having to write custom extensions. This means you can construct queries with information from like [Munki](https://github.com/munki/munki) application usage statistics at `/Library/Managed Installs/application_usage.sqlite`, TCC permissions, or quarantined files downloaded through a web browser. - -Example: - -``` -{ - "auto_table_construction": { - "tcc_system_entries": { - "query": "SELECT service, client, auth_value, last_modified FROM access;", - "path": "/Library/Application Support/com.apple.TCC/TCC.db", - "columns": [ - "service", - "client", - "auth_value", - "last_modified" - ], - "platform": "darwin" - } - } -} -``` - -When targeting Windows you'll need to escape the `\` character `\\Users\\%\\AppData\\Local\\foo\\Settings`. - -You'll need to do some legwork if you don't know the structure of the SQLite database. -Taking the `tcc_system_entries` ATC table as an example, which controls which permissions are granted to specific macOS applications, the first step is to open the TCC database. From your terminal, open the database with `sqlite3`: - -`$ sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db` - -The SQLite shell might feel familiar if you're used to `osqueryi`. That's because osquery uses syntax derived from SQLite for queries. - -Let's see what tables exist in our local SQLite database. - -``` -sqlite> .tables -access active_policy expired -access_overrides admin policies -``` - -If you run `select * from access`, you'll see this table contains permissions granted to different applications, which is exactly what we want to query. Looking at the schema for the `access` table gives us the column names which we can use to define our ATC table. - -``` -sqlite> .schema access -CREATE TABLE access ( service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, auth_value INTEGER NOT NULL, auth_reason INTEGER NOT NULL, auth_version INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, indirect_object_identifier_type INTEGER, indirect_object_identifier TEXT NOT NULL DEFAULT 'UNUSED', indirect_object_code_identity BLOB, flags INTEGER, last_modified INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type, indirect_object_identifier), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE); -``` - -Open a text editor and create a file named `atc_tables.json` using the columns, path, and SQLite table you discovered: - -``` -{ - "auto_table_construction": { - "tcc_system_entries": { - "query": "SELECT service, client, auth_value, last_modified FROM access;", - "path": "/Library/Application Support/com.apple.TCC/TCC.db", - "columns": [ - "service", - "client", - "auth_value", - "last_modified" - ], - "platform": "darwin" - }, - "tcc_user_entries": { - "query": "SELECT service, client, auth_value, last_modified FROM access;", - "path": "/Users/%/Library/Application Support/com.apple.TCC/TCC.db", - "columns": [ - "service", - "client", - "auth_value", - "last_modified" - ], - "platform": "darwin" - } - } -} -``` - -You can test this locally before deploying to your fleet and add more columns as necessary: `/usr/local/bin/osqueryi --verbose --config_path atc_tables.json` - -### Events - -"Events" refers to the event-based tables. -Events are published into osquery by operating system or application specific APIs; and within osquery certain tables "subscribe" to these publishers. -There is usually a 1-to-many relationship between publishers and subscribers. -See the [development documentation](../development/pubsub-framework.md) for more information on event publishing and subscribing. -Events are almost always tweaked via CLI flags and _options_ referenced above. - -The configuration supports a method to explicitly allow and deny events subscribers. -If you choose to explicitly allow subscribers, then all will be disabled except for those specified in the allow list. -If you choose to explicitly deny subscribers, then all will be enabled except for those specified in the deny list. - -You may want to explicitly disable subscribers if you are only interested in a single type of data produced by a general publisher. - -Here is an example configuration: - -```json -{ - "schedule": {...}, - "events": { - "disable_subscribers": ["yara_events"] - } -} -``` - -You can inspect the list of subscribers using the query `SELECT * FROM osquery_events where type = 'subscriber';`. -This table will show `1` for the `active` column if a subscriber is enabled. -Note that publishers are more complex and cannot be disabled and enabled this way, please look for a specific CLI flag to control specific publishers. -Also note that different platforms such as Windows and Linux have different sets of subscriber tables. - -## Chef Configuration - -Here are example Chef cookbook recipes and files for macOS and Linux deployments. Consider improving the recipes using node attributes to further control what nodes and clients enable osquery. It helps to create a canary or a testing set that implements a separate "testing" configuration. These recipes assume you are deploying the macOS package or the Linux package separately. - -### Chef macOS - -Consider the default recipe: - -```ruby -# Domain used by the macOS LaunchDaemon. -domain = 'io.osquery.agent' -config_path = '/var/osquery/osquery.conf' -pid_path = '/var/osquery/osquery.pid' -flagfile = '/var/osquery/osquery.flags' - -directory '/var/osquery' do - recursive true - mode 0755 -end - -template "/Library/LaunchDaemons/#{domain}.plist" do - source 'launchd.plist.erb' - mode '0444' - owner 'root' - group 'wheel' - variables(domain: domain, - config_path: config_path, - pid_path: pid_path, - flagfile: flagfile - ) - notifies :restart, "service[#{domain}]" -end - -cookbook_file "/etc/newsyslog.d/#{domain}.conf" do - source "#{domain}.conf" - mode 0644 - owner 'root' - group 'wheel' -end - -['osquery.flags', 'osquery.conf'].each do |file| - cookbook_file "/var/osquery/#{file}" do - source file - mode 0444 - owner 'root' - group 'wheel' - notifies :restart, "service[#{domain}]" - end -end - -service domain do - action [:enable, :start] -end -``` - -And the following files/templates used by the recipe: - -**templates/default/launchd.plist.erb** - -```xml - - - - - Label - <%= @domain %> - ProgramArguments - - /opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd - --config_path - <%= @config_path %> - --pidfile - <%= @pid_path %> - --flagfile - <%= @flagfile %> - - RunAtLoad - - KeepAlive - - Disabled - - ThrottleInterval - 60 - - -``` - -**files/default/io.osquery.agent.conf** - -```shell -# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num] -/var/log/osquery/osqueryd.results.log root:wheel 600 2 10000 * NZ -``` - -**files/default/osquery.conf** - -```json -{ - "options": { - "host_identifier": "hostname", - "schedule_splay_percent": 10 - }, - "schedule": { - "macosx_kextstat": { - "query": "SELECT * FROM kernel_extensions;", - "interval": 10 - } - } -} -``` - -### Chef Linux - -Consider the default recipe: - -```ruby -# Service name installed by the osquery package. -service_name = 'osqueryd' - -cookbook_file '/etc/osquery/osquery.conf' do - source 'osquery.conf' - mode 0444 - owner 'root' - group 'wheel' - notifies :restart, "service[#{service_name}]" -end - -service service_name do - action [:enable, :start] -end -``` - -And the same configuration file from the macOS example is appropriate. - -## osqueryctl helper - -To test a deploy or configuration we include a short helper script called `osqueryctl`. There are several actions including `start`, `stop`, and `config-check` that apply to both macOS and Linux. diff --git a/docs/wiki/deployment/debugging.md b/docs/wiki/deployment/debugging.md deleted file mode 100644 index 6a3cd55af5b..00000000000 --- a/docs/wiki/deployment/debugging.md +++ /dev/null @@ -1,239 +0,0 @@ -# Debugging osquery - -Here are some debugging tips and tricks related to the daemon and shell from a deployment and usage perspective. Please see the development documentation in the next section for debugging code changes. - -Almost every situation requiring debugging should ultimately be solved with bug fixes or better documentation. In these cases, the documentation usually surfaces in the form of verbose messages in the tools. - -Please feel encouraged to add additional messages in the code, or create GitHub issues documenting your experience and suggestions for documentation or code improvements. - -## Running the shell or daemon in verbose mode - -This is pretty simple! Just append `--verbose` as a switch. - -```shell -$ osqueryi --verbose -I0412 08:04:56.012428 3056837568 init.cpp:380] osquery initialized [version=2.4.0] -I0412 08:04:56.014837 168243200 interface.cpp:317] Extension manager service starting: /Users/$USER/.osquery/shell.em -I0412 08:04:56.015383 3056837568 init.cpp:615] Error reading config: config file does not exist: /var/osquery/osquery.conf -Using a virtual database. Need help, type '.help' -osquery> -``` - -To see the daemon's verbose messages you'll need to run it in the foreground, see the next section. Be aware that verbose messages are treated like others and sent to your downstream logger plugin. If you are collecting these logs, the verbose messages will be collected too! - -## Running the daemon in the foreground - -The daemon has some restrictions that make verbose debugging difficult, let's walk through how to run it in the foreground. - -```shell -osqueryd --ephemeral --disable_database --disable_logging -``` - -The `ephemeral` flag tells the daemon that it may co-exist with other persistent daemons. The `disable_database` must be present or `database_path` must be overridden as the default database location is not writable/readable by a non-privileged user. The same applies for `disable_logging`, and if you use the default logger plugin `filesystem` then alternatively `--logger_path` may be overridden. Now we can append `--verbose`: - -```shell -$ osqueryd --ephemeral --disable_database --disable_logging --verbose -I0412 08:03:59.664191 3056837568 init.cpp:380] osquery initialized [version=2.4.0] -I0412 08:03:59.666533 196194304 watcher.cpp:465] osqueryd watcher (35549) executing worker (35550) -I0412 08:03:59.688765 3056837568 init.cpp:377] osquery worker initialized [watcher=35549] -I0412 08:03:59.690062 3056837568 rocksdb.cpp:205] Opening RocksDB handle: /tmp/osquery.db -``` - -Also note the daemon expects to be owned by the superuser if executed as the superuser. It also resists running in a tmpfs or sticky-bit directory. For special testing and debugging cases use `--allow_unsafe`. - -If you are using a `--flagfile` to define additional command line switches then it should be readable by your user. In cases where the Remote API is used, an enroll secret or TLS client private key is needed. If these are read-restricted to the superuser you may need to also debug as the superuser. - -## Checking the config sanity - -The daemon will not start with an invalid configuration. And no configuration is provided by default. See the [configuration](../deployment/configuration.md) guide for details on how to move the example config to an active config. - -To check your configuration with the shell (or daemon): - -```shell -$ osqueryi --config_path ./build/testing/invalid_osquery.conf --config_check || echo 'config has an error' -Error reading config: Error parsing the config JSON -config has an error -``` - -This works for all the configuration plugins. - -You can print/dump the config using `--config_dump` to be double-extra sure: - -```shell -osqueryi --config_path ./build/testing/invalid_osquery.conf --config_dump -{"./build/testing/osquery.conf": /* I PUT THIS JSON ERROR HERE, NOOOOO! */ -{ - "packs": {} -} -``` - -The daemon and shell should exit after printing the config. - -This example contains a C-style comment which was allowed in boost 1.58, but is deprecated and removed in 1.59. To be future-proof, stick to the JSON specification and do not include comments. - -### Scheduled query failures and the watchdog - -The osquery watchdog is only used in the daemon. It is enabled by default and can be disabled with `--disable_watchdog=true`. The watchdog enforces limits on a forked 'worker' process to protect systems from CPU expensive and memory-intensive queries. If the watchdog observes limit violations it will emit errors similar to: - -```text -Scheduled query may have failed: pack_threat_detectors_launch_daemons -``` - -This line is created when a worker starts and finds a 'dirty bit' toggled for the currently-executing-query. If a daemon is stopped abruptly and a query does not finish, a similar line may be emitted spuriously. - -Lines that indicate the watchdog has taken action include either of the following: - -```text -osqueryd worker (92234) system performance limits exceeded -osqueryd worker (8368) memory limits exceeded: 99573760 -``` - -The pid of the offending worker is included in parenthesis. - -If the worker finds itself in a re-occurring error state or the watchdog continues to stop the worker, additional lines like the following are created: - -```text -osqueryd worker respawning too quickly: 1 times -``` - -The watchdog implements an exponential backoff when respawning workers and the associated 'dirty' query is denylisted from running for 24 hours. - -### Distributed query denylisting - -If the watchdog stops the daemon while a distributed query was running then such query will be denylisted from running for 24 hours. -This behavior is similar to the denylisting of scheduled queries. - -When a distributed query is denylisted, the **Distributed write** request POST body will contain a non-zero status for the query and a `"distributed query is denylisted"` message: -```json -{ - "node_key": "...", - "queries": { - "id1": [ - {"column1": "value1", "column2": "value2"}, - {"column1": "value1", "column2": "value2"} - ], - "id3": [] - }, - "statuses": { - "id1": 0, - "id3": 1, - }, - "messages": { - "id3": "distributed query is denylisted" - } -} -``` - -Additionally, when running in verbose mode, the following line will be generated when a distributed query is denylisted: -```text -Not executing distributed denylisted query: "SELECT * FROM hash WHERE path LIKE '/Users/%/%%';" -``` - -### Inspecting daemon state using the shell - -The `osqueryi` shell can "connect" to another osquery extension socket. Queries within that shell will be forwarded to the remote socket. This feature is especially helpful to inspect a daemon's `osquery_schedule` and `osquery_flags` configuration. The `osquery_schedule` table maintains runtime statistics for schedule execution. Keep in mind that this runtime data is transient, and only available to a daemon. - -Please consider the following example that demonstrates this functionality: - -```shell -$ osqueryi -osquery> .socket -/home/$USER/.osquery/shell.em -osquery> select pid from osquery_info; -+------+ -| pid | -+------+ -| 1533 | -+------+ -osquery> -``` - -Then in another shell, the `.connect` method is used: - -```shell -osquery> select pid from osquery_info; -+-------+ -| pid | -+-------+ -| 20123 | -+-------+ -osquery> .connect /home/$USER/.osquery/shell.em -[*]osquery> select pid from osquery_info; -+------+ -| pid | -+------+ -| 1533 | -+------+ -[*]osquery> -``` - -### Checking the database sanity - -The osquery backing store is almost always RocksDB. This is built-in to osquery when using [our packages](https://osquery.io/downloads). Most errors with RocksDB are caused by read and write permissions on the `--database_path` and spurious processes wanting to lock access to that directory. - -The database path can only be used by a **single** daemon, concurrency is implemented at the API level, not the process level. - -```shell -$ ps aux | grep osquery -# pgrep osquery -``` - -If osquery daemons or shells are using the database path wanted by a daemon you're attempting to start, it will fail and exit non-0. There is one unfortunate caveat introduced by the lock, consider the following flow: - -```shell -$ sudo osqueryctl start -$ sudo osqueryctl config-check -E0118 17:10:09.520731 1913696256 init.cpp:421] [Ref #1629] osqueryd initialize failed: Could not open RocksDB -$ sudo osqueryctl status -io.osquery.agent is running. pid: 81943 -$ sudo osqueryctl stop -$ sudo osqueryctl config-check || echo 'config has an error' -``` - -The first `config-check` fails because it attempts to verify the sanity of the RocksDB directory while a daemon is running. The second attempt succeeds and should be the actual indicator! - -While not expected, the backing store may be corrupted by problems with the filesystem, incorrect shutdowns, or running out of disk space. If any corruption is detect via the startup sanity checks or during runtime osquery may backup the database and attempt a recovery. The most basic recovery is just to move the database content to the backup location and start 'fresh'. - -If your `--database_path` is `/var/osquery/osquery.db` then the backup is `/var/osquery/osquery.db.backup`. The database is always a folder and the backup location is the suffix ".backup" appended. - -### Inspecting TLS/HTTPS body request and responses - -When using the TLS-related plugins the hidden flag `--tls_dump` can be used with `--verbose`. This flag will print all of the HTTPS body content (usually JSON data) to `stderr`. - -### Using event publishers and tables in the shell - -Remember! The `osqueryi` shell and the `osqueryd` daemon do not communicate. The daemon is intended to be run as a privileged process and the shell may be run by any user. The daemon is intended to subscribe to operating system events that require non-default configurations and impose potential performance concerns. That said, the shell can mimic this behavior for testing and debugging. - -If you try to select from an events-based table in the shell you will see something similar to the following warning: - -``` -osquery> select * from file_events; -virtual_table.cpp:542] Table file_events is event-based but events are disabled -virtual_table.cpp:549] Please see the table documentation: https://osquery.io/schema/current/#file_events -``` - -If you start the shell using `osqueryi --disable_events=0` you will no longer get this warning. BUT! It is most likely the case that the events you are trying to inspect require future configuration. `file_events` requires a [file integrity monitoring](file-integrity-monitoring.md) configurations, `process_events` requires either additional flags or OpenBSM configuration, these situations are described in [process auditing](process-auditing.md). - -On Linux and macOS the `hardware_events` table is enabled for-free, so try to plug in a USB and run `select * from hardware_events`. - -### Testing event subscribers - -Each event subscriber, tables that end with `_events`, includes a `HIDDEN` column called `eid`. This is an internal incrementing ID assigned by osquery to every event row added to a subscriber table. Each table maintains its own counter. The `eid` can be used to check for drops and duplicates occurring via an optimization or indexing bug. - -Consider the query: - -```sql -SELECT *, eid FROM file_events; -``` - -If this query is in your schedule then the first `eid` should be `000000001` or similar. Each time the query runs the following should hold: `count(0) == max(eid) - min(eid)` and `min(eid) + 1 == max(eid from last run)`. - -### Table exception handling - -The osquery virtual table code will catch and log (then ignore) any exceptions in table implementations. You may see a log line similar to the following: - -```text -Exception while executing table TABLE_NAME: EXCEPTION_MESSAGE -``` - -If you would like to debug the exception and view the stacktrace you may disable the exception catching with the `--table_exceptions` flag. diff --git a/docs/wiki/deployment/dependency-security.md b/docs/wiki/deployment/dependency-security.md deleted file mode 100644 index d3999341402..00000000000 --- a/docs/wiki/deployment/dependency-security.md +++ /dev/null @@ -1,132 +0,0 @@ -# Security Alerts for Third-party Dependencies Used by osquery - -When the osquery maintainers are made aware of a newly disclosed vulnerability in one of the third-party dependencies -built into osquery, we investigate the potential for security impact to osquery. Not all issues affect osquery, for -reasons such as: - -- osquery does not exercise all code paths through every one of its dependencies -- sometimes, the issue is in the parent repository of osquery's dependency, but not the actual dependency as used -- not all code from osquery's dependencies is even present in the actual osquery executable as compiled -- the [osquery security design mitigates](https://github.com/osquery/osquery/blob/master/ASSURANCE.md) attacks and - reduces attack surface (_i.e._, some would-be bugs in third-party dependencies are not exposed) - -Below are some recent reports, and the impact assessments performed by the osquery team, if they may be useful for -you in deciding how to deploy or when to update osquery. - -When osquery's maintainers decide that a dependency must be updated to address a potential security impact to osquery, -the update will typically be merged into the main branch in hours or days, but depending on the timing and perceived -risk, may not be available in a tested release of osquery until the next scheduled release version. Other times, a more -urgent response is needed and there will be an unscheduled patch release (for example 5.2.3, as opposed to planned -releases like 5.1, 5.3, 5.4 etc.). - -Security issues with verified security impact to osquery will be labeled in the -[CHANGELOG](https://github.com/osquery/osquery/blob/master/CHANGELOG.md) for osquery but will not receive [security -advisories](https://github.com/osquery/osquery/security/advisories) on the osquery project page (those are reserved -specifically for issues in osquery's own codebase) or any new CVEs beyond the ones already filed against their own -upstream projects. - -Lastly, realize that it is not possible to create an _exhaustive_ list, but this page may be updated as often as is -practically possible, as a convenience or reference for osquery deployment decision makers and admins. If you have a -question about the determined impact of any particular threat to a dependency, please ask in the osquery Slack or in our -GitHub issues. - -**NOTE**: The osquery project has introduced a CI scan to find CVEs in the third party libraries it uses. They will be opened as issues labeled with `cve`, `security`, and `libraries`. -For more details on this new system refer to [Managing CVEs](../development/cve-scan.md). - - -## librpm prior to 4.17.0, CVE-2021-20266 - -Conclusion: This has the potential to be used to DoS a query of the affected tables (`rpm` tables). - -Updated in: osquery version 5.2.3. - -## OpenSSL 1.1.1l, CVE-2022-0778 - -Conclusion: This impacted osquery where it does certificate parsing. - -Updated in: osquery version 5.2.3. - -## OpenSSL 1.1.1n, CVE-2022-1292 - -Report excerpt: - -> The c_rehash script does not properly sanitise shell metacharacters to prevent command injection. This script is -> distributed by some operating systems in a manner where it is automatically executed. On such operating systems, an -> attacker could execute arbitrary commands with the privileges of the script. Use of the c_rehash script is considered -> obsolete and should be replaced by the OpenSSL rehash command line tool. - -Conclusion: - -The vulnerable `c_rehash` Perl script is not used in the osquery build process, nor is it included in the build -artifacts or release packages. Therefore, osquery is not affected. - -## sqlite 3.35.5, CVE-2022-21227 - -Report excerpt: - -> The package sqlite3 before 5.0.3 are vulnerable to Denial of Service (DoS) which will invoke the `toString` function -> of the passed parameter. If passed an invalid Function object it will throw and crash the V8 engine. - -Conclusion: - -A key detail of this report is incorrect: it erroneously attributes a V8-specific bug in the library bindings to the -actual C library underneath. These are different projects, maintained and published by different entities. Therefore -osquery is not affected. - -## sqlite 3.35.5, CVE-2021-45346 - -Report excerpt: - -> A Memory Leak vulnerability exists in SQLite Project SQLite3 3.35.1 and 3.37.0 via maliciously crafted SQL Queries -> (made via editing the Database File), it is possible to query a record, and leak subsequent bytes of memory that -> extend beyond the record, which could let a malicious user obtain sensitive information. - -Conclusion: - -This is a low impact issue for osquery, because while it entails a potential information leak of the osquery process -memory, the leak is limited to disclosing _the next records in the database_. The difficulty for the attacker is also -high: they need both to supply a query to osquery and to both read and write its SQLite database file. With the -privileges required for this, there is no benefit to the attacker; they could just acquire the same information more -directly. - -Additional Technical Details: - -In the attack scenario, the database file would need to be edited to extend the boundaries of certain columns, causing -sqlite to read more bytes than necessary when retrieving the column data from disk. - -This is not considered a “read primitive,” since it does not allow read operations at an arbitrary offset. Due to this -limitation, the attack can only read in one direction (_i.e.,_ toward growing addresses) starting from the corrupted -column and only for the fixed amount of bytes that have been preselected when the file has been tampered on disk. -Additional checks within sqlite make sure that the read does not exceed the table storage, effectively limiting the -information leak to just neighbor records. - -In order to exploit this bug, the attacker needs to either: - -1) Corrupt a sqlite database on disk, enable the ATC feature of osquery and force osquery to open the tampered database - file, then run the attacker’s choice of SQL query (either by editing query packs, or operating on the Fleet manager); - or, -2) Corrupt an existing sqlite database on disk that is already configured under ATC and that has active queries running - on that specific table. - -Corruption requires both read and write access to the database file, in order to: - -- Locate where the table starts -- Locate a suitable record to target, i.e. a record that precedes the rows that will have to be leaked -- Decode, update and rewrite the record - -It should be kept in mind that the configuration files, query packs and the osquery fleet manager are managed and -operated by what the osquery security model considers administrators. These users are already able to read a large -variety of data without needing to use any exploit by issuing SQL queries or configuration updates (like a more broad -ATC configuration that extends access to the whole sqlite database file). - -Running the query is however not enough on its own, since the attacker also needs to be able to read the information -leak _output_ in the results log. This is stored either in a Fleet manager, or the end storage of a logger plugin. Some -examples include S3 buckets, Kinesis, Firehose, Kafka, or (more rare) a local folder if the filesystem logger plugin was -used. For most deployments, then, the attacker needs additional credentials on multiple systems. - -## zlib v1.2.11, CVE-2018-25032 - -Conclusion: only affects zlib compression if an attacker can fully control the parameters of a compression operation, -which is not believed to be possible in osquery. Therefore osquery was not affected. - -Updated in: osquery 5.2.3. diff --git a/docs/wiki/deployment/extensions.md b/docs/wiki/deployment/extensions.md deleted file mode 100644 index 12f912be745..00000000000 --- a/docs/wiki/deployment/extensions.md +++ /dev/null @@ -1,97 +0,0 @@ -# Developing osquery Extensions - -osquery supports proprietary tables, config plugins, and logger plugins built in C++ (or other languages) through a Thrift-based extensions API. This is helpful if your deployment of osquery uses a custom method for osquery configuration or log collection. You can internally develop and maintain these custom behaviors in an extension, and ask osquery to depend on the plugins exposed by the extension. To make deployment and management of extensions simpler, `osqueryd` may "autoload", or subprocess, these extension binaries and monitor their performance. - -If you are interested in writing extensions, please read the [SDK and Extensions](../development/osquery-sdk.md) development article. That wiki article describes the Thrift API and provides example C++ code for an extension. Every extension runs as a separate process and communicates to the main osquery process using Thrift and a UNIX domain socket. A single extension may contain an arbitrary number of plugins, and each are registered using a setUp API call. Facebook, for example, is known to deploy an `fb-osquery` package and a single extension binary that contains its Facebook-specific tables and internal configuration/logging APIs. - -## Extensions Binary Permissions - -First, a note: the osquery agent will refuse to load an extension executable from the filesystem if the file's permissions allow write or modify by non-privileged accounts. Before loading an extension, change the owner of the `your_extension.ext` file to be the root account. - -On Windows, because of permission inheritance, just changing the owner of a file is not sufficient. You must also change the owner of the parent directory, remove all inherited DACLs, and disable inheritance. For example, if your osquery extensions are in the `.\Extensions` directory, the following commands will set permissions that satisfy osquery: - -```PowerShell -icacls .\Extensions /setowner Administrators /t -icacls .\Extensions /grant Administrators:f /t -icacls .\Extensions /inheritance:r /t -icacls .\Extensions /inheritance:d /t -``` - -## Auto-loading Extensions - -The following [CLI flags](../installation/cli-flags.md) control extension auto-loading: - -```sh ---extensions_autoload=/etc/osquery/extensions.load ---extensions_timeout=3 ---extensions_interval=3 -``` - -`extensions_autoload` points to a line-delimited set of paths to executables. When osquery launches, each path is evaluated for "safe permissions" (extension executable files must be owned by root or Administrator) and executed as a monitored child process. Each executable receives 3 argument switches: `socket`, `timeout`, `interval`. An extension process may use these to find the osquery process's Thrift socket, as well as hints on retry/backoff configuration if any latency or errors occur. If the `--verbose` flag is passed to osqueryd, the flag will also be received by the executable. - -The simplest `extensions.load` file contains a single extension path: - -```sh -$ cat /etc/osquery/extensions.load -/usr/lib/osquery/extensions/fb_osquery.ext -$ file /usr/lib/osquery/extensions/fb_osquery.ext -/usr/lib/osquery/extensions/fb_osquery.ext: ELF 64-bit LSB executable -``` - -The *autoload* workflow is similar to: - -- Check if extensions are enabled. -- Read `--extensions_autoload` and check permissions/ownership of each path. -- Checks if the file name extension of the path is .ext. Filename extension must be .ext. -- Fork and execute each path with the switches described above. -- Treat each child process as a "worker" and enforce sane memory/cycle usage. -- Read set config plugin from `--config_plugin`. -- If the config plugin does not exist and at least 1 extension was autoload: -- Wait `--extensions_timeout * --extensions_interval` for the extension to register the config plugin. -- Fail if the plugin is not registered or the plugin returns a failed status. - -The same dependency check is applied to the logger plugin setting after a valid config is read. Every registered plugin is available throughout the run of the shell or daemon. - -## Manually Loading Extensions - -Extensions can also be loaded individually on the osquery command line, for example: - -```sh -osqueryi --extension /path/to/your_extension.ext -``` - -## More Options - -Extensions are most useful when used to expose config or logger plugins. Along with auto-loading extensions, you can start daemon services with non-default plugins using `--flagfile=PATH`. The `osqueryd` initscript or SystemV service on Linux searches for a `/etc/osquery/osquery.flags` path containing flags. This is a great place to add non-default extensions options or for replacing plugins: - -```sh -$ cat /etc/osquery/osquery.flags ---config_plugin=custom_plugin ---logger_plugin=scribe -``` - -## Retrieving Tables and Columns with SQL - -Aside from the `.tables` and `.schema` shell builtins, there is an alternative way to retrieve all available tables and columns: using SQL. - -To retrieve all tables: - -```sqlite -SELECT * FROM osquery_registry - WHERE active = true - AND internal = false - AND registry = 'table'; -``` - -To retrieve all columns for a given table: - -```sqlite -PRAGMA TABLE_INFO("table-name"); -``` - -Note: [`PRAGMA`](https://www.sqlite.org/pragma.html) is unavailable in v4.6.0, but was added back in later versions. - -## Troubleshooting - -- Ensure that your osquery config has `--disable_extensions=false`, which ought to be the default value. -- If you observe a runtime error from osquery, `Extension binary has unsafe permissions`, you have to lock down the filesystem permissions on the extension executable. See the steps in "Extensions Binary Permissions," above. For quick testing, you can bypass this by running osquery with the `--allow_unsafe` flag (not recommended in deployment). diff --git a/docs/wiki/deployment/file-carving.md b/docs/wiki/deployment/file-carving.md deleted file mode 100644 index 3be59f83a93..00000000000 --- a/docs/wiki/deployment/file-carving.md +++ /dev/null @@ -1,28 +0,0 @@ -# File Carving with osquery - -Osquery has the capability to pull files from endpoints that it is monitoring with file carving. - -Simply query the `carves` table with your desired filepath and `carve=1`, which tells osquery that you want to start this carve. - -```sql -SELECT * FROM carves WHERE path LIKE '/tmp/files/%%' AND carve=1; -``` - -The carving will happen once the scheduler dispatches the request. You can check on the `status` of a carve to see if it's completed yet. The status will be one of [STARTING, PENDING, SUCCESS, or FAILED](https://www.osquery.io/schema/current/#carves). - -## How to enable file carving - -File carving is disabled by default. In order to enable it, you must pass the flag `--disable_carver=false`. - -Additionally you may want to configure the following flags for your backend. -``` ---carver_compression=true ---carver_block_size=300000 ---carver_start_endpoint=/start_uploads ---carver_continue_endpoint=/upload_blocks ---carver_disable_function=false -``` -Excerpted from [this blog post](https://www.metalliccode.com/carving): - -- `carver_compression` turns on Zstd compression for the files being returned -- `carver_disable_function` allows for using carve as a function \ No newline at end of file diff --git a/docs/wiki/deployment/file-integrity-monitoring.md b/docs/wiki/deployment/file-integrity-monitoring.md deleted file mode 100644 index efc53b8eae9..00000000000 --- a/docs/wiki/deployment/file-integrity-monitoring.md +++ /dev/null @@ -1,188 +0,0 @@ -# File Integrity Monitoring with osquery - -File integrity monitoring (FIM) is available for Linux (in `file_events`, using the inotify subsystem, and in `process_file_events` using the Audit subsystem), Windows (in `ntfs_journal_events`, using NTFS Journaling) and macOS (in `file_events`, using FSEvents). - -## FIM basics in osquery - -Collecting file events in osquery requires that you first specify a list of files/directories to monitor from the osquery configuration. The events that relate to those selected files will then populate the corresponding tables on each platform. - -FIM is also disabled by default in osquery. To enable it, first ensure that events are enabled in osquery (`--disable_events=false`), then ensure that the desired FIM table is enabled with the corresponding CLI flag (`--enable_file_events=true` for `file_events`, `--disable_audit=false` for `process_file_events`, `--enable_ntfs_event_publisher=true` for `ntfs_journal_events`). - -To specify which files and directories you wish to monitor, you must use *fnmatch*-style, or filesystem globbing, patterns to represent the target paths. You may use standard wildcards `*`/`**` or SQL-style wildcards `*%*`, as shown below. - -## Matching wildcard rules - -- `%`: Match all files and folders for one level. -- `%%`: Match all files and folders recursively. -- `%abc`: Match all within-level ending in "abc". -- `abc%`: Match all within-level starting with "abc". - -## Matching examples - -The three elements of a FIM config in osquery are (a) the scheduled query against `file_events`, (b) the added `file_paths` section, and (c) the `exclude_paths` sections. - -The `file_events` query is scheduled to collect all of the FIM events that have occurred on any files within the paths specified within `file_paths` but excluding the paths specified within `exclude_paths` on a five minute interval. At a high level, this means events are buffered within osquery and sent to the configured _logger_ every five minutes. That is, the events are always recorded in real time; the interval is just how often the already recorded events will be logged. - -After you identify the files and directories you wish to monitor, add their match rules to the `file_paths` section in the osquery FIM config. - -- `/Users/%/Library`: Monitor for changes to every user's Library folder, *but not the contents within*. -- `/Users/%/Library/`: Monitor for changes to files *within* each Library folder, but not the contents of their subdirectories. -- `/Users/%/Library/%`: Same, changes to files within each Library folder. -- `/Users/%/Library/%%`: Monitor changes recursively within each Library. -- `/bin/%sh`: Monitor the `bin` directory for changes ending in `sh`. - -**Note:** You cannot match recursively inside a path. For example `/Users/%%/Configuration.conf` is not a valid wildcard. - -**Note:** Many applications may *replace* a file instead of editing it in place. If you monitor the file directly, osquery will need to be restarted in order to monitor the replacement. You can avoid this by monitoring the containing *directory* instead. - -**Note:** Remember to specify home paths as, for instance, `/Users/%`, instead of `~/%` which will not work. - -## Example FIM Config - -```json -{ - "schedule": { - "crontab": { - "query": "SELECT * FROM crontab;", - "interval": 300 - }, - "file_events": { - "query": "SELECT * FROM file_events;", - "removed": false, - "interval": 300 - } - }, - "file_paths": { - "homes": [ - "/root/.ssh/%%", - "/home/%/.ssh/%%" - ], - "etc": [ - "/etc/%%" - ], - "tmp": [ - "/tmp/%%" - ] - }, - "exclude_paths": { - "homes": [ - "/home/not_to_monitor/.ssh/%%" - ], - "tmp": [ - "/tmp/too_many_events/" - ] - } -} -``` - -Do not use arbitrary category names under the `exclude_paths` node; only valid names are allowed. - -- **Valid categories** - Categories referenced under the `file_paths` node. In the above example config, `homes`, `etc` and `tmp` are valid categories. -- **Invalid categories** - Any name not referenced under `file_paths`. In the above example, any name besides `homes`, `etc` and `tmp` is invalid. Invalid categories get dropped silently, i.e., they don't have any effect on the events generated. - -In addition to `file_paths`, you can use `file_paths_query` to specify the file paths to monitor as the `path` column of the results of the given query. For example: - -```json -{ - "file_paths_query": { - "category_name": [ - "SELECT DISTINCT '/home/' || username || '/.gitconfig' as path FROM last WHERE username != '' AND username != 'root';" - ] - } -} -``` - -## Sample Event Output - -As file changes happen, events will appear in the [**file_events**](https://osquery.io/schema/current/#file_events) table. During a file change event, the md5, sha1, and sha256 for the file will be calculated if possible. A sample event looks like this: - -```json -{ - "action":"ATTRIBUTES_MODIFIED", - "category":"homes", - "md5":"bf3c734e1e161d739d5bf436572c32bf", - "sha1":"9773cf934440b7f121344c253a25ae6eac3e3182", - "sha256":"d0d3bf53d6ae228122136f11414baabcdc3d52a7db9736dd256ad81229c8bfac", - "target_path":"\/root\/.ssh\/authorized_keys", - "time":"1429208712", - "transaction_id":"0" -} -``` - -## Tuning Linux inotify limits - -On Linux, the `file_events` table in osquery uses inotify to subscribe to file changes. There are inherently some limitations on the number of files that can be monitored, since each inotify watch takes up a certain amount of memory in kernel space (non-swappable memory). Adjusting your limits accordingly can help increase the file limit, at a cost of kernel memory. - -### Example sysctl.conf modifications - -```text -#/proc/sys/fs/inotify/max_user_watches = 8192 -fs.inotify.max_user_watches = 524288 - -#/proc/sys/fs/inotify/max_user_instances = 128 -fs.inotify.max_user_instances = 256 - -#/proc/sys/fs/inotify/max_queued_events = 16384 -fs.inotify.max_queued_events = 32768 -``` - -## File Accesses (Linux only) - -In addition to FIM, which generates events if a file is created/modified/deleted, osquery also supports file *access* monitoring which can generate events if a file is accessed. - -Monitoring file accesses on Linux uses inotify and may incur unexpected and unwanted performance overhead. To prevent 'flooding' of access events alongside FIM, enabling access events for `file_path` categories is an explicit opt-in. You may add categories that were defined in your `file_paths` stanza: - -```json -{ - "file_paths": { - "homes": [ - "/root/.ssh/%%", - "/home/%/.ssh/%%" - ], - "etc": [ - "/etc/%%" - ], - "tmp": [ - "/tmp/%%" - ] - }, - "file_accesses": ["homes", "etc"] -} -``` - -The above configuration snippet will enable file integrity monitoring for `homes`, `etc`, and `tmp` but only enable access monitoring for the `homes` and `etc` directories. - -> NOTICE: The hashes of files will not be calculated, to avoid generating additional access events. - -## Troubleshooting FIM - -Sometimes, despite a correct osquery configuration, the file events tables don't receive any events. - -First, make sure you are launching osquery as root/Administrator. - -In some cases, the problem might be interference from additional security permissions settings: - -- On CentOS-like systems, check that the SElinux settings are not preventing osquery from performing FIM. -- On Debian-like systems, check that AppArmor is not blocking osquery. -- On macOS, the `osqueryd` agent (or `Terminal.app`, if using `osqueryi`) may need Full Disk Access permissions, in Security and Privacy settings. - -Also, remember you can run `osqueryd` with the `--verbose` flag to see if any helpful warnings appear. - -If you're attempting to use FIM on Linux via Audit and you see an error like the following, your system is probably configured to use `auditd`; unfortunately osquery cannot share access to the Audit subsystem with `auditd`. You can use osquery with one of the other FIM sources, or discontinue the use of `auditd`. - -```text -osquery> I1107 17:49:42.229321 8235 auditdnetlink.cpp:601] Failed to set the netlink owner -``` - -Last but not least, see the troubleshooting guidance in [process auditing with osquery](./process-auditing.md), which similarly covers the topic of event-based tables in osquery and its use of the Audit subsystem on Linux. - -## Known Issues - -Implementing FIM across all platforms and using multiple sources means that there are a few problematic corner cases. This is not an exhaustive list, but rather, some long-standing issues to be aware of. If you encounter one that is not in our tracked issues, please submit it. - -- On some platforms, it may not be possible to monitor a given path, until a file or directory already exists at that path. [Issue 3212](https://github.com/osquery/osquery/issues/3212) -- If a watched file is deleted, inotify stops watching that path. [Issue 6495](https://github.com/osquery/osquery/issues/6495) -- With inotify, moving a directory of directories into a watched directory does not immediately add all of those subdirectories to the watched set. [Issue 1969](https://github.com/osquery/osquery/issues/1969) -- With inotify, you'll get a "modify" event on every occurrence of an open-file-with-write-permission action. [Issue 3920](https://github.com/osquery/osquery/issues/3920) -- If you have a directory with an extremely large number of subdirectories, setting a watch on it using inotify will exhaust the available inotify handles and result in receiving no events. Setting an `exclude_path` on the subdirectories will not help here; the workaround is to be more specific with the `file_paths`. Unfortunately, this means not being able to watch for new files/directories getting created in a directory that already has many subdirectories. [Issue 4296](https://github.com/osquery/osquery/issues/4296) -- inotify may not track events done via hard links [Issue 5704](https://github.com/osquery/osquery/issues/5704) diff --git a/docs/wiki/deployment/log-aggregation.md b/docs/wiki/deployment/log-aggregation.md deleted file mode 100644 index e3bb7b51b50..00000000000 --- a/docs/wiki/deployment/log-aggregation.md +++ /dev/null @@ -1,96 +0,0 @@ -# Log aggregation - -osquery is designed to work with any environment's existing data infrastructure. Since the problem space of forwarding logs is so well developed, osquery does not implement log forwarding internally, but rather via plugins. - -**Note**: Ultimately, the solution for forwarding and analyzing osquery logs depends on your particular environment and deployment needs. This page offers advice and some options for you to consider, but at the end of the day, you know your infrastructure best and you should make your decisions based on that knowledge. - -When it comes to aggregating the logs that `osqueryd` generates, you have several options. If you use the filesystem logger plugin (which is the default), then you're responsible for shipping the logs off somewhere. There are many open source and commercial products which excel in this area. This section will explore a few of those options. - -## Logstash - -[LogStash](https://www.elastic.co/logstash) is an open source tool enabling you to collect, parse, index and forward logs. Logstash enables you to ingest osquery logs with its [file](https://www.elastic.co/guide/en/logstash/current/plugins-inputs-file.html) input plugin and then send the data to an aggregator via its extensive list of [output plugins](https://www.elastic.co/guide/en/logstash/current/output-plugins.html). A common datastore for logstash logs is [ElasticSearch](https://www.elastic.co/elasticsearch/). - -An example Logstash to ElasticSearch config may look like this: - -```JSON -input { - file { - path => "/var/log/osquery/osqueryd.results.log" - type => "osquery_json" - codec => "json" - } -} - -filter { - if [type] == "osquery_json" { - date { - match => [ "unixTime", "UNIX" ] - } - } -} - -output { - stdout {} - elasticsearch { - hosts=> "127.0.0.1:9200" - } -} -``` - -This will send the JSON formatted logs from the results log to an ElasticSearch instance listening on `127.0.0.1` (the `hosts` field can be an Elasticsearch node at any IP address). - -## Splunk - -If you use Splunk, you're probably already familiar with the [Splunk Universal Forwarder](https://docs.splunk.com/Splexicon:Universalforwarder). An example Splunk forwarder config (`inputs.conf`) may look as follows: - -```ini -[monitor:///var/log/osquery/osqueryd.results.log] -index = main -sourcetype = osquery:results - -[monitor:///var/log/osquery/osqueryd.*INFO*] -index = main -sourcetype = osquery:info - -[monitor:///var/log/osquery/osqueryd.*ERROR*] -index = main -sourcetype = osquery:error - -[monitor:///var/log/osquery/osqueryd.*WARNING*] -index = main -sourcetype = osquery:warning -``` - -### Fluentd - -[Fluentd](https://www.fluentd.org) is an open source data collector and log forwarder. It's very extensible and many people swear by it. - -### Rsyslog - -[rsyslog](https://www.rsyslog.com) is a tried and testing UNIX log forwarding service. If you are deploying `osqueryd` in a production Linux environment where you do not have to worry about lossy network connections, this may be your best option. - -## Analyzing logs - -The way in which you analyze logs is very dependent on how you aggregate logs. At the end of the day, osquery produces results logs in JSON format, so the logs are very easy to analyze on most modern backend log aggregation platforms. - -### Kibana - -If you are forwarding logs with [LogStash](https://www.elastic.co/logstash/) to [ElasticSearch](https://www.elastic.co/elasticsearch/), then you probably want to perform your analytics using [Kibana](https://www.elastic.co/kibana/). - -Logstash will index logs into ElasticSearch using a default index format of logstash-YYYY-MM-DD. Kibana has a default Logstash dashboard and automatically field-extracts all log lines making them available for search. - -An example Kibana log entry: - -![An example Kibana log entry in table view](https://i.imgur.com/thivGYc.png) - -### Splunk - -Splunk will automatically extract the relevant fields for analytics, as shown below: - -![Splunk, showing the interesting fields](https://i.imgur.com/tWCPx51.png) - -## Rsyslog, Fluentd, Scribe, etc - -If you are using a log forwarder which has less requirements on how data is stored (for example, Splunk Forwarders require the use of Splunk, etc.), then you have many options on how you can interact with `osqueryd` data. It is recommended that you use whatever log analytics platform that you are comfortable with. - -Many people are very comfortable with [Logstash](https://www.elastic.co/logstash/). If you already have an existing Logstash/ElasticSearch deployment, that is a great option to exercise. If your organization uses a different backend log management solution, osquery should tie into that with minimal effort. diff --git a/docs/wiki/deployment/logging.md b/docs/wiki/deployment/logging.md deleted file mode 100644 index f980a8d95d3..00000000000 --- a/docs/wiki/deployment/logging.md +++ /dev/null @@ -1,291 +0,0 @@ -# Logging osquery - -The osquery daemon uses a default **filesystem** logger plugin. Like the config, output from the filesystem plugin is written as JSON. Results from the query schedule are written to `/var/log/osquery/osqueryd.results.log`. - -There are two types of logs: - -- Status logs (`INFO`, `WARNING`, `ERROR`) -- Query schedule results logs, including logs from snapshot queries - -If you run `osqueryd` in verbose mode, then peek at `/var/log/osquery/`: - -```bash -$ ls -l /var/log/osquery/ -total 24 -lrwxr-xr-x 1 root wheel 77 Sep 30 17:37 osqueryd.INFO -> osqueryd.INFO.20140930 --rw------- 1 root wheel 1226 Sep 30 17:37 osqueryd.INFO.20140930 --rw------- 1 root wheel 388 Sep 30 17:37 osqueryd.results.log -``` - -On Windows the osquery log directory defaults to `C:\Program Files\osquery\log`. - -## Logger plugins - -osquery includes logger plugins that support configurable logging to a variety of interfaces. The built in logger plugins are **filesystem** (default), **tls**, **syslog** (for POSIX), **windows_event_log** (for Windows), **kinesis**, **firehose**, and **kafka_producer**. Multiple logger plugins may be used simultaneously, effectively copying logs to each interface. To enable multiple loggers set the `--logger_plugin` option to a comma separated list, do not include spaces, of the requested plugins. - -For information on configuring logger plugins, see [logging/results flags](../installation/cli-flags.md#loggingresults-flags). Developing new logger plugins is explored in the [development docs](../development/logger-plugins.md). We recommend setting the logger plugin and logger settings via the osquery *flagfile*. - -### Status logs - -Status logs are generated by the [Glog logging framework](https://github.com/google/glog/). The default **filesystem** logger plugin writes these logs to disk the same way Glog would. Logger plugins may intercept these status logs and write them to system or otherwise. - -As the above directory listing reveals, `osqueryd.INFO` is a symlink to the most recent execution's `INFO` log. The same is true for the `WARNING`, `ERROR` and `FATAL` logs. For more information on the format of Glog logs, please refer to the [Glog documentation](https://github.com/google/glog/blob/master/README.rst). - -Note: The `osqueryi` shell only shows `WARNING` and `ERROR` status logs, the `INFO` logs are silenced for a better shell-like experience. - -By default the `osqueryd` daemon sends `INFO`, `WARNING`, and `ERROR` logs to the configured logger plugins and to the process's `stderr`. You may configure this behavior using several flags documented in [CLI flags](../installation/cli-flags.md). - -- To disable writing status logs to stderr, use `--logger_stderr=false` -- To set the minimum status log severity (`INFO=0`) written to stderr, use `--logger_min_stderr=0` -- To set the minimum status log written to stderr and logger plugins, use `--logger_min_status=0` - -Note: In the LaunchDaemon, systemd, and initscript provided in the osquery packages, the minimum `stderr` reporting is limited to `WARNING` to help minimize the content duplicated to syslog. - -### Results logs - -#### Differential logs - -The results of your scheduled queries are logged to the "results log". These are differential changes between the last (most recent) query execution and the current execution. Each log line is a JSON string that indicates what data has been added/removed by which query. The first time the query is executed (there is no "last" run), the last run is treated as having null results, so the differential consists entirely of log lines with the added indication. There are two format options, *single*, or event, and *batched*. Some queries do not make sense to log "removed" events like: - -```sql -SELECT i.*, p.resident_size, p.user_time, p.system_time, t.minutes AS c - FROM osquery_info i, processes p, time t - WHERE p.pid = i.pid; -``` - -By adding an outer join of `time` and using `time.minutes` as a counter, this query will always log a single `"added"` and a single `"removed"` line. The purpose is to create a continuous monitor of osquery's performance. For these cases add a `"removed": false` to the scheduled query. - -```json -{ - "schedule": { - "osquery_monitor": { - "query": "SELECT ... t.minutes AS c FROM time t WHERE ...", - "interval": 60, - "removed": false - } - } -} -``` - -#### Snapshot logs - -Snapshot logs are an alternate form of query result logging. A snapshot is an 'exact point in time' set of results, with no differentials. For instance if you always want a *list* of mounts, not the *added and removed* mounts, then use a snapshot. In the mounts case, where differential results are seldom emitted (assuming hosts do not often mount and unmount), a complete snapshot will log after every query execution. This *will* be a lot of data amortized across your fleet. - -Data snapshots may generate *a large amount* of output. For log collection safety, output is written to a dedicated sink. The **filesystem** logger plugin writes snapshot results to `/var/log/osquery/osqueryd.snapshots.log`. - -To schedule a snapshot query, use: - -```json -{ - "schedule": { - "mounts": { - "query": "SELECT * FROM mounts;", - "interval": 3600, - "snapshot": true - } - } -} -``` - -### Logging as a Kafka producer - -Users can configure logs to be directly published to a Kafka topic. - -#### Configuration - -There are 3 Kafka configurations exposed as options: a comma-delimited list of brokers with or without the port (by default `9092`) [default value: `localhost`], a default topic [default value: `""`], and acks (the number acknowledgments the logger requires from the Kafka leader before the considering the request complete) [default: `all`; valid values: `0`, `1`, `all`]. [See the official Kafka documentation for more details.](https://kafka.apache.org/documentation/#producerconfigs) - -To publish queries to specific topics, add a `kafka_topics` field at the top level of `osquery.conf` (see example below). If a given query was not explicitly configured in `kafka_topics` then the base topic will be used. If there is no base topic configured, then that query will not be logged. There is however a performance cost for the falling back of unconfigured queries to the base topic, so it is advised that when using multiple topics to explicitly configure all scheduled queries in `kafka_topics`. - -The configuration parameters are exposed via command-line options and can be set in a JSON configuration file: - -```json -{ - "options": { - "logger_kafka_brokers": "some.example1.com:9092,some.example2.com:9092", - "logger_kafka_topic": "base_topic", - "logger_kafka_compression": "gzip", - "logger_kafka_acks": "1" - }, - "packs": { - "system-snapshot": { - "queries": { - "some_query1": { - "query": "select * from system_info", - "snapshot": true, - "interval": 60 - }, - "some_query2": { - "query": "select * from md_devices", - "snapshot": true, - "interval": 60 - }, - "some_query3": { - "query": "select * from md_drives", - "snapshot": true, - "interval": 60 - } - } - } - }, - "kafka_topics": { - "test1_topic": [ - "pack_system-snapshot_some_query1" - ], - "test2_topic": [ - "pack_system-snapshot_some_query2" - ], - "test3_topic": [ - "pack_system-snapshot_some_query3" - ], - } -} -``` - -Client ID and msg key used are a concatenation of the OS hostname and binary name (`argv[0]`). Currently there can only be one topic passed into the configuration, so all logs will be published to the same topic. - -## Schedule results - -### Event format - -Event is the default result format. Each log line represents a state change. This format works best for log aggregation systems like Logstash or Splunk. - -Example output of `SELECT name, path, pid FROM processes;` (whitespace added for readability): - -```json -{ - "action": "added", - "columns": { - "name": "osqueryd", - "path": "/opt/osquery/bin/osqueryd", - "pid": "97830" - }, - "name": "processes", - "hostname": "hostname.local", - "calendarTime": "Tue Sep 30 17:37:30 2014", - "unixTime": "1412123850", - "epoch": "314159265", - "counter": "1", - "numerics": false -} -``` - -```json -{ - "action": "removed", - "columns": { - "name": "osqueryd", - "path": "/opt/osquery/bin/osqueryd", - "pid": "97650" - }, - "name": "processes", - "hostname": "hostname.local", - "calendarTime": "Tue Sep 30 17:37:30 2014", - "unixTime": "1412123850", - "epoch": "314159265", - "counter": "1", - "numerics": false -} -``` - -This tells us that a binary called `osqueryd` was stopped and a new binary with the same name was started (note the different `pid`). The data is generated by keeping a cache of previous query results and only logging when the cache changes. If no new processes are started or stopped, the query won't log any results. - -### Snapshot format - -Snapshot queries attempt to mimic the differential event format, instead of emitting "columns", the snapshot data is stored using "snapshot". An action is included as, you guessed it, "snapshot"! - -Consider the following example: - -```json -{ - "action": "snapshot", - "snapshot": [ - { - "parent": "0", - "path": "/sbin/launchd", - "pid": "1" - }, - { - "parent": "1", - "path": "/usr/sbin/syslogd", - "pid": "51" - }, - { - "parent": "1", - "path": "/usr/libexec/UserEventAgent", - "pid": "52" - }, - { - "parent": "1", - "path": "/usr/libexec/kextd", - "pid": "54" - } - ], - "name": "process_snapshot", - "hostIdentifier": "hostname.local", - "calendarTime": "Mon May 2 22:27:32 2016 UTC", - "unixTime": "1462228052", - "epoch": "314159265", - "counter": "1", - "numerics": false -} -``` - -### Batch format - -If a query identifies multiple state changes, the batched format will include all results in a single log line. If you're programmatically parsing lines and loading them into a backend datastore, this is probably the best solution. - -To enable batch log lines, launch osqueryd with the `--logger_event_type=false` argument. - -Example output of `SELECT name, path, pid FROM processes;` (whitespace added for readability): - -```json -{ - "diffResults": { - "added": [ - { - "name": "osqueryd", - "path": "/opt/osquery/bin/osqueryd", - "pid": "97830" - } - ], - "removed": [ - { - "name": "osqueryd", - "path": "/opt/osquery/bin/osqueryd", - "pid": "97650" - } - ] - }, - "name": "processes", - "hostname": "hostname.local", - "calendarTime": "Tue Sep 30 17:37:30 2014", - "unixTime": "1412123850", - "epoch": "314159265", - "counter": "1", - "numerics": false -} -``` - -Most of the time the **Event format** is the most appropriate. The next section in the deployment guide describes [log aggregation](log-aggregation.md) methods. The aggregation methods describe collecting, searching, and alerting on the results from a query schedule. - -## Special top-level fields - -### Schedule epoch - -When [differential logs](#differential-logs) were described above, we mentioned that after the initial execution of a scheduled query, only differential results are logged. While this is very efficient from a size-of-logs perspective, it introduces some challenges. To begin with, if the logs are stored in a log management system of some kind, it becomes difficult or impossible to identify which log results are from the initial run of the query, and which ones are differentials to the initial results. In some situations, this becomes problematic. For example, for some tables like the `users` table that don't change very often and thus don't generate differential results very often, one would have to search far into historical logs to find the last results returned by osquery. Conversely, for tables like `processes` that change frequently, one would have to do a fair amount of logic, applying the effects of added and removed rows to reconstruct the current state of running processes. - -To aid with this, osquery maintains an `epoch` marker along with each scheduled query execution, and calculates differentials only if the epoch of the last run matches the current epoch. If it doesn't, then it treats the current execution of the query as an initial run. You can set the epoch marker by starting osquery with the `--schedule_epoch=` flag or by updating the `schedule_epoch` flag remotely from a TLS backend. The epoch is transmitted with each log result, so that it is easy to identify which results belong to which execution of the scheduled query. - -### Schedule counter - -When setting up alerts for [differential logs](#differential-logs) data you might want to skip the initial `added` records. `counter` can be used to identify if the added records are all records from initial query or if they are new records. For initial query results that include all records counter will be **"0"**, while initial results without all records (like event tables) will start at **"1"**. For subsequent query executions counter will be incremented by **1**. When `epoch` changes, counter will be reset back to the initial query state. - -### Numerics - -This is an indicator for all results, `true` if osquery attempted to log numerics as numbers, otherwise `false` indicates they were logged as strings. - -### Unique host identification - -If you need a way to uniquely identify hosts embedded into `osqueryd`'s results log, then the `--host_identifier` flag is what you're looking for. -By default, **host_identifier** is set to "hostname". The host's hostname will be used as the host identifier in results logs. If hostnames are not unique or consistent in your environment, you can launch osqueryd with `--host_identifier=uuid`. diff --git a/docs/wiki/deployment/performance-safety.md b/docs/wiki/deployment/performance-safety.md deleted file mode 100644 index 8abb35b701d..00000000000 --- a/docs/wiki/deployment/performance-safety.md +++ /dev/null @@ -1,145 +0,0 @@ -# Performance safety - -High-performance visibility capability is a core feature of osquery. However, user-formed queries are very powerful, and generate opportunities to ruin the performance guarantees of osquery using ill-formed queries. - -This guide provides an overview and tutorial for assuring performance of the osquery scheduled queries, as well as performance-centric development practices/enforcements. - -## Testing query performance - -The osquery tooling provides a full-featured profiling script. The script can evaluate table, query, and scheduled query performance on a system. Before scheduling a set of queries on your enterprise hosts, it is best practice to measure the expected performance impact. - -Consider the following `osquery.conf`: - -```json -{ - "schedule": { - "alf_services": { - "query": "SELECT service, process FROM alf_services WHERE state != 0;", - "interval": 60 - }, - "installed_applications": { - "query": "SELECT name, path, bundle_version, minimum_system_version, applescript_enabled, bundle_executable FROM apps;", - "interval": 60 - }, - "all_kexts": { - "query": "SELECT name, version FROM kernel_extensions;", - "interval": 60 - }, - "non_apple_kexts": { - "query": "SELECT * FROM kernel_extensions WHERE name NOT LIKE 'com.apple.%' AND name != '__kernel__';", - "interval": 60 - }, - "processes_binding_to_ports": { - "query": "SELECT DISTINCT process.name, listening.port, listening.protocol, listening.family, listening.address, process.pid, process.path, process.on_disk, process.parent, process.start_time FROM processes AS process JOIN listening_ports AS listening ON process.pid = listening.pid;", - "interval": 60 - }, - "processes_not_on_disk": { - "query": "SELECT * FROM processes WHERE on_disk != 1;", - "interval": 60 - } - } -} -``` - -Each query provides useful information and will run every minute. But what sort of impact will this have on the client machines? - -For this we can use `./tools/analysis/profile.py` to profile the queries by running them for a configured number of rounds and reporting the pre-defined performance category of each. A higher category result means higher impact. High impact queries should be avoided, but if the information is valuable, consider running them less-often. - -```bash -$ sudo -E python ./tools/analysis/profile.py --config osquery.conf -Profiling query: SELECT * FROM kernel_extensions WHERE name NOT LIKE 'com.apple.%' AND name != '__kernel__'; - D:0 C:0 M:0 F:0 U:1 non_apple_kexts (1/1): duration: 0.519426107407 cpu_time: 0.096729864 memory: 6447104 fds: 5 utilization: 9.5 -Profiling query: SELECT name, path, bundle_version, minimum_system_version, applescript_enabled, bundle_executable FROM apps; - D:0 C:0 M:0 F:0 U:1 installed_applications (1/1): duration: 0.507317066193 cpu_time: 0.113432314 memory: 7639040 fds: 6 utilization: 11.15 -Profiling query: SELECT service, process FROM alf_services WHERE state != 0; - D:0 C:0 M:0 F:0 U:0 alf_services (1/1): duration: 0.525090932846 cpu_time: 0.021108868 memory: 5406720 fds: 5 utilization: 1.9 -Profiling query: SELECT * FROM processes WHERE on_disk != 1; - D:0 C:0 M:0 F:0 U:0 processes_not_on_disk (1/1): duration: 0.521270990372 cpu_time: 0.030440911 memory: 6148096 fds: 5 utilization: 2.8 -Profiling query: SELECT name, version FROM kernel_extensions; - D:0 C:0 M:0 F:0 U:1 all_kexts (1/1): duration: 0.522475004196 cpu_time: 0.089579066 memory: 6500352 fds: 5 utilization: 8.65 -Profiling query: SELECT DISTINCT process.name, listening.port, listening.protocol, listening.family, listening.address, process.pid, process.path, process.on_disk, process.parent, process.start_time FROM processes AS process JOIN listening_ports AS listening ON process.pid = listening.pid; - D:2 C:1 M:0 F:0 U:2 processes_binding_to_ports (1/1): duration: 1.02116107941 cpu_time: 0.668809664 memory: 6340608 fds: 5 utilization: 44.3 -``` - -The results (utilization=2) suggest running `processes_binding_to_ports` less often. - -To estimate how often these should run, you should evaluate what a differential in the information means from your visibility requirement perspective (how meaningful is a change vs. how often you check for the change). Then weigh the value of that information against the performance impact of running the query. - -### Understanding the output from profile.py - -The osquery `profile.py` script uses `utils.py` in `tools/tests/` which uses python’s `psutil` library to collect process stats for osqueryi as its running given queries. - -The script returns 5 stats: - -**Utilization (U)**: Utilization is calculated by taking the average of non-0 results of the cpu_percent(interval=1) function in `psutils.Process()`. This value can be greater than 100% for processes with threads running across different CPUs. The script sets an interval of 1 meaning that the function compares process time to system CPU times elapsed before and after the 1 second interval. This is a blocking call. - -**CPU time (C)**: CPU time uses the `psutils.Process()`'s `cpu_times()` function. It returns a named tuple containing user, system, children_user, system_user, and iowait - -- user: time spent in user mode. -- system: time spent in kernel mode. -- children_user: user time of all child processes (always 0 on Windows and macOS). -- system_user: user time of all child processes (always 0 on Windows and macOS). -- iowait: (Linux) time spent waiting for blocking I/O to complete. This value is excluded from user and system times count (because the CPU is not doing any work). - -The profile script adds user and system together for the CPU Time output. - -**Duration (D)**: -Duration is calculated by taking the subtracting `start_time` - 2 from the current time. The start time is set before the script starts the `osqueryi` process to run the query. The `start_time` - 2 comes from the `--profile_delay` flag used by the profile.py script. This flag causes osquery to wait before and after running the code under test. The 2 is to make up for this wait time. - -**fds (F)**: Uses the `num_fds()` function and returns the file descriptors used by the `osqueryi` process during query execution - -**Memory (M)**: Uses the `memory_info_ex()` function which is deprecated. psutils documentation suggests using `memory_info()` instead. The function returns a named tuple and the script uses the `rss` value in the tuple. RSS stands for resident set size and is the non-swapped physical memory used by the process. This should match the RES column in `top`. - -### Understanding Profile.py Categories - -The numbers next to the stats in the script output (categories) are determined by the `RANGES` dictionary in `profile.py` - -```python -KB = 1024 * 1024 -RANGES = { - "colors": (utils.blue, utils.green, utils.yellow, utils.red), - "utilization": (8, 20, 50), - "cpu_time": (0.4, 1, 10), - "memory": (8 * KB, 12 * KB, 24 * KB), - "fds": (10, 20, 50), - "duration": (0.8, 1, 3), -} -``` - -The script will take the value of the stat and compare it with the tuple at the corresponding stat's key in `RANGES`. If the value is less than the value in the tuple then the index for the value in the tuple is what appears in the script output. If the value for the stat is greater than all values of the tuple, then the length of the tuple is what appears in the script output. For example, if `cpu_time` for a query is 0.2, then you'll see `C: 0` in the script output. If `cpu_time` is 11, then you'll see `C:3` in the script output. - -Queries that fail to execute (for example, due to a non-existent table) will return the highest category result `3` and the value `-1` for all statistics. - -## Continuous Build - -Each build on the Continuous Integration server will run on each of the supported operating system platform/versions and include the following phases: - -- Build and run tests -- Attempt to detect memory leaks using `./tools/analysis/profile.py --leaks` -- Run a performance measurement using `./tools/analysis/profile.py` -- Check performance against the latest release tag and commit to master -- Build docs and API spec on release tag or commit to master - -## Virtual table denylist - -Performance impacting virtual tables are most likely the result of missing features/tooling in osquery. Because of their dependencies on core optimizations, there is no harm including the table generation code in master as long as the table is denylisted when a non-developer builds the tool suite. - -If you are developing latent tables that would be denylisted, please make sure you are relying on a feature with a clear issue and traction. Then add your table name (as it appears in the `.table` spec) to [`specs/denylist`](https://github.com/osquery/osquery/blob/master/specs/denylist) and define the following in your build step: - -```bash -DISABLE_DENYLIST=1 make -``` - -## Deployment profiling - -Before deploying an osquery config, use: - -```sh -./tools/analysis/profile.py --config /path/to/osquery.conf --count 1 --rounds 4 -``` - -to estimate the amount of CPU/memory load that the system will incur for each query. - -## Wishlist - -Query implementation isolation options. diff --git a/docs/wiki/deployment/process-auditing.md b/docs/wiki/deployment/process-auditing.md deleted file mode 100644 index 926a71bedd6..00000000000 --- a/docs/wiki/deployment/process-auditing.md +++ /dev/null @@ -1,418 +0,0 @@ -# Process and socket auditing with osquery - -Enabling these auditing features requires additional configuration of osquery. osquery can leverage either BPF, Audit, OpenBSM or EndpointSecurity subsystems to record process executions and network connections in near real-time on Linux and macOS systems. Although these auditing features are extremely powerful for recording the activity from a host, they may introduce additional CPU overhead and greatly increase the number of log events generated by osquery. - -To read more about how event-based tables are created and designed, check out the osquery [Table Pubsub Framework](../development/pubsub-framework.md). - -Because different platforms have different choices for collecting real-time event data, osquery has multiple tables to present this information depending on the source and platform: - -| Event type | osquery Table | Source | Supported Platform | -| -------------- | -------- | -------- | -------- | -| Process events | [`process_events`](https://osquery.io/schema/current#process_events) | Audit (Linux), OpenBSM (macOS) | Linux, macOS (10.15 and older) | -| Process events | [`bpf_process_events`](https://osquery.io/schema/current#bpf_process_events) | BPF | Linux (kernel 4.18 and newer) | -| Process events | [`es_process_events`](https://osquery.io/schema/current#es_process_events) | EndpointSecurity | macOS (10.15 and newer) | -| Socket events | [`socket_events`](https://osquery.io/schema/current#process_events) | Audit (Linux), OpenBSM (macOS) | Linux, macOS (10.15 and older) | -| Socket events | [`bpf_socket_events`](https://osquery.io/schema/current#bpf_socket_events) | BPF | Linux (kernel 4.18 and newer) | - -To collect process events, you would add a query like the following to your query schedule, or to a query pack: - -```sql -SELECT * FROM process_events; -``` - -Each of these auditing features is enabled on a per-source basis using additional osquery configuration settings. Enabling any of them may have performance impact depending on the host activity, and should be tested in your environment before deployment. See the OS-specific sections for guidance. - -## General Troubleshooting - -Though some testing of underlying operating system configuration can -be performed via `osqueryi`; `osqueryi` and `osqueryd` operate -independently and do not communicate. - -The `--verbose` flag can be really useful when trying to debug a problem. - -### Examine configuration flags - -To verify that osquery's flags are set correct, you can query the -`osquery_flags` table. For example, on a macOS machine, this shows -osquery will process OpenBSM events. - -```sql -osquery> select * from osquery_flags where name in ("disable_events", "disable_audit"); -+----------------+------+---------------------------------------------------+---------------+-------+------------+ -| name | type | description | default_value | value | shell_only | -+----------------+------+---------------------------------------------------+---------------+-------+------------+ -| disable_audit | bool | Disable receiving events from the audit subsystem | true | false | 0 | -| disable_events | bool | Disable osquery publish/subscribe system | false | false | 0 | -+----------------+------+---------------------------------------------------+---------------+-------+------------+ -``` - -### Examine event table - -osquery keeps state about the events subsystem in the `osquery_events` -table. The `events` column is of note here. - -This example is from a macOS machine with events enabled, but no -events. You should try triggering an event, and then confirming that -the event count is non-0. If it remains at zero, the problem is likely -in how the OS auditing side is configured. See the platform specific -instructions. - -```sql -osquery> select * from osquery_events; -+-------------------------+-----------------+------------+---------------+--------+-----------+--------+ -| name | publisher | type | subscriptions | events | refreshes | active | -+-------------------------+-----------------+------------+---------------+--------+-----------+--------+ -| diskarbitration | diskarbitration | publisher | 1 | 0 | 0 | 1 | -| event_tapping | event_tapping | publisher | 1 | 0 | 0 | 0 | -| fsevents | fsevents | publisher | 0 | 0 | 24 | 1 | -| iokit | iokit | publisher | 1 | 0 | 0 | 1 | -| openbsm | openbsm | publisher | 9 | 0 | 0 | 0 | -| scnetwork | scnetwork | publisher | 0 | 0 | 0 | 0 | -| disk_events | diskarbitration | subscriber | 1 | 0 | 0 | 1 | -| file_events | fsevents | subscriber | 0 | 0 | 0 | 1 | -| hardware_events | iokit | subscriber | 1 | 0 | 0 | 1 | -| process_events | openbsm | subscriber | 8 | 0 | 0 | 1 | -| user_events | openbsm | subscriber | 1 | 0 | 0 | 1 | -| user_interaction_events | event_tapping | subscriber | 1 | 0 | 0 | 1 | -| yara_events | fsevents | subscriber | 0 | 0 | 0 | 1 | -+-------------------------+-----------------+------------+---------------+--------+-----------+--------+ -``` - -## Linux process auditing using Audit - -On Linux, osquery can the Audit system to collect and process events. It accomplishes this by monitoring syscalls such as `execve()` and `execveat()`. `auditd` should not be running when using osquery's process auditing, as it will conflict with `osqueryd` over access to the audit netlink socket. You should also ensure `auditd` is not configured to start at boot. - -The only prerequisite for using osquery's auditing functionality on Linux is that you must use a kernel version that contains the Audit functionality. Most kernels over version 2.6 have this capability. - -There is no requirement to install `auditd` or `libaudit`. Osquery only uses the audit features that exist in the kernel. - -A sample log entry from process_events may look something like this: - -```json -{ - "action": "added", - "columns": { - "uid": "0", - "time": "1527895541", - "pid": "30219", - "path": "/usr/bin/curl", - "auid": "1000", - "cmdline": "curl google.com", - "ctime": "1503452096", - "cwd": "", - "egid": "0", - "euid": "0", - "gid": "0", - "parent": "" - }, - "unixTime": 1527895550, - "hostIdentifier": "vagrant", - "name": "process_events", - "numerics": false -} -``` - -To better understand how this works, let's walk through 4 configuration options. These flags can be set at the [command line](../installation/cli-flags.md) or placed into the `osquery.flags` file. - -1. `--disable_audit=false` by default this is set to `true` and prevents osquery from opening the kernel audit's netlink socket. By setting it to `false`, we are telling osquery that we want to enable auditing functionality. -2. `--audit_allow_config=true` by default this is set to `false` and prevents osquery from making changes to the audit configuration settings. These changes include adding/removing rules, setting the global enable flags, and adjusting performance and rate parameters. Unless you plan to set all of those things manually, you should leave this as true. If you are configuring audit, using a control binary, or `/etc/audit.conf`, your osquery *may* override your settings. -3. `--audit_persist=true` but default this is `true` and instructs osquery to 'regain' the audit netlink socket if another process also accesses it. However, you should do your best to ensure there will be no other program running which is attempting to access the audit netlink socket. -4. `--audit_allow_process_events=true` this flag indicates that you would like to record process events - -## Linux socket auditing using Audit - -Osquery can also be used to record network connections by enabling `socket_events`. This table uses the syscalls `bind()` and `connect()` to gather information about network connections. This table is not automatically enabled when process_events are enabled because it can introduce considerable load on the system. - -To enable socket events, use the `--audit_allow_sockets` flag. - -A sample socket_event log entry looks like this: - -```json -{ - "action": "added", - "columns": { - "time": "1527895541", - "status": "succeeded", - "remote_port": "80", - "action": "connect", - "auid": "1000", - "family": "2", - "local_address": "", - "local_port": "0", - "path": "/usr/bin/curl", - "pid": "30220", - "remote_address": "172.217.164.110" - }, - "unixTime": 1527895545, - "hostIdentifier": "vagrant", - "name": "socket_events", - "numerics": false -} -``` - -If you would like to log UNIX domain sockets use the hidden flag: `--audit_allow_unix`. This will put considerable strain on the system as many default actions use domain sockets. You will also need to explicitly select the `socket` column from the `socket_events` table. - -The `success` column has been deprecated and replaced with `status`: -| Status value | Description | -|-|-| -| failed | Definitely failed | -| succeeded | Definitely succeeded | -| in_progress | The `connect`()` syscall has been marked as "in progress" (EINPROGRESS) and osquery can't determine whether it will succeed or not. Reserved for non-blocking sockets. | -| no_client | The `accept` or `accept4` syscall returned with EAGAIN since there were not incoming connections. Reserved for non-blocking sockets. | - -The behavior of the socket_events table can be changed with the following boolean flags: - -| Flag | Description | -|-|-| -| --audit_allow_sockets | Allow the audit publisher to install socket-related rules | -| --audit_allow_unix | Allow socket events to collect domain sockets | -| --audit_allow_failed_socket_events | Include rows for socket events that have failed | -| --audit_allow_accept_socket_events | Include rows for accept socket events | -| --audit_allow_null_accept_socket_events | Allow non-blocking accept() syscalls that returned EAGAIN/EWOULDBLOCK | - -## Troubleshooting Audit-based process and socket auditing on Linux - -There are a few different methods to ensure you have configured auditing correctly. - -1. Ensure you are supplied all of the necessary flags mentioned above in either a command-line argument or in your flagfile. -2. Verify `auditd` is not running, if it is installed on the system. -3. Run `auditctl -s` if the binary is present on your system and verify that `enable` is not set to zero and the `pid` corresponds to a process for osquery -4. Verify that your osquery configuration has a query to `SELECT` from the process_events and/or socket_events tables -5. You may also run auditing using osqueryi **as root**: - -```sh -osqueryi --audit_allow_config=true --audit_allow_sockets=true --audit_persist=true --disable_audit=false --events_expiry=1 --events_max=50000 --logger_plugin=filesystem --disable_events=false -``` - -If you would like to debug the raw audit events as `osqueryd` sees them, use the hidden flag `--audit_debug`. This will print all of the RAW audit lines to osquery's `stdout`. - -> NOTICE: Linux systems running `journald` will collect logging data originating from the kernel audit subsystem (something that osquery enables) from several sources, including audit records. To avoid performance problems on busy boxes (specially when osquery event tables are enabled), it is recommended to mask audit logs from entering the journal with the following command `systemctl mask --now systemd-journald-audit.socket`. - -### Avoid throttling, losing events and interpreting Audit publisher throttling messages - -If osquery is CPU constrained and is processing a high enough stream of events, you may receive this warning message: -`The Audit publisher has throttled reading records from Netlink for seconds. Some events may have been lost.`. - -This message can only appear at most every minute and it indicates that the Audit publisher had to slow down reading records from the Netlink socket for the reported duration, since the previous throttling message. -This happens when osquery is not processing records fast enough to prevent its internal buffers growing too much and consuming too much memory. - -Throttling may cause loss of events, since the Audit subsystem backlog buffer could fill up; if that happens the kernel will be forced to drop some of them. -You can check if this is happening looking at the `lost` field via `auditctl -s`. - -Throttling currently starts when more than 4096 records have been read and are still in the queue to be processed by osquery; this is a number of records -which can support high spikes of events, and is a limit for osquery to avoid consuming memory indefinitely. -Keep in mind that if the high rate of events continues, even with throttling happening, you might still have to increase your default [watchdog memory limit](../installation/cli-flags.md#daemon-control-flags) or reduce the interval of the scheduled query on the evented table, due to the amount of rows that it will have to generate at once. - -There's also a second throttling point in the Audit publisher pipeline, which exists after the records have been read from the Netlink socket and are then parsed into a more computer friendly format. -When throttling happens here, another message will be logged which is: -`The Audit publisher has throttled record processing for seconds. This may cause further throttling and loss of events.`. - -This message exists mostly for debugging purposes and will only appear if `--verbose` is active, because this doesn't necessarily cause loss of events: a bottleneck in this point of the pipeline will have to cause throttling in the Netlink socket reading side, before possibly causing loss of events. -So as long as no throttling is happening on the reading side, no loss of events should happen due to this. - -To avoid throttling there isn't much to be done beyond reducing constraints on the CPU or in general have osquery process less events. - -To attempt avoiding losing events, first of all we should ensure that throttling happens as few times as possible. Then when can try to increase the backlog buffer that the Audit subsystem is using via the `--audit_backlog_limit` flag, to attempt to support bigger/slightly longer events spikes. -Keep in mind that increasing this will increase the amount of memory used by the Audit subsystem and that this memory is not allocated by osquery, so it won't be accounted for by the watchdog. - -## User event auditing with Audit - -On Linux, a companion table called `user_events` is included that provides several authentication-based events. If you are enabling process auditing it should be trivial to also include this table. - -## Linux process and socket auditing using BPF - -When osquery is running on a recent kernel (>= 4.18), the BPF eventing framework can be used. This event publisher needs to monitor for more system calls to reach feature parity with the Audit-based tables. For this reason, enabling BPF will also enable both the `bpf_process_events` and `bpf_socket_events` tables. - -In order to start the publisher and enable the subscribers, the following flags must be passed: `--disable_events=false --enable_bpf_events=true`. The `--verbose` flag can also be extremely useful when setting up the configuration for the first time, since it emit more debug information when something fails. - -The BPF framework will make use of a perf event array and several per-cpu maps in order to receive events and correctly capture strings and buffers. These structures can be configured using the following command line flags: - -- **bpf_perf_event_array_exp**: size of the perf event array, as a power of two -- **bpf_buffer_storage_size**: how many slots of 4096 bytes should be available in each memory pool - -Memory usage depends on both: - - 1. How many processors are currently online - 2. How many processors can be added by hotswapping - -The BPF event publisher uses 6 memory pools, grouping system calls in order to evenly distribute memory usage. Not counting the internal maps used to merge sys_enter/sys_exit events (the size for these maps is rather small), memory usage can be easily estimated with the following formula: - -```cpp -buffer_storage_bytes = memory_pool_count * (bpf_buffer_storage_size * 4096) * possible_cpu_count -``` - -```cpp -perf_bytes = (2 ^ bpf_perf_event_array_exp) * online_cpu_count -``` - -The cpu count numbers can be read from the `/sys` folder: - -```text -possible_cpu_count: /sys/devices/system/cpu/possible -online_cpu_count: /sys/devices/system/cpu/online -``` - -VMware Fusion (and possibly other systems as well) supports CPU hotswapping, raising the `possible_cpu_count` to 128. This causes a huge increase in memory usage, and it is for this reason that the default settings are rather low. - -This problem can be easily fixed by disabling hotswapping. This setting is unfortunately not available through the user interface, so it needs to be changed directly in the .vmx file (`vcpu.hotadd=FALSE`). - -## macOS process & socket auditing - -### Auditing processes with OpenBSM - -To enable OpenBSM-based process auditing in osquery, set the following command-line flags: - -- `--disable_audit=false` -- `--disable_events=false` -- `--audit_allow_config` - -**Note:**: macOS systems 10.15 and earlier ship with the OpenBSM subsystem enabled, but the default settings do not audit process execution or the root user. The osquery command-line flag `--audit_allow_config` will make run-time configuration changes to your system audit to enable these features. This is all you need to get up and running. - -Alternatively, instead of using the `--audit_allow_config` flag, you may edit the `audit_control` file in `/etc/security/` for more granular/nuanced needs. This is optional and considered an "advanced configuration". An example configuration is provided below, but the important flags are: `ex`, `pc`, `argv`, and `arge`. The `ex` flag will log `exec` events while `pc` logs `exec`, `fork`, and `exit`. If you don't need `fork` and `exit` you may leave that flag out however in the future, getting parent pid may require `fork`. If you care about getting the arguments and environment variables you also need `argv` and `arge`. More about these flags can be found [here](https://www.freebsd.org/cgi/man.cgi?apropos=0&sektion=5&query=audit_control&manpath=FreeBSD+7.0-current&format=html). Note that it might require a reboot of the system for these new flags to take effect. `audit -s` should restart the system but your mileage may vary. - -**Note:** Prior to macOS 10.15, OpenBSM was the primary source of real-time audit events. Since macOS 10.15, EndpointSecurity has been available as a newer alternative [and eventual replacement to the now-deprecated OpenBSM](https://developer.apple.com/videos/play/wwdc2020/10159/). However, with osquery, you can collect events from either of these sources. - -### Auditing processes with EndpointSecurity - -To enable EndpointSecurity in osquery, set `--disable_endpointsecurity=false` in the configuration. - -EndpointSecurity is already enabled in the OS on all macOS hosts beginning with macOS 10.15, and needs no special configuration. There are however some additional steps to permit osquery to collect events. - -For osquery to capture events in its `es_process_events` table, it must have the Full Disk Access (FDA) permission enabled in macOS Privacy & Security settings. Without this permission, osquery will run as normal, but the table will always be empty. **Note:** If osquery is already running without the permission, it must be restarted after you have granted the permission. - -**If osquery is not granted the FDA permission, it will not prompt the user to grant it.** It will just issue a warning (when running with `--verbose`), and the `es_process_events` table will simply be empty when queried. - -#### Full Disk Access - -The FDA permission (or lack thereof) is inherited from `Terminal.app` when running osquery interactively, but is *not* inherited from `launchctl` when running as a service (including when started using the `osqueryctl` helper script). - -| Parent Process | Steps Taken Before Launching osquery | Querying `es_process_events` | -| -------- | -------- | -------- | -| `Terminal.app`¹ | Give Full Disk Access to `Terminal.app` only | Success | -| `Terminal.app`¹ | Give FDA only to osquery only, or do nothing | No events | -| `launchctl` | Give Full Disk Access to `/opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd`² only | Success | -| `launchctl` | Give FDA to `launchctl` only, or do nothing | No events | - -¹ : if you use a third-party terminal emulator like `iTerm.app`, grant that the permission instead of `Terminal.app`. - -² : whether running osquery via `osqueryi`, `osqueryd`, or `osqueryd -S`, the permissions will be the same in each case. - -##### Manually Granting Permissions - -To manually enable FDA permissions for an executable: open System Preferences, go to Security & Privacy, select the Privacy tab, and find Full Disk Access item on the left side. Unlock the System Preferences pane (lower left side lock icon) and enter your credentials. On the right side, clicking the `+` icon adds a new entry to the list, and you can select the executable to be granted this permission. The Finder-based file browser doesn't see paths like `/usr` by default, but you can either drag-and-drop the executable from another Finder window, or you can begin typing with `/` and enter the path explicitly. **Note:** the executable must already exist at that path before it can be manually granted the permission this way. - -##### Automatically Granting Permissions (Silent Installs) - -If a macOS host is enrolled in MDM, The FDA permissions can be granted silently by pushing a "PPPC payload" configuration profile (Privacy Preferences Policy Control) that sets the `SystemPolicyAllFiles` (*i.e.*, the FDA) key. A PPPC payload silently sets permissions, provided with an executable identifier called the `CodeRequirement`. - -To get the appropriate `CodeRequirement` identifier, use the `codesign` tool and then copy everything in the output after the `designated =>`. - -```shell -> codesign -dr - /opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd -Executable=/opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd -designated => identifier "io.osquery.agent" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "3522FA9PXF" -``` - -For your deployment, either generate an equivalent profile using your MDM dashboard (specifying `/usr/local/bin/osqueryd` as `Identifier` and `path` as the `Identifier Type` and setting `SystemPolicyAllFiles` to `Allow`), or just use the example configuration profile below, ensuring the correct value for the following fields: - -- `PayloadOrganization` (your organization) -- `CodeRequirement` (see above) - -```xml - - - - - PayloadContent - - - PayloadDescription - osqueryd - PayloadDisplayName - osqueryd - PayloadIdentifier - BDBD19F2-A35A-4AEC-9E96-3CA7E2994666 - PayloadOrganization - Trail of Bits - PayloadType - com.apple.TCC.configuration-profile-policy - PayloadUUID - 89121197-3B5F-4502-BB8C-4331261D3B8C - PayloadVersion - 1 - Services - - SystemPolicyAllFiles - - - Allowed - - CodeRequirement - identifier "io.osquery.agent" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "3522FA9PXF" - Comment - - Identifier - io.osquery.agent - IdentifierType - bundleID - - - - - - PayloadDescription - osqueryd - PayloadDisplayName - osqueryd - PayloadIdentifier - BDBD19F2-A35A-4AEC-9E96-3CA7E2994666 - PayloadOrganization - Trail of Bits - PayloadScope - System - PayloadType - Configuration - PayloadUUID - 28A8A2B7-A91E-4C26-BAEC-00F6F542742E - PayloadVersion - 1 - - -``` - -### Auditing processes and sockets with OpenBSM - -To enable OpenBSM in osquery, set `--disable_audit=false` in the configuration. - -OpenBSM is already enabled in the OS on all macOS installations, but with its default settings it doesn't audit process execution or the root user. To start process auditing on macOS, edit the `audit_control` file in `/etc/security/`. An example configuration is provided below, but the important flags are: `ex`, `pc`, `argv`, and `arge`. The `ex` flag will log `exec` events, while `pc` logs `exec`, `fork`, and `exit`. If you don't need `fork` and `exit` you may leave that flag out. However, in the future, getting the parent pid may require `fork`. If you care about getting the arguments and environment variables, you also need `argv` and `arge`. More about these flags can be found [here](https://www.freebsd.org/cgi/man.cgi?apropos=0&sektion=5&query=audit_control&manpath=FreeBSD+7.0-current&format=html). Note that it might require a reboot of the system for these new flags to take effect. `audit -s` should restart the system, but your mileage may vary. - -```text -# -# $P4: //depot/projects/trustedbsd/openbsm/etc/audit_control#8 $ -# -dir:/var/audit -flags:ex,pc,ap,aa,lo,nt -minfree:5 -naflags:no -policy:cnt,argv,arge -filesz:2M -expire-after:10M -superuser-set-sflags-mask:has_authenticated,has_console_access -superuser-clear-sflags-mask:has_authenticated,has_console_access -member-set-sflags-mask: -member-clear-sflags-mask:has_authenticated -``` - -## osquery events optimization - -This section provides a brief overview of common and recommended -optimizations for event-based tables. These optimizations also apply -to the FIM events. - -1. `--events_optimize=true` apply optimizations when `SELECT`ing from events-based tables, enabled by default. -2. `--events_expiry` the lifetime of buffered events in seconds with a default value of 86000. -3. `--events_max` the maximum number of events to store in the buffer before expiring them with a default value of 1000. - -The goal of optimizations are to protect the running process and system from impacting performance. By default these are all enabled, which is good for configuration and performance, but may introduce inconsistencies on highly-stressed systems using process auditing. - -Optimizations work best when `SELECT`ing often from event-based tables. Otherwise the events are in a buffered state. When an event-based table is selected within the daemon, the backing storage maintaining event data is cleared according to the `--event_expiry` lifetime. Setting this value to `1` will auto-clear events whenever a `SELECT` is performed against the table, reducing all impact of the buffer. diff --git a/docs/wiki/deployment/remote.md b/docs/wiki/deployment/remote.md deleted file mode 100644 index 6858e6a8a28..00000000000 --- a/docs/wiki/deployment/remote.md +++ /dev/null @@ -1,354 +0,0 @@ -# Remotely interfacing osquery - -osquery's remote configuration and logger plugins are completely optional. The default built-in plugins receive and report via `https://` URI endpoints. osquery provides somewhat flexible node (the machine running osquery) authentication and identification though an 'enrollment' concept. - -The remote settings and plugins are mostly provided as examples. It is best to write custom plugins that implement specific web services or integrations. The remote settings uses a lot of additional [CLI-flags](../installation/cli-flags.md) for configuring the osquery clients, they are mostly organized under the **Remote Settings** heading. - -## Remote authentication - -The most important differentiator to the **filesystem** suite of plugins is an authentication (and enrollment) step. Machines running `osqueryd` processes are called **nodes** and must authenticate to the remote server for every config retrieval and log submission request. - -The initial step is called an "enroll step" and in the case of **tls** plugins, uses an implicit *enroll* plugin, also called **tls**. If you enable either config or logger **tls** plugins the enrollment plugin will turn on automatically. Enrollment provides an initial secret to the remote server in order to negotiate a private node secret used for future identification. The process is simple: - -1. Configure a target `--tls_hostname`, `--enroll_tls_endpoint`. -2. Configure a proxy `--proxy_hostname` (Optional Step). -3. Place your server's root certificate authority's PEM-encoded certificate into a file, for example `/path/to/server-root.pem` and configure the client to pin to these roots: `--tls_server_certs=`. -4. Submit an `--enroll_secret_path`, an `--enroll_secret_env`, or use TLS-client authentication, to the enroll endpoint. -5. Receive a **node_key** and store within the node's persistent storage (RocksDB). -6. Make config/logger requests while providing **node_key** as identification/authentication. - -The validity of a **node_key** is determined and implemented in the TLS server. The node will request the key during an initial enroll step then post the key during subsequent requests for config or logging. - -**Note:** `--proxy_hostname` is used to communicate via proxy server. - -### Simple shared secret enrollment - -A deployment key, called an enrollment shared secret, is the simplest **tls** plugin enrollment authentication method. A protected shared secret is written to disk and osquery reads then posts the content to `--enroll_tls_endpoint` once during enrollment. The TLS server may implement an enrollment request approval process that requires manual intervention/approval for each new enrollment request. - -After enrollment, a node maintains the response **node_key** for authenticated requests to config and logger TLS endpoints. - -The shared secret can be stored in a plain-text file which is specified at runtime with the flag `--enroll_secret_path=/path/to/file.ext`. -The shared secret can alternatively be kept in an environment variable which is specified with the flag `--enroll_secret_env=NAME_OF_VARIABLE`. - -### TLS client-auth enrollment - -If the **node** machines have a deployed TLS client certificate and key, they should include those paths using `--tls_client_cert` and `--tls_client_key`. The TLS server may implement an enroll process to supply nodes with identifying **node_key**s or return blank keys during enrollment and require TLS client authentication for every endpoint request. - -If using TLS client authentication the enrollment step can be skipped entirely. Note that it is NOT skipped automatically. If your service does not need/implement enrollment include `--disable_enrollment` in the osquery configuration. - -## Remote server API - -The most basic TLS-based server should implement 3 HTTP POST endpoints. This API is a simple reference and should be built upon using custom plugins based on the included **tls** plugin suite. Although this API is basic, it is functional using the built-in plugins. - -**Enrollment** request POST body: - -```json -{ - "enroll_secret": "...", // Optional. - "host_identifier": "..." // Determined by the --host_identifier flag - "host_details": { // A dictionary of keys mapping to helpful osquery tables. - "os_version": {}, - "osquery_info": {}, - "system_info": {}, - "platform_info": {} - } -} -``` - -**Enrollment** response POST body: - -```json -{ - "node_key": "...", // Optionally blank - "node_invalid": false // Optional, return true to indicate failure. -} -``` - -## Remote configuration - -**Configuration** request POST body: - -```json -{ - "node_key": "..." // Optionally blank -} -``` - -Configuration responses should be exactly the same JSON/format as read by the **filesystem** config plugin. There is no concept of multiple configuration sources with the provided **tls** plugin. A server should amalgamate/merge several configs itself. - -**Configuration** response POST body: - -```json -{ - "schedule": { - "query_name": { - "query": "...", - "interval": 10 - } - }, - "node_invalid": false // Optional, return true to indicate re-enrollment. -} -``` - -The POSTed logger data is exactly the same as logged to disk by the **filesystem** plugin with an additional important key: `log_type`. The filesystem plugin differentiates log types by writing distinct file names. The **tls** plugin includes: `result` or `status`. Snapshot queries are `result` queries. - -## Remote logging - -**Logger** request POST body: - -```json -{ - "node_key": "...", // Optionally blank - "log_type": "result", // Either "result" or "status" - "data": [ - {...} // Each result event, or status event - ] -} -``` - -**Logger** response POST body: - -```json -{ - "node_invalid": false // Optional, return true to indicate re-enrollment. -} -``` - -## Distributed queries - -As of version 1.5.3, osquery provides support for "ad-hoc" or distributed queries. The concept of running a query outside of the schedule and having results returned immediately. Distributed queries must be explicitly enabled with a [CLI flag](../installation/cli-flags.md) or option, and you must explicitly enable and configure the distributed plugin. - -**Distributed read** request POST body: - -```json -{ - "node_key": "..." // Optionally blank -} -``` - -The read request sends the enrollment **node_key** for identification. The distributed plugin should work in concert with the enrollment plugin. - -**Distributed read** response POST body: - -```json -{ - "queries": { - "id1": "SELECT * FROM osquery_info;", - "id2": "SELECT * FROM osquery_schedule;", - "id3": "SELECT * FROM does_not_exist;" - }, - "node_invalid": false // Optional, return true to indicate re-enrollment. -} -``` - -**Distributed write** request POST body: - -```json -{ - "node_key": "...", - "queries": { - "id1": [ - {"column1": "value1", "column2": "value2"}, - {"column1": "value1", "column2": "value2"} - ], - "id2": [ - {"column1": "value1", "column2": "value2"}, - {"column1": "value1", "column2": "value2"} - ], - "id3": [] - }, - "statuses": { - "id1": 0, - "id2": 0, - "id3": 2, - } -} -``` - -As of osquery version 2.1.2, the distributed write API includes a top-level `statuses` key. These error codes correspond to SQLite error codes. Consider non-0 values to indicate query execution failures. - -**Distributed write** response POST body: - -```json -{ - "node_invalid": false // Optional, return true to indicate re-enrollment. -} -``` - -### Discovery queries on distributed queries - -Distributed queries support "discovery queries", which are similar in semantics to [discovery queries in Packs](./configuration.md#discovery-queries). -A distributed discovery query controls whether or not a distributed query will be executed on a host. -- If a distributed query has no corresponding discovery query, then it is always executed on the host. -- If a discovery query returns one or more results, then its corresponding distributed query will be executed on the host. -- If a discovery query returns no results, then its corresponding distributed query will not be executed on the host. - -Sample of a `distributed/read` response with discovery queries: -```json -{ - "queries": { - "always_execute": "select version from osquery_info;", - "windows_info": "select name from system_info;", - "darwin_time": "select day from time;" - }, - "discovery": { - "windows_info": "select * from os_version where platform='windows';", - "darwin_time": "select * from os_version where platform='darwin';" - } -} -``` - -When processing the above `distributed/read` response, osquery will: -- Always execute the `"always_execute"` query because there isn't a discovery query defined for it. -- Only execute `"windows_info"` query on Windows hosts. -- Only execute `"darwin_time"` on macOS hosts. - -Here's the corresponding osquery `distributed/write` request on a Windows host: -```json -{ - "node_key": "...", - "queries": { - "always_execute": [ - {"version": "5.5.1"} - ], - "windows_info": [ - {"name": "windows"} - ] - }, - "statuses": { - "always_execute": 0, - "windows_info": 0 - } -} -``` - -Here's the corresponding osquery `distributed/write` request on a macOS host: -```json -{ - "node_key": "...", - "queries": { - "always_execute": [ - {"version": "5.4.0"} - ], - "darwin_time": [ - {"day": "17"} - ] - }, - "statuses": { - "always_execute": 0, - "darwin_time": 0 - } -} -``` - -## Customizations - -There are several unlisted flags to further control the remote settings. These controls are helpful if using a somewhat opaque API. - -`--tls_secret_always=True` will always send the enrollment secret. This will not perform an enrollment request with every config/logger attempt but rather "also" include the secret. If this is enabled, the secret is appended as a URI variable. - -`--tls_enroll_override=enroll_secret` this allows one to rename the enrollment key request body or URI variable. - -## Remote logging buffering - -In most cases the client plugins default to "3-strikes-you're-out" when attempting to `HTTP GET`/`HTTP POST` to the configured endpoints. If a configuration cannot be retrieved the client will exit non-0 but a non-responsive logger endpoint will cause logs to buffer in RocksDB. The logging buffer size can be controlled by a [CLI flag](../installation/cli-flags.md), and if the size overflows the logs will drop. - -The TLS client does not handle HTTP errors, if the service returns a bad request or otherwise an indicator of overflowed length, the request will fail. The default max size of values combined with the maximum number of log events to send per request are sane and should not overflow default HTTP server maximum request limits. - -## Server testing - -We include a very basic example python TLS/HTTPS server: [./tools/tests/test_http_server.py](https://github.com/osquery/osquery/blob/master/tools/tests/test_http_server.py). And a set of unit/integration tests: [./osquery/remote/transports/tests/tls_transports_tests.cpp](https://github.com/osquery/osquery/blob/master/osquery/remote/transports/tests/tls_transports_tests.cpp) for a reference server implementation. - -The TLS clients built into osquery are implemented in its own `http_client`, built on top of Boost.Beast ASIO header-library and a statically linked copy of OpenSSL (does not use a system OpenSSL library, even if present). - -The osquery TLS client implementation supports only TLS protocol v1.2, and intentionally no longer supports the deprecated TLS 1.1/1.0, as of osquery v4.7.0. The following cipher suites are supported by the TLS client (see `/osquery/remote/transports/tls.h`): - -```text -ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:\ -DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:\ -!aNULL:!MD5:!CBC:!SHA -``` - -Additionally, the osquery TLS clients use a `osquery/X.Y.Z` UserAgent, where "X.Y.Z" is the client build version. - -## Example projects - -[Doorman](https://github.com/mwielgoszewski/doorman) is a project that implements the TLS remote settings API. Doorman uses "tags", which can be applied to nodes, packs, and queries, in order to dynamically generate configurations for a unique set or all nodes being managed. Doorman also supports the distributed read and write API, allowing an administrator to schedule ad-hoc queries to be run immediately or in the future. - -[Zentral](https://github.com/zentralopensource/zentral) is a Framework and Django based web server, which implements the TLS remote settings API. Zentral will dynamically generate configurations in "probes", these can contain packs and single queries. Probes process events with input filters (added, removed, "tags", etc.), optionally trigger output actions, and notifications for results returned. Scope for "probes" is available for all devices, business units, and "tagged" device groups, sharding for "probes" is available for scopes. Zentral also supports the distributed read and write API to schedule ad-hoc queries. - -## Remote settings testing - -The most basic example of a server implementing the remote settings API is the [./tools/tests/test_http_server.py](https://github.com/osquery/osquery/blob/master/tools/tests/test_http_server.py) example script. Let's start this server and have `osqueryd` exercise the API: - -```shell -$ ./tools/tests/test_http_server.py -h -usage: test_http_server.py [-h] [--tls] [--persist] [--timeout TIMEOUT] - [--cert CERT_FILE] [--key PRIVATE_KEY_FILE] - [--ca CA_FILE] [--use_enroll_secret] - [--enroll_secret SECRET_FILE] - PORT - -osquery python https server for client TLS testing. - -positional arguments: - PORT Bind to which local TCP port. - -optional arguments: - -h, --help show this help message and exit - --tls Wrap the HTTP server socket in TLS. - --persist Wrap the HTTP server socket in TLS. - --timeout TIMEOUT If not persisting, exit after a number of seconds - --cert CERT_FILE TLS server cert. - --key PRIVATE_KEY_FILE - TLS server cert private key. - --ca CA_FILE TLS server CA list for client-auth. - --use_enroll_secret Require an enrollment secret for node enrollment - --enroll_secret SECRET_FILE - File containing enrollment secret -$ ./tools/tests/test_http_server.py --tls --persist --cert ./tools/tests/configs/test_server.pem --key ./tools/tests/configs/test_server.key --ca .tools/tests/configs/test_server_ca.pem --use_enroll_secret --enroll_secret ./tools/tests/test_enroll_secret.txt 8080 --- [DEBUG] Starting TLS/HTTPS server on TCP port: 8080 -``` - -This starts a HTTPS server bound to port 8080 using some fake CA/server cert and an example shared enrollment key from the text file **./tools/tests/test_enroll_secret.txt**. If you inspect the file, see that the enrollment secret is **this_is_a_deployment_secret**. The server's enroll step will expect osquery clients to submit this secret. - -We will use an `osqueryd` client and set the required TLS settings. When enforcing TLS server authentication, note that the example server is using a toy certificate with the subject: `C=US, ST=California, O=osquery-testing, CN=localhost`: - -```shell -$ osqueryd --verbose --ephemeral --disable_database \ - --tls_hostname localhost:8080 \ - --tls_server_certs ./tools/tests/test_server_ca.pem \ - --config_plugin tls \ - --config_tls_endpoint /config \ - --logger_tls_endpoint /logger \ - --logger_plugin tls \ - --enroll_tls_endpoint /enroll \ - --enroll_secret_path ./tools/tests/test_enroll_secret.txt -``` - -There are a LOT of command line switches here! The basics notes are (1) set the TLS hostname and port, note that no `https://` is used, as well as the explicit set of certificates to expect; (2) set the plugin options for the config and logger; (3) set the plugin options for enrollment. Turning `verbose` mode on helps describe the expected behavior. - -```text -I1015 10:36:06.894544 2032685056 init.cpp:263] osquery initialized [version=1.5.3] -I1015 10:36:06.935755 2032685056 tls.cpp:68] TLSEnrollPlugin requesting a node enroll key from: https://localhost:8080/enroll -I1015 10:36:06.936123 2032685056 tls.cpp:196] TLS/HTTPS POST request to URI: https://localhost:8080/enroll -I1015 10:36:06.947465 2032685056 tls.cpp:196] TLS/HTTPS POST request to URI: https://localhost:8080/config -I1015 10:36:10.288635 3825664 scheduler.cpp:56] Executing query: SELECT * FROM processes; -I1015 10:36:10.366140 3825664 scheduler.cpp:101] Found results for query (tls_proc) for host: YOURHOSTNAME.local -I1015 10:36:11.019227 528384 tls.cpp:196] TLS/HTTPS POST request to URI: https://localhost:8080/logger -[...] -``` - -And the example TLS server will show something similar: - -```text -127.0.0.1 - - [15/Oct/2015 10:36:06] "POST /enroll HTTP/1.1" 200 - --- [DEBUG] Request: {u'enroll_secret': u'this_is_a_deployment_secret', u'host_identifier': u'YOURHOSTNAME.local'} --- [DEBUG] Replying: {u'node_key': u'this_is_a_node_secret'} -127.0.0.1 - - [15/Oct/2015 10:36:06] "POST /config HTTP/1.1" 200 - --- [DEBUG] Request: {u'node_key': u'this_is_a_node_secret'} --- [DEBUG] Replying: {u'schedule': {u'tls_proc': {u'query': u'select * from processes', u'interval': 1}}} -127.0.0.1 - - [15/Oct/2015 10:36:11] "POST /logger HTTP/1.1" 200 - --- [DEBUG] Request: {u'node_key': u'this_is_a_node_secret', u'data': [...]} -[...] -``` diff --git a/docs/wiki/deployment/syslog.md b/docs/wiki/deployment/syslog.md deleted file mode 100644 index 65292fea2b1..00000000000 --- a/docs/wiki/deployment/syslog.md +++ /dev/null @@ -1,118 +0,0 @@ -# Reading syslog with osquery - -osquery 1.7.3 introduced support for consuming and querying the macOS system log via Apple System Log (ASL). osquery 1.7.4 introduced support for the Linux syslog via **rsyslog**. This document explains how to configure and use these syslog tables. - -## macOS Syslog - -On macOS, the `asl` virtual table makes use of Apple's ASL store, querying this structured store using the routines provided in [`asl.h`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/asl_set.3.html). - -### macOS Configuration - -No configuration is required to begin using the `asl` table. Note, however, that the table is only able to query logs that are available in the ASL store. - -If your target logs are not already being sent to the ASL store by your current configuration, take a look at the [man page](https://www.unix.com/man-page/osx/5/asl.conf/) for `asl.conf`, and use the `store` action to ensure your logs of interest are available in the store. `asl.conf` is also responsible for the rotation and retention settings of the ASL store. - -The configuration for `/var/log/install.log` and `/var/log/commerce.log` is hardcoded into the Apple provided syslog binaries, and we are not aware of a way to configure ASL to send these logs to the store. - -### macOS Usage - -The `asl` table can be queried like any other osquery table. It exposes many of the columns of structured data from the ASL store, and other additional columns are made available as a JSON dictionary in the `extra` column. Use `.schema asl` in the `osqueryi` shell to see the schema. - -Basic query predicates (`<`, `<=`, `=`, `>=`, `>`) are able to be efficiently queried in the store. The `LIKE` predicate is also supported, however it must be tested after applying all other predicates and reading logs from the store. For performance reasons, it is suggested to use at least one basic predicate in a query against the `asl` table. For example, - -```sql -SELECT time, message FROM asl WHERE facility = 'authpriv' AND sender = 'sudo' AND message LIKE '%python%'; -``` - -## Linux Syslog - -On Linux, the `syslog` table queries logs forwarded over a named pipe from a properly configured `rsyslogd`. This method was chosen to support the widest range of Linux flavors (in theory, anything running at least `rsyslogd` version 5, and tested with Ubuntu 12+, CentOS 7.1+, RHEL 7.2+), and to ensure that existing syslog routines and configurations are not modified. As syslog is ingested into osquery, it is written into the backing store (RocksDB) and made available for querying. - -Alternatively you can also use **syslog-ng** to forward log messages to osquery. - -> NOTICE: the Syslog ingestion is NOT recommended for hosts functioning as syslog aggregators. We have not tested ingestion for massive-throughput or lossless setups. - -### Linux Configuration - -The `syslog` table requires additional configuration before it can be used. Append `--enable_syslog` to your command line arguments or `--flagfile` to enable osquery's `syslog` event publisher thread. - -When an osquery process that supports the `syslog` table starts up, it will attempt to create (and properly set permissions for) a named pipe for `rsyslogd` to write to. The path for this pipe is determined by the configuration flag `--syslog_pipe_path` (defaults to `/var/osquery/syslog_pipe`). If verbose logging is turned on, you should see a status message indicating whether osquery was able to successfully open the pipe for reading. - -Permissions for the pipe must at least allow `rsyslogd` to read/write, and osquery to read. For security, it is advised that the least possible privileges are enabled to allow this. - -Once the named pipe is created, `rsyslogd` must be configured to write logs to the pipe. Add the following to your **rsyslog** configuration files (usually located in `/etc/rsyslog.conf` or `/etc/rsyslog.d/`): - -#### rsyslog versions < 7 - -```t -$template OsqueryCsvFormat, "%timestamp:::date-rfc3339,csv%,%hostname:::csv%,%syslogseverity:::csv%,%syslogfacility-text:::csv%,%syslogtag:::csv%,%msg:::csv%\n" -*.* |/var/osquery/syslog_pipe;OsqueryCsvFormat -``` - -#### rsyslog versions >= 7 - -The above configuration should also work, but **rsyslog** strongly recommends using the new style configuration syntax. - -``` -template( - name="OsqueryCsvFormat" - type="string" - string="%timestamp:::date-rfc3339,csv%,%hostname:::csv%,%syslogseverity:::csv%,%syslogfacility-text:::csv%,%syslogtag:::csv%,%msg:::csv%\n" -) -*.* action(type="ompipe" Pipe="/var/osquery/syslog_pipe" template="OsqueryCsvFormat") -``` - -#### All versions - -`rsyslogd` must be restarted for the changes to take effect. On many systems, this can be achieved by `sudo service rsyslog restart`. - -> NOTICE: `rsyslogd` will only check once, at startup, whether it can write to the pipe. If `rsyslogd` cannot write to the pipe, it will not retry until restart. - -#### Other configuration - -Configuration flags control the retention of syslog logs. `--syslog_events_expiry` (default 30 days) defines how long (in seconds) to keep logs. `--syslog_events_max` (default 100,000) sets a maximum number of logs to retain (oldest logs are deleted first if this number is surpassed). - -#### Configuring syslog-ng - -Configuring osquery to receive logs from syslog-ng is no different from rsyslog, so here only the syslog-ng part is shown. Add the following to your **syslog-ng** configuration files (usually located in `/etc/syslog-ng/syslog-ng.conf` or `/etc/syslog-ng/conf.d/`): - -```t -# Reformat log messages in a format that osquery accepts -rewrite r_csv_message { - set("$MESSAGE", value("CSVMESSAGE") ); - subst("\"","\"\"", value("CSVMESSAGE"), flags(global) ); -}; - -template t_csv { -template("\"${ISODATE}\",\"${HOST}\",\"${LEVEL_NUM}\",\"${FACILITY}\",\"${PROGRAM}\",\"${CSVMESSAGE}\"\n"); - template_escape(no); -}; - -# Sends messages to osquery -destination d_osquery { - pipe("/var/osquery/syslog_pipe" template(t_csv)); -}; - -# Stores messages sent to osquery in a log file, useful for troubleshooting - destination d_osquery_copy { - file("/var/log/csv_osquery" template(t_csv)); -}; - -# Log path to send incoming messages to osquery - log { - source(s_sys); - rewrite(r_csv_message); - destination(d_osquery); - # destination(d_osquery_copy); -}; -``` - -The rewrite is needed to make sure that quotation marks are escaped. The template re-formats the messages as expected by osquery. Binaries provided by the osquery project expect syslog messages in this pipe: you might need to change the location if you compiled osquery yourself. If you want to see what messages are sent to osquery you can uncomment the `d_osquery_copy` destination in the log path. The `s_sys` source refers to your local log messages and might be different on your system (this example is from CentOS). - -### Usage - -Once configuration is complete, the `syslog` table can be queried like any other osquery table. It's schema can be viewed with `.schema syslog`. - -> NOTICE: only logs produced after this table was properly configured (and while osquery is running) will be available for querying. - -If no logs are available to query, try turning on verbose logging, and see [issue #1964](https://github.com/osquery/osquery/issues/1964) for debugging suggestions. diff --git a/docs/wiki/deployment/yara.md b/docs/wiki/deployment/yara.md deleted file mode 100644 index 956ca2057f4..00000000000 --- a/docs/wiki/deployment/yara.md +++ /dev/null @@ -1,285 +0,0 @@ -# YARA-based scanning with osquery - -YARA is a tool that allows you to find textual or binary patterns inside of files. - -There are two YARA-related tables in osquery, which serve very different purposes. The first table, called -`yara_events`, uses osquery's [Events framework](../development/pubsub-framework.md) to monitor for filesystem changes -and will execute YARA when a file change event fires. The second table, just called `yara`, is a table for performing an -on-demand YARA scan. - -In this document, "signature file" is intended to be synonymous with "YARA rule file" (plain-text files commonly -distributed with a `.yar` or `.yara` filename extension, although any extension is allowed). - -For more information about YARA, check out the [documentation](https://yara.readthedocs.io/en/stable/). - -## YARA Configuration - -The configuration for osquery is simple. Here is an example config, grouping some YARA rule files from the local -filesystem: - -```json -{ - // Description of the YARA feature. - "yara": { - "signatures": { - // Each key is an arbitrary group name to give the signatures listed - "sig_group_1": ["/Users/wxs/sigs/foo.yar", "/Users/wxs/sigs/bar.yar"], - "sig_group_2": ["/Users/wxs/sigs/baz.yar"] - }, - "file_paths": { - // Each key is a key from file_paths - // The value is a list of signature groups to run when an event fires - // These will be watched for and scanned when the event framework - // fire off an event to yara_events table - "system_binaries": ["sig_group_1"], - "tmp": ["sig_group_1", "sig_group_2"] - } - }, - - // Paths to watch for filesystem events - "file_paths": { - "system_binaries": ["/usr/bin/%", "/usr/sbin/%"], - "tmp": ["/Users/%/tmp/%%", "/tmp/%"] - } -} -``` - -The first thing to notice is the `file_paths` section, which is used to describe which paths to monitor for changes. -Each key is an arbitrary category name and the value is a list of paths. The syntax used is documented on the osquery -wildcard rules described on the [FIM](../deployment/file-integrity-monitoring.md) page. The paths, when expanded out -by osquery, are monitored for changes and processed by the -[`file_events`](https://osquery.io/schema/current/#file_events) table. - -The second thing to notice is the `yara` section, which contains the configuration to use for YARA within osquery. The -`yara` section contains two keys: `signatures` and `file_paths`. The `signatures` key contains a set of arbitrary key -names, called "signature groups." The value for each of these groups are the paths to the signature files that will be -compiled and stored within osquery. The paths to the signature files must be absolute paths (not relative paths). The -`file_paths` key maps the category name for an event described in the global `file_paths` section to a signature -grouping to use when scanning. - -For example, when a file in `/usr/bin/` and `/usr/sbin/` is changed it will be scanned with `sig_group_1`, which -consists of `foo.yar` and `bar.yar`. When a file in `/Users/%/tmp/` (recursively) is changed it will be scanned with -`sig_group_1` and `sig_group_2`, which consists of all three signature files. - -### Retrieving YARA Rules at Runtime - -The default behavior of the `yara` table is to use YARA rules specified in a file on the osquery host. However, it -might be more convenient to manage your YARA rules in one location, and have the `yara` table fetch those rules -at runtime, rather than have to update (and version-manage) a YARA rules file on every individual osquery host. Your -organization may also treat YARA rules as security-sensitive data, and you may not wish to store that data on the -filesystem of every osquery host. - -To configure osquery to allow the fetching of YARA rules at runtime, you have to set up your `yara` configuration file -with the `signature_urls` section. This will be an array that can be a mix of full URLs pointing to single Yara rule, -or a partial URLs, where the path part can be a regex which will be used to match multiple URLs and rules. -Each entry exists to later allow single or multiple URLs, provided via the `sigurl` constraint in the query. - -Since the path part of a URL string (the part after the domain) is always parsed as regex, we need to escape -the regex special characters like `.`, if we want to use them to specify a full URL. - -Below a configuration example: - -```json -"yara": { - "signature_urls": [ - "https://raw.githubusercontent.com/Yara-Rules/rules/master/cve_rules/CVE-2010-0805\\.yar", - "https://raw.githubusercontent.com/Yara-Rules/rules/master/crypto/crypto_signatures\\.yar", - "https://raw.githubusercontent.com/Yara-Rules/rules/master/malware/APT_APT3102\\.yar", - "https://raw.githubusercontent.com/Yara-Rules/rules/devel/CVE_Rules/CVE-.*" - ] -} -``` - -and a couple of queries examples: - -```sh -# This is valid -SELECT * FROM yara WHERE path="/usr/bin/ls" AND sigurl='https://raw.githubusercontent.com/Yara-Rules/rules/master/cve_rules/CVE-2010-0805.yar'; - -# This too -SELECT * FROM yara WHERE path="/usr/bin/ls" AND sigurl='https://raw.githubusercontent.com/Yara-Rules/rules/devel/CVE_Rules/CVE-2010-0805.yar'; - -# This is not allowed -SELECT * FROM yara WHERE path="/usr/bin/ls" AND sigurl='https://raw.githubusercontent.com/Yara-Rules/rules/devel/malware/APT_APT3102.yar'; - -YARA signature url https://raw.githubusercontent.com/Yara-Rules/rules/devel/malware/APT_APT3102.yar not allowed -Failed to get YARA rule url: https://raw.githubusercontent.com/Yara-Rules/rules/devel/malware/APT_APT3102.yar -Query must specify sig_group, sigfile, or sigrule for scan -``` - -YARA rule strings are omitted from output by default, to prevent disclosure in osquery's results and logs. To include -the YARA rules in the `sigrule` column, set the `enable_yara_string` flag to `true`. - -#### Authentication - -Request authentication can be enabled with the `--yara_sigurl_authenticate` flag. When enabled, instead of a `GET` request osquery will send a `POST` request with a JSON body containing the node key. The receiving server can then authenticate the request using the node key before responding with the yara rules. All other behavior remains unchanged. - -#### Notes - -- Retrieved YARA rules are retrieved only once and then cached; the cached copy is used until it is stale as specified - by the HTTP `Last-Modified` header in the server's response. -- The osquery agent always validates the HTTPS server certificate of the server providing the YARA signatures, but - currently has no support for client authentication. YARA rule files must be accessible without authentication. - -## Continuous monitoring using the yara_events table - -Using the configuration above you can see it in action. While osquery is running, we execute `touch /Users/wxs/tmp/foo` -in another terminal. Here are the relevant queries to show what was detected: - -```sql -osquery> SELECT * FROM file_events; -+--------------------+----------+------------+---------+----------------+----------------------------------+------------------------------------------+------------------------------------------------------------------+ -| target_path | category | time | action | transaction_id | md5 | sha1 | sha256 | -+--------------------+----------+------------+---------+----------------+----------------------------------+------------------------------------------+------------------------------------------------------------------+ -| /Users/wxs/tmp/foo | tmp | 1430078285 | CREATED | 33859499 | d41d8cd98f00b204e9800998ecf8427e | da39a3ee5e6b4b0d3255bfef95601890afd80709 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 | -+--------------------+----------+------------+---------+----------------+----------------------------------+------------------------------------------+------------------------------------------------------------------+ -osquery> SELECT * FROM yara_events; -+--------------------+----------+------------+---------+----------------+-------------+-------+ -| target_path | category | time | action | transaction_id | matches | count | -+--------------------+----------+------------+---------+----------------+-------------+-------+ -| /Users/wxs/tmp/foo | tmp | 1430078285 | CREATED | 33859499 | always_true | 1 | -+--------------------+----------+------------+---------+----------------+-------------+-------+ -osquery> -``` - -The [`file_events`](https://osquery.io/schema/current/#file_events) table recorded that a file named -`/Users/wxs/tmp/foo` was created with the corresponding hashes and a timestamp. - -The [`yara_events`](https://osquery.io/schema/current/#yara_events) table recorded that 1 matching rule (`always_true`) -was found when the file was created. In this example every file will always have at least one match because we are -using a rule which always evaluates to true. In the next example we'll issue the same command to create a file in a -monitored directory but have removed the `always_true` rule from our signature files. - -```sql -osquery> SELECT * FROM yara_events; -+--------------------+----------+------------+---------+----------------+-------------+-------+ -| target_path | category | time | action | transaction_id | matches | count | -+--------------------+----------+------------+---------+----------------+-------------+-------+ -| /Users/wxs/tmp/foo | tmp | 1430078285 | CREATED | 33859499 | always_true | 1 | -| /Users/wxs/tmp/foo | tmp | 1430078524 | CREATED | 33860795 | | 0 | -+--------------------+----------+------------+---------+----------------+-------------+-------+ -``` - -As you can see, even though no matches were found, a row is still created and stored. - -## On-demand YARA scanning - -The [`yara`](https://osquery.io/schema/current/#yara) table is used for on-demand scanning. With this table -you can arbitrarily YARA scan any available file on the filesystem with any available signature files or -signature group from the configuration. In order to scan, the table must be given a constraint which says -where to scan and what to scan with. - -In order to determine where to scan, the `path` constraint must be a full path to a single file, or a -`path LIKE` with a wildcard pattern. There is no expansion or recursion with this constraint. Note that -you must use `LIKE` if you want to use a wildcard pattern. - -Once the `where` is out of the way, you must specify the "what" part. This is done through either the -`sigfile` or `sig_group` constraints. The `sigfile` constraint must be an absolute path to a signature -file on the filesystem, not a relative path. The signature file will be compiled only for the execution -of this one query and removed afterwards. The `sig_group` constraint must consist of a named signature -grouping from your configuration file. - -Here are some examples of the `yara` table in action: - -```sql -osquery> SELECT * FROM yara WHERE path="/bin/ls" AND sig_group="sig_group_1"; -+---------+-------------+-------+-------------+---------+---------+---------+ -| path | matches | count | sig_group | sigfile | strings | tags | -+---------+-------------+-------+-------------+---------+---------+---------+ -| /bin/ls | always_true | 1 | sig_group_1 | | | | -+---------+-------------+-------+-------------+---------+---------+---------+ - -osquery> SELECT * FROM yara WHERE path="/bin/ls" AND sig_group="sig_group_2"; -+---------+---------+-------+-------------+---------+---------+---------+ -| path | matches | count | sig_group | sigfile | strings | tags | -+---------+---------+-------+-------------+---------+---------+---------+ -| /bin/ls | | 0 | sig_group_2 | | | | -+---------+---------+-------+-------------+---------+---------+---------+ -``` - -As you can see in these examples, we scan the same file with two different signature groups and get different results. - -```sql -osquery> SELECT * FROM yara WHERE path LIKE "/bin/%sh" AND sig_group="sig_group_1"; -+-----------+-------------+-------+-------------+---------+----------+----------+ -| path | matches | count | sig_group | sigfile | strings | tags | -+-----------+-------------+-------+-------------+---------+----------+----------+ -| /bin/bash | always_true | 1 | sig_group_1 | | | | -| /bin/csh | always_true | 1 | sig_group_1 | | | | -| /bin/ksh | always_true | 1 | sig_group_1 | | | | -| /bin/sh | always_true | 1 | sig_group_1 | | | | -| /bin/tcsh | always_true | 1 | sig_group_1 | | | | -| /bin/zsh | always_true | 1 | sig_group_1 | | | | -+-----------+-------------+-------+-------------+---------+----------+----------+ -``` - -The above illustrates using the `path LIKE` constraint to scan `/bin/%sh` with a signature group. - -```sql -osquery> select * from yara where path LIKE 'C:\tmp\%' and sigfile = "C:\tmp\test.yar.txt"; -+------------------------------+-------------+-------+-----------+---------------------+-----------------+------+ -| path | matches | count | sig_group | sigfile | strings | tags | -+------------------------------+-------------+-------+-----------+---------------------+-----------------+------+ -| C:\tmp\New Text Document.txt | TextExample | 1 | | C:\tmp\test.yar.txt | $text_string:0 | | -| C:\tmp\test.yar.txt | TextExample | 1 | | C:\tmp\test.yar.txt | $text_string:35 | | -+------------------------------+-------------+-------+-----------+---------------------+-----------------+------+ -``` - -The above is an example of using an absolute path for `sigfile` combined with `path LIKE`. Because the sigfile -contains the string its rule is searching for, it has also returned itself as a result. - -**Tip:** you can specify `AND count > 0` in your query to return only positive YARA results. - -### Inline YARA rules with sigrule - -Above, we documented how to query the `yara` table using YARA signatures specified in a local file or retrieved from a -remote host. YARA rules can also be provided inline with the query, using the hidden column `sigrule` as a constraint. - -YARA rules take the form of `'rule rulename { condition: [whatever] }'` and follow the -[standard YARA rule syntax](https://yara.readthedocs.io/en/stable/writingrules.html). - -For example: - -```sql -osquery> select * from yara where path = '/etc/passwd' and sigrule = 'rule always_true { condition: true }'; -``` - -YARA rules don't have a line-terminating character. To enter a multi-line YARA rule, use newlines. This -even works in `osqueryi`: - -```sql -osquery> select * from yara where path LIKE 'C:\tmp\%' and sigrule = 'rule hello_world { - ...> strings: - ...> $a = "Hello world" - ...> condition: $a - ...> }'; -+------------------------------+-------------+-------+-----------+---------+---------+------+ -| path | matches | count | sig_group | sigfile | strings | tags | -+------------------------------+-------------+-------+-----------+---------+---------+------+ -| C:\tmp\New Text Document.txt | hello_world | 1 | | | | | -+------------------------------+-------------+-------+-----------+---------+---------+------+ -``` - -**Note:** when entering a `sigrule` inline, be careful to avoid double-quoting the rule and then also a string -variable within the rule, as the second `"` will terminate the rule and cause a `syntax error`. In the example -above, the `sigrule` string has been single-quoted so the enclosed variable `"Hello world"` can be double-quoted. - -Because allowing arbitrary YARA rules would also make it possible to retrieve arbitrary file data in the `strings` -column, as a protection, the `strings` column will default to returning empty unless you also set the hidden flag -`enable_yara_string` to `true` (its default is `false`). - -## Troubleshooting - -### YARA compile error - -Before a YARA scan is performed, the YARA engine compiles the rule(s). An error here indicates there is probably an -issue with the YARA rule(s), but, the first thing to check is whether the same rule can be run with the YARA -command-line utility: `yara64.exe myYaraRule.yar fileToScan.foo`. You will be able to get more helpful messages -about the compile error. If, however, this actually works as intended, then perhaps you've found a bug! Please let -the osquery team know, on Slack or by opening an issue on GitHub. - -### Error loading YARA rules: 8 - -At this time, osquery only supports loading _plaintext_ YARA rules/signatures, which it compiles itself at runtime. If -these rules have already been compiled into their binary form (_e.g._ with the `yarac` CLI tool), osquery will -generate an error trying to load the rules. diff --git a/docs/wiki/development/building.md b/docs/wiki/development/building.md deleted file mode 100644 index afae088556c..00000000000 --- a/docs/wiki/development/building.md +++ /dev/null @@ -1,483 +0,0 @@ -# Building osquery from source - -osquery supports many flavors of Linux, macOS, and Windows. - -While osquery runs on a large number of operating systems, we only provide build instructions for a select few. - -The supported compilers are: the osquery toolchain (LLVM/Clang 9.0.1) on Linux, MSVC v142 on Windows, and AppleClang from Xcode Command Line Tools 14.x. - -## Prerequisites - -Git (>= 2.14.0), CMake (>= 3.21.4), Python 3 are required to build. The rest of the dependencies are downloaded by CMake. - -The default build type is `RelWithDebInfo` (optimizations active + debug symbols) and can be changed in the CMake configure phase by setting the `CMAKE_BUILD_TYPE` flag to `Release` or `Debug`. - -The build type is chosen when building on Windows, through the `--config` option, not during the configure phase. - -Note: the recommended system memory for building osquery is at least 8GB, or Clang may crash during the compilation of third-party dependencies. - -## Linux (Ubuntu 18) - -The initial directory is assumed to be `/home/`. - -```bash -# Install the prerequisites -sudo apt install --no-install-recommends git python3 bison flex make - -# Optional: install python tests prerequisites -sudo apt install --no-install-recommends python3-pip python3-setuptools python3-psutil python3-six python3-wheel -pip3 install timeout_decorator thrift==0.11.0 osquery pexpect==3.3 - -# Optional: install RPM packaging prerequisites -sudo apt install --no-install-recommends rpm binutils - -# Download and install the osquery toolchain -export ARCH=$(uname -m) # There is toolchain support for x86_64 and aarch64. -wget https://github.com/osquery/osquery-toolchain/releases/download/1.1.0/osquery-toolchain-1.1.0-${ARCH}.tar.xz -sudo tar xvf osquery-toolchain-1.1.0-${ARCH}.tar.xz -C /usr/local - -# Download and install a newer CMake. -# Afterward, verify that `/usr/local/bin` is in the `PATH` and comes before `/usr/bin`. -wget https://cmake.org/files/v3.21/cmake-3.21.4-linux-${ARCH}.tar.gz -sudo tar xvf cmake-3.21.4-linux-${ARCH}.tar.gz -C /usr/local --strip 1 - -# Download source -git clone https://github.com/osquery/osquery -cd osquery - -# Build osquery -mkdir build; cd build -cmake -DOSQUERY_TOOLCHAIN_SYSROOT=/usr/local/osquery-toolchain .. -cmake --build . -j10 # where 10 is the number of parallel build jobs -``` - -## macOS - -The current build of osquery supports deployment to the same set of macOS versions (macOS 10.15 and newer). _Building_ -osquery from source on macOS now requires macOS 12 Monterey alongwith Xcode 14 or newer. - -The initial directory is assumed to be `/Users/` - -### Step 1: Install macOS prerequisites - -Please ensure [Homebrew](https://brew.sh/) has been installed, and install a _full copy_ of Xcode 14 or newer (not just the Xcode command-line tools, although you need to install those too — launch Xcode after installing or upgrading, and complete its installation of the "additional components" when prompted). - -Then do the following. - -```bash -# Install prerequisites -xcode-select --install -brew install ccache git git-lfs cmake python clang-format flex bison - -# Optional: install python tests prerequisites -pip3 install --user setuptools pexpect==3.3 psutil timeout_decorator six thrift==0.11.0 osquery -``` - -### Step 2: Download and build source on macOS - -In the following example, the use of the additional CMake argument `-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15` specifies macOS 10.15 as the minimum compatible macOS version to which you can deploy osquery (this affects the version of the macOS SDK used at build time). - -```bash -# Download source -git clone https://github.com/osquery/osquery -cd osquery - -# Configure -mkdir build; cd build -cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .. - -# Build -cmake --build . -j $(sysctl -n hw.ncpu) -``` - -### Features Requiring Special Build Entitlements - -Certain functionality on macOS requires an entitled and code-signed executable. By default, macOS builds from source will be _unsigned_ and these particular features will be disabled at runtime. - -Specifically, the `es_process_events` and `es_process_file_events` tables makes use of the EndpointSecurity APIs, which require osquery to be code-signed with a certificate possessing the EndpointSecurity Client entitlement. If unsigned, osquery will still run as normal, but `es_process_events` and `es_process_file_events` will be disabled. - -Organizations wishing to code-sign osquery themselves will need their Apple Developer team _account owner_ to manually request and obtain the EndpointSecurity Client entitlement from Apple, for their organization's code-signing certificate. - -#### Testing EndpointSecurity Without Entitlements from Apple - -Developers can also disable SIP in a development VM (disabling SIP decreases your system's security and is _not_ recommended except on a VM dedicated to building osquery) and use ad-hoc code-signing to test `es_process_events` and `es_process_file_events` without pursuing the entitlement. - -If using VMware Fusion 12, for example, you can reach Recovery Mode by going to Virtual Machine, Settings, Startup Disk. There, hold the Option key, and click `Restart to Firmware...`. Restarting the VM will now enter the VMware virtual EFI shell. From here, select `Enter Setup`, `Boot from a File`, and then arrow down to the Recovery partition. Hit return to find and select the `boot.efi`, and hit return again to enter Recovery Mode. From a Terminal in Recovery Mode, you can [disable SIP](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/ConfiguringSystemIntegrityProtection/ConfiguringSystemIntegrityProtection.html) and then reboot to macOS. - -To perform the ad-hoc signing, first create an entitlements file (`entitlements.plist`) with the following contents: - -``` - - - - - com.apple.developer.endpoint-security.client - - - -``` - -Then sign the osquery binary (update binary and entitlement file paths as appropriate). This codesign command must be run on the VM where the testing is taking place: - -``` -codesign -s - -f --entitlements ./entitlements.plist ./osqueryd -``` - -## Windows 10 - -The initial directory is assumed to be `C:\` - -**Note:** Windows and `msbuild` have traditionally had a 260 character max path limit. If you encounter problems with the long paths generated by CMake, we recommend building in a shorter path, like `C:\Projects\osquery`. If that still isn't working, since Windows 10 since Version 1607 there is a registry key that can enable longer paths. From an elevated command prompt: - -`REG ADD HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f` - -After changing that key, reboot your build machine and re-attempt the build. - -### Step 1: Install Windows prerequisites - -Note: It may be easier to install these prerequisites using [Chocolatey](https://chocolatey.org/). - -- [CMake](https://cmake.org/) (>= 3.21.4): the MSI installer is recommended. During installation, select the option to add it to the system `PATH` for all users. If there is any older version of CMake installed (e.g., using Chocolatey), uninstall that version first! Do not install CMake using the Visual Studio Installer, because it contains an older version than required. -- Visual Studio 2019 (2 options) - 1. [Visual Studio 2019 Build Tools Installer](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (without Visual Studio): In the installer choose the "C++ build tools" workload, then on the right, under "Optional", select "MSVC v142 - VS 2019 C++", "Windows 10 SDK", "C++ ATL tools", and "C++ Clang tools for Windows". - 2. [Visual Studio 2019 Community Installer](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=16): In the installer choose the "Desktop development with C++" workload, then on the right, under "Optional", select "MSVC v142 - VS 2019 C++", "Windows 10 SDK", and "C++ Clang tools for Windows". -- [Git for Windows](https://github.com/git-for-windows/git/releases/latest): Select "checkout as-is, commit as-is". Later check "Enable symbolic links" support. -- [Python 3](https://www.python.org/downloads/windows/), specifically the 64-bit version. -- [Wix Toolset](https://wixtoolset.org/releases/) -- [Strawberry Perl](https://strawberryperl.com/) for the OpenSSL formula. It is recommended to install it to the default destination path. -- [7-Zip](https://www.7-zip.org/) if building the Chocolatey package. - -### Optional: Install Python tests prerequisites - -Python 3 is assumed to be installed in `C:\Program Files\Python37` - -```PowerShell -# Using a PowerShell console -& 'C:\Program Files\Python37\python.exe' -m pip install setuptools psutil timeout_decorator thrift==0.11.0 osquery pywin32 -``` - -The use of an Administrator shell is recommended because the build process creates symbolic links. These [require a special permission to create on Windows](https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links), and the simplest solution is to build as Administrator. If you wish, you can instead assign just the `SeCreateSymbolicLinkPrivilege` permission to the user account. The setting can be found in "Local Security Policy" under Security Settings, Local Policies, User Rights Assignment. The user then has to log out and back in for the policy change to apply. - -### Step 2: Download and build source on Windows - -```PowerShell -# Using a PowerShell console as Administrator (see note, below) -# Download source -git clone https://github.com/osquery/osquery -cd osquery - -# Configure -mkdir build; cd build -cmake -G "Visual Studio 16 2019" -A x64 .. - -# Build -cmake --build . --config RelWithDebInfo -j10 # Number of projects to build in parallel -``` - -## Testing - -To build with tests active, add `-DOSQUERY_BUILD_TESTS=ON` to the osquery configure phase, then build the project. CTest will be used to run the tests and give a report. - -### Run tests on Windows - -To run the tests and get just a summary report: - -```PowerShell -cmake --build . --config --target run_tests -``` - -To get more information when a test fails using PowerShell: - -```PowerShell -$Env:CTEST_OUTPUT_ON_FAILURE=1 -cmake --build . --config --target run_tests -``` - -To run a single test, in verbose mode: - -```PowerShell -ctest -R -C -V -``` - -A "single" test case often still involves dozens or hundreds of unit tests. To run a single _unit test_, you can pass the [`GTEST_FILTER`](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#running-a-subset-of-the-tests) variable, for example: - -```PowerShell -$Env:GTEST_FILTER='windowsEventLog.*' -ctest -R tests_integration_tables-test -C RelWithDebInfo -V #runs just the windowsEventLog under the integration tables tests -``` - -### Run tests on Linux and macOS - -To run the tests and get just a summary report: - -```bash -cmake --build . --target test -``` - -To get more information when a test fails: - -```bash -CTEST_OUTPUT_ON_FAILURE=1 cmake --build . --target test -``` - -To run a single test, in verbose mode: - -```bash -ctest -R -V -``` - -A "single" test case often still involves dozens or hundreds of unit tests. To run a single _unit test_, you can pass the [`GTEST_FILTER`](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#running-a-subset-of-the-tests) variable, for example: - -```bash -GTEST_FILTER=sharedMemory.* ctest -R -V #runs just the sharedMemory tests under the set. -``` - -## Formatting the code - -Osquery uses `clang-format` to format its code, but it's not run on the whole project or files each time; it's run only on the modified lines instead, -using custom scripts. - -On Linux the `clang-format` executable is shipped along with the osquery toolchain, and it is the recommended way to run it. -For the other platforms please refer to their **Install the prerequisites** section if you haven't already. - -On Windows remember to update the PATH environment variable with the `clang-format` root folder, so that the scripts can find it. -You should be able to find `clang-format` folder in the path where you installed either the Build Tools or the full Visual Studio, and from there `VC\Tools\Llvm\bin`. - -To verify that all the commits that are present on the branch but not on master are properly formatted, run the following command from the build folder: - -```bash -cmake --build . --target format_check -``` - -This is the same command the CI runs to verify formatting. - -If the code is not formatted, you can do so with the following command run from the build folder, -but the code has to be put in the stage area first if it was already committed: - -```bash -cmake --build . --target format -``` - -To avoid having to move the committed files to the stage area and back each time, remember to format the code before committing. - -## Running cppcheck - -The `cppcheck` tool runs some static analysis checks on the C++ code to detect possible bugs or undefined behaviors. - -1. Install the cppcheck prerequisite: - - On Linux: `apt install cppcheck` - - On macOS: `brew install cppcheck` - - On Windows: download and run [the cppcheck MSI installer](https://github.com/danmar/cppcheck/releases). -2. Build the `cppcheck` target: `cmake --build . --target cppcheck` - -## Running clang-tidy (Linux only) - -The `clang-tidy` executable is shipped along with the osquery toolchain, and it is the recommended way to run it. It is however possible to use the system one, provided it's accessible from the PATH environment variable. - -1. When configuring, pass `-DOSQUERY_ENABLE_CLANG_TIDY=ON` to CMake -2. Configure the checks: `-DOSQUERY_CLANG_TIDY_CHECKS=check1,check2` **(optional)** -3. Build osquery - -By default, the following checks are enabled: - -1. cert-\* -2. cppcoreguidelines-\* -3. performance-\* -4. portability-\* -5. readability-\* -6. modernize-\* -7. bugprone-\* - -## Using Vagrant - -If you are familiar with Vagrant, there is a helpful configuration in the root directory for testing osquery. - -### AWS-EC2-Backed Vagrant Targets - -The osquery vagrant infrastructure supports leveraging AWS EC2 to run virtual machines. -This capability is provided by the [vagrant-aws](https://github.com/mitchellh/vagrant-aws) plugin, which is installed as follows: - -```sh -vagrant plugin install vagrant-aws -``` - -Before launching an AWS-backed virtual machine, set a few environment variables: - -```sh -# Required. Credentials for AWS API. vagrant-aws will error if these are unset. -export AWS_ACCESS_KEY_ID=... -export AWS_SECRET_ACCESS_KEY=... -# Name of AWS keypair for launching and accessing the EC2 instance. -export AWS_KEYPAIR_NAME=my-osquery-vagrant-security-group -# Path to local private key for SSH authentication -export AWS_SSH_PRIVATE_KEY_PATH=/path/to/keypair.pem -# Name of AWS security group that allows TCP/22 from vagrant host. -# Leaving this unset may work in some AWS/EC2 configurations. -# If using a non-default VPC use the security group ID instead. -export AWS_SECURITY_GROUP=my-osquery-vagrant-security-group -# Set this to the AWS region, "us-east-1" (default) or "us-west-1". -export AWS_DEFAULT_REGION=... -# Set this to the AWS instance type. If unset, m3.large is used. -export AWS_INSTANCE_TYPE=m3.medium -# (Optional) Set this to the VPC subnet ID. -# (Optional) Make sure your subnet assigns public IPs and there is a route. -export AWS_SUBNET_ID=... -``` - -Spin up a VM in EC2 and SSH in (remember to suspend/destroy when finished): - -```sh -vagrant up aws-amazon2015.03 --provider=aws -vagrant ssh aws-amazon2015.03 -``` - -## Building packages - -The packaging logic is now in a separate repository, [osquery-packaging](https://github.com/osquery/osquery-packaging). Packages are created in two steps: - -1. Create the package data from osquery/osquery -2. Use the osquery/osquery-packaging logic to create the actual packages - -This approach allows maintainers to easily re-generate all the officially supported packages without going through the whole build process, which is especially useful when applying code signing from a protected machine. - -### Generating the package data - -1. Build osquery -2. Create a destination folder and set its path in the `DESTDIR` environment variable -3. Run the `install` target - -```sh -cd build_folder -mkdir package_data -export DESTDIR=$(pwd)/package_data # on Windows: `set DESTDIR=path` or `$Env:DESTDIR=path` for PowerShell -cmake --build . --target install # on Windows: add --config Release -``` - -The newly created folder will include several components: - -- The executables: `osqueryd`, `osqueryi` -- The lenses provided by our Augeas third-party dependency -- A default, or fall-back, OpenSSL certificate store (found within the repository) -- The example query packs from the repository -- Folder structures required for logging -- Windows: small management scripts: `osqueryctl` and `manage-osqueryd.ps1` -- Linux: systemd units, with initd script wrappers -- macOS: a LaunchDaemon unit -- Package control files (i.e.: deb-specific configurations, XML data required by WIX to generate MSI, etc..) - -### Identifying the osquery version - -**Linux, macOS** - -```sh -cd osquery_source_folder -git fetch --tags - -export OSQUERY_VERSION=$(git describe --tags --always) -``` - -**Windows** - -```batch -cd osquery_source_folder - -git fetch --tags -git describe --tags --abbrev=0 - -set OSQUERY_VERSION= -``` - -### Preparing to build the osquery-packaging repository - -Pre-requisites (for RPM builds): - -```sh -sudo apt install binutils elfutils -``` - -Generating an RPM package: - -```sh -git clone https://github.com/osquery/osquery-packaging - -mkdir build -cd build -``` - -Common input parameters - -- **OSQUERY_VERSION**: can be customized, but we usually use the output of `git describe --always` -- **OSQUERY_DATA_PATH**: Where the package data has been installed - -### Creating the Linux packages - -When generating packages, the install path for osquery is determined by `CMAKE_INSTALL_PREFIX` (default: -`/usr/local/`) when building the TGZ "package", and `CMAKE_PACKAGING_INSTALL_PREFIX` (default: `/usr/`) -when building either the DEB or RPM packages. - -Linux-specific parameters: - -- **CPACK_GENERATOR**: Either `DEB`, `RPM` or `TGZ` -- **OSQUERY_SOURCE_DIRECTORY_LIST**: An optional list of paths, populated when creating the debuginfo and dbgsym packages for DEB/RPM. Pass the source and the build folders of osquery if you want to generate them. - -_Note: RPM will always try to create debuginfo packages, to do so though it needs the source folder to be in a path that's longer than `/usr/src/debug/osquery/src_0` and the build folder to be in a path that's longer than `/usr/src/debug/osquery/src_1`._ - -```sh -cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCPACK_GENERATOR=DEB \ - -DOSQUERY_PACKAGE_VERSION=${OSQUERY_VERSION} \ - -DOSQUERY_DATA_PATH=${DESTDIR} \ - -DOSQUERY_SOURCE_DIRECTORY_LIST="osquery-src-path;osquery-build-path" \ - ../osquery-packaging - -cmake --build . --target package -``` - -### Creating the Windows packages - -Windows-specific parameters: - -- **CPACK_GENERATOR**: Either `WIX` or `NuGet` -- **OSQUERY_BITNESS**: Either 32 or 64, depending on which architecture has been built - -_Note: Please note that the NuGet and WIX generators only support the `a.b.c` version format. If the commit being built is not tagged, consider using `git describe --tags --abbrev=0`_ - -```batch -call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - -cmake -DCMAKE_BUILD_TYPE=Release ^ - -DCPACK_GENERATOR=WIX ^ - -DOSQUERY_PACKAGE_VERSION=%OSQUERY_VERSION% ^ - -DOSQUERY_DATA_PATH=%DESTDIR% ^ - -DOSQUERY_BITNESS=64 ^ - ..\osquery-packaging - -cmake --build . --config Release --target package -``` - -### Creating the macOS packages - -macOS-specific parameters: - -- **CPACK_GENERATOR**: Either `productbuild` (PKG files) or `TGZ` - -```sh -cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCPACK_GENERATOR=productbuild \ - -DOSQUERY_PACKAGE_VERSION=${OSQUERY_VERSION} \ - -DOSQUERY_DATA_PATH=${DESTDIR} \ - ../osquery-packaging - -cmake --build . --target package -``` - -## Build Performance - -Generating a virtual table should _not_ impact system performance. This is easier said than done, as some tables may _seem_ inherently latent (if you expect to run queries like `SELECT * from suid_bin;` which performs a complete filesystem traversal looking for binaries with suid permissions). Please read the osquery features and guide on [performance safety](../deployment/performance-safety.md). - -Some quick features include: - -- Performance regression and leak detection CI guards. -- Denylisting performance-impacting virtual tables. -- Scheduled query optimization and profiling. -- Query implementation isolation options. diff --git a/docs/wiki/development/config-plugins.md b/docs/wiki/development/config-plugins.md deleted file mode 100644 index 8c0b4cef2b2..00000000000 --- a/docs/wiki/development/config-plugins.md +++ /dev/null @@ -1,67 +0,0 @@ -# Configuration Plugins - -For details on how `osqueryd` schedules queries and loads information from a config, see the [configuration](../deployment/configuration.md) deployment guide. - -You may need to distribute osquery configurations in a different way than the default method. To support all deployment types, the way that `osqueryd` retrieves its configuration is itself completely pluggable and customizable. By default, `osqueryd` will look for a JSON file on disk using the default config plugin: **filesystem**. If you distribute configurations via something like [Zookeeper](https://zookeeper.apache.org) or [etcd](https://github.com/etcd-io/etcd), you need to write a C++ function that can acquire a string of JSON. This developer tutorial will walk through the default filesystem config plugin as a demonstration for creating new config inputs. - -## Example: Filesystem config - -The following code is more-or-less the **filesystem** config plugin. This is the default config plugin, it simply reads JSON data from a flat file. Let's walk through the implementation: - -```cpp -// Note 1: REQUIRED includes -#include -#include - -namespace osquery { - -// Note 2: Setup any invocation arguments -FLAG(string, config_path, "osquery.conf", "Path to config"); - -// Note 3: Inherit from ConfigPlugin -class FilesystemConfigPlugin : public ConfigPlugin { - public: - osquery::Status genConfig(std::map& config) { - std::string content; - std::ifstream content_stream(FLAGS_config_path); - - content_stream.seekg(0, std::ios::end); - content.reserve(config_stream.tellg()); - content_stream.seekg(0, std::ios::beg); - - content.assign((std::istreambuf_iterator(content_stream)), - std::istreambuf_iterator()); - - // Note 4: Return an osquery Status and JSON encoded config. - config["default_source"] = std::move(content); - return Status(0, "OK"); - } -}; - -// Note 5: Register the plugin -REGISTER(FilesystemConfigPlugin, "config", "filesystem"); -} -``` - -There are 5 parts of a config plugin: - -1. Include the plugin macros as well as command line argument macros. -2. If your config requires customization expose it as arguments. -3. Inherit from `ConfigPlugin` and implement: `osquery::Status genConfig(std::map&)**`. -4. Return an osquery Status and map of config sources to JSON-encoded strings. -5. Register the plugin using a string-identifier. - -The filesystem plugin is very very simple the config plugin architecture expects config plugins to yield valid JSON. - -## Additional overloads - -Packs may be retrieved subsequently after a configuration is read. If pack content is not provided inline with the configuration the string value is passed to the plugin. The config plugin in question must implement the virtual -method: - -```cpp -Status genPack(const std::string& name, const std::string& value, std::string& pack); -``` - -## Using the plugin - -Now when starting `osqueryd` you may use `--config_plugin=yourconfigpluginname` where the name is the string identifier used in **REGISTER**. diff --git a/docs/wiki/development/creating-tables.md b/docs/wiki/development/creating-tables.md deleted file mode 100644 index 23042175541..00000000000 --- a/docs/wiki/development/creating-tables.md +++ /dev/null @@ -1,255 +0,0 @@ -# Creating tables - -SQL tables are used to represent abstract operating system concepts, such as running processes. - -A table can be used in conjunction with other tables via operations like sub-queries and joins. This allows for a rich data exploration experience. While osquery ships with a default set of tables, osquery provides an API that allows you to create new tables. - -You can explore current schema here: [https://osquery.io/schema](https://osquery.io/schema/). Tables that are up for grabs in terms of development can be found on GitHub issues using the "virtual tables" + "[up for grabs tag](https://github.com/osquery/osquery/issues?q=is%3Aopen+is%3Aissue+label%3A%22virtual+tables%22)". - -## New Table Walkthrough - -Let's walk through an exercise where we build a 'time' table. The table will have one row, and that row will have three columns: hour, minute, and second. - -Column values (a single row) will be dynamically computed at query time. - -### Table specifications - -Under the hood, osquery uses libraries from SQLite core to create "virtual tables". The default API for creating virtual tables is relatively complex. osquery has abstracted this complexity away, allowing you to write a simple table declaration. - -To make table-creation simple, osquery uses a *table spec* file. -The current specs are organized by operating system in the [specs](https://github.com/osquery/osquery/tree/master/specs) source folder. -For our time exercise, a new spec file written to `./specs/time_example.table` would look like the following: - -```python -# This .table file is called a "spec" and is written in Python -# This syntax (several definitions) is defined in /tools/codegen/gentable.py. -table_name("time_example") - -# Provide a short "one line" description, please use punctuation! -description("Returns the current hour, minutes, and seconds.") - -# Define your schema, which accepts a list of Column instances at minimum. -# You may also describe foreign keys and "action" columns. -schema([ - # Declare the name, type, and documentation description for each column. - # The supported types are INTEGER, BIGINT, TEXT, DATE, and DATETIME. - Column("hour", INTEGER, "The current hour"), - Column("minutes", INTEGER, "The current minutes past the hour"), - Column("seconds", INTEGER, "The current seconds past the minute"), -]) - -# Use the "@gen{TableName}" to communicate the C++ symbol name. -implementation("time@genTimeExample") -``` - -You can leave the comments out in your production spec. Shoot for simplicity, try to avoid things like inheritance for Column objects, loops in your table spec, etc. - -You might wonder "this syntax looks similar to Python?" Well, it is! The build process actually parses the spec files as Python code and meta-programs necessary C/C++ implementation files. - -### Where to put the spec - -You may be wondering how osquery handles cross-platform support while still allowing operating-system specific tables. The osquery build process takes care of this by only generating the relevant code based on on logic in `./specs/CMakeLists.txt`. Additionally, we use a simple directory structure to help organize the many spec files. - -- Cross-platform: [specs/](https://github.com/osquery/osquery/tree/master/specs/) -- macOS: [specs/darwin/](https://github.com/osquery/osquery/tree/master/specs/darwin) -- General Linux: [specs/linux/](https://github.com/osquery/osquery/tree/master/specs/linux) -- Windows: [specs/windows/](https://github.com/osquery/osquery/tree/master/specs/windows) -- POSIX: [specs/posix/](https://github.com/osquery/osquery/tree/master/specs/posix) -- You get the picture ;) - -> NOTICE: the CMake build provides custom defines for each platform and platform version. - -To make our new `time_example` work, find the function in `./specs/CMakeLists.txt` called `generateNativeTables` and add a line `time_example.table`. - -### Specfile nuances - -Each column in a **specfile** may have keyword arguments that effect how the SQLite behaves. If you require a column to be present in the `WHERE` predicate, like a `path` in the `file` table, then it must be reflected in the spec. - -- **required=True**: This will create a warning if the table is used and the column does not appear in the predicate. -- **index=True**: This sets the `PRIMARY KEY` for the table, which helps the SQLite optimizer remove potential duplicates from complex `JOIN`s. If multiple columns have `index=True` then a primary key is created as the set of columns. -- **additional=True**: This is weird, but use **additional** if the presence of the column in the predicate would somehow alter the logic in the table generator. This tells SQLite not to optimize out any use of this column in the predicate. -- **hidden=True**: Sets the `HIDDEN` attribute for the column, so a `SELECT * FROM` will not include this column. -- **optimized=True**: Use **optimized** if the presence of the column in the predicate reduces the generated dataset. The table generator must iterate over all constraints for the column as this tells the SQLite optimizer it will process `IN` constraints all-at-once. As well, the table generator must be able to handle empty string constraints for an optimized column. - -The table may also set `attributes`: - -```python -attributes(user_data=True) -``` - -There are several attributes that help with table documentation and optimization. These are keyword arguments in the `attributes` optional method. - -- **event_subscriber=True**: Indicates that the table is an abstraction on top of an event subscriber. The specfile for your subscriber must set this attribute. -- **user_data=True**: This tells the caller that they should provide a `uid` in the query predicate. By default the table will inspect the current user's content, but may be asked to include results from others. -- **cacheable=True**: The results from the table can be cached within the query schedule. If this table generates a lot of data it is best to cache the results so that queries needing access in the schedule with a shorter interval can simply copy the already generated structures. -- **utility=True**: This table will be included in the osquery SDK, it is considered a core/non-platform specific utility. - -Specs may also include an **extended_schema** for a specific platform. They are the same as **schema** but the first argument is a function returning a bool. If true the columns are added and not marked hidden, otherwise they are all appended with `hidden=True`. This allows tables to keep a consistent set of columns and types while providing a good user experience for default selects. - -### Creating your implementation - -As indicated in the spec file, our implementation will be in a function called `genTimeExample`. Since this is a very general table and should compile on all supported operating systems we can place it in `./osquery/tables/utility/time_example.cpp`. The directory `./osquery/tables` contains the set of implementation categories. Each category *may* contain a platform-restricted directory. If a table requires a different implementation on different platform, use these subdirectories. Place implementations in the corresponding category using your best judgment. The appropriate `CMakeLists.txt` must define the files within the platform-related directory to know what to build. - -Here is that code for `./osquery/tables/utility/time_example.cpp`: - -```cpp -/** - * Copyright (c) 2014-present, The osquery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -#include -#include - -namespace osquery { -namespace tables { - -QueryData genTimeExample(QueryContext &context) { - QueryData rows; - - Row r; - - time_t _time = time(0); - struct tm* now = localtime(&_time); - - r["hour"] = now->tm_hour; - r["minutes"] = now->tm_min; - r["seconds"] = now->tm_sec; - - rows.push_back(std::move(r)); - return rows; -} -} -} -``` - -And then edit `./osquery/tables/utility/CMakeLists.txt`, find the function `generateTablesUtilityUtilitytable` and add the new file `time_example.cpp` to list seen there. - -Key points to remember: - -- Your implementation function should be in the `osquery::tables` namespace. -- Your implementation function should accept on `QueryContext&` parameter and return an instance of `TableRows`. -- Your implementation function should use `context.isAnyColumnUsed` to run only the code necessary for the query. - -### Adding an integration test - -You may add small unit tests using GTest, but each table *should* have an integration test where the end-to-end selecting and checking data formats occurs. - -Create a file `./tests/integration/tables/time_example.cpp`. - -```cpp -/** - * Copyright (c) 2014-present, The osquery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -#include - -#include - -namespace osquery { -namespace table_tests { - -class TimeExample : public testing::Test { - protected: - void SetUp() override { - setUpEnvironment(); - } -}; - -TEST_F(TimeExample, test_sanity) { - QueryData data = execute_query("select * from time_example"); - - ASSERT_EQ(data.size(), 1ul); - - ValidationMap row_map = { - {"hour", IntMinMaxCheck(0, 24)}, - {"minutes", IntMinMaxCheck(0, 59)}, - {"seconds", IntMinMaxCheck(0, 59)}, - }; - validate_rows(data, row_map); -} - -} // namespace table_tests -} // namespace osquery -``` - -Here you can see we are selecting from `time_example` and expecting exactly 1 row. We then place restrictions on the data in each column. There are a set of default checks that simple verify the data type, for example, `NonNegativeInt`, `NonEmptyString`, `NormalType`, ranging to more complex and specific checks. The [`helper.h`](https://github.com/osquery/osquery/tree/master/tests/integration/tables/helper.h) header classes and enumerations for checking validity. Please feel empowered to extend it. - -To make this compile, open `./tests/integration/tables/CMakeLists.txt`, find the function `generateTestsIntegrationTablesTestsTest` and add `time_example.cpp`. You will need to configure CMake with `-DOSQUERY_BUILD_TESTS=ON` for the integration test to run. Please see the Building and [Testing](../building/#testing) documentation for more details. - -## Using where clauses - -The `QueryContext` data type is osquery's abstraction of the underlying SQL engine's query parsing. It is defined in `osquery/core/tables.h`. - -The most important use of the context is query predicate constraints (e.g., `WHERE col = 'value'`). Some tables MUST have a predicate constraint, others may optionally use the constraints to increase performance. - -Examples: - -`hash` requires a predicate, since the resultant rows are the hashes of the EQUALS constraint operators (`=`). The table implementation includes: - -```cpp - auto paths = context.constraints["path"].getAll(EQUALS); - for (const auto& path_string : paths) { - boost::filesystem::path path = path_string; - [...] - } -``` - -`processes` optionally uses a predicate. A syscall to list process PIDs requires few resources. Enumerating "/proc" information and parsing environment/argument uses MANY resources. The table implementation includes: - -```cpp - for (auto &pid : pidlist) { - if (!context.constraints["pid"].matches(pid)) { - // Optimize by not searching when a pid is a constraint. - continue; - } - [...] - } -``` - -## SQL data types - -Data types like `TableRows`, `TableRow`, `DiffResults`, etc. are osquery's built-in data result types. They're all defined in [osquery/database/database.h](https://github.com/osquery/osquery/blob/master/osquery/database/database.h). - -`TableRow` is an interface; each table has a generated implementation with strongly-typed fields for each column in the table. There's also `DynamicTableRow`, which is backed by a `std::map` mapping column names to the string representations of their values. `DynamicTableRow` exists to support tables that were written before the strongly-typed row support was added, and for plugins. - -`TableRows` is just a `typedef` for a `std::vector`. Table rows is just a list of rows. Simple enough. - -To populate the data that will be returned to the user at runtime, your implementation function must generate the data that you'd like to display and populate a `TableRows` list with the appropriate `TableRow`s. Then, just return the `TableRows`. - -In our case, we used system APIs to create a struct of type `tm` which has fields such as `tm_hour`, `tm_min` and `tm_sec` which represent the current time. We can then set our three fields in our `TimeRow` variable: hour_col, minutes_col and seconds_col. Then we push that single row onto the `TableRows` variable and return it. Note that if we wanted our table to have many rows (a more common use-case), we would just push back more `TableRow` maps onto `results`. - -### Testing your table - -If your code compiled properly, launch the interactive query console by executing `./build/osquery/osqueryi` and try issuing your new table a command: `SELECT * FROM time;`. If your table implementation has nontrivial conditional code based on the columns used in the query, try issuing more focused commands to test that logic as well. - -Run the leaks analysis to check for memory leaks: - -```bash -./tools/analysis/profile.py --leaks --query "select * from time" --verbose -``` - -If your table parses content from the filesystem you should define fuzzing rules. In your table specification add: - -```python -fuzz_paths([ - "/path/to/directory/used", -]) -``` - -Then run `./tools/analysis/fuzz.py --table time`. - -### Getting your query ready for use in osqueryd - -You don't have to do anything to make your query work in the osqueryd daemon. All osquery queries work in osqueryd. It's worth noting, however, that osqueryd is a long-running process. If your table leaks memory or uses a lot of systems resources, you will notice poor performance from osqueryd. For more information on ensuring a performant table, see [performance overview](../deployment/performance-safety.md). - -When in doubt, use existing open source tables to guide your development. diff --git a/docs/wiki/development/cve-scan.md b/docs/wiki/development/cve-scan.md deleted file mode 100644 index a5c2cf1af67..00000000000 --- a/docs/wiki/development/cve-scan.md +++ /dev/null @@ -1,97 +0,0 @@ -# CVE Scan - -The osquery project has a CI job which once a day scans for CVEs that are present and not yet addressed in its third party libraries. -The scan is done by a python script at `tools/ci/scripts/cve/third_party_libraries_cves_scanner.py`, which uses the NIST database queried via their NVD APIs; a manifest file at `libraries/third_party_libraries_manifest.json` contains the list of third party libraries and their metadata necessary to correctly download the CVEs. - -The manifest file format is validated everytime the CVEs download happen and on every PR by the script `tools/ci/scripts/cve/validate_maninfest_libraries_scanner.py`; additionally the third party library versions in the manifest are verified, to ensure that they are up to date with their state in the repository. - -After having downloaded the list of CVEs, the script will open issues in the osquery repository, for all the unresolved CVEs, checking against the already opened ones to prevent duplicates, and it will go back in time up to 6 months old. -The issues can be recognized because they will be opened by the `github-bot` author and will have the `security`, `libraries`, `cve` and `severity-` labels on them. - -NOTE: This product uses the NVD API but is not endorsed or certified by the NVD. - -# Updating a third party library to resolve a CVE - -The process of updating a third party library is the usual, but in the PR updating the library the contributor MUST: - - 1. Link the CVE(s) issue(s) the PR is going to close, so that when it's merged, they are automatically closed - 2. Update the manifest and specifically the `version` and `commit` fields with the information of the new library. - Remember that the `commit` has to be the one of the submodule in the osquery repository, which might not always match the commit of the library original repository. - -Failing to do step 1. only leads to having to manually close those issue and link them back to the PR for tracking purposes. - -Failing to do step 2. will lead to the PR not being mergeable because the CI checks the `commit` field against the actual git submodule commit. -Note that if the `commit` is updated but not the `version`, this will not be detected by the CI and the periodic scan will use the incorrect -version to download CVEs, finding again the fixed CVE and reopening the issue. -If this happens, one just needs to do another PR that updates the `version` correctly. - -Any other situation where the `version` is incorrect (older than what previously was or newer than what actually is) is still not detected, -and will either cause the script to open issues for already fixed CVEs or to miss CVEs, so it's very important that the PR review process -double checks the new `version`. - -Important: Do not merge this kind of PR if the CI CVE scan job is running (which happens only once a day between 23:00 and 00:00 UTC), otherwise the job could start with an old view of the repository and open new issues on already fixed CVEs. -If this happens, we just need to close or even delete those issues, but it's mostly to avoid additional work or confusions. - -# Ignoring a CVE not affecting osquery - -There are cases where the API returns CVEs that are not affecting a third party library, not directly, but they are affecting other software that uses the third party library. There might be something we can do in the future to resolve what seems a bug in the API, but for now in the manifest it's possible to list CVEs that should be ignored, so that issues for those are not opened again in the future. - -Additionally we often have the case where a CVE is not affecting osquery due to how or what parts of the third party library are used, so having a way to ignore a CVE helps with that too. - -The process therefore is to: - - 1. Open a PR which updates the manifest and specifically updates the `ignored-cves` field of the library the CVE comes from. - 2. Describe in the CVE(s) issue(s) the reason why they are going to be closed - 3. Link the above issues to the PR, so that they are closed when the PR gets merged - -Important: As with updating a library, one has to ensure that the CI CVE scan job is not running - -# Adding a new library - -When a new library gets added, the manifest needs to be updated too, otherwise the CI check that verifies the manifest in the PR will fail. - -Currently the JSON format for a third party library as a submodule (taking as an example `libdpkg`) is: - -```json -"libdpkg": { - "product": "dpkg", - "vendor": "debian", - "version": "1.21.7", - "commit": "e61f582015a9c67bbb3791cb93a864cfeb9c7151", - "ignored-cves": [] -}, -``` - -The name of the library, `libdpkg`, and the `commit` field must match the name of the folder containing the submodule source code folder, and the commit at which the submodule currently is, respectively. - -The `product`, `vendor` and `version` fields are used in the NVD APIs instead, and they must match what the NIST database uses. -This is a matter of using the CPE search at https://nvd.nist.gov/products/cpe/search, and trying to find which are the correct `product` and `vendor` -using a cpe like `cpe:2.3:a:**:*:*:*:*:*:*:*:*:*` and for the product `cpe:2.3:a::**:*:*:*:*:*:*:*:*`. - -Another way could be to download the full dictionary from https://nvd.nist.gov/feeds/xml/cpe/dictionary/official-cpe-dictionary_v2.3.xml.gz, then playing with grep and awk. -For instance if we want to see all the unique products that the `amazon` vendor has: -```sh -cat official-cpe-dictionary_v2.3.xml | grep -Eo "cpe:2.3:a:amazon:[^\"]*" | awk -F ":" '{ print $5 }' | sort | uniq | less -``` - -For the `ignored-cves` refer to [Ignoring a CVE not affecting osquery](#ignoring-a-cve-not-affecting-osquery); the field will likely be empty at the beginning. - -## Special cases - -### Libraries without a CPE - -Some libraries do not have a CPE assigned, so no CVEs will be found in the NIST database. We track these libraries in the manifest, because the validation script will check for it, but one can provide less fields; only `vendor` and `commit` are required. - -The script though also needs to know that this is a library of that kind, so the contributor has to update the list of libraries that does not have a CPE in `tools/scripts/ci/cve/osquery/manifest_api.py` - -### Libraries not imported as a submodule - -Right now there's only one case and ideally, the only, but `openssl` is not a submodule, so there's no `commit` to use to check if the manifest is up to date. `version` is used instead, and it's parsed from the CMake file at `libraries/cmake/formula/openssl/CMakeLists.txt`. -If it will ever happen that osquery needs to add another library of this kind, then logic to get its version should be written in the `tools/scripts/ci/cve/validate_manifest_libraries_versions.py` script. - -Additionally the name of the library should be added in `tools/scripts/ci/cve/osquery/manifest_api.py` and finally the manifest fields requirements would be the same as for a normal library, just without the `commit` field. - -### Libraries not used in the release build - -Another case is when the library is only used for testing purposes; this doesn't need to be tracked in the manifest, but still needs to be ignored by the script that checks that all the necessary libraries are present and up to date in the manifest. -The script to update is `tools/scripts/ci/cve/validate_manifest_libraries_versions.py` (the current example is `googletest`). diff --git a/docs/wiki/development/logger-plugins.md b/docs/wiki/development/logger-plugins.md deleted file mode 100644 index 43033812163..00000000000 --- a/docs/wiki/development/logger-plugins.md +++ /dev/null @@ -1,37 +0,0 @@ -# Logger plugins - -For details on how osqueryd schedules queries and loads information from a config, see the [configuration](../deployment/configuration.md) deployment guide. - -If you would like to use services like [scribe](https://github.com/facebookarchive/scribe) or [flume](https://flume.apache.org), you need to write a C++ function that consumes/handles a string argument. - -## Example: glog logger - -This following is a overly simplified logger plugin that writes results to a glog info line. - -```cpp -#include -#include - -namespace osquery { - -class GlogLoggerPlugin : public LoggerPlugin { - public: - Status logString(const std::string& message) { - LOG(INFO) << message; - return Status(0, "OK"); - } - - virtual ~GlogLoggerPlugin() {} -}; - -REGISTER(GlogLoggerPlugin, "logger", "glog"); -} -``` - -Essentially, you are just implementing a **logString** method. When the daemon identifies a change to a query schedule it will call the active logger plugin's **logString** method after converting the change details into JSON. - -## Using the plugin - -Add your source file to `osquery/plugins/logger/CMakeLists.txt` and it will be compiled and linked. - -Now when starting osqueryd you may use `--logger_plugin=name` where the name is the string identifier used in **REGISTER**. diff --git a/docs/wiki/development/options-arguments.md b/docs/wiki/development/options-arguments.md deleted file mode 100644 index a1e83436ab2..00000000000 --- a/docs/wiki/development/options-arguments.md +++ /dev/null @@ -1,27 +0,0 @@ -# Adding new options and arguments to osquery - -How do I add a new command-line flag/option/argument to osquery? Well, first familiarize yourself with [gflags](https://github.com/gflags/gflags), then take note of the wrapper below. - -[osquery/core/flags.h](https://github.com/osquery/osquery/blob/master/osquery/core/flags.h) contains a single wrapper for `gflags::DEFINE_` type style macros. osquery includes a simple wrapper for defining arguments/options/flags for the osqueryd daemon and shell. - -Instead of writing the normal gflags macro for defining a new option: - -```cpp -#include -// This is the WRONG way to define a flag in osquery. -DEFINE_bool(you_are_awesome, true, "Ground truth for awesome."); // DON'T DO THIS! -``` - -Use the following wrapper: - -```cpp -#include - -FLAG(bool, you_are_awesome, true, "Ground truth for awesome."); -``` - -If you are declaring a flag before defining it, no change is needed. Use `DECLARE_bool(you_are_awesome);` like normal. There is no change for accessing the flag either. Use `if (FLAG_you_are_awesome)` like normal. - -This will allow osquery callers to show pretty displays when `-h, --help` is used. - -> NOTICE: restrict your default values to code literals. It does not help to abstract the default variable into a constant then use it singularly in the macro. diff --git a/docs/wiki/development/osquery-sdk.md b/docs/wiki/development/osquery-sdk.md deleted file mode 100644 index 36ddf768a45..00000000000 --- a/docs/wiki/development/osquery-sdk.md +++ /dev/null @@ -1,225 +0,0 @@ -# The osquery SDK - -The osquery "public API" or SDK is the set of osquery headers and a subset of the source "cpp" files implementing what we call osquery **core**. The core code can be thought of as the framework or platform, it is everything except for the SQLite code and most table implementations. The public headers can be found in [/osquery/osquery/sdk/](https://github.com/osquery/osquery/tree/master/osquery/sdk). - -osquery is organized into a **core**, **additional**, and **testing** during a default build from source. We call the set of public headers implementing **core** the 'osquery SDK'. This SDK can be used to build osquery outside of our CMake build system with a minimum set of dependencies. This organization better isolates OS API dependencies from development tools and libraries and provides a logical separation between code needed for extensions and module compiling. - -The public API and SDK headers are documented via **doxygen**. To generate web-based documentation, you will need to install doxygen, run `make docs` from the repository root, then open `./build/docs/html/index.html`. - -## Extensions - -Extensions are separate processes that communicate over a [Thrift](https://thrift.apache.org/) IPC channel to osquery **core** in order to register one or more plugins or virtual tables. An extension allows you to develop independently: it may be compiled and linked using an external build system, against proprietary code. Your extension will be version-compatible with our publicly-built binary packages on [https://osquery.io/downloads](https://osquery.io/downloads). - -Extensions use osquery's [Thrift API](https://github.com/osquery/osquery/blob/master/osquery/extensions/thrift/osquery.thrift) to communicate between `osqueryi` or `osqueryd` and the extension process. Extensions are commonly written in C++, but can also be developed [in Python](https://github.com/osquery/osquery-python), [in Go](https://github.com/kolide/osquery-go), or in really any language that supports [Thrift](https://thrift.apache.org/). - -Only the osquery SDK provides the simple `startExtension` symbol that manages the life of your process, including the Thrift service threads and a watchdog. - -osquery extensions should statically link the **core** code and use the `` helper include file. C++ extensions should link: boost, thrift, glog, gflags, and optionally rocksdb for eventing. Let's walk through a basic example extension in C++ (source for [read_only_table/main.cpp](https://github.com/osquery/osquery/tree/master/external/examples)): - -```cpp -// Note 1: Include the SDK and helpers -#include -#include -#include - -using namespace osquery; - -// Note 2: Define at least one plugin or table. -class ExampleTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("example_text", TEXT_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("example_integer", INTEGER_TYPE, ColumnOptions::DEFAULT), - }; - } - - TableRows generate(QueryContext& request) { - TableRows results; - - auto r = make_table_row(); - r["example_text"] = "example"; - r["example_integer"] = INTEGER(1); - - results.push_back(std::move(r)); - return results; - } -}; - -// Note 3: Use REGISTER_EXTERNAL to define your plugin or table. -REGISTER_EXTERNAL(ExampleTablePlugin, "table", "example"); - -int main(int argc, char* argv[]) { - // Note 4: Start logging, threads, etc. - osquery::Initializer runner(argc, argv, ToolType::EXTENSION); - - // Note 5: Connect to osqueryi or osqueryd. - auto status = startExtension("example", "0.0.1"); - if (!status.ok()) { - LOG(ERROR) << status.getMessage(); - runner.requestShutdown(status.getCode()); - } - - // Finally wait for a signal / interrupt to shutdown. - runner.waitForShutdown(); - return runner.shutdown(0); -} -``` - -The `osqueryi` or `osqueryd` processes start an "extension manager" Thrift service thread that listens for extension register calls on a UNIX domain socket. Extensions may only communicate if the processes can read/write to this socket. An extension process running as a non-privileged user cannot register plugins to an `osqueryd` process running as root. Both the osquery core and C++ extensions using `startExtension` will deregister plugins if the communication becomes latent. Both of these settings are configurable using gflags or the osquery config options. - -### Using the example extension - -Please see the deployment [guide on extensions](../deployment/extensions.md) for a more-complete overview of how and why extensions are used. - -If you [build from source](../development/building.md), you will build an example extension. The code can be found in the [`osquery/external/examples`](https://github.com/osquery/osquery/tree/master/external/examples) folder; it adds a config plugin called "example" and additional table called "example". There are two ways to run an extension: load the extension at an arbitrary time after shell or daemon execution, or request an "autoload" of extensions. The auto-loading method has several advantages, such as allowing dependencies on external config plugins and inheriting the same process monitoring as is applied to the osquery core worker processes. - -The `osqueryi` shell also allows a quick and easy command-line autoload using `--extension`. Let's review both options. - -First, to load the example extension in the osquery interactive shell, we start osqueryi: - -```sh -$ ./build/darwin/osquery/osqueryi -osquery> SELECT path FROM osquery_extensions; -+-------------------------------------+ -| path | -+-------------------------------------+ -| /Users/USERNAME/.osquery/shell.em | -+-------------------------------------+ -osquery> ^Z -[1] + 98777 suspended ./build/darwin/osquery/osqueryi -``` - -Here we have started the osquery shell process, inspected the UNIX domain socket path it uses to communicate with extension processes, and then suspended the process temporarily. - -```sh -$ ./build/darwin/osquery/example_extension.ext --help -osquery 1.7.0, your OS as a high-performance relational database -Usage: example_extension.ext [OPTION]... - -osquery extension command line flags: - - --interval VALUE Seconds delay between connectivity checks - --socket VALUE Path to the extensions UNIX domain socket - --timeout VALUE Seconds to wait for autoloaded extensions - -osquery project page . -$ ./build/darwin/osquery/example_extension.ext --socket /Users/USERNAME/.osquery/shell.em & -``` - -Before executing the extension we've inspected the potential CLI flags, which are a subset of the shell or daemon's [CLI flags](../installation/cli-flags.md). We executed the example extension in the background, so now we can resume the osquery shell and use the 'example' table provided by the extension. - -```sh -[2] 98795 -$ fg -[1] - 98777 continued ./build/darwin/osquery/osqueryi -osquery> SELECT * FROM example; -+--------------+-----------------+ -| example_text | example_integer | -+--------------+-----------------+ -| example | 1 | -+--------------+-----------------+ -osquery> -``` - -If the responsible osquery shell or daemon process ends, the extension will detect the loss of communication and also shutdown soon after. Read more about the lifecycle of extensions in the deployment guide. - -The second, and simpler, way to manually load an extension is at the osqueryi command line with `--extension`: - -```sh -./build/darwin/osquery/osqueryi --extension ./build/darwin/osquery/example_extension.ext -``` - -### Building external extensions - -Your "external" extension, in the sense that the code is developed and contained somewhere external from the osquery repository, can be built semi-automatically. - -1. Symlink your external extension source code directory into `./external`. (`ln -s [extension_source_dir] [osquery_external_subdir]` on Linux or macOS, `mklink /D [osquery_external_subdir] [extension_source_dir]` on Windows) -2. Make sure the symlink contains `extension_` as a prefix. -3. Run `make externals`. - -This will find and compile all `.*\.{cpp,c,mm}` files within your external directory. If you need something more complicated, add a `CMakeLists.txt` to your directory and add your targets to the `externals` target. - -See [`/osquery/external/cmake/cmakelibs.cmake`](https://github.com/osquery/osquery/blob/master/external/cmake/cmakelibs.cmake) for more information about the `addOsqueryExtension` and `addOsqueryExtensionEx` CMake macros. - -Example: - -```sh -(osquery) $ ln -s ~/git/fun-osquery-extension ./external/extension_fun -(osquery) $ ls ./external/extension_fun/ -fun.cpp -(osquery) $ make externals -[...] -[100%] Built target libosquery -Scanning dependencies of target external_extension_awesome -[100%] Building CXX object external/CMakeFiles/external_extension_fun.dir/extension_fun/fun.cpp.o -[100%] Linking CXX executable external_extension_fun.ext -[100%] Built target external_extension_fun -[100%] Built target externals -``` - -## Bundling multiple extensions into a single-executable extension - -All of the extensions declared with the **add_osquery_extension_ex()** CMake function will be automatically bundled into a single executable. - -The executable name and version can be changed using the following two environment variables: - -1. `OSQUERY_EXTENSION_GROUP_NAME` (default: `osquery_extension_group`) -2. `OSQUERY_EXTENSION_GROUP_VERSION` (default: `1.0`) - -It is important to provide a header file that can be included by the generated main.cpp file; its purpose is to define the types used by the **REGISTER_EXTERNAL** directive. - -An example is included in the `osquery/examples/extension_group_example`. - -Please note that when using bundling, the source directory of each extension is added to the `include` list; developers should always use uniquely named include files. Additionally, if you are using RapidJSON documents in your extension, then you should instead leverage the osquery `json.h` header to avoid linking issues due to how we have configured RapidJSON `#define`s. - -## Thrift API - -[Thrift](https://thrift.apache.org/) is a code-generation/cross-language service development framework. osquery uses Thrift to allow extensions to implement plugins for config retrieval, log export, table implementations, event subscribers, and event publishers. We also use Thrift to wrap our SQL implementation using SQLite. - -### Extension API - -An extension process should implement the following API. During an extension's set up it will "broadcast" all of its registered plugins to the osquery shell or daemon process. Then the extension will be asked to start a UNIX domain socket and Thrift service thread implementing the `ping` and `call` methods. - -```thrift -service Extension { - /// Ping to/from an extension and extension manager for metadata. - ExtensionStatus ping(), - /// Call an extension (or core) registry plugin. - ExtensionResponse call( - /// The registry name (e.g., config, logger, table, etc.). - 1:string registry, - /// The registry item name (plugin name). - 2:string item, - /// The Thrift-equivalent of an osquery::PluginRequest. - 3:ExtensionPluginRequest request), -} -``` - -When an extension becomes unavailable, the shell or daemon process will automatically deregister those plugins. - -### Extension Manager API (osqueryi/osqueryd) - -```thrift -service ExtensionManager extends Extension { - /// Return the list of active registered extensions. - InternalExtensionList extensions(), - /// Return the list of bootstrap or configuration options. - InternalOptionList options(), - /// The API endpoint used by an extension to register its plugins. - ExtensionStatus registerExtension( - 1:InternalExtensionInfo info, - 2:ExtensionRegistry registry), - ExtensionStatus deregisterExtension( - 1:ExtensionRouteUUID uuid, - ), - /// Allow an extension to query using an SQL string. - ExtensionResponse query( - 1:string sql, - ), - /// Allow an extension to introspect into SQL used in a parsed query. - ExtensionResponse getQueryColumns( - 1:string sql, - ), -} -``` diff --git a/docs/wiki/development/pubsub-framework.md b/docs/wiki/development/pubsub-framework.md deleted file mode 100644 index 163a0f8651a..00000000000 --- a/docs/wiki/development/pubsub-framework.md +++ /dev/null @@ -1,105 +0,0 @@ -# The pub-sub evented data framework of osquery - -Most of osquery's virtual tables are generated when an SQL statement requests data. For example, the [time](https://github.com/osquery/osquery/blob/master/osquery/tables/utility/time.cpp) gets the current time and returns it as a single row. So whenever a call selects data from time, e.g., `SELECT * FROM time;` the current time of the call will return. - -From an operating systems perspective, query-time synchronous data retrieval is lossy. Consider the [processes](https://github.com/osquery/osquery/blob/master/osquery/tables/system/linux/processes.cpp) table: if a process like `ps` runs for a fraction of a moment there's no way `SELECT * FROM processes;` will ever include the details. - -To solve this, osquery exposes a [pubsub framework](https://github.com/osquery/osquery/tree/master/osquery/events) for aggregating operating system information asynchronously at event time, storing related event details in the osquery backing store, and performing a lookup to report stored rows query time. This reporting pipeline is much more complicated than typical query-time virtual table generation. The time of event, storage history, and applicable (final) virtual table data information must be carefully considered. As events occur, the rows returned by a query will compound, as such selecting from an event-based virtual table generator should always include a time range. - -If no time range is provided, as in: `SELECT * FROM process_events;`, it is assumed you want to scan from `t=[0, now)`. Otherwise, all of the `*_events` tables must have a `time` column, this is used to optimize searching: `SELECT * FROM process_events WHERE time > NOW() - 300;`. - -## Query and table usage - -Every pubsub-based table ends with `_events`. These tables will perform lookups into the osquery backing storage: RocksDB, for events buffered by the subscribers. These tables are a "query-time" abstraction that allow you to use SQL aggregations and a `time` column for optimizing lookups. - -When using the `osqueryi` shell, these tables will mostly remain empty. This is because the event loops start and stop with the process. If the shell is not running, no events are being buffered. Furthermore, some of the APIs used by the runloops require super-user privileges or non-default flags and options. The shell does **not** communicate with the osquery daemon, nor does it use the same RocksDB storage. Thus the shell cannot be used to explore events buffered by the daemon. - -The buffered events will eventually expire! The `--events_expiry` flag controls the lifetime of buffered events. This is set to 1 day by default, this expiration occurs when events are selected from their subscriber table. For example: the `process_events` subscriber will buffer process starts until a query selects from this table. At that point all results will be returned and immediately after, any event that happened `time-86400` seconds ago will be deleted. If you select from this table every second you will constantly see a window of 1 day's worth of process events. - -When scheduling queries that include `_events` (subscriber-based) tables, additional optimizations are invoked. These optimization can be disabled using `--events_optimize=false`. The subscriber tables can detect they are responding to a schedule and may keep track of the last time the scheduled query has executed. This allows each subscriber to return the exact window of the schedule and delete buffered events immediately. This saves the most memory and disk usage possible while still allowing flexible scheduling. - -## Architecture - -An osquery event publisher is a combination of a threaded run loop and event storage abstraction. The publisher loops on some selected resource or uses operating system APIs to register callbacks. The loop or callback introspects on the event and sends it to every appropriate subscriber. An osquery event subscriber will send subscriptions to a publisher, save published data, and react to a query by returning appropriate data. - -The pubsub runflow is exposed as a publisher `setUp()`, a series of `addSubscription(const SubscriptionRef)` by subscribers, a publisher `configure()`, and finally a new thread scheduled with the publisher's `run()` static method as the entry point. For every event the publisher receives it will loop through every `Subscription` and call `fire(const EventContextRef, EventTime)` to send the event to the subscriber. - -## Example: inotify - -Filesystem events are the simplest example. Let's consider Linux's inotify framework: [osquery/events/linux/inotify.cpp](https://github.com/osquery/osquery/blob/master/osquery/events/linux/inotify.cpp) implements an osquery publisher. - -There's a list of yet-to-be-implemented uses of the inotify publisher, but a simple example includes querying for every change to `/etc/passwd`. The [osquery/tables/events/linux/file_events.cpp](https://github.com/osquery/osquery/blob/master/osquery/tables/events/linux/file_events.cpp) table uses a pubsub subscription and implements a subscriber. The subscriptions are constructed from the configuration. See the file [integrity monitoring deployment](../deployment/file-integrity-monitoring.md) guide for details. - -## Event Subscribers - -Let's continue to use the inotify event publisher as an example. And let's implement a table that reports new files created in `/etc/`. The first thing we need is a [table spec](creating-tables.md): - -```python -table_name("new_etc_files") -schema([ - Column("path", TEXT), - Column("time", TEXT), -]) -implementation("new_etc_files@NewETCFilesEventSubscriber::genTable") -``` - -Now with the simplest table spec possible, we need to write `NewETCFilesEventSubscriber`! - -```cpp -#include -#include - -namespace osquery { -namespace tables { - -class NewETCFilesEventSubscriber : public EventSubscriber { - public: - // Implement the pure virtual init interface. - Status init() override; -}; -``` - -Done! Well, not exactly. This subscriber will do nothing since it hasn't given the publisher a subscription nor set up a callback to save appropriate events. To create a subscription we must know more about the publisher, for inotify we must create a [`INotifySubscriptionContext`](https://github.com/osquery/osquery/blob/master/osquery/events/linux/inotify.h) then subscribe to the publisher using this context and a callback. - -Let's implement `NewETCFilesEventSubscriber::init()` to add the subscription: - -```cpp -Status NewETCFilesEventSubscriber::init() { - // We templated our subscriber to create an inotify publisher-specific - // subscription context. - auto sc = createSubscriptionContext(); - sc->path = "/etc"; - sc->recursive = true; - // 'mask' is specific to inotify. - sc->mask = IN_CREATE; - subscribe(&NewETCFilesEventSubscriber::Callback, sc); -} -``` - -The final line in `init()` binds the subscription context to a callback, which we haven't defined or implemented. Let's modify the subscriber to include this callback: - -```cpp -class NewETCFilesEventSubscriber : public EventSubscriber { - public: - Status init() override; - Status Callback(const ECRef& ec, const SCRef& sc); -}; - -REGISTER(NewETCFilesEventSubscriber, "event_subscriber", "new_etc_files"); -``` - -Now the call to `subscribe` is meaningful: If the publisher generates an event and if the event details match the subscription request details, or new files all subfolders within "/etc", `Callback` will _fire_. - -Finally, we must implement the callback. The callback is responsible for turning the event information into an osquery `Row` data structure and saving that to the backing store (RocksDB). Such that, at query time the rows can be fetched and returned to the caller. - -```cpp -Status NewETCFilesEventSubscriber::Callback(const ECRef& ec, const SCRef& sc) { - Row r; - r["path"] = ec->path; - r["time"] = ec->time_string; - add(r, ec->time); - return Status(0, "OK"); -} -``` - -Simple. Notice that `ec->time_string` provides a string-formatted time that eliminates the need for casting. diff --git a/docs/wiki/development/reading-files.md b/docs/wiki/development/reading-files.md deleted file mode 100644 index 283a6707115..00000000000 --- a/docs/wiki/development/reading-files.md +++ /dev/null @@ -1,47 +0,0 @@ -# Reading Files - -[/osquery/filesystem/filesystem.h](https://github.com/osquery/osquery/blob/master/osquery/filesystem/filesystem.h) contains utilities for accessing the filesystem. - -Consider the following example for reading a file from the filesystem: - -```cpp -#include -#include -#include - -const std::string kPath = "/foo/bar.txt" - -int main(int argc, char* argv[]) { - auto s = osquery::pathExists(kPath); - if (s.ok()) { - std::string content; - s = osquery::readFile(kPath, content); - if (s.ok()) { - std::cout << "Contents of " << kPath << ":\n"; - std::cout << content; - } else { - std::cerr << "Error reading file: " << s.toString(); - return s.code(); - } - } else { - std::cerr << "The path doesn't exist\n"; - return 1; - } - return 0; -} -``` - -To internalize the main API, consider the same example without error checking: - -```cpp -#include -#include -#include - -int main(int argc, char* argv[]) { - std::string content; - osquery::readFile("/foo/bar.txt", content); - std::cout << "Contents of " << "/foo/bar.txt" << ":\n" << content; - return 0; -} -``` diff --git a/docs/wiki/development/unit-tests.md b/docs/wiki/development/unit-tests.md deleted file mode 100644 index 449bee442fa..00000000000 --- a/docs/wiki/development/unit-tests.md +++ /dev/null @@ -1,55 +0,0 @@ -# Unit testing in osquery - -All commits to osquery should be well unit-tested. Having tests is useful for many reasons. In addition to the subtle advantage of being able to assert program correctness, tests are often the smallest possible executable which can run a given bit of code. This makes testing new features for memory leaks much easier. Using tools like Valgrind in conjunction with compiled tests, we can directly analyze the desired code with minimal outside influence. - -## Writing a test - -### Prerequisite - -This guide is going to take you through the process of creating and building a new unit test in the osquery project. - -First ensure that you can properly build the code, by referring to the ["building osquery"](building.md) guide. - -Before you modify osquery code (or any code for that matter), make sure that you can successfully execute all tests. The steps for building and running tests are particular to the platform and build toolchain you are using, so again refer to the ["building osquery"](building.md) guide for the appropriate information for your setup. - -## Adding a test - -We'll create a test in the "osquery/examples" subdirectory of the main repository. Let's create a file "example_test.cpp" in that directory. - -Let's start with the following content: - -```cpp -#include - -namespace osquery { -namespace example { - -class ExampleTests : public testing::Test {}; - -TEST_F(ExampleTests, test_plugin) { - EXPECT_TRUE(1 == 1); -} -} -} - -int main(int argc, char* argv[]) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} -``` - -The above code is very simple. If you're unfamiliar with the syntax/concepts of the Google Test framework, read the [Google Test Primer](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#basic-concepts). - -## Building a test - -Each component of osquery you're working on has its own "CMakeLists.txt" file. For example, the _tables_ component (folder) has its own "CMakeLists.txt" file at [osquery/tables/CMakeLists.txt](https://github.com/osquery/osquery/blob/master/osquery/tables/CMakeLists.txt). The file that we're going to be modifying today is [osquery/CMakeLists.txt](https://github.com/osquery/osquery/tree/master/osquery/CMakeLists.txt). Edit that file to include the following content: - -```CMake -add_osquery_executable(example_test example_test.cpp) -``` - -After you specify the test sources, add whatever libraries you have to link against and properly set the compiler flags, make sure you call `ADD_TEST` with your unit test. This registers it with CTest (CMake's test runner). - -## Extending a test - -Your test is just C++ code. Use the [Google Test documentation](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#assertions) to assist you in writing meaningful tests. diff --git a/docs/wiki/index.md b/docs/wiki/index.md deleted file mode 100644 index 3a477e32246..00000000000 --- a/docs/wiki/index.md +++ /dev/null @@ -1,40 +0,0 @@ -osquery is an operating system instrumentation framework for Windows, OS X (macOS), and Linux. The tools make low-level operating system analytics and monitoring both performant and intuitive. - -osquery exposes an operating system as a high-performance relational database. This allows you to write SQL queries to explore operating system data. With osquery, SQL tables represent abstract concepts such as running processes, loaded kernel modules, open network connections, browser plugins, hardware events or file hashes. - -## Getting Started - -If you're interested in **installing osquery** check out the install guide for [Windows](installation/install-windows.md), [macOS](installation/install-macos.md), and [Linux](installation/install-linux.md). - -If you're interested in **developing queries** and **exploring** tables, check out [using osqueryi](introduction/using-osqueryi.md). - -If you're interested in **deploying osquery** to provide your organization with deeper insight into your Linux, macOS, -and Windows hosts check out the [using osqueryd guide](introduction/using-osqueryd.md). If, as part of deploying -osquery, you've run a vulnerability analyzer on either the osquery executable or the open-source repository and it has -flagged a vulnerability in one of osquery's dependencies, please check our most up-to-date [bulletins about known issues in third-party dependencies](deployment/dependency-security.md). - -If you're interested in **extending one of the existing osquery tools** or improving core libraries, read the developer documentation pages. You should start with "[building the code](development/building.md)" and read the project's "[CONTRIBUTING.md](https://github.com/osquery/osquery/blob/master/CONTRIBUTING.md)". - -If you're interested in **integrating osquery** into your own tool, check out the [osquery SDK](development/osquery-sdk.md). - -## High Level Features - -The high-performance and low-footprint distributed host monitoring daemon, `osqueryd`, allows you to schedule queries to be executed across your entire infrastructure. The daemon takes care of aggregating the query results over time and generates logs which indicate state changes in your infrastructure. You can use this to maintain insight into the security, performance, configuration, and state of your entire infrastructure. `osqueryd`'s logging can integrate into your internal log aggregation pipeline, regardless of your technology stack, via a robust plugin architecture. - -The interactive query console, `osqueryi`, gives you a SQL interface to try out new queries and explore your operating system. With the power of a complete SQL language and dozens of useful tables built-in, `osqueryi` is an invaluable tool when performing incident response, diagnosing a systems operations problem, troubleshooting a performance issue, etc. - -osquery is cross-platform. Even though osquery takes advantage of very low-level operating system APIs, you can build and use osquery on Windows, macOS, Ubuntu, CentOS and other popular enterprise Linux distributions. This has the distinct advantage of allowing you to be able to use one platform for monitoring complex operating system state across your entire infrastructure. Monitor your corporate Windows or macOS clients the same way you monitor your production Linux servers. - -To make deploying osquery in your infrastructure as easy as possible, osquery comes with native packages for all supported operating systems. There is extensive tooling and documentation around creating packages so packaging and deploying your custom osquery tools can be just as easy too. - -To assist with the rollout process, the osquery user guide has detailed documentation on internal deployment. osquery was built so that every environment-specific aspect of the toolchain can be hot-swapped at runtime with custom plugins. Use these interfaces to deeply integrate osquery into your infrastructure, if none of the several existing plugins suit your needs. - -Additionally, osquery's codebase is made up of high-performance, modular components with clearly documented public APIs. These components can be easily strung together to create new, interesting applications and tools. Language bindings exist for many languages using a Thrift interface, so you can continue using comfortable and familiar technologies. - -## Getting Help - -If any part of osquery is not working as expected, please create a [GitHub Issue](https://github.com/osquery/osquery/issues). Keep in touch with osquery developers and users in our Slack: [Join osquery](https://join.slack.com/t/osquery/shared_invite/zt-1wipcuc04-DBXmo51zYJKBu3_EP3xZPA). - -## Documentation - -This wiki, hosted on ReadTheDocs.io, is written in Markdown and kept within the osquery GitHub repository in the [docs/wiki](https://github.com/osquery/osquery/tree/master/docs/wiki) directory. Please submit changes using GitHub pull requests. The wiki is built automatically with every commit and available as "[latest](https://osquery.readthedocs.io/en/latest/)". A "stable" release of the documentation is built alongside every GitHub-tagged release of osquery. diff --git a/docs/wiki/installation/cli-flags.md b/docs/wiki/installation/cli-flags.md deleted file mode 100644 index 7b8177e0d8b..00000000000 --- a/docs/wiki/installation/cli-flags.md +++ /dev/null @@ -1,714 +0,0 @@ -# Command-line flags - -The osquery shell and daemon use optional command-line (CLI) flags to control initialization, disable/enable features, and select plugins. These flags are powered by Google Flags and are somewhat complicated. Understanding how flags work in osquery will help with stability and greatly reduce issue debugging time. - -Most flags apply to both tools, `osqueryi` and `osqueryd`. The shell contains a few more to help with printing and other helpful one-off modes of operation. Expect Linux, macOS, and Windows to include platform specific flags too. Most platform specific flags will control the OS API and library integrations used by osquery. Warning, this list is still not the 'complete set' of flags. Refer to the techniques below for obtaining ground truth and check other components of this Wiki. - -Flags that do not control startup settings may be included as "options" within [configuration](../deployment/configuration.md). Essentially, any flag needed to help osquery determine and discover a configuration must be supplied via command-line arguments. Google Flags enhances this to allow flags to be set within environment variables or via a "master" flag file. - -To see a full list of flags for your osquery version use `--help` or select from the `osquery_flags` table: - -```sql -$ osqueryi -osquery> SELECT * FROM osquery_flags; -``` - -To see the flags that have been updated by your configuration, a flag file, or by the shell try: - -```sql -osquery> SELECT * FROM osquery_flags WHERE default_value <> value; -``` - -## Flagfile - -A special flag, part of Google Flags, can be used to read additional flags from a line-delimited file. On macOS and Linux this `--flagfile` is the recommended way to add/remove the following CLI-only initialization flags. - -`--flagfile /etc/osquery/osquery.flags` - -Include line-delimited switches to be interpreted and used as CLI-flags: - -```text ---config_plugin=custom_plugin ---logger_plugin=custom_plugin ---distributed_plugin=custom_plugin ---watchdog_level=0 -``` - -If no `--flagfile` is provided, osquery will try to find and use a "default" flagfile at `/etc/osquery/osquery.flags.default`. Both the shell and daemon will discover and use the defaults. - -> NOTICE: Flags in a `flagfile` should not be wrapped in quotes, shell-macro/variable expansion is not applied! - -## Configuration control flags - -`--config_plugin=filesystem` - -Config plugin name. The type of configuration retrieval, the default **filesystem** plugin reads a configuration JSON from disk. - -Built-in options include: **filesystem**, **tls** - -`--config_path=/etc/osquery/osquery.conf` - -The **filesystem** config plugin's path to a JSON file. -On macOS the default path is `/var/osquery/osquery.conf`. -If you want to read from multiple configuration paths, create a directory: `/etc/osquery/osquery.conf.d/`. All files within that optional directory will be read and merged in lexical order. - -`--config_refresh=0` - -An optional configuration refresh interval in seconds. By default a configuration is fetched only at osquery load. If the configuration should be auto-updated, set a "refresh" time to a value in seconds greater than 0. If the configuration endpoint cannot be reached during runtime, the normal retry approach is applied (e.g., the **tls** config plugin will retry 3 times). - -`--config_accelerated_refresh=300` - -If a configuration refresh is used (`config_refresh > 0`) and the refresh attempt fails, the accelerated refresh will be used. This allows plugins like **tls** to fetch fresh data after having been offline for a while. - -`--config_check=false` - -Check the format of an osquery config and exit. Arbitrary config plugins may be used. osquery will return a non-0 exit if the parsing failed. - -`--config_dump=false` - -Request that the configuration JSON be printed to standard out before it is updated, then exit the process. In this case "updated" means applied to the active config. When osquery starts it performs an initial update from the config plugin. To quickly debug the content retrieved by custom config plugins use this in tandem with `--config_check`. - -## Daemon control flags - -`--force=false` - -Force `osqueryd` to kill previously-running daemons. The daemon will check for an existing "pidfile". If found, and if it contains a pid of a process named "osqueryd", the process will be killed. - -`--pidfile=/var/osquery/osqueryd.pidfile` - -Path to the daemon pidfile mutex. The file is used to prevent multiple osqueryd processes starting. - -`--disable_watchdog=false` - -Disable userland watchdog process. `osqueryd` uses a watchdog process to monitor the memory and CPU utilization of threads executing the query schedule. If any performance limit is violated, the "worker" process will be restarted. - -`--watchdog_level=0` - -Performance limit level (`0`=normal, `1`=restrictive, `-1`=disabled). The watchdog process uses a "level" to configure performance limits. - -The level limits are as follows: -Memory: default 200M, restrictive 100M -CPU: default 10% (for 12 seconds), restrictive 5% (for 6 seconds) - -It is better to set the level to disabled (`-1`) rather than disabling the watchdog outright, as the worker/watcher concept is used for extensions auto-loading too. - -The watchdog "profiles" can be overridden for Memory and CPU Utilization. - -`--watchdog_memory_limit=0` - -If this value is >0 then the watchdog level (`--watchdog_level`) for maximum memory is overridden. Use this if you would like to allow the `osqueryd` process to allocate more than 200M, but somewhere less than 10G. This memory limit is expressed as a value representing MB. - -`--watchdog_utilization_limit=0` - -If this value is >0 then the watchdog level (`--watchdog_level`) for maximum sustained CPU utilization is overridden. Use this if you would like to allow the `osqueryd` process to use more than 10% of CPU for `--watchdog_latency_limit` seconds of wall time. - -This value sets a maximum number of allowed CPU cycles counted as the `processes` table's `user_time` and `system_time`. The default value is `10`, meaning 10% of CPU utilization allowed. - -The CPU utilization limit is calculated the following way: -```text -cpu_limit_ms = number_of_cores * check_interval_ms * (watchdog_utilization_limit / 100.0) -``` -Where: -- `number_of_cores` is the number of all cpu cores (physical + virtual). -- `check_interval_ms` is 3 seconds. It is how often the watchdog checks worker and extension processes for CPU utilization. - -Example: On a 2020 MacBook with Quad-Core Intel Core i7, the number of physical cores is 4 but `number_of_cores` is 8, thus by default the CPU utilization limit is `2400ms` meaning that every three seconds the processes are expected to use less than `2400ms` of CPU time (10% of all the CPU time `8 * 3000ms`). - -> NOTE: Before osquery 5.10.0, on systems with virtual cores, osquery is actually using the count of physical cores, which is normally half the amount, which results in setting the limit to half of the configured utilization (`1200ms` is actually 5% of the full `8 * 3000ms` CPU time). - -`--watchdog_latency_limit=0` - -If this value is >0 then the watchdog level (`--watchdog_level`) for time allowed to spend at maximum sustained CPU utilization is overridden. Use this if you would like to allow the `osqueryd` process to use more than the cpu utilization limit for longer than the defaults. - -This value is a duration in seconds that the watchdog allows osquery to spend at maximum sustained CPU utilization. - -The default value is 12 seconds. - -`--watchdog_delay=60` - -A delay in seconds before the watchdog process starts enforcing memory and CPU utilization limits. The default value `60s` allows the daemon to perform resource intense actions, such as forwarding logs, at startup. - -`--watchdog_forced_shutdown_delay=4` - -Amount of seconds to wait to issue a forced shutdown, after the watchdog has issued a graceful shutdown request to a worker or extension, due to resource limits being hit. -Note that on Windows this doesn't have any effect currently, since the watchdog issues a TerminateProcess as a "graceful" shutdown, which immediately kills the process. - -`--enable_extensions_watchdog=false` - -By default the watchdog monitors extensions for improper shutdown, but NOT for performance and utilization issues. Enable this flag if you would like extensions to use the same CPU and memory limits as the osquery worker. This means that your extensions or third-party extensions may be asked to stop and restart during execution. - -`--enable_watchdog_debug=false` - -If set to true, every 3 seconds the watchdog will log the measured CPU utilization and memory footprint of all the monitored processes. -(To generate the logs this flag requires `--verbose` to be set.) - -`--table_delay=0` - -Add a millisecond delay between multiple table calls (when a table is used in a JOIN). A `200` millisecond delay will trade about 20% additional time for a reduced 5% CPU utilization. - -`--hash_cache_max=500` - -The `hash` table implements a cache that is invalidated when file path inodes are changed. Eviction occurs in chunks if the max-size is reached. This max should remain relatively low since it will persist in the daemon's resident memory. - -`--hash_delay=20` - -Add a millisecond delay between multiple `hash` attempts (aka when scanning a directory). This adds about 50% additional wall-time for 150 files. This reduces the instantaneous resource need from hashing new files. - -`--disable_hash_cache=false` - -Set this to true if you would like to disable file hash caching and always regenerate the file hashes every request. The default osquery configuration may report hashes incorrectly if things are editing filesystems outside of the OS's control. - -### Windows-only daemon control flags - -Windows builds include a `--install` and `--uninstall` that will create a Windows service using the `osqueryd.exe` binary and preserve an optional `--flagfile` if provided. - -## Backing storage control flags - -`--database_path=/var/osquery/osquery.db` - -If using a disk-based backing store, specify a path. osquery will keep state using a "backing store" using RocksDB by default. This state holds event information such that it may be queried later according to a schedule. It holds the results of the most recent query for each query within the schedule. This last-queried result allows query-differential logging. - -`--database_dump=false` - -Helpful for debugging database problems. This will print a line for each key in the backing store. Note: There could be MBs worth of data in the backing store. - -## Extensions control flags - -`--disable_extensions=false` - -Disable extension API. See the [SDK development](../development/osquery-sdk.md) page for more information on osquery extensions, and the [deployment](../deployment/extensions.md) page for how to use extensions. - -`--extensions_socket=/var/osquery/osquery.em` - -Path to the extensions UNIX domain socket. -[Extensions](../deployment/extensions.md) use a UNIX domain socket for communication. It is very uncommon to change the location of the file. The osquery shell may use extensions, but the socket location is relative to the user invoking the shell and does not support concurrent shells. - -`--extensions_autoload=/etc/osquery/extensions.load` - -Optional path to a list of auto-loaded and managed extensions. -If using an extension to provide a proprietary config or logger plugin the extension process can be started by the daemon. Include line-delimited paths to extension executables. See the extensions [deployment](../deployment/extensions.md) page for more details on extension auto-loading. - -`--extensions_timeout=3` - -Seconds to wait for auto-loaded extensions to register. -osqueryd may depend on a config plugin from an extension. If the requested config plugin name is not registered within the timeout the daemon will exit with a failure. - -`--extensions_interval=3` - -Seconds delay between extension connectivity checks. -Extensions are loaded as processes. They are expected to start a thrift service thread. The osqueryd process will continue to check this API. If an extension process is incorrectly stopped, osqueryd will detect the connectivity failure and unregister the extension. - -`--extensions_require=custom1,custom1` - -Optional comma-delimited set of extension names to require before `osqueryi` or `osqueryd` will start. The tool will fail if the extension has not started according to the interval and timeout. - -`--extensions_default_index=true` - -Enable INDEX (and thereby constraints) on all extension table columns. Provides backwards compatibility for extensions (or SDKs) that don't correctly define indexes in column options. See issue 6006 for more details. - -## Remote settings flags (optional) - -When using non-default [remote](../deployment/remote.md) plugins such as the **tls** config, logger and distributed plugins, there are process-wide settings applied to every plugin. - -`--tls_hostname=` - -When using **tls**-based config or logger plugins, a single TLS host URI is used. Using separate hosts for configuration and logging is not supported among the **tls**-based plugin suite. Provide a host name and optional port, e.g.: `facebook.com` or `facebook.com:443`. - -`--tls_session_reuse=true` - -Reuse TLS session sockets. - -`--tls_session_timeout=3600` - -Once a socket is created, the lifetime is governed by this flag. If this value is set to `0`, then transport never times out unless the remote end closes the connection or an error occurs. - -`--tls_client_cert=` - -See the **tls**/[remote](../deployment/remote.md) plugin documentation. Optionally provide a path to a PEM-formatted client TLS certificate. - -`--tls_client_key=` - -See the **tls**/[remote](../deployment/remote.md) plugin documentation. Optionally provide a path to a decrypted/password-less PEM-formatted client TLS private key. - -`--tls_server_certs=` - -See the **tls**/[remote](../deployment/remote.md) plugin documentation. Optionally provide a path to a PEM-formatted server or authority certificate bundle. This path will be used as either an explicit set of accepted certificates or an OpenSSL-verify path directory of well-formed filename certificates. - -`--disable_enrollment=false` - -See the **tls**/[remote](../deployment/remote.md) plugin documentation. Remote plugins use an enrollment process to enable possible server-side implemented authentication and identification/authorization. Config and logger plugins implicitly require enrollment features. It is not recommended to disable enrollment and this option may be removed in the future. - -`--enroll_secret_path=` - -See the **tls**/[remote](../deployment/remote.md) plugin documentation. A very simple authentication/enrollment involves posting a deployment or staged shared secret. This secret should be protected on the host, but potentially shared among an enterprise or fleet. Provide a path for the osquery process to read and use during enrollment phases. - -`--config_tls_endpoint=` - -The **tls** endpoint path, e.g.: `/api/v1/config` when using the **tls** config plugin. See the other **tls_** related CLI flags. - -`--config_tls_max_attempts=3` - -The total number of attempts that will be made to the remote config server if a -request fails. If an attempt fails, it will be retried with exponential -backoff, up to the max number of attempts set. - -`--logger_tls_endpoint=` - -The **tls** endpoint path, e.g.: `/api/v1/logger` when using the **tls** logger plugin. See the other **tls_** related CLI flags. - -`--enroll_tls_endpoint=` - -See the **tls**/[remote](../deployment/remote.md) plugin documentation. An enrollment process will be used to allow server-side implemented authentication and identification/authorization. You must provide an endpoint relative to the `--tls_hostname` URI. - -`--tls_enroll_max_attempts=12` - -The total number of attempts that will be made to the remote enroll server if a request fails. -If an attempt fails, it will be retried up to the max number of attempts set, with exponential backoff limited by the flag `--tls_enroll_max_interval`. -If the flag is set to 0, the amount of attempts will be infinite. - -`--tls_enroll_max_interval=600` - -Maximum wait time in seconds between enroll retry attempts. This works in conjunction with `--tls_enroll_max_attempts`, and affects both the limited and the infinite attempts case. - -`--logger_tls_period=3` - -See the **tls**/[remote](../deployment/remote.md) plugin documentation. This is a number of seconds before checking for buffered logs. Results are sent to the TLS endpoint in intervals, not on demand (unless the period=0). - -`--logger_tls_compress=false` - -Optionally enable GZIP compression for request bodies when sending. This is optional and disabled by default, as the deployment must explicitly know that the logging endpoint supports GZIP for content encoding. - -`--logger_tls_max_linesize=1048576` - -It is common for TLS/HTTPS servers to enforce a maximum request body size. The default behavior in osquery is to enforce each log line be under 1MB (`1048576` bytes). This means each result line from a query's results cannot exceed 1M, this is very unlikely. Each log attempt will try to forward up to 1024 lines. If your service is limited request bodies, configure the client to limit the log line size. - -Use this only in emergency situations as size violations are dropped. It is extremely uncommon for this to occur, as the `--value_max` for each column would need to be drastically larger, or the offending table would have to implement several hundred columns. - -`--logger_tls_max_lines=1024` - -This configures the max number of log lines to send every period (meaning every `logger_tls_period`). - -`--logger_tls_backoff_max=3600` - -Maximum seconds to wait before flushing logs over TLS/HTTPS. The exponential backoff kicks in when regular flush of buffered logs fails. Should be a multiple of `logger_tls_period`. 0 disables backoff. The max backoff time can be updated dynamically via `config_tls_endpoint`. - -`--distributed_tls_read_endpoint=` - -The URI path which will be used, in conjunction with `--tls_hostname`, to create the remote URI for retrieving distributed queries when using the **tls** distributed plugin. - -`--distributed_tls_write_endpoint=` - -The URI path which will be used, in conjunction with `--tls_hostname`, to create the remote URI for submitting the results of distributed queries when using the **tls** distributed plugin. - -`--distributed_tls_max_attempts=3` - -The total number of attempts that will be made to the remote distributed query server if a request fails when using the **tls** distributed plugin. - -## Daemon runtime control flags - -`--schedule_splay_percent=10` - -Percent to splay config times. -The query schedule often includes several queries with the same interval. -It is often not the intention of the schedule author to run these queries together at that interval. But rather, each query should run at about the interval. A default schedule splay of 10% is applied to each query when the configuration is loaded. - -`--schedule_max_drift=60` - -Max time drift in seconds. -The scheduler tries to compensate the splay drift until the delta exceeds this value. -If the max drift is exceeded the splay will be reset to zero and the compensation process will start from the beginning. -This is needed to avoid the problem of endless compensation (which is CPU greedy) after a long SIGSTOP/SIGCONT pause or something similar. Set it to zero to disable drift compensation. - -`--pack_refresh_interval=3600` - -Query Packs may optionally include one or more discovery queries, which allow you to use osquery queries to manage which packs should be loaded at runtime. osquery will natively re-run the discovery queries from time to time, to make sure that all of the correct packs are executing. This flag allows you to specify that interval. - -`--pack_delimiter=_` - -Control the delimiter between pack name and pack query names. When queries are added to the daemon's schedule they inherit the name of the pack. A query named `info` within the `general_info` pack will become `pack_general_info_info`. Changing the delimiter to "/" turned the scheduled name into: `pack/general_info/info`. - -`--disable_caching=false` - -"Caching" refers to short cutting the table implementation and returning the same results from the previous query against the table. This is not related to differential results from scheduled queries, but does affect the performance of the schedule. Results are cached when different scheduled queries in a schedule use the same table, without providing query constraints. Caching should NOT affect data freshness since the cache life is determined as the minimum interval of all queries against a table. - -`--schedule_default_interval=3600` - -Optionally set the default interval value. This is used if you schedule a query which does not define an interval. - -`--schedule_timeout=0` - -Limit the schedule. Use `0` for no limit. Optionally limit the `osqueryd`'s life by adding a schedule limit in seconds. This should only be used for testing. - -`--disable_tables=table_name1,table_name2` - -Comma-delimited list of table names to be disabled. This allows osquery to be launched without certain tables. - -`--read_max=52428800` (50 MB) - -Maximum file read size. The daemon or shell will first 'stat' each file before reading. If the reported size is greater than `read_max` a "file too large" error will be returned. - -## Linux-only runtime control flags - -`--malloc_trim_threshold=200` - -Memory threshold in MB used to decide when a malloc_trim will be called to reduce the retained memory. -When the flag is not provided, the value will be chosen automatically between 80% of the `watchdog_memory_limit` if the watchdog is not disabled and 200MB in the case it is. -Providing the flag with a value always overrides the automatic behavior and setting it to 0 completely disables calling malloc_trim. -This is an attempt to reduce the amount of memory that the malloc allocator has in its caches in the worker process, which sometimes leads to hitting the watchdog memory limit more often. -The downside is that in some cases there can be a performance hit in a query execution, since the cache was there to speed up future allocations. -Note: When the watchdog starts, it takes a snapshot of the amount of memory that the worker uses in that moment; from that value then it adds the allocated memory limit set in `watchdog_memory_limit` and finds at how much memory used by the worker it should trigger. -This means that if the `watchdog_memory_limit` is set to 200MB, the watchdog triggers at 200MB + something (around 15 to 30MB) used, not at 200MB. The malloc_trim system though doesn't have access to that information, so the best thing it can do is to use `watchdog_memory_limit` to calculate its own threshold. -This should be good enough, but the user should be aware that how soon malloc_trim acts in respect to how soon the watchdog would've acted is actually slightly variable. - - -## Windows-only runtime control flags - -`--users_service_delay=250` - -Defines the amount of milliseconds to wait during a scan of users information, between a batch of 100 users and the other. This is meant to throttle the CPU usage of osquery and especially the LSASS process on a Windows Server DC. The first users batch is always gathered immediately at the start of the scan. - -`--users_service_interval=1800` - -Defines the amount of seconds to wait between full scans of users information. The background service first obtains a list of all the users that are present on a machine, then start obtaining their details, using `users_service_delay` to slow down the process, then when the whole list has been processed, it will sleep `users_service_interval` seconds. - -`--groups_service_delay=150` - -Works the same as `users_service_delay`, but for the groups service. The default value is lower because collecting groups information is less performance intensive. - -`--groups_service_interval=1800` - -Works the same as `users_service_interval`, but for the groups service. - -## Events control flags - -`--disable_events=false` - -Disable or enable osquery Operating System [eventing publish-subscribe](../development/pubsub-framework.md) APIs. Setting this to `true` (which is the default value) disables tables that report evented data (tables whose names end with `_events`) and querying them will generate a warning. - -`--events_expiry=3600` - -Expiration age for evented data (in seconds), applied once the data is queried. Until an evented table is queried, its collected events are cached in backing-store. *Events are only expired (i.e., removed from the table) when the evented table is queried.* For example, if `--events_expiry=1`, then events older than 1 second will only appear in the next `SELECT` from the subscriber. If no `SELECT` occurs, those events will be saved in the backing store *indefinitely* or until the `events_max` limit is reached (see below). If, on the other hand, the table contains recent events that have not yet reached expiration age, the same table can be queried repeatedly in quick succession and the same data will continue to be present unless it had reached the expiration age when it was last queried, at which point it will be removed. `3600` seconds is the default, but if querying on an interval shorter than `3600`, you may wish to lower this value to avoid retrieving duplicate events. - -`--events_optimize=true` - -Since event rows are only "added" it does not make sense to emit "removed" results. An optimization can occur within the osquery daemon's query schedule. Every time the `SELECT` query runs on a subscriber, the current time is saved. Subsequent `SELECT`s will use the previously saved time as the lower bound. This optimization is removed if any constraints on the "time" column are included. - -`--events_max=50000` - -Maximum number of events to buffer in the backing store while waiting for a query to "drain" them (if and only if the events are old enough to be expired out, see above). For example, the default value indicates that a maximum of the `50000` most recent events will be stored. The right value for *your* osquery deployment, if you want to avoid missed/dropped events, should be considered based on the combination of your host's event occurrence frequency and the interval of your scheduled queries of those tables. - -`--events_enforce_denylist=false` - -This controls whether watchdog denylisting is enforced on queries using "*_events" (event-based) tables. As these these queries operate on meta-generated table logic, performance issues are unavoidable. It does not make sense to denylist. Enforcing this may lead to adverse and opposite effects because events will buffer longer and impact RocksDB storage. - -This only considers queries that are entirely event-based. For example `SELECT * FROM process_events` is considered, but `SELECT * FROM process_events join time` is not. - -It is not recommended to set this to `true`. - -### Windows-only events control flags - -`--enable_ntfs_event_publisher Enables the NTFS event publisher` - -`--enable_powershell_events_subscriber Enables Powershell events` - -`--enable_windows_events_publisher Enables the Windows events publisher` - -`--enable_windows_events_subscriber Enables Windows Event Log events` - -On Windows, in addition to the `--disable_events=false` flag mentioned above, each category of evented data must also be enabled individually, by enabling the corresponding osquery publisher and osquery subscriber. By default, all are disabled, and the corresponding evented tables will be empty. Note that an event publisher within osquery subscribes to events *from the OS* and then publishes them to an osquery event subscriber. For the current complete list of event sources usable by osquery, see `osqueryi.exe --help | findstr -i Event`. - -`--windows_event_channels=System,Application,Setup,Security` - -List of Windows Event Log channels for osquery to subscribe to. By default, osquery's Windows Event Log publisher will deliver some of the more common major event log channels. However, you can select additional channels using the `Log Name` field value in the Windows event viewer. Note the lack of quotes around the channel names. For example, to subscribe to Windows PowerShell script block logging, one would first enable the feature in Windows itself, and then subscribe to the channel with `--windows_event_channels=Microsoft-Windows-PowerShell/Operational` - -### Linux-only events control flags - -`--hardware_disabled_types=partition` - -This is a comma-separated list of UDEV types to drop. On machines with flash-backed storage it is likely you'll encounter lots of noise from `disk` and `partition` types. - -### macOS-only events control flags - -`--disable_endpointsecurity=true` - -Setting to `false` (in combination with `--disable_events=false`) turns on EndpointSecurity-based event collection within osquery (supported in macOS 10.15 and newer), and enables the use of the `es_process_events` table. This feature requires running osquery as root. It also requires that the osquery executable be code-signed and notarized to have the Endpoint Security client entitlement; official release builds of osquery will be appropriately code-signed. Lastly, it requires that the host give Full Disk Access permission to the osqueryd executable; for more information see the [process auditing section of osquery's deployment documentation](../deployment/process-auditing.md) as well as [installing osquery on macOS](./install-macos.md). - -`--disable_endpointsecurity_fim=true` - -Setting to `false` (in addition to `--disable_events=false` and `--disable_endpointsecurity=false`) will turn on EndpointSecurity based file event collection in osquery, running on macOS 10.15 and newer. This enables the use of `es_process_file_events` table. - -`--es_fim_mute_path_literal` - -This is a comma delimited list of path literals, which when set, is passed to EndpointSecurity based `es_process_file_events` table. This will result in events being muted for the paths set in here. - -`--es_fim_mute_path_prefix` - -This is a comma delimited list of path prefixes, which when set is passed to EndpointSecurity based `es_process_file_events` table. This will result in events being muted which match the path prefixes. - -## Logging/results flags - -`--logger_plugin=filesystem` - -Logger plugin name. The default logger is **filesystem**. This writes the various log types as JSON to specific file paths. - -Multiple logger plugins may be used simultaneously, effectively copying logs to each interface. Separate plugin names with a comma when specifying the configuration (`--logger_plugin=filesystem,syslog`). - -Built-in options include: **filesystem**, **tls**, **syslog**, and several Amazon/AWS options. - -`--disable_logging=false` - -Disable `ERROR`/`WARNING`/`INFO` (a.k.a. status logs) and query result [logging](../deployment/logging.md). - -`--logger_event_type=true` - -Log scheduled results as events. - -`--logger_snapshot_event_type=false` - -Log scheduled snapshot results as events, similar to differential results. If this is set to `true` then each row from a snapshot query will be logged individually. - -`--logger_min_status=0` - -The minimum level for status log recording. Use the following values: `INFO = 0, WARNING = 1, ERROR = 2`. To disable all status messages use `3` or higher. When using `--verbose`, this value is ignored. - -`--logger_min_stderr=0` - -The minimum level for status logs written to stderr. Use the following values: `INFO = 0, WARNING = 1, ERROR = 2`. To disable all status messages use `3` or higher. It does **not** limit or control the types sent to the logger plugin. When using `--verbose` this value is ignored. - -`--logger_stderr=true` - -The default behavior is to also write status logs to stderr. Set this flag to false to disable writing (copying) status logs to stderr. In this case `--verbose` is respected. - -`--logger_path=/var/log/osquery/` - -Directory path for `ERROR`/`WARN`/`INFO` and query result logging by the **filesystem** plugin. - -`--logger_mode=0640` - -File mode for output log files by the **filesystem** plugin, provided as an octal string. Note that this affects both the query result log and the status logs and only works on POSIX platforms. (Versions previous to osquery 5.0.0 were incorrectly interpreting `logger_mode` as a number in decimal format, not octal.) -**Warning**: If run as root, log files may contain sensitive information! - -`--logger_rotate=false` - -When enabled, the **filesystem** plugin will rotate logs based on size. An example includes `/var/log/osquery/osqueryd.results.log` being rotated to `/var/log/osquery/osqueryd.results.log.1` when the trigger size is reached. Files after the first rotation will be Zstandard-compressed and will use the `.zst` file extension. A max number of log files will be maintained and logs overflowing this count will be deleted after rotation. - -`--logger_rotate_size=26214400` (25MB) - -A size, specified in bytes, to trigger rotation when enabled with `--logger_rotate`. A result or snapshot log will be rotated when it grows past this size. The size is checked before each new write to the logfile. - -`--logger_rotate_max_files=25` - -The max number of result and snapshot rotation files. The count applies to each individually, meaning by default osquery will maintain 25 results files and 25 snapshot files. If a rotation happens after hitting this max, the oldest file will be removed. - -`--logger_syslog_facility` - -Set the syslog facility (number) `0`-`23` for the results log by the **syslog** plugin. When using the **syslog** logger plugin, the default facility is `19` at the `LOG_INFO` level, which does not log to `/var/log/system`. - -`--logger_syslog_prepend_cee` - -Prepend a `@cee:` cookie to JSON-formatted messages sent to the **syslog** logger plugin. Several syslog parsers use this cookie to indicate that the message payload is parseable JSON. The default value is false. - -`--logger_kafka_brokers` - -A comma-delimited list of Kafka brokers to connect to. Format can be `protocol://host:port`, `host:port` or just `host` with the port number falling back to the default value of `9092`. `protocol` can be `plaintext` (default) or `ssl`. When protocol is `ssl`, `--tls_server_certs` value is used as certificate trust store. Optionally `--tls_client_cert` and `--tls_client_key` can be provided for TLS client authentication with Kafka brokers. - -`--logger_kafka_topic` - -The Kafka topic to publish logs to. When using multiple topics this configuration becomes the base topic that unconfigured queries fall back to. Please see the Kafka section of the [logging wiki](../deployment/logging.md) for more details. - -`--logger_kafka_acks` - -The number of acknowledgments the Kafka leader has to receive before a publish is considered successful. Valid options are (0, 1, "all"). - -`--logger_kafka_compression` - -Compression codec to use for compressing message sets. Valid options are ("none", "gzip"). Default is "none". - -`--buffered_log_max=1000000` - -There are multiple logger plugins that use a "buffered logging" implementation. The TLS and AWS loggers use this approach. This flag sets the maximum number of logs to buffer before dropping new logs. If the buffered logs have not been shuttled to the logger destination they will be purged in order of their timestamp. The oldest logs are purged first. - -Setting this to value to `0` means unlimited logs will be buffered. - -`--host_identifier=hostname` - -Field used to identify the host running osquery: `hostname`, `uuid`, `ephemeral`, `instance`, `specified`. - -DHCP may assign variable hostnames, if this is the case, you may need a consistent logging label. Four options are available to you: - -- `uuid` uses the platform (DMTF) host UUID, fetched at process start. -- `instance` uses an instance-unique UUID generated at process start, persisted in the backing store. -- `ephemeral` uses an instance-unique UUID generated at process start, not persisted. -- `specified` uses an ID provided by the `--specified_identifier` flag. - -`--specified_identifier=this.is.the.identifier` - -If `--host_identifier=specified` is set, use this value as the host identifier. - -`--verbose=false` - -Enable verbose informational messages. - -`--thrift_verbose=false` - -Enable thrift global output. - -`--value_max=512` - -Maximum returned row value size. - -`--schedule_lognames=false` - -Log executing scheduled query names at the `INFO` level, and not the `VERBOSE` level - -`--distributed_loginfo=false` - -Log executing distributed queries at the `INFO` level, and not the `VERBOSE` level - -`--decorations_top_level` - -Add decorators as top level JSON object members instead of being nested in a `decorations` member; this works for both result and status logs. For more details look at [Decorator queries](../deployment/configuration.md#decorator-queries) paragraph. -**NOTE**: Since osquery 5.10 the standard decorations like `unixTime`, `severity` and `line` are represented as true numbers in JSON, not as their string representations. - -## Distributed query service flags - -`--distributed_plugin=tls` - -Distributed plugin name. The default distributed plugin is not set. You must set `--disable_distributed=false --distributed_plugin=tls` (or whatever plugin you'd rather use instead of TLS) to enable the distributed feature. - -`--disable_distributed=true` - -Disable distributed queries functionality. By default, this is set to `true` (the distributed feature is disabled). Set this to `false` to enable distributed queries. - -`--distributed_interval=60` - -In seconds, the amount of time that osqueryd will wait between periodically checking in with a distributed query server to see if there are any queries to execute. - -## Syslog consumption flags - -There is a `syslog` virtual table that uses Events and a **rsyslog** configuration to capture results *from* syslog. Please see the [Syslog Consumption](../deployment/syslog.md) deployment page for more information. - -`--enable_syslog=false` - -Turn on the syslog ingestion event publisher. This is an 'explicit'-enable because it requires external configuration of **rsyslog**. - -`--syslog_pipe_path=/var/osquery/syslog_pipe` - -Path to the named pipe used for forwarding **rsyslog** events. - -`--syslog_rate_limit=100` - -Maximum number of logs to ingest per run (~200ms between runs). Use this as a fail-safe to prevent osquery from becoming overloaded when syslog is spammed. - -## Augeas flags - -`--augeas_lenses=/opt/osquery/share/osquery/lenses` - -Augeas lenses are bundled with osquery distributions. On Linux they are installed in `/opt/osquery/share/osquery/lenses`. On macOS, lenses are installed in the `/private/var/osquery/lenses` directory. Specify the path to the directory containing custom or different version lenses files. - -## Docker flags - -`--docker_socket=/var/run/docker.sock` - -Docker information for containers, networks, volumes, images etc is available in different tables. osquery uses docker's UNIX domain socket to invoke docker API calls. Provide the path to Docker's domain socket file. User running `osqueryd` / `osqueryi` should have permission to read the socket file. - -## Shell-only flags - -Most of the shell flags are self-explanatory and are adapted from the SQLite shell. Refer to the shell's `.help` command for details and explanations. - -There are several flags that control the shell's output format: `--json`, `--list`, `--line`, `--csv`. For all of the output types there is `--nullvalue` and `--separator` that can be used appropriately. - -`--planner=false` - -When prototyping new queries, the planner enables verbose decisions made by the SQLite virtual table API. This is customized by osquery code so it is very helpful to learn what predicate constraints are selected and what full-table scans are required for `JOIN` and nested queries. - -`--header=true` - -Set this value to `false` to disable column name (header) output. If using the shell in an automation or script the header line in `line` or `csv` mode may not be needed. - -## Numeric monitoring flags - -`--enable_numeric_monitoring=false` - -Enable numeric monitoring system. By default it is disabled. - -`--numeric_monitoring_plugins=filesystem` - -Comma-separated numeric monitoring plugins. By default there is only one: `filesystem`. - -`--numeric_monitoring_pre_aggregation_time=60` - -Time period in _seconds_ for numeric monitoring pre-aggregation buffer. During this period of time, monitoring points will be pre-aggregated and accumulated in a buffer. At the end of this period, the aggregated points will be flushed to `--numeric_monitoring_plugins`. `0` means to work without a buffer at all. For most monitoring data, some aggregation will be applied on the user side. In these cases, particular points don't mean much. To reduce disk usage and network traffic, some pre-aggregation is applied on the osquery side. - -`--numeric_monitoring_filesystem_path=OSQUERY_LOG_HOME/numeric_monitoring.log` - -File to dump numeric monitoring records one per line. The format of the line is ``. File will be opened in append mode. - -## Enable and Disable flags - -`--disable_tables=table1,table2` - -Comma separated list of tables to disable. By default no tables are disabled. - -`--enable_tables=table1,table2` - -Comma separated list of tables to enable. By default every table is enabled. If a specific table is set in both `--enable_tables` and `--disable_tables`, disabling take precedence. If `--enable_tables` is defined and `--disable_tables` is not set, every table but the one defined in `--enable_tables` become disabled. - -## AWS - -`--aws_imdsv2_request_attempts=3` - -How many attempts to do at requesting an IMDSv2 token. Such a token is retrieved from an AWS metadata service that might not always be accessible, and it's used by plugins like the loggers `AWS Kinesis`, `AWS Firehose` or the EC2 tables. - -`--aws_imdsv2_request_interval=3` - -Base seconds to wait between attempts at requesting an IMDSv2 token. Scales quadratically with the number of attempts. - -`--aws_disable_imdsv1_fallback=false` - -Whether to disable support for IMDSv1 and fail if an IMDSv2 token could not be retrieved - -`--aws_enforce_fips` - -Enforces that only FIPS endpoints can be used for the logger plugins (Kinesis, Firehose), the STS authentication and the EC2 tables. -Using a non compliant region for the logger plugins will cause osquery to fail to start; for other non compliant cases the specific service will fail to work. -In all non compliant cases, an error or warning message will be printed. In verbose mode an additional message will show if a certain service has FIPS enforced. - -`--aws_region` - -Configure the default region to use for the AWS services and tables. If not specified and a more specific region flag is not provided, -a fallback mechanism will be used, which will try to read the local profile configuration and take the region from there. -If that fails too, the default region of `us-east-1` will be chosen. - -`--aws_sts_region` - -Configure the region to use when acquiring STS credentials. If not specified, the `--aws_region` flag value will be used if set, -otherwise its fallback mechanism will be used. - -`--aws_firehose_region` - -Configure the region to use for the AWS Firehose logger plugin. If not specified, the `--aws_region` flag value will be used if set, -otherwise its fallback mechanism will be used. - -`--aws_kinesis_region` - -Configure the region to use for the AWS Kinesis logger plugin. If not specified, the `--aws_region` flag value will be used if set, -otherwise its fallback mechanism will be used. - -## macOS keychain flags - -By default, Osquery limits frequent access to keychain files on macOS. This limit applies to `certificates`, `keychain_acls`, and `keychain_items` tables. - -`--keychain_access_cache=true` - -Whether to use a cache for keychain access (default true). The cache resides in-memory, and independent entries are used for each table and keychain file. -If the keychain file has NOT been modified, osquery will return the cached result. The cache does not expire. It is cleared when osquery is restarted. - -`--keychain_access_interval=5` - -Minimum minutes required between keychain accesses (default is 5). Keychain cache must be enabled. -The access interval is the minimum time that must elapse before osquery will open and read a keychain file. -Starting from the first access and until time + `--keychain_access_interval`, osquery will return cached results for a given keychain file. -The interval is applied independently for each table. Therefore, multiple tables can read the same keychain file, but they can only do so once within the interval. -Since keychain files are generally not updated frequently, we expect that most keychain accesses will not be impacted by this interval. -To disable the keychain access interval: `--keychain_access_interval=0` diff --git a/docs/wiki/installation/custom-packages.md b/docs/wiki/installation/custom-packages.md deleted file mode 100644 index 4466f1a867a..00000000000 --- a/docs/wiki/installation/custom-packages.md +++ /dev/null @@ -1,79 +0,0 @@ -# Custom packaging - -!!! warning "Deprecated" - The following package guidance is deprecated. Please use CPack to generate packages. - -We support building custom deployment packages (pkg/deb/rpm) for less common use cases: - -- Slipstreaming additional tools into osquery's existing packages -- Proprietary modifications to "core" features that are not simple additional plugins -- Custom dependency modifications (patched versions of glog, thrift, etc.) - -The first step to creating custom packages is having [built](../development/building.md) and tested osquery. This means reading the development guides and in most cases having a dedicated "build host". - -## Linux - -In your cloned osquery repository, once you have [built the code](../development/building.md) (hopefully a tagged release): - -```sh -make packages -``` - -This will use CMake and *fpm*, installed as an osquery build dependency, to generate an `osquery-VERSION.{rpm/deb}` and optionally debug and devel packages. - -## OS X / macOS - -In your cloned osquery repository, once you have [built the code](../development/building.md) (hopefully a tagged release): - -```sh -make packages -``` - -The macOS deployment is a bit more complicated and customizable compared to Linux. We include some help and guidance for a more esoteric script `make_osx_package.sh`: - -```sh -./tools/deployment/make_osx_package.sh -h -``` - -This tool will build an OS X/macOS package with: - -- the **osqueryi** and **osqueryd** binaries -- the [LaunchDaemon](https://github.com/osquery/osquery/blob/master/tools/deployment/io.osquery.agent.plist) that is responsible for osqueryd -- the osqueryd config file that was specified via the command line using "-c" - -Here is the output from us running `make_osx_package.sh`: - -```sh -$ ./tools/deployment/make_osx_package.sh -c ~/Desktop/osquery.conf -[+] no custom launchd path was defined. using ~/git/osquery/tools/deployment/io.osquery.agent.plist -[+] copying osquery binaries -[+] copying osquery configurations -[+] finalizing preinstall and postinstall scripts -[+] creating package -[+] package created at ~/git/osquery/build/darwin/osquery-VERSION.pkg -``` - -The distributable package can be found at `./build/darwin/osquery-VERSION.pkg`. - -You can now use your existing package distribution system ([JAMF](https://www.jamf.com), [Chef](https://www.chef.io/products/chef-infra), etc.) to push this package to your infrastructure. - -### Custom LaunchDaemon - -If you want to modify the command-line arguments used to start `osqueryd`, copy and modify the [LaunchDaemon](https://github.com/osquery/osquery/blob/master/tools/io.osquery.agent.plist), which is included with this repository, to suit your liking. - -When you run **make_osx_package.sh**, include a `-l`/`--launchd-path` flag which indicates the path of your new LaunchDaemon. If specified, this will be used instead of the default LaunchDaemon. For example: - -```sh -$ ./tools/deployment/make_osx_package.sh -c /internal/osquery/osquery.conf \ - -l /internal/osquery/io.osquery.agent.plist -``` - -### Removing the LaunchDaemon - -Perhaps you just want to deploy the osquery binaries via a pkg and you'd like to manage the scheduling of `osqueryd` via some other mechanism. To do this, when you run **make_osx_package.sh**, include a `-n`/`--no-launchd` flag. For example: - -```sh -./tools/deployment/make_osx_package.sh -n -``` - -This will make the package just lay the binaries down. The LaunchDaemon won't be included and no LaunchDaemon will be unloaded or loaded by the post-install script of the package. diff --git a/docs/wiki/installation/install-linux.md b/docs/wiki/installation/install-linux.md deleted file mode 100644 index bba27c64b12..00000000000 --- a/docs/wiki/installation/install-linux.md +++ /dev/null @@ -1,44 +0,0 @@ -# Installing osquery on Linux - -A 'universal' Linux package can be created for each package distribution system. These packages contain the osquery daemon, shell, example configuration and startup scripts. Note that the `/etc/init.d/osqueryd` script does not automatically start the daemon until a configuration file is created (see "Running osquery," below). - -Each osquery tag (stable release) is published to **yum** and **apt** repositories for our supported operating systems: [https://osquery.io/downloads](https://osquery.io/downloads/). - -The default packages create the following structure: - -```sh -/etc/init.d/osqueryd -/etc/osquery/ -/etc/sysconfig/osqueryd -/opt/osquery/bin/osqueryctl -/opt/osquery/bin/osqueryd -/usr/local/bin/osqueryi -> /opt/osquery/bin/osqueryd -/usr/local/bin/osqueryctl -> /opt/osquery/bin/osqueryctl -/usr/lib/systemd/system/osqueryd.service -/opt/osquery/share/osquery/certs/certs.pem -/opt/osquery/share/osquery/lenses/{*}.aug -/opt/osquery/share/osquery/packs/{*}.conf -/opt/osquery/share/osquery/osquery.example.conf -/var/log/osquery/ -/var/osquery/ -``` - -## Installing osquery - -To install osquery, follow the instructions on the [Downloads](https://osquery.io/downloads/official) page according to your distro. - -> NOTICE: Linux systems running `journald` will collect logging data originating from the kernel audit subsystem (something that osquery enables) from several sources, including audit records. To avoid performance problems on busy boxes (specially when osquery event tables are enabled), it is recommended to mask audit logs from entering the journal with the following command `systemctl mask --now systemd-journald-audit.socket`. - -## Running osquery - -To start a standalone osquery use: `osqueryi`. This does not need an osquery server or service. All the table implementations are included! - -After exploring the rest of the documentation you should understand the basics of configuration and logging. These and most other concepts apply to `osqueryd`, the daemon, too. To start the daemon: - -```sh -sudo cp /opt/osquery/share/osquery/osquery.example.conf /etc/osquery/osquery.conf -# sudo service osqueryd start -sudo systemctl start osqueryd -``` - -> NOTICE: The interactive shell and daemon do NOT communicate! diff --git a/docs/wiki/installation/install-macos.md b/docs/wiki/installation/install-macos.md deleted file mode 100644 index d1a12e75b93..00000000000 --- a/docs/wiki/installation/install-macos.md +++ /dev/null @@ -1,76 +0,0 @@ -# Installing on macOS - -Continuous Integration currently tests macOS builds of osquery against macOS 11 (see the `os: [macos-` line in -the `build_macos` section of the [CI -configuration](https://github.com/osquery/osquery/blob/master/.github/workflows/build.yml). All core functionality of -osquery should work on macOS 10.15 or newer, although some tables may read data present only on certain versions of -macOS, as Apple adds new data sources or deprecates others. Versions of macOS 10.14 and older are no longer supported. - -## Package Installation - -If you plan to manage an enterprise osquery deployment, the easiest installation method is a macOS package installer. You will have to manage and deploy updates. - -Each osquery tag (release) builds a macOS package: [osquery.io/downloads](https://osquery.io/downloads/). There are no package or library dependencies. - -The default package creates the following structure: - -```sh -/private/var/osquery/io.osquery.agent.plist -/private/var/osquery/osquery.example.conf -/private/var/log/osquery/ -/private/var/osquery/lenses/{*}.aug -/private/var/osquery/packs/{*}.conf -/opt/osquery/lib/osquery.app -/usr/local/bin/osqueryi -> /opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd -/usr/local/bin/osqueryctl -> /opt/osquery/lib/osquery.app/Contents/Resources/osqueryctl -``` - -**Note:** With the release of osquery 5.x, osquery is now installed as an app bundle at `/opt/osquery/lib/osquery.app`. The new location for `osqueryd` and `osqueryctl` is inside the app bundle at `/opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd` and `/opt/osquery/lib/osquery.app/Contents/Resources/osqueryctl` respectively. Symlinks to `osqueryi` and `osqueryctl` are provided in `/usr/local/bin` for convenience. - -This package does **not** install a LaunchDaemon to start `osqueryd`. You may use the `osqueryctl start` script to copy the sample launch daemon job plist and associated configuration into place. - -### Note on upgrading from osquery 4.x to 5.x - -When upgrading from older versions to newer, osquery itself does not provide a mechanism to stop the service of older version, upgrade osquery, and then restart the service. - -### Post installation steps - -These steps only apply if this is the first time you have ever installed and run `osqueryd` on this Mac. - -After completing the package installation run the following commands. If you are using the Chef recipe to install osquery, then these steps are not necessary: the [recipe](https://osquery.readthedocs.io/en/latest/deployment/configuration/#chef-macos) has this covered. - -```sh -# You can use the helper script: -sudo osqueryctl start - -# Or, install the example config and launch daemon yourself: -sudo cp /var/osquery/osquery.example.conf /var/osquery/osquery.conf -sudo cp /var/osquery/io.osquery.agent.plist /Library/LaunchDaemons -sudo launchctl load /Library/LaunchDaemons/io.osquery.agent.plist -``` - -### Removing osquery - -To remove osquery from a macOS system, run the following commands: - -```sh -# Unload and remove io.osquery.agent.plist launchdaemon -sudo launchctl unload /Library/LaunchDaemons/io.osquery.agent.plist -sudo rm /Library/LaunchDaemons/io.osquery.agent.plist - -# Remove files/directories created by osquery installer pkg -sudo rm -rf /private/var/log/osquery -sudo rm -rf /private/var/osquery -sudo rm /usr/local/bin/osquery* -sudo rm -rf /opt/osquery - -sudo pkgutil --forget io.osquery.agent -``` - -## Running osquery - -To start a standalone osquery use: `osqueryi`. This does not need a server or service. All the table implementations are included! - -After exploring the rest of the documentation you should understand the basics of configuration and logging. These and most other concepts apply to `osqueryd`, the daemon. - -> NOTICE: The interactive shell and daemon do **not** communicate! diff --git a/docs/wiki/installation/install-windows.md b/docs/wiki/installation/install-windows.md deleted file mode 100644 index f7f90a243ad..00000000000 --- a/docs/wiki/installation/install-windows.md +++ /dev/null @@ -1,111 +0,0 @@ -# Installing osquery on Windows - -We recommend installing on Windows using the [Chocolatey package manager](https://chocolatey.org/packages/osquery/), or from the latest official binaries available on [the Downloads page](https://osquery.io/downloads/official/). - -For those needing more customization of their deployment, the steps taken by the installation are explained in more detail, below. - -## Installing with Chocolatey - -Each osquery tag (stable release) is published to **Chocolatey** for our supported versions: [https://chocolatey.org/packages/osquery/](https://chocolatey.org/packages/osquery/) - -By default Chocolatey will install the binaries, example packs, example configuration, and an OpenSSL certificate bundle to `C:\Program Files\osquery` and nothing more. You can pass Chocolatey the `--params='/InstallService'` flag or make use of osquery's `--install` flag with `C:\Program Files\osquery\osqueryd\osqueryd.exe --install` to install a Windows `SYSTEM`-level service for the `osqueryd` daemon. - -## Installing osquery via the MSI package - -For generating an **MSI** installer package, we support two methods. - -The first method is with minor modifications to the CMake build steps: - -1. First, install the Wix Toolset. With Chocolatey, `choco install wixtoolset` and then add `C:\Program Files (x86)\WiX Toolset v3.11\bin` to the system PATH. As of the time of this writing, the Chocolatey package installer doesn't add this to the PATH for you; you must add it yourself. -2. When configuring the build, specify a version string for the osquery package using the CMake parameter `-DOSQUERY_VERSION`. -3. When building, provide an additional CMake parameter, `--target package`. - -An example of a CMake build that generates an MSI package: - -```PowerShell -cd \projects\osquery\build -cmake -G "Visual Studio 16 2019" -A x64 -T v141 -DOSQUERY_VERSION="4.0.0" ..\src -cmake --build . --config RelWithDebInfo --target package -``` - -The second method is to use the script `make_windows_package.ps1` included in the source tree. This is a PowerShell script that will generate an MSI package for installing osquery. Running `.\tools\deployment\make_windows_package.ps1 'msi'` from the source root will generate a standalone MSI package along with the example packs, configuration, and OpenSSL cert bundle. - -## Installing Manually - -To get osquery running as a `SYSTEM`-level service on Windows, one must ensure two things: - -1. `osqueryd.exe` is running with safe permissions -2. The Windows Service Control Manager has all of the correct information for running the daemon - -The `osqueryd.exe` daemon is considered safe if the binary and the directory in which the binary resides do not allow non-privileged write accesses and both are owned by either the Administrators group or the `SYSTEM` account. - -The recommended way to set these ACLs is with PowerShell, and we've written a helper function to handle these permissions. To do so, `.` source the file and call the function, as follows: - -```PowerShell -C:\Users\Thor\work\repos\osquery [master ≡] -λ . .\tools\deployment\chocolatey\tools\osquery_utils.ps1 -C:\Users\Thor\work\repos\osquery [master ≡] -λ Set-SafePermissions "C:\Program Files\osquery\osqueryd\" -True -``` - -If you'd prefer to manually set the permissions, check the `C:\Program Files\osquery\osqueryd` directory and ensure that no users or groups have write permissions with the exception of the Administrators group or the SYSTEM account. Read and execute permissions are expected and safe, so also ensure the Users group has both. - -Now that osquery is properly laid out on the filesystem, we need to create a new Windows service to launch and manage the daemon. If you're using Chocolatey, you can pass the `--params='/InstallService'` flag during installation to have Chocolatey set up the Windows service for you. In general, any method to install a Windows system service will suffice. You just need to ensure to specify the `--flagfile` option in the service binary path, and give the full paths for both the daemon binary and flag file. - -For example: - -* To install the service using Powershell we bundle a helper function living in the repo at `.\tools\manage-osqueryd.ps1` which can be invoked as follows: - -````PowerShell -C:\Program Files\osquery -λ .\manage-osqueryd.ps1 -install -startupArgs "--flagfile=`"C:\Program Files\osquery\osquery.flags`"" -```` - -* If you'd rather use Powershell to manually create the service you can run: - -```PowerShell -C:\Users\Thor\work\repos\osquery [master ≡] -λ New-Service -Name "osqueryd" -BinaryPathName "`"C:\Program Files\osquery\osqueryd\osqueryd.exe`" --flagfile=`"C:\Program Files\osquery\osquery.flags`"" -``` - -* Lastly, if you'd prefer to use the Windows service utility `sc.exe` you can use: - -```PowerShell -C:\Users\Thor\work\repos\osquery [master ≡] -λ sc.exe create osqueryd type= own start= auto error= normal binpath= "`"C:\Program Files\osquery\osqueryd\osqueryd.exe`" --flagfile=`"C:\Program Files\osquery\osquery.flags`"" displayname= 'osqueryd' -``` - -## Running osquery - -Out of the box via the Chocolatey installation, one can run osquery in the interactive shell mode using `osqueryi`. More commonly, however, the daemon is configured to be a system service. To set this up, you'll need to install the daemon via the service installation flags as detailed in the steps above, and then provide the daemon with a config file. The simplest way to get `osqueryd` up and running is to rename the `C:\Program Files\osquery\osquery.example.conf` file provided to `osquery.conf`. Once the configuration file is in place, you can start the Windows service: - -* `Start-Service osqueryd` if you're using **Powershell** -* `sc.exe start osqueryd` if you're using **cmd.exe** - -We recommend configuring large fleets with Chef or SCCM. - -## Managing the daemon service - -osquery provides a helper script for [managing the osquery daemon service](https://github.com/osquery/osquery/blob/master/tools/manage-osqueryd.ps1), which is installed to `C:\Program Files\osquery\manage-osqueryd.ps1`. - -## Packaging osquery - -If you'd like to create your own osquery Chocolatey package, you can run [`.\tools\deployment\make_windows_package.ps1`](https://github.com/osquery/osquery/blob/master/tools/deployment/make_windows_package.ps1). This script will grab the built binaries, the [`packs`](https://github.com/osquery/osquery/blob/master/packs) directory, the [`osquery.example.conf`](https://github.com/osquery/osquery/blob/master/tools/deployment/osquery.example.conf), and attempt to find the OpenSSL `certs.pem` at `C:\Program Files\chocolatey\lib\openssl\local\certs`. - -## Enabling Windows Event Log support - -In order to enable support for the Windows Event Log, you first have to install the manifest file. To install and uninstall it manually, you can use the built-in `wevtutil` command: - -* **Install**: `wevtutil im "C:\Program Files\osquery\osquery.man"` -* **Uninstall**: `wevtutil um "C:\Program Files\osquery\osquery.man"` -The same operation can be performed using the osquery manager (`C:\Program Files\osquery\manage-osqueryd.ps1`): - -* **Install**: `.\manage-osqueryd.ps1 -installWelManifest` -* **Uninstall**: `.\manage-osqueryd.ps1 -uninstallWelManifest` - -The manifest file path can also be overridden using the `-welManifestPath` switch. - -To verify that everything has been configured correctly, open the Event Viewer and search for the **osquery** folder under `Applications and Services Logs/Facebook/osquery`. - -To instruct osquery to use the channel you just created, change the configuration file to use the **windows_event_log** logger plugin. diff --git a/docs/wiki/introduction/sql.md b/docs/wiki/introduction/sql.md deleted file mode 100644 index 1e21a06ffd5..00000000000 --- a/docs/wiki/introduction/sql.md +++ /dev/null @@ -1,615 +0,0 @@ -# Everything in SQL - -It may seem weird at first, but try to think of your operating system as a series of tabular concepts. Each concept becomes a SQL table, like processes, or sockets, the filesystem, a host alias, a running kernel module, etc. There are several informational things — like OS version, CPU features, memory details, UEFI platform vendor details — that are not tabular but rather a body of details with labeled data. We can represent this type of data as a table with a single row and many columns, or a series of key/value rows. When you want to inspect a concept, you `SELECT` the data, and the associated OS APIs are called in real-time. - -Now consider event streams: each event is a row, like a new USB device connection, or file attribute modification. These are the same concepts with an 'event-like' twist. We do not inspect event-time data in real-time, but rather buffer the events as they occur and represent that buffer as a table! Concept 'actions' can be represented too, you perform an action and generate tabular data. Consider `stat`-ing a file, hashing a blob of data, parsing JSON, reading a SQLite database, traversing a directory, or requesting a user's list of installed browser plugins. Actions use primary keys as input and generate rows as output, and are best used when `JOIN`ing. - -The world of osquery is centered around SQL: decorating, scheduling, differentials, eventing, targeting. Everything is SQL, and hopefully as expressive as possible. Continue reading our deployment and development guides for a deep-dive into how SQL can power intrusion detection, incident response, process auditing, file integrity monitoring and more. - -## SQL as understood by osquery - -The osquery SQL language is a superset of SQLite's. Please read [SQL as understood by SQLite](https://www.sqlite.org/lang.html) for reference. This is a great starting place if coming from MySQL, PostgreSQL, or MSSQL. - -`SELECT` only! All mutation-based verbs exist, like `INSERT`, `UPDATE`, `DELETE`, and `ALTER`, but they do nothing -- unless you're fancy and creating run-time tables or `VIEW`s, or using an extension. Mutation-based verbs are allowed in extensions, if the extension supports them. - -> NOTICE: Several tables, `file` for example, require a predicate for one of the columns, and **will not work without it**. See [Tables with arguments](#tables-with-arguments) for more information. - -Before diving into osquery's specific implementation of SQL, please familiarize yourself with the osquery [development shell](../introduction/using-osqueryi.md). This shell is designed for ad-hoc exploration of your OS and SQL query prototyping. Then, fire up `osqueryi` as your user or as a superuser, and try some of the concepts below. Know that this 'shell' does not connect to a remote server; it is completely standalone. - -### Shell help - -Within the shell, try: `.help` - -```text -$ osqueryi -Using a virtual database. Need help, type '.help' -osquery> .help -Welcome to the osquery shell. Please explore your OS! -You are connected to a transient 'in-memory' virtual database. - -.all [TABLE] Select all from a table -.bail ON|OFF Stop after hitting an error -.connect PATH Connect to an osquery extension socket -.disconnect Disconnect from a connected extension socket -.echo ON|OFF Turn command echo on or off -[...] -osquery> -``` - -Try the meta-commands `.tables` and `.schema` to list all of the tables and their schema. The `schema` meta-command takes an argument that helps limit the output to a partial string match. - -```text -osquery> .schema process -[...] -CREATE TABLE process_memory_map(`pid` INTEGER, `start` TEXT, `end` TEXT, `permissions` TEXT, `offset` BIGINT, `device` TEXT, `inode` INTEGER, `path` TEXT, `pseudo` INTEGER, PRIMARY KEY (`pid`)) WITHOUT ROWID; -CREATE TABLE process_open_files(`pid` BIGINT, `fd` BIGINT, `path` TEXT, PRIMARY KEY (`pid`)) WITHOUT ROWID; -CREATE TABLE process_open_sockets(`pid` INTEGER, `fd` BIGINT, `socket` BIGINT, `family` INTEGER, `protocol` INTEGER, `local_address` TEXT, `remote_address` TEXT, `local_port` INTEGER, `remote_port` INTEGER, `path` TEXT, `state` TEXT, `net_namespace` TEXT HIDDEN, PRIMARY KEY (`pid`, `fd`, `socket`, `family`, `protocol`, `local_address`, `remote_address`, `local_port`, `remote_port`, `path`, `state`, `net_namespace`)) WITHOUT ROWID; -CREATE TABLE processes(`pid` BIGINT, `name` TEXT, `path` TEXT, `cmdline` TEXT, `state` TEXT, `cwd` TEXT, `root` TEXT, `uid` BIGINT, `gid` BIGINT, `euid` BIGINT, `egid` BIGINT, `suid` BIGINT, `sgid` BIGINT, `on_disk` INTEGER, `wired_size` BIGINT, `resident_size` BIGINT, `total_size` BIGINT, `user_time` BIGINT, `system_time` BIGINT, `disk_bytes_read` BIGINT, `disk_bytes_written` BIGINT, `start_time` BIGINT, `parent` BIGINT, `pgroup` BIGINT, `threads` INTEGER, `nice` INTEGER, `elevated_token` INTEGER HIDDEN, `secure_process` INTEGER HIDDEN, `protection_type` TEXT HIDDEN, `virtual_process` INTEGER HIDDEN, `elapsed_time` BIGINT HIDDEN, `handle_count` BIGINT HIDDEN, `percent_processor_time` BIGINT HIDDEN, `upid` BIGINT, `uppid` BIGINT, `cpu_type` INTEGER, `cpu_subtype` INTEGER, `translated` INTEGER, `cgroup_path` TEXT HIDDEN, PRIMARY KEY (`pid`)) WITHOUT ROWID; -``` - -This [complete schema](https://osquery.io/schema/) for all supported platforms is available on the homepage. To see schema in your shell for tables foreign to your OS, like kernel modules on macOS, use the `--enable_foreign` [command line flag](../installation/cli-flags.md). - -### Your first query - -On macOS (or Linux), select 1 process's pid, name, and path. Then change the display mode and issue the same query: - -```text -osquery> SELECT pid, name, path FROM processes WHERE pid > 0 LIMIT 1; -+-----+---------+---------------+ -| pid | name | path | -+-----+---------+---------------+ -| 1 | launchd | /sbin/launchd | -+-----+---------+---------------+ -osquery> .mode line -osquery> SELECT pid, name, path FROM processes WHERE pid > 0 LIMIT 1; - pid = 1 - name = launchd - path = /sbin/launchd -osquery> .mode pretty -``` - -Then try: `SELECT pid, name, path FROM processes ORDER BY start_time DESC LIMIT 1;` several times and you will continue to select the last-most-recent process to start. This data is equivalent to `ps` and is a real-time representation of processes. - -To really hammer home the real-time representation, try `SELECT * FROM time;`. Feel free to inspect other concepts/tables. Use `.mode line` for the best output within smaller terminal views. - -Then, let's look at a "meta" table that provides details to osquery about itself. These tables are prefixed with `osquery_`: - -```text -osquery> .mode line -osquery> SELECT * FROM osquery_info; - pid = 15982 - uuid = 4892E1C6-F800-5F8E-92B1-BC2216C29D4F - instance_id = 94c004b0-49e5-4ece-93e6-96c1939c0f83 - version = 2.4.6 - config_hash = - config_valid = 0 - extensions = active -build_platform = darwin - build_distro = 10.12 - start_time = 1496552549 - watcher = -1 -``` - -This will always show the current PID of the running osquery process, shell or otherwise. - -Let's use this to demonstrate `JOIN`ing: - -```text -osquery> SELECT pid, name, path FROM osquery_info JOIN processes USING (pid); - pid = 15982 - name = osqueryi - path = /usr/local/bin/osqueryi -``` - -Now let's get fancy and complicated, by performing two `JOIN`s and adding a `WHERE` clause: - -```text -osquery> SELECT p.pid, name, p.path as process_path, pf.path as open_path - ...> FROM osquery_info i - ...> JOIN processes p ON p.pid = i.pid - ...> JOIN process_open_files pf ON pf.pid = p.pid - ...> WHERE pf.path LIKE '/dev/%'; - pid = 15982 - name = osqueryi -process_path = /usr/local/bin/osqueryi - open_path = /dev/ttys000 - - pid = 15982 - name = osqueryi -process_path = /usr/local/bin/osqueryi - open_path = /dev/ttys000 - - pid = 15982 - name = osqueryi -process_path = /usr/local/bin/osqueryi - open_path = /dev/ttys000 - - pid = 15982 - name = osqueryi -process_path = /usr/local/bin/osqueryi - open_path = /dev/null -``` - -We can expand upon this later using subqueries and more tables. - -### Tables with arguments - -Several tables, `file` for example, require arguments to be queried. Consider `SELECT * FROM file`: you would not want this to trigger a complete walk of the mounted file systems. The query must be constrained with some sort of argument or input parameter. Tables like this are indicated by a *pushpin icon* in the [schema documentation](https://osquery.io/schema/) next to the columns that act as arguments to constrain a query. - -As an example of this concept, let's exercise the `file` table: - -``` -osquery> .mode line -osquery> SELECT * FROM file; -osquery> SELECT * FROM file WHERE path = '/dev/zero'; - path = /dev/zero - directory = /dev - filename = zero - inode = 304 - uid = 0 - gid = 0 - mode = 0666 - device = 50331651 - size = 0 -block_size = 131072 - atime = 1463786341 - mtime = 1463786341 - ctime = 1463786341 - btime = 0 -hard_links = 1 - type = character -osquery> SELECT count(1) FROM file WHERE path LIKE '/dev/%'; -count(1) = 568 -``` - -The documentation for [`file`](https://osquery.io/schema/current/#file) says both `path` and `directory` can be used as input parameters. In *most* cases these columns and tables should "do the right thing" and respond to various operators. String data, like paths, are not easily compared so `=` or `<>` and `LIKE` are the only operators that make sense. - -Let's get semi-fancy: - -``` -osquery> .mode line -osquery> SELECT path, inode, size, type - ...> FROM file - ...> WHERE path IN (SELECT '/dev/zero'); - path = /dev/zero -inode = 304 - size = 0 - type = character -``` - -Now let's introduce the [`hash`](https://osquery.io/schema/current/#hash) table and hopefully show something useful, like the hash of the last file modified in `/etc`: - -``` -osquery> .mode line -osquery> SELECT path, mtime, sha256 - ...> FROM file - ...> JOIN hash USING (path) - ...> WHERE file.directory = '/etc' - ...> ORDER BY mtime DESC LIMIT 1; - path = /etc/krb5.keytab - mtime = 1464730624 -sha256 = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -``` - -### SQL additions - -osquery includes various 'additional' SQL functions and aggregations. We try to balance SQL feature requests using a locality question: "does this make sense to execute on a single host, or many?" If the aggregation or function would be better suited with information from a fleet or group of hosts, find a mechanism to perform the function/aggregation after-the-fact. - -#### Math functions - -osquery includes the following C-math functions: `sqrt`, `log`, `log10`, `ceil`, `floor`, `power`, `pi`. -

    -C-Math function examples: -

    - - osquery> .mode line - - osquery> select disk_size as disk_size from disk_info; - disk_size = 107372805120 - - osquery> select sqrt(disk_size) as disk_size from disk_info; - disk_size = 327677.898430761 - - osquery> select log(disk_size) as disk_size from disk_info; - disk_size = 25.3995727757846 - - osquery> select log10(disk_size) as disk_size from disk_info; - disk_size = 11.0308942992233 - - osquery> select ceil(disk_size) as disk_size from disk_info; - disk_size = 107372805120 - - osquery> select floor(disk_size) as disk_size from disk_info; - disk_size = 107372805120 - - osquery> select power(disk_size, 2) as disk_size from disk_info; - disk_size = 1.15289192793375e+22 - - osquery> select pi() * disk_size as disk_size from disk_info; - disk_size = 337321615760.32 - -

    -
    - - -The following trig functions: `sin`, `cos`, `tan`, `cot`, `asin`, `acos`, `atan`, and `radians` to `degrees` conversions. -
    -Trig functions examples: -

    - - osquery .mode line - - osquery> select sin(30); - sin(30) = -0.988031624092862 - - osquery> select cos(30); - cos(30) = 0.154251449887584 - - osquery> select tan(30); - tan(30) = -6.40533119664628 - - osquery> select cot(30); - cot(30) = -0.156119952161659 - - osquery> select asin(.5); - asin(.5) = 0.523598775598299 - - osquery> select acos(.5); - acos(.5) = 1.0471975511966 - - osquery> select atan(.5); - atan(.5) = 0.463647609000806 - - osquery> select radians(60); - radians(60) = 1.0471975511966 - - osquery> select degrees(1.3); - degrees(1.3) = 74.484513367007 - -

    -
    - -#### String functions - - -- `concat(ARG1, ARG2, ARG3...)`: Concatenate arguments, ignoring nulls. - -
    - Concat function example: -

    - - osquery> .mode line - - osquery> select concat('hello', NULL, ' ', 'world'); - concat('hello', NULL, ' ', 'world') = hello world -

    -
    - - -- `concat_ws(SEPARATOR, ARG1, ARG2, ARG3...)`: Concatenate arguments, ignoring nulls, and interleaved with `SEPARATOR`. - -
    - Concat_ws function example: -

    - - osquery> .mode line - - osquery> select concat_ws(' ', 'hello', NULL, 'world'); - concat_ws(' ', 'hello', NULL, 'world') = hello world -

    -
    - -- `split(COLUMN, TOKENS, INDEX)`: split `COLUMN` using any character token from `TOKENS` and return the `INDEX` result. If an `INDEX` result does not exist, a `NULL` type is returned. - -
    - Split function example: -

    - - osquery> .mode line - - osquery> select uid from users; - uid = 500 - - uid = 1001 - - osquery> select split(uid, 1, 0) from users; - split(uid, 1, 0) = 500 - - split(uid, 1, 0) = 00 - -

    -
    - -- `regex_split(COLUMN, PATTERN, INDEX)`: similar to split, but instead of `TOKENS`, apply the POSIX regex `PATTERN` (as interpreted by std::regex). - -
    - Regex Split function example: -

    - - osquery> .mode line - - osquery> select uid from users; - uid = 500 - - uid = 1001 - - osquery> select regex_split(uid, "[1-5]", 0) from users; - split(uid, 1, 0) = 00 - - split(uid, 1, 0) = 00 - -

    -
    - -- `regex_match(COLUMN, PATTERN, INDEX)`: Runs regex match across the column, and returns matched subgroups. (The 0 index is the full match, subsequent numbers are the groups). - -
    - Regex Match function example: -

    - - osquery> .mode line - - osquery> select regex_match('hello world. Goodbye', '(\w+) \w+', 0) as m0, - regex_match('hello world. Goodbye', '(\w+) \w+', 1) as m1; - m0 = hello world - m1 = hello -

    -
    - - -- `inet_aton(IPv4_STRING)`: return the integer representation of an IPv4 string. - -
    - IPv4 Int representation example: -

    - - osquery> .mode line - - osquery> select inet_aton("1.0.1.5") as ipInt - ipInt = 16777477 - -

    -
    - -- `version_compare(LEFT_VERSION, RIGHT_VERSION, COMPARE_FLAVOR)`: return `-1` if the left version is less than the right, `0` if they are equal, or `1` if the left version is greater than the right. `COMPARE_FLAVOR` is optional, and can be of value (`ARCH`, `DPKG`, or `RHEL`), which equates to the respective linux distribution package versioning. The default `COMPARE_FLAVOR` is semantic versioning. - -
    - Version Compare function example: -

    - - osquery> .mode line - - osquery> select version_compare('1.0', '1.0'); - version_compare('1.0', '1.0') = 0 - - osquery> select version_compare('4:1.1.0', '4:1.1.0-3', 'ARCH'); - version_compare('4:1.1.0', '4:1.1.0-3', 'ARCH') = 0 - - osquery> select version_compare('50.4.1b', '50.4.1c'); - version_compare('50.4.1b', '50.4.1c') = -1 - - osquery> select version_compare('1.0.0~rc2^2021', '1.0.0', 'RHEL'); - version_compare('1.0.0~rc2^2021', '1.0.0', 3) = -1 - - osquery> select version_compare('1:1.2.13-2', '4.2.1', 'ARCH'); - version_compare('1:1.2.13-2', '4.2.1', 1) = 1 - -

    -
    - -#### Hashing functions - -We have added `sha1`, `sha256`, and `md5` functions that take a single argument and return the hashed value. -
    -Hashing functions example: -

    - - osquery> .mode line - - osquery> select username from users; - username = Guest - - username = System - - osquery> select sha1(username) as usernameHash from users; - usernameHash = face83ee3014bdc8f98203cc94e2e89222452e90 - - usernameHash = 29d43743c43bda9873fc7a79c99f2ec4b6b442b1 - - osquery> select sha256(username) as usernameHash from users; - usernameHash = a835887ac13e6558ea6cb404aae6a35b7cbff6796af813d72f7b8d08f3fa0ec9 - - usernameHash = 4d2c882abd33183be08ec6f4b47a1f09d3dd211de7556d9b587f7e34eec5ed0b - - osquery> select md5(username) as usernameHash from users; - usernameHash = 7d4ef62de50874a4db33e6da3ff79f75 - - usernameHash = 2a44946d16fe86e63a7e078744c58d56 - -

    -
    - -- `community_id_v1(SOURCE_ADDR, DEST_ADDR, SOURCE_PORT, DEST_PORT, PROTOCOL, SEED)`: returns the [Community ID v1 hash](https://github.com/corelight/community-id-spec) of the network connection. This can be used to match with the Community ID generated by other tools such as Zeek and Suricata. `SEED` is optional and will be set to `0` if omitted. If some values are missing or cannot be parsed, the function will return an empty result and log a warning. For strict error checking (resulting in failure of the query), use `community_id_v1_strict`. - -
    - Community ID v1 Example: -

    - - osquery> .mode line - - osquery> select community_id_v1('66.35.250.204', '128.232.110.120', 80, 34855, 6) AS community_id; - community_id = 1:LQU9qZlK+B5F3KDmev6m5PMibrg= - osquery> select community_id_v1('66.35.250.204', '2001:0:3238:DFE1:63::FEFB', 80, 2347, 6) AS community_id; - community_id = 1:rxU6O+b2d9kbSWjRmVDoBbowx6g= - osquery> select community_id_v1('66.35.250.204', '2001:0:3238:DFE1:63::FEFB', 80, 2347, 6, 37) AS community_id_with_seed; - community_id_with_seed = 1:jmJ2ORP31di4mtsQPIKzyoEb3yo= - osquery> select community_id_v1(local_address,remote_address,local_port,remote_port,protocol) as community_id from process_open_sockets limit 2; - community_id = 1:PaAbtXl8lgQoYFUShUQwXpcNVfw= - W0129 10:34:47.759569 195012032 sqlite_hashing.cpp:226] Community ID saddr cannot be parsed as IP - - community_id = - -

    -
    - -#### Encoding functions - -There are also encoding functions available, to process query results. - -- `to_base64`: base64 encode a string. -
    - Base64 encode example: -

    - - osquery> .mode line - - osquery> select device_id from cpu_info; - device_id = CPU0 - - osquery> select to_base64(device_id) as device_id from cpu_info; - device_id = Q1BVMA== - -

    -
    -- `from_base64`: Decode a base64 encoded string. If the string is not valid base64 an empty string is returned. -
    - Base64 decode example: -

    - - osquery> .mode line - - osquery> select device_id from cpu_info; - device_id = CPU0 - - osquery> select to_base64(device_id) as device_id from cpu_info; - device_id = Q1BVMA== - - select from_base64(to_base64(device_id)) as device_id from cpu_info; - device_id = CPU0 - -

    -
    -- `conditional_to_base64`: Encode a string if and only if the string contains non-printable ASCII characters. (Specifically, it considers `0x20` through `0x7e` printable) -
    - Conditional Base64 encode example: -

    - - osquery> .mode line - - osquery> select device_id from cpu_info; - device_id = CPU0 - - osquery> select conditional_to_base64(device_id) as device_id from cpu_info; - device_id = CPU0 - - osquery> select conditional_to_base64(device_id || char(183)) as device_id from cpu_info; - device_id = Q1BVMMK3 - -

    -
    - -#### Network functions - -- `in_cidr_block(CIDR_RANGE, IP_ADDRESS)`: return 1 if the IP address is within the CIDR block, otherwise 0. - -
    - in_cidr_block function example: -

    - - osquery> .mode line - - osquery> SELECT in_cidr_block('10.0.0.0/26', '10.0.0.24'); - in_cidr_block('10.0.0.0/26', '10.0.0.24') = 1 - - osquery> SELECT in_cidr_block('2001:db8::/48', '2001:db8:0:ffff:ffff:ffff:ffff:ffff'); - in_cidr_block('2001:db8::/48', '2001:db8:0:ffff:ffff:ffff:ffff:ffff') = 1 - -

    -
    - -#### Collations - -- `version`: - -
    - Version collation example: -

    - - osquery> .mode line - - osquery> SELECT '1.0' = '1.0' COLLATE VERSION; - '1.0' = '1.0' COLLATE VERSION = 1 - - osquery> SELECT '50.4.1b' < '50.4.1c' COLLATE VERSION; - '50.4.1b' < '50.4.1c' COLLATE VERSION = 1 - - osquery> SELECT '20.10a' > '20.102' COLLATE VERSION; - '20.10a' > '20.102' COLLATE VERSION = 1 -

    -
    - -- `version_arch`: - -
    - Version ARCH collation example: -

    - - osquery> .mode line - - osquery> SELECT '4:2' = '4:2-1' COLLATE VERSION_ARCH; - '4:2' = '4:2-1' COLLATE VERSION_ARCH = 1 - - osquery> SELECT '2-2pre' < '2-2rc' COLLATE VERSION_ARCH; - '2-2pre' < '2-2rc' COLLATE VERSION_ARCH = 1 - - osquery> SELECT '42.2-1' > '42.1-2' COLLATE VERSION_ARCH; - '42.2-1' > '42.1-2' COLLATE VERSION_ARCH = 1 - -

    -
    - -- `version_dpkg`: - -
    - Version DPKG collation example: -

    - - osquery> .mode line - - osquery> SELECT '1:2.0-10' = '1:2.0-10' COLLATE VERSION_DPKG; - '1:2.0-10' = '1:2.0-10' COLLATE VERSION_DPKG = 1 - - osquery> SELECT '22.07.5-2ubuntu1.3' < '22.07.5-2ubuntu1.4' COLLATE VERSION_DPKG; - '22.07.5-2ubuntu1.3' < '22.07.5-2ubuntu1.4' COLLATE VERSION_DPKG = 1 - - osquery> SELECT '2:8.2.3995-1ubuntu2.9' > '2:8.2.3995-1ubuntu2.3' COLLATE VERSION_DPKG; - '2:8.2.3995-1ubuntu2.9' > '2:8.2.3995-1ubuntu2.3' COLLATE VERSION_DPKG = 1 - -

    -
    - -- `version_rhel`: - -
    - Version RHEL collation example: -

    - - osquery> .mode line - - osquery> SELECT '0.5.0~rc1^202' = '0.5.0~rc1^202' COLLATE VERSION_RHEL; - '0.5.0~rc1^202' = '0.5.0~rc1^202' COLLATE VERSION_RHEL = 1 - - osquery> SELECT '1.1.0~BETA2' < '1.1.0~CR1' COLLATE VERSION_RHEL; - '1.1.0~BETA2' < '1.1.0~CR1' COLLATE VERSION_RHEL = 1 - - osquery> SELECT '1.0.0' > '1.0.0~rc2' COLLATE VERSION_RHEL; - '1.0.0' > '1.0.0~rc2' COLLATE VERSION_RHEL = 1 - -

    -
    - -### Table and column name deprecations - -Over time it may make sense to rename tables and columns. osquery tries to apply plurals to table names and achieve the easiest foreign key JOIN syntax. This often means slightly skewing concept attributes or biasing towards diction used by POSIX. - -osquery makes an effort to mark deprecated tables and create 'clone' `VIEW`s so that previously scheduled queries continue to work. Similarly, for old column names, the column will be marked `HIDDEN` and only returned if explicitly selected. This does not make queries using `*` future-proof, as they will begin using the new column names when the client is updated. All of these changes are considered osquery API changes and marked as such in [release notes](https://github.com/osquery/osquery/releases) on GitHub. diff --git a/docs/wiki/introduction/using-osqueryd.md b/docs/wiki/introduction/using-osqueryd.md deleted file mode 100644 index fb47ae68938..00000000000 --- a/docs/wiki/introduction/using-osqueryd.md +++ /dev/null @@ -1,45 +0,0 @@ -# Using osquery - -`osqueryd` is the host monitoring daemon that allows you to **schedule** queries and record OS state changes. The daemon aggregates query results over time and generates logs, which indicate state change according to each query. The daemon also uses OS eventing APIs to record monitored file and directory changes, hardware events, network events, and more. - -The installation and deployment guides are mostly focused on the osquery daemon lifecycle. On Linux, the daemon starts as an SystemV initscript; on macOS as a launch daemon. The service is highly configurable and extendable. - -## Configuration and query schedule - -The primary daemon feature is executing a query schedule. This schedule is defined in an [osquery configuration](../deployment/configuration.md) and includes a list of semi-broad queries and their interval. The interval is an approximate time to run the query. - -```json -{ - "usb_devices": { - "query": "SELECT vendor, model FROM usb_devices;", - "interval": 60 - } -} -``` - -This simple **usb_devices** query will run approximately every 60 seconds on the host running `osqueryd`. - -## Logging and reporting - -Each query represents a monitored view of your operating system. The first time a scheduled query runs, it logs every row in the resulting table with the "added" action. In this example, on a macOS laptop, after the first 60 seconds it would log: - -```json -[ - {"model":"XHCI Root Hub SS Simulation","vendor":"Apple Inc."}, - {"model":"XHCI Root Hub USB 2.0 Simulation","vendor":"Apple Inc."}, - {"model":"BRCM20702 Hub","vendor":"Apple Inc."}, - {"model":"Internal Memory Card Reader","vendor":"Apple"}, - {"model":"Apple Internal Keyboard \/ Trackpad","vendor":"Apple Inc."}, - {"model":"Bluetooth USB Host Controller","vendor":"Apple Inc."} -] -``` - -If there are no USB devices added to or removed from the laptop, this query would never log a result again. The query would still run every 60 seconds, but the results would match the previous run, and thus no state change would be detected. If a USB memory stick was inserted and left in the laptop for 60 seconds, the daemon would log: - -```json -[ - {"model":"U3 Cruzer Micro","vendor":"SanDisk Corporation"} -] -``` - -Each line in the results is decorated with a bit more information, as described in the [logging](../deployment/logging.md) guide. This includes time, hostname, added or removed action, etc. diff --git a/docs/wiki/introduction/using-osqueryi.md b/docs/wiki/introduction/using-osqueryi.md deleted file mode 100644 index f0e1a4bff63..00000000000 --- a/docs/wiki/introduction/using-osqueryi.md +++ /dev/null @@ -1,113 +0,0 @@ -# Using osqueryi - -`osqueryi` is the osquery interactive query console/shell. In this mode, it is completely standalone, does not communicate with a daemon, and does not need to run as an administrator (although some tables may return fewer results when running as non-administrator). Use the osquery shell to prototype queries and explore the current state of your operating system. - -## Executing SQL queries - -`osqueryi` lets you run meta-commands and query osquery tables. See the [schema API](https://osquery.io/schema/) for a complete list of tables, types, and column descriptions. For SQL syntax help, see [SQL as understood by SQLite](https://www.sqlite.org/lang.html). - -***Note***: the `osqueryd` binary, when run as `osqueryd -S`, operates as `osqueryi`. It will also operate in the interactive mode if the executable is renamed as `osqueryi`. - -Here is an example query: - -``` -$ osqueryi -osquery> SELECT DISTINCT - ...> process.name, - ...> listening.port, - ...> process.pid - ...> FROM processes AS process - ...> JOIN listening_ports AS listening - ...> ON process.pid = listening.pid - ...> WHERE listening.address = '0.0.0.0'; - -+----------+-------+-------+ -| name | port | pid | -+----------+-------+-------+ -| Spotify | 57621 | 18666 | -| ARDAgent | 3283 | 482 | -+----------+-------+-------+ -osquery> -``` - -The shell accepts a single positional argument and one of the several output modes. If you want to output JSON or CSV values, try: - -``` -$ osqueryi --json "SELECT * FROM routes WHERE destination = '::1'" -[ - {"destination":"::1","flags":"2098181","gateway":"::1","interface":"","metric":"0","mtu":"16384","netmask":"128","source":"","type":"local"} -] -``` - -You may also pipe a query as *stdin*. The input will be executed on the `osqueryi` shell and must be well-formed SQL or `osqueryi` meta-commands. Note the added ';' to the query when using *stdin*: - -``` -echo "SELECT * FROM routes WHERE destination = '::1';" | osqueryi --json -``` - -## Getting help - -`osqueryi` is a modified version of the SQLite shell. -It accepts several meta-commands, prefixed with a '.': - -* to list all tables: `.tables` -* to list the schema (columns, types) of a specific table: `.schema table_name` or `pragma table_info(table_name);` for more details -* to list all available commands: `.help` -* to exit the console: `.exit` or `^D` - -Here are some example shell commands: - -``` -osquery> .tables - => alf_services - => apps - => ca_certs - => etc_hosts - => interface_addresses - => interface_details - => kernel_extensions - => launchd - => listening_ports - => nvram - => processes - => routes -[...] - -osquery> .schema routes -CREATE VIRTUAL TABLE routes USING routes( - destination TEXT, - netmask TEXT, - gateway TEXT, - source TEXT, - flags INTEGER, - interface TEXT, - mtu INTEGER, - metric INTEGER, - type TEXT -); - -osquery> PRAGMA table_info(routes); -+-----+-------------+---------+---------+------------+----+ -| cid | name | type | notnull | dflt_value | pk | -+-----+-------------+---------+---------+------------+----+ -| 0 | destination | TEXT | 0 | | 0 | -| 1 | netmask | TEXT | 0 | | 0 | -| 2 | gateway | TEXT | 0 | | 0 | -| 3 | source | TEXT | 0 | | 0 | -| 4 | flags | INTEGER | 0 | | 0 | -| 5 | interface | TEXT | 0 | | 0 | -| 6 | mtu | INTEGER | 0 | | 0 | -| 7 | metric | INTEGER | 0 | | 0 | -| 8 | type | TEXT | 0 | | 0 | -+-----+-------------+---------+---------+------------+----+ - -osquery> .exit -$ -``` - -The shell does not keep much state, or connect to the `osqueryd` daemon. -If you would like to run queries and log changes to the output or log operating system events, consider deploying a query **schedule** using [osqueryd](using-osqueryd.md). - - > Note: Event publishers are not started by default. To enable event-based tables, use the flag `--disable_events=false`. - -`osqueryi` uses an in-memory database by default. To connect to an existing events database, use the flag `--database_path=/var/osquery/osquery.db` (only one process may attach to the database; see [Checking the database sanity](../deployment/debugging.md#checking-the-database-sanity)). diff --git a/docs/wiki/theme/osquery.css b/docs/wiki/theme/osquery.css deleted file mode 100644 index 042a467bb11..00000000000 --- a/docs/wiki/theme/osquery.css +++ /dev/null @@ -1,5 +0,0 @@ -/** osquery-specific read the docs / markdown themeing */ - -.wy-side-nav-search { - background-color: rgb(165, 150, 255); -} From 6d6a729c347272ae6e4be5c5918178aa4f354ea1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 23:08:12 +0000 Subject: [PATCH 3/8] docs: Stage 1 - Inline documentation (1365 files) [skip ci] --- external/examples/config_plugin/.main.md | 37 ++++++ external/examples/read_only_table/.main.md | 53 ++++++++ external/examples/string_batch/.main.md | 41 ++++++ external/examples/writable_table/.main.md | 45 +++++++ .../generated/linux/aarch64/code/.lexer.md | 50 ++++++++ .../generated/linux/aarch64/code/.parser.md | 65 ++++++++++ .../generated/linux/aarch64/config/.config.md | 43 +++++++ .../generated/linux/x86_64/code/.lexer.md | 44 +++++++ .../generated/linux/x86_64/code/.parser.md | 74 +++++++++++ .../generated/linux/x86_64/config/.config.md | 49 ++++++++ .../generated/macos/aarch64/code/.lexer.md | 66 ++++++++++ .../generated/macos/aarch64/code/.parser.md | 67 ++++++++++ .../generated/macos/aarch64/config/.config.md | 49 ++++++++ .../generated/macos/x86_64/code/.lexer.md | 51 ++++++++ .../generated/macos/x86_64/code/.parser.md | 64 ++++++++++ .../generated/macos/x86_64/config/.config.md | 55 ++++++++ .../generated/linux/aarch64/config/.config.md | 51 ++++++++ .../generated/linux/aarch64/lib/.alloca.md | 41 ++++++ .../generated/linux/aarch64/lib/.ctype.md | 41 ++++++ .../generated/linux/aarch64/lib/.fcntl.md | 49 ++++++++ .../generated/linux/aarch64/lib/.langinfo.md | 51 ++++++++ .../generated/linux/aarch64/lib/.limits.md | 49 ++++++++ .../generated/linux/aarch64/lib/.locale.md | 39 ++++++ .../generated/linux/aarch64/lib/.stdint.md | 58 +++++++++ .../generated/linux/aarch64/lib/.stdio.md | 43 +++++++ .../generated/linux/aarch64/lib/.stdlib.md | 47 +++++++ .../generated/linux/aarch64/lib/.string.md | 43 +++++++ .../generated/linux/aarch64/lib/.time.md | 40 ++++++ .../generated/linux/aarch64/lib/.unistd.md | 50 ++++++++ .../generated/linux/aarch64/lib/.wchar.md | 43 +++++++ .../generated/linux/aarch64/lib/.wctype.md | 42 +++++++ .../linux/aarch64/lib/selinux/.context.md | 41 ++++++ .../linux/aarch64/lib/selinux/.selinux.md | 48 +++++++ .../generated/linux/aarch64/lib/sys/.stat.md | 40 ++++++ .../generated/linux/aarch64/lib/sys/.time.md | 46 +++++++ .../generated/linux/aarch64/lib/sys/.types.md | 29 +++++ .../generated/linux/aarch64/lib/sys/.wait.md | 50 ++++++++ .../generated/linux/x86_64/config/.config.md | 51 ++++++++ .../generated/linux/x86_64/lib/.alloca.md | 42 +++++++ .../generated/linux/x86_64/lib/.ctype.md | 48 +++++++ .../generated/linux/x86_64/lib/.fcntl.md | 47 +++++++ .../generated/linux/x86_64/lib/.fnmatch.md | 50 ++++++++ .../linux/x86_64/lib/.getopt-cdefs.md | 47 +++++++ .../generated/linux/x86_64/lib/.getopt.md | 45 +++++++ .../generated/linux/x86_64/lib/.langinfo.md | 50 ++++++++ .../generated/linux/x86_64/lib/.limits.md | 34 +++++ .../generated/linux/x86_64/lib/.locale.md | 46 +++++++ .../generated/linux/x86_64/lib/.stdint.md | 52 ++++++++ .../generated/linux/x86_64/lib/.stdio.md | 51 ++++++++ .../generated/linux/x86_64/lib/.stdlib.md | 40 ++++++ .../generated/linux/x86_64/lib/.string.md | 39 ++++++ .../generated/linux/x86_64/lib/.time.md | 39 ++++++ .../generated/linux/x86_64/lib/.unistd.md | 38 ++++++ .../generated/linux/x86_64/lib/.wchar.md | 46 +++++++ .../generated/linux/x86_64/lib/.wctype.md | 44 +++++++ .../linux/x86_64/lib/selinux/.context.md | 42 +++++++ .../linux/x86_64/lib/selinux/.selinux.md | 53 ++++++++ .../generated/linux/x86_64/lib/sys/.stat.md | 45 +++++++ .../generated/linux/x86_64/lib/sys/.time.md | 45 +++++++ .../generated/linux/x86_64/lib/sys/.types.md | 29 +++++ .../generated/linux/x86_64/lib/sys/.wait.md | 47 +++++++ .../generated/macos/aarch64/config/.config.md | 47 +++++++ .../generated/macos/aarch64/lib/.alloca.md | 40 ++++++ .../generated/macos/aarch64/lib/.argz.md | 53 ++++++++ .../generated/macos/aarch64/lib/.ctype.md | 42 +++++++ .../generated/macos/aarch64/lib/.fcntl.md | 42 +++++++ .../generated/macos/aarch64/lib/.fnmatch.md | 49 ++++++++ .../macos/aarch64/lib/.getopt-cdefs.md | 35 ++++++ .../generated/macos/aarch64/lib/.getopt.md | 41 ++++++ .../generated/macos/aarch64/lib/.langinfo.md | 50 ++++++++ .../generated/macos/aarch64/lib/.limits.md | 50 ++++++++ .../generated/macos/aarch64/lib/.locale.md | 52 ++++++++ .../generated/macos/aarch64/lib/.stdint.md | 64 ++++++++++ .../generated/macos/aarch64/lib/.stdio.md | 56 +++++++++ .../generated/macos/aarch64/lib/.stdlib.md | 49 ++++++++ .../generated/macos/aarch64/lib/.string.md | 49 ++++++++ .../generated/macos/aarch64/lib/.time.md | 50 ++++++++ .../generated/macos/aarch64/lib/.unistd.md | 32 +++++ .../generated/macos/aarch64/lib/.wchar.md | 41 ++++++ .../generated/macos/aarch64/lib/.wctype.md | 40 ++++++ .../macos/aarch64/lib/selinux/.context.md | 39 ++++++ .../macos/aarch64/lib/selinux/.selinux.md | 52 ++++++++ .../generated/macos/aarch64/lib/sys/.stat.md | 49 ++++++++ .../generated/macos/aarch64/lib/sys/.time.md | 33 +++++ .../generated/macos/aarch64/lib/sys/.types.md | 30 +++++ .../generated/macos/aarch64/lib/sys/.wait.md | 50 ++++++++ .../generated/macos/x86_64/config/.config.md | 49 ++++++++ .../generated/macos/x86_64/lib/.alloca.md | 43 +++++++ .../generated/macos/x86_64/lib/.argz.md | 49 ++++++++ .../generated/macos/x86_64/lib/.ctype.md | 64 ++++++++++ .../generated/macos/x86_64/lib/.fcntl.md | 50 ++++++++ .../generated/macos/x86_64/lib/.fnmatch.md | 44 +++++++ .../macos/x86_64/lib/.getopt-cdefs.md | 42 +++++++ .../generated/macos/x86_64/lib/.getopt.md | 42 +++++++ .../generated/macos/x86_64/lib/.langinfo.md | 51 ++++++++ .../generated/macos/x86_64/lib/.limits.md | 39 ++++++ .../generated/macos/x86_64/lib/.locale.md | 53 ++++++++ .../generated/macos/x86_64/lib/.stdint.md | 46 +++++++ .../generated/macos/x86_64/lib/.stdio.md | 53 ++++++++ .../generated/macos/x86_64/lib/.stdlib.md | 45 +++++++ .../generated/macos/x86_64/lib/.string.md | 48 +++++++ .../generated/macos/x86_64/lib/.time.md | 54 ++++++++ .../generated/macos/x86_64/lib/.unistd.md | 49 ++++++++ .../generated/macos/x86_64/lib/.wchar.md | 41 ++++++ .../generated/macos/x86_64/lib/.wctype.md | 45 +++++++ .../macos/x86_64/lib/selinux/.context.md | 41 ++++++ .../macos/x86_64/lib/selinux/.selinux.md | 52 ++++++++ .../generated/macos/x86_64/lib/sys/.stat.md | 47 +++++++ .../generated/macos/x86_64/lib/sys/.time.md | 46 +++++++ .../generated/macos/x86_64/lib/sys/.types.md | 30 +++++ .../generated/macos/x86_64/lib/sys/.wait.md | 43 +++++++ .../aarch64/include/aws/common/.config.md | 33 +++++ .../x86_64/include/aws/common/.config.md | 36 ++++++ .../aarch64/include/aws/common/.config.md | 32 +++++ .../x86_64/include/aws/common/.config.md | 40 ++++++ .../aarch64/include/aws/common/.config.md | 33 +++++ .../x86_64/include/aws/common/.config.md | 35 ++++++ .../linux/aarch64/aws/core/.SDKConfig.md | 22 ++++ .../linux/aarch64/aws/core/.VersionConfig.md | 25 ++++ .../linux/x86_64/aws/core/.SDKConfig.md | 28 +++++ .../linux/x86_64/aws/core/.VersionConfig.md | 33 +++++ .../macos/aarch64/aws/core/.SDKConfig.md | 20 +++ .../macos/aarch64/aws/core/.VersionConfig.md | 25 ++++ .../macos/x86_64/aws/core/.SDKConfig.md | 27 ++++ .../macos/x86_64/aws/core/.VersionConfig.md | 26 ++++ .../windows/aarch64/aws/core/.SDKConfig.md | 21 ++++ .../aarch64/aws/core/.VersionConfig.md | 25 ++++ .../windows/x86_64/aws/core/.SDKConfig.md | 25 ++++ .../windows/x86_64/aws/core/.VersionConfig.md | 27 ++++ .../linux/aarch64/aws/crt/.Config.md | 17 +++ .../linux/x86_64/aws/crt/.Config.md | 18 +++ .../macos/aarch64/aws/crt/.Config.md | 18 +++ .../macos/x86_64/aws/crt/.Config.md | 23 ++++ .../windows/aarch64/aws/crt/.Config.md | 18 +++ .../windows/x86_64/aws/crt/.Config.md | 19 +++ .../source/dbus/config/aarch64/.config.md | 57 +++++++++ .../source/dbus/config/x86_64/.config.md | 54 ++++++++ .../generated/aarch64/dbus/.dbus-arch-deps.md | 49 ++++++++ .../generated/x86_64/dbus/.dbus-arch-deps.md | 48 +++++++ .../expat/config/aarch64/.expat_config.md | 57 +++++++++ .../expat/config/x86_64/.expat_config.md | 55 ++++++++ .../generated/gflags/.gflags_completions.md | 41 ++++++ .../linux/aarch64/private/.defines.md | 47 +++++++ .../linux/aarch64/public/gflags/.gflags.md | 65 ++++++++++ .../aarch64/public/gflags/.gflags_declare.md | 49 ++++++++ .../linux/x86_64/private/.defines.md | 47 +++++++ .../linux/x86_64/public/gflags/.gflags.md | 61 +++++++++ .../x86_64/public/gflags/.gflags_declare.md | 47 +++++++ .../macos/aarch64/private/.defines.md | 45 +++++++ .../macos/aarch64/public/gflags/.gflags.md | 68 ++++++++++ .../aarch64/public/gflags/.gflags_declare.md | 49 ++++++++ .../macos/x86_64/private/.defines.md | 44 +++++++ .../macos/x86_64/public/gflags/.gflags.md | 67 ++++++++++ .../x86_64/public/gflags/.gflags_declare.md | 53 ++++++++ .../windows/aarch64/private/.defines.md | 41 ++++++ .../windows/aarch64/public/gflags/.gflags.md | 69 ++++++++++ .../aarch64/public/gflags/.gflags_declare.md | 52 ++++++++ .../windows/x86_64/private/.defines.md | 38 ++++++ .../windows/x86_64/public/gflags/.gflags.md | 61 +++++++++ .../x86_64/public/gflags/.gflags_declare.md | 38 ++++++ .../linux/aarch64/private/.config.md | 54 ++++++++ .../linux/aarch64/public/glog/.export.md | 40 ++++++ .../linux/aarch64/public/glog/.logging.md | 71 +++++++++++ .../linux/aarch64/public/glog/.raw_logging.md | 49 ++++++++ .../linux/aarch64/public/glog/.stl_logging.md | 50 ++++++++ .../linux/aarch64/public/glog/.vlog_is_on.md | 41 ++++++ .../generated/linux/x86_64/private/.config.md | 45 +++++++ .../linux/x86_64/public/glog/.export.md | 44 +++++++ .../linux/x86_64/public/glog/.logging.md | 58 +++++++++ .../linux/x86_64/public/glog/.raw_logging.md | 49 ++++++++ .../linux/x86_64/public/glog/.stl_logging.md | 45 +++++++ .../linux/x86_64/public/glog/.vlog_is_on.md | 33 +++++ .../macos/aarch64/private/.config.md | 52 ++++++++ .../macos/aarch64/public/glog/.export.md | 36 ++++++ .../macos/aarch64/public/glog/.logging.md | 60 +++++++++ .../macos/aarch64/public/glog/.raw_logging.md | 48 +++++++ .../macos/aarch64/public/glog/.stl_logging.md | 43 +++++++ .../macos/aarch64/public/glog/.vlog_is_on.md | 42 +++++++ .../generated/macos/x86_64/private/.config.md | 56 +++++++++ .../macos/x86_64/public/glog/.export.md | 36 ++++++ .../macos/x86_64/public/glog/.logging.md | 68 ++++++++++ .../macos/x86_64/public/glog/.raw_logging.md | 43 +++++++ .../macos/x86_64/public/glog/.stl_logging.md | 45 +++++++ .../macos/x86_64/public/glog/.vlog_is_on.md | 36 ++++++ .../windows/aarch64/private/.config.md | 45 +++++++ .../windows/aarch64/public/glog/.export.md | 41 ++++++ .../windows/aarch64/public/glog/.logging.md | 63 ++++++++++ .../aarch64/public/glog/.raw_logging.md | 39 ++++++ .../aarch64/public/glog/.stl_logging.md | 49 ++++++++ .../aarch64/public/glog/.vlog_is_on.md | 40 ++++++ .../windows/x86_64/private/.config.md | 53 ++++++++ .../windows/x86_64/public/glog/.export.md | 42 +++++++ .../windows/x86_64/public/glog/.logging.md | 82 ++++++++++++ .../x86_64/public/glog/.raw_logging.md | 47 +++++++ .../x86_64/public/glog/.stl_logging.md | 46 +++++++ .../windows/x86_64/public/glog/.vlog_is_on.md | 32 +++++ .../cmake/source/krabsetw/config/.krabs.md | 23 ++++ .../config/linux/aarch64/.config.md | 42 +++++++ .../libarchive/config/linux/x86_64/.config.md | 64 ++++++++++ .../config/macos/aarch64/.config.md | 56 +++++++++ .../libarchive/config/macos/x86_64/.config.md | 59 +++++++++ .../config/windows/aarch64/.config.md | 59 +++++++++ .../config/windows/x86_64/.config.md | 59 +++++++++ .../source/libaudit/config/aarch64/.config.md | 66 ++++++++++ .../source/libaudit/config/x86_64/.config.md | 64 ++++++++++ .../libaudit/generated/.aarch64_tables.md | 36 ++++++ .../source/libaudit/generated/.actiontabs.md | 44 +++++++ .../source/libaudit/generated/.arm_tables.md | 38 ++++++ .../source/libaudit/generated/.errtabs.md | 40 ++++++ .../source/libaudit/generated/.fieldtabs.md | 38 ++++++ .../source/libaudit/generated/.flagtabs.md | 57 +++++++++ .../source/libaudit/generated/.ftypetabs.md | 33 +++++ .../source/libaudit/generated/.gen_tables.md | 41 ++++++ .../source/libaudit/generated/.i386_tables.md | 39 ++++++ .../source/libaudit/generated/.ia64_tables.md | 32 +++++ .../source/libaudit/generated/.machinetabs.md | 44 +++++++ .../libaudit/generated/.msg_typetabs.md | 44 +++++++ .../source/libaudit/generated/.optabs.md | 30 +++++ .../source/libaudit/generated/.ppc_tables.md | 43 +++++++ .../source/libaudit/generated/.s390_tables.md | 29 +++++ .../libaudit/generated/.s390x_tables.md | 31 +++++ .../libaudit/generated/.x86_64_tables.md | 40 ++++++ .../libcap/generated/.cap_names.list.md | 37 ++++++ .../source/libcap/generated/.cap_names.md | 39 ++++++ .../source/libcryptsetup/config/.config.md | 118 ++++++++++++++++++ .../source/libdevmapper/config/.config.md | 53 ++++++++ .../source/libdevmapper/config/.configure.md | 90 +++++++++++++ .../libdevmapper/include/.lvm-version.md | 42 +++++++ .../source/libdpkg/config/aarch64/.config.md | 87 +++++++++++++ .../source/libdpkg/config/x86_64/.config.md | 68 ++++++++++ .../libgcrypt/config/aarch64/.config.md | 88 +++++++++++++ .../source/libgcrypt/config/x86_64/.config.md | 49 ++++++++ .../generated/aarch64/cipher/.gost-sb.md | 44 +++++++ .../generated/aarch64/mpi/.asm-syntax.md | 30 +++++ .../generated/aarch64/mpi/.mod-source-info.md | 32 +++++ .../generated/aarch64/mpi/.mpi-asm-defs.md | 21 ++++ .../generated/aarch64/mpi/.mpih-lshift.md | 39 ++++++ .../generated/aarch64/mpi/.mpih-rshift.md | 34 +++++ .../generated/aarch64/src/.gcrypt.md | 48 +++++++ .../generated/x86_64/cipher/.gost-sb.md | 27 ++++ .../generated/x86_64/mpi/.mod-source-info.md | 31 +++++ .../generated/x86_64/mpi/.mpi-asm-defs.md | 24 ++++ .../generated/x86_64/mpi/.mpih-add1.md | 41 ++++++ .../generated/x86_64/mpi/.mpih-lshift.md | 41 ++++++ .../generated/x86_64/mpi/.mpih-mul1.md | 36 ++++++ .../generated/x86_64/mpi/.mpih-mul2.md | 33 +++++ .../generated/x86_64/mpi/.mpih-mul3.md | 38 ++++++ .../generated/x86_64/mpi/.mpih-rshift.md | 41 ++++++ .../generated/x86_64/mpi/.mpih-sub1.md | 44 +++++++ .../libgcrypt/generated/x86_64/src/.gcrypt.md | 61 +++++++++ .../libgpg-error/config/aarch64/.config.md | 57 +++++++++ .../libgpg-error/config/x86_64/.config.md | 52 ++++++++ .../generated/aarch64/.code-from-errno.md | 37 ++++++ .../generated/aarch64/.code-to-errno.md | 30 +++++ .../generated/aarch64/.err-codes-sym.md | 37 ++++++ .../generated/aarch64/.err-codes.md | 34 +++++ .../generated/aarch64/.err-sources-sym.md | 34 +++++ .../generated/aarch64/.err-sources.md | 39 ++++++ .../generated/aarch64/.errnos-sym.md | 34 +++++ .../generated/aarch64/.gpg-error.md | 48 +++++++ .../libgpg-error/generated/aarch64/.gpgrt.md | 57 +++++++++ .../generated/aarch64/.mkerrcodes.md | 41 ++++++ .../generated/x86_64/.code-from-errno.md | 41 ++++++ .../generated/x86_64/.code-to-errno.md | 30 +++++ .../generated/x86_64/.err-codes-sym.md | 34 +++++ .../generated/x86_64/.err-codes.md | 41 ++++++ .../generated/x86_64/.err-sources-sym.md | 36 ++++++ .../generated/x86_64/.err-sources.md | 37 ++++++ .../generated/x86_64/.errnos-sym.md | 29 +++++ .../generated/x86_64/.gpg-error.md | 43 +++++++ .../libgpg-error/generated/x86_64/.gpgrt.md | 51 ++++++++ .../generated/x86_64/.mkerrcodes.md | 45 +++++++ .../source/libiptables/config/.config.md | 53 ++++++++ .../libmagic/config/linux/aarch64/.config.md | 46 +++++++ .../libmagic/config/linux/x86_64/.config.md | 55 ++++++++ .../libmagic/config/macos/aarch64/.config.md | 49 ++++++++ .../libmagic/config/macos/x86_64/.config.md | 50 ++++++++ .../cmake/source/libmagic/include/.magic.md | 84 +++++++++++++ .../config/linux/aarch64/.config.md | 42 +++++++ .../librdkafka/config/linux/x86_64/.config.md | 41 ++++++ .../config/macos/aarch64/.config.md | 39 ++++++ .../librdkafka/config/macos/x86_64/.config.md | 41 ++++++ .../config/windows/aarch64/.config.md | 55 ++++++++ .../config/windows/x86_64/.config.md | 53 ++++++++ .../source/librpm/config/aarch64/.config.md | 56 +++++++++ .../source/librpm/config/x86_64/.config.md | 61 +++++++++ .../cmake/source/libudev/config/.config.md | 45 +++++++ .../lzma/config/linux/aarch64/.config.md | 54 ++++++++ .../lzma/config/linux/x86_64/.config.md | 55 ++++++++ .../lzma/config/macos/aarch64/.config.md | 55 ++++++++ .../lzma/config/macos/x86_64/.config.md | 55 ++++++++ .../lzma/config/windows/aarch64/.config.md | 51 ++++++++ .../lzma/config/windows/x86_64/.config.md | 69 ++++++++++ .../popt/config/linux/aarch64/.config.md | 51 ++++++++ .../popt/config/linux/x86_64/.config.md | 51 ++++++++ .../cmake/source/rocksdb/patches/.env_win.md | 22 ++++ .../config/linux/aarch64/tsk/.tsk_config.md | 44 +++++++ .../config/linux/aarch64/tsk/.tsk_incs.md | 39 ++++++ .../config/linux/x86_64/tsk/.tsk_config.md | 47 +++++++ .../config/linux/x86_64/tsk/.tsk_incs.md | 31 +++++ .../config/macos/aarch64/tsk/.tsk_config.md | 49 ++++++++ .../config/macos/aarch64/tsk/.tsk_incs.md | 30 +++++ .../config/macos/x86_64/tsk/.tsk_config.md | 53 ++++++++ .../config/macos/x86_64/tsk/.tsk_incs.md | 35 ++++++ .../config/linux/aarch64/thrift/.config.md | 53 ++++++++ .../config/linux/x86_64/thrift/.config.md | 50 ++++++++ .../config/macos/aarch64/thrift/.config.md | 53 ++++++++ .../config/macos/x86_64/thrift/.config.md | 48 +++++++ .../config/windows/aarch64/thrift/.config.md | 49 ++++++++ .../config/windows/x86_64/thrift/.config.md | 50 ++++++++ .../source/thrift/patches/.random_shuffle.md | 35 ++++++ .../util-linux/config/aarch64/.config.md | 48 +++++++ .../util-linux/config/x86_64/.config.md | 51 ++++++++ .../generated/aarch64/include/blkid/.blkid.md | 97 ++++++++++++++ .../generated/x86_64/include/blkid/.blkid.md | 64 ++++++++++ openframe/.openframe_authorization_manager.md | 34 +++++ ...penframe_authorization_manager_provider.md | 25 ++++ openframe/.openframe_encryption_service.md | 43 +++++++ openframe/.openframe_token_extractor.md | 37 ++++++ openframe/.openframe_token_refresher.md | 38 ++++++ osquery/.empty.md | 14 +++ osquery/carver/.carver.md | 40 ++++++ osquery/carver/.carver_utils.md | 40 ++++++ osquery/carver/tests/.carver_tests.md | 42 +++++++ osquery/config/.config.md | 62 +++++++++ osquery/config/.packs.md | 51 ++++++++ osquery/config/tests/.config_tests.md | 40 ++++++ osquery/config/tests/.packs.md | 42 +++++++ osquery/config/tests/.test_utils.md | 37 ++++++ osquery/core/.core.md | 30 +++++ osquery/core/.flagalias.md | 32 +++++ osquery/core/.flags.md | 55 ++++++++ osquery/core/.init.md | 34 +++++ osquery/core/.query.md | 60 +++++++++ osquery/core/.shutdown.md | 47 +++++++ osquery/core/.system.md | 52 ++++++++ osquery/core/.tables.md | 43 +++++++ osquery/core/.watcher.md | 41 ++++++ osquery/core/plugins/.logger.md | 58 +++++++++ osquery/core/plugins/.plugin.md | 57 +++++++++ osquery/core/plugins/.sql.md | 49 ++++++++ osquery/core/posix/.platform.md | 22 ++++ osquery/core/sql/.column.md | 45 +++++++ osquery/core/sql/.diff_results.md | 44 +++++++ osquery/core/sql/.query_data.md | 47 +++++++ osquery/core/sql/.query_performance.md | 45 +++++++ osquery/core/sql/.row.md | 57 +++++++++ osquery/core/sql/.scheduled_query.md | 48 +++++++ osquery/core/sql/.table_row.md | 52 ++++++++ osquery/core/sql/.table_rows.md | 47 +++++++ osquery/core/tests/.flags_tests.md | 35 ++++++ osquery/core/tests/.process_tests.md | 45 +++++++ .../core/tests/.query_performance_tests.md | 32 +++++ osquery/core/tests/.query_tests.md | 45 +++++++ osquery/core/tests/.system_test.md | 26 ++++ osquery/core/tests/.tables_tests.md | 37 ++++++ osquery/core/tests/.watcher_tests.md | 39 ++++++ .../core/tests/posix/.permissions_tests.md | 39 ++++++ osquery/core/tests/windows/.wmi_tests.md | 41 ++++++ .../windows/.global_users_groups_cache.md | 34 +++++ osquery/core/windows/.handle.md | 41 ++++++ osquery/core/windows/.ntapi.md | 64 ++++++++++ osquery/core/windows/.platform.md | 28 +++++ osquery/core/windows/.wmi.md | 50 ++++++++ osquery/database/.database.md | 58 +++++++++ osquery/database/.ephemeral.md | 42 +++++++ osquery/database/.idatabaseinterface.md | 50 ++++++++ .../benchmarks/.database_benchmarks.md | 45 +++++++ osquery/database/tests/.database.md | 47 +++++++ osquery/database/tests/.results.md | 47 +++++++ osquery/database/tests/.test_utils.md | 41 ++++++ osquery/devtools/.devtools.md | 45 +++++++ osquery/devtools/.printer.md | 39 ++++++ osquery/devtools/.shell.md | 39 ++++++ osquery/devtools/tests/.printer_tests.md | 39 ++++++ osquery/dispatcher/.dispatcher.md | 74 +++++++++++ osquery/dispatcher/.distributed_runner.md | 32 +++++ osquery/dispatcher/.scheduler.md | 54 ++++++++ osquery/dispatcher/tests/.dispatcher.md | 38 ++++++ osquery/dispatcher/tests/.scheduler.md | 33 +++++ osquery/distributed/.distributed.md | 51 ++++++++ .../distributed/tests/.distributed_tests.md | 31 +++++ osquery/events/.audit_flags.md | 36 ++++++ osquery/events/.eventer.md | 51 ++++++++ osquery/events/.eventfactory.md | 47 +++++++ osquery/events/.eventpublisher.md | 67 ++++++++++ osquery/events/.eventpublisherplugin.md | 62 +++++++++ osquery/events/.events.md | 26 ++++ osquery/events/.eventsubscriber.md | 37 ++++++ osquery/events/.eventsubscriberplugin.md | 49 ++++++++ osquery/events/.file_events_flags.md | 28 +++++ osquery/events/.pathset.md | 51 ++++++++ osquery/events/.subscription.md | 40 ++++++ osquery/events/.types.md | 44 +++++++ .../events/benchmarks/.events_benchmarks.md | 37 ++++++ osquery/events/darwin/.diskarbitration.md | 50 ++++++++ osquery/events/darwin/.endpointsecurity.md | 49 ++++++++ .../events/darwin/.endpointsecurity_fim.md | 46 +++++++ osquery/events/darwin/.es_utils.md | 40 ++++++ osquery/events/darwin/.event_taps.md | 46 +++++++ osquery/events/darwin/.fsevents.md | 47 +++++++ osquery/events/darwin/.iokit.md | 57 +++++++++ osquery/events/darwin/.openbsm.md | 37 ++++++ osquery/events/darwin/.scnetwork.md | 46 +++++++ osquery/events/linux/.apparmor_events.md | 25 ++++ osquery/events/linux/.auditdnetlink.md | 43 +++++++ osquery/events/linux/.auditeventpublisher.md | 60 +++++++++ osquery/events/linux/.inotify.md | 44 +++++++ osquery/events/linux/.process_events.md | 38 ++++++ osquery/events/linux/.process_file_events.md | 37 ++++++ osquery/events/linux/.selinux_events.md | 35 ++++++ osquery/events/linux/.socket_events.md | 26 ++++ osquery/events/linux/.syslog.md | 40 ++++++ osquery/events/linux/.udev.md | 49 ++++++++ osquery/events/linux/bpf/.bpferrorstate.md | 38 ++++++ .../events/linux/bpf/.bpfeventpublisher.md | 54 ++++++++ osquery/events/linux/bpf/.filesystem.md | 48 +++++++ osquery/events/linux/bpf/.ifilesystem.md | 50 ++++++++ .../linux/bpf/.iprocesscontextfactory.md | 56 +++++++++ .../events/linux/bpf/.isystemstatetracker.md | 63 ++++++++++ .../linux/bpf/.processcontextfactory.md | 51 ++++++++ osquery/events/linux/bpf/.serializers.md | 28 +++++ osquery/events/linux/bpf/.setrlimit.md | 27 ++++ .../events/linux/bpf/.systemstatetracker.md | 52 ++++++++ osquery/events/linux/bpf/.uniquedir.md | 33 +++++ osquery/events/tests/.events_tests.md | 42 +++++++ .../events/tests/.eventsubscriberplugin.md | 51 ++++++++ .../events/tests/.mockedosquerydatabase.md | 41 ++++++ .../events/tests/darwin/.fsevents_tests.md | 55 ++++++++ osquery/events/tests/linux/.audit_tests.md | 40 ++++++ osquery/events/tests/linux/.inotify_tests.md | 54 ++++++++ .../tests/linux/.process_file_events_tests.md | 40 ++++++ osquery/events/tests/linux/.socket_events.md | 41 ++++++ osquery/events/tests/linux/.syslog_tests.md | 39 ++++++ .../tests/linux/bpf/.bpfeventpublisher.md | 39 ++++++ .../events/tests/linux/bpf/.bpftestsmain.md | 40 ++++++ .../tests/linux/bpf/.mockedfilesystem.md | 37 ++++++ .../linux/bpf/.mockedprocesscontextfactory.md | 42 +++++++ .../tests/linux/bpf/.processcontextfactory.md | 44 +++++++ .../tests/linux/bpf/.systemstatetracker.md | 48 +++++++ osquery/events/tests/linux/bpf/.utils.md | 53 ++++++++ .../windows/.usn_journal_reader_tests.md | 42 +++++++ osquery/events/windows/.evtsubscription.md | 36 ++++++ .../events/windows/.ntfs_event_publisher.md | 54 ++++++++ osquery/events/windows/.usn_journal_reader.md | 52 ++++++++ .../events/windows/.windowseventlogparser.md | 57 +++++++++ .../windows/.windowseventlogparserservice.md | 50 ++++++++ .../windows/.windowseventlogpublisher.md | 49 ++++++++ .../windows/etw/.etw_concurrent_queue.md | 47 +++++++ osquery/events/windows/etw/.etw_controller.md | 49 ++++++++ osquery/events/windows/etw/.etw_data_event.md | 55 ++++++++ .../events/windows/etw/.etw_kernel_session.md | 42 +++++++ osquery/events/windows/etw/.etw_krabs.md | 35 ++++++ .../etw/.etw_post_processing_pipeline.md | 44 +++++++ .../windows/etw/.etw_provider_config.md | 51 ++++++++ osquery/events/windows/etw/.etw_publisher.md | 46 +++++++ .../events/windows/etw/.etw_publisher_dns.md | 51 ++++++++ .../windows/etw/.etw_publisher_processes.md | 59 +++++++++ .../events/windows/etw/.etw_user_session.md | 49 ++++++++ .../events_stream/.events_stream.md | 18 +++ .../events_stream/.events_stream_registry.md | 38 ++++++ .../osquery/experiments/.linuxevents.md | 15 +++ .../linuxevents/src/.bpfprocesseventstable.md | 43 +++++++ .../linuxevents/src/.linuxevents.md | 37 ++++++ .../linuxevents/src/.linuxeventsservice.md | 35 ++++++ .../include/osquery/experiments/.loader.md | 24 ++++ .../experiments/loader/src/.loader.md | 34 +++++ osquery/extensions/.extensions.md | 52 ++++++++ osquery/extensions/.impl_thrift.md | 58 +++++++++ osquery/extensions/.interface.md | 40 ++++++ osquery/extensions/tests/.extensions.md | 40 ++++++ osquery/extensions/thrift/gen/.Extension.md | 51 ++++++++ .../thrift/gen/.ExtensionManager.md | 52 ++++++++ .../gen/.ExtensionManager_server.skeleton.md | 36 ++++++ .../thrift/gen/.Extension_server.skeleton.md | 48 +++++++ .../thrift/gen/.osquery_constants.md | 21 ++++ .../extensions/thrift/gen/.osquery_types.md | 51 ++++++++ osquery/filesystem/.file_compression.md | 41 ++++++ osquery/filesystem/.fileops.md | 68 ++++++++++ osquery/filesystem/.filesystem.md | 71 +++++++++++ osquery/filesystem/.mock_file_structure.md | 34 +++++ osquery/filesystem/darwin/.bsd_file_flags.md | 48 +++++++ .../darwin/benchmarks/.plist_benchmarks.md | 38 ++++++ osquery/filesystem/linux/.mem.md | 41 ++++++ osquery/filesystem/linux/.mounts.md | 52 ++++++++ osquery/filesystem/linux/.proc.md | 57 +++++++++ osquery/filesystem/posix/.fileops.md | 60 +++++++++ osquery/filesystem/posix/.xattrs.md | 38 ++++++ osquery/filesystem/tests/.fileops.md | 47 +++++++ osquery/filesystem/tests/.filesystem.md | 52 ++++++++ .../tests/darwin/.bsd_file_flags_tests.md | 31 +++++ .../filesystem/tests/darwin/.plist_tests.md | 46 +++++++ osquery/filesystem/tests/linux/.proc_tests.md | 34 +++++ .../tests/posix/.filesystem_tests.md | 26 ++++ osquery/filesystem/tests/posix/.xattrs.md | 35 ++++++ .../tests/windows/.filesystem_tests.md | 34 +++++ osquery/filesystem/windows/.fileops.md | 49 ++++++++ osquery/hashing/.hashing.md | 49 ++++++++ osquery/hashing/tests/.hashing.md | 36 ++++++ osquery/logger/.data_logger.md | 58 +++++++++ osquery/logger/.logger.md | 37 ++++++ .../logger/benchmarks/.logger_benchmarks.md | 31 +++++ osquery/logger/tests/.logger.md | 49 ++++++++ osquery/main/.benchmarks.md | 38 ++++++ osquery/main/.empty.md | 13 ++ osquery/main/.main.md | 32 +++++ osquery/main/.tests.md | 35 ++++++ osquery/main/harnesses/.fuzz_config.md | 32 +++++ osquery/main/harnesses/.fuzz_sqlquery.md | 28 +++++ osquery/main/harnesses/.fuzz_utils.md | 23 ++++ osquery/main/posix/.main.md | 35 ++++++ osquery/main/windows/.main.md | 40 ++++++ .../numeric_monitoring/.numeric_monitoring.md | 53 ++++++++ .../numeric_monitoring/.plugin_interface.md | 39 ++++++ .../.pre_aggregation_cache.md | 42 +++++++ .../tests/.numeric_monitoring.md | 39 ++++++ .../tests/.pre_aggregation_cache.md | 46 +++++++ osquery/process/.process.md | 64 ++++++++++ osquery/process/posix/.process.md | 45 +++++++ osquery/process/posix/.process_ops.md | 51 ++++++++ osquery/process/windows/.process.md | 48 +++++++ osquery/process/windows/.process_ops.md | 48 +++++++ osquery/profiler/.code_profiler.md | 42 +++++++ osquery/profiler/posix/.code_profiler.md | 43 +++++++ osquery/profiler/windows/.code_profiler.md | 27 ++++ osquery/registry/.registry.md | 28 +++++ osquery/registry/.registry_factory.md | 41 ++++++ osquery/registry/.registry_interface.md | 50 ++++++++ osquery/registry/tests/.registry.md | 43 +++++++ osquery/remote/.http_client.md | 52 ++++++++ osquery/remote/.requests.md | 54 ++++++++ osquery/remote/.uri.md | 47 +++++++ osquery/remote/.utility.md | 47 +++++++ osquery/remote/enroll/.enroll.md | 42 +++++++ osquery/remote/enroll/tests/.enroll_tests.md | 31 +++++ osquery/remote/serializers/.json.md | 39 ++++++ .../tests/.json_serializers_tests.md | 35 ++++++ osquery/remote/tests/.requests_tests.md | 40 ++++++ osquery/remote/tests/.test_utils.md | 48 +++++++ osquery/remote/transports/.tls.md | 50 ++++++++ .../transports/tests/.tls_transports_tests.md | 50 ++++++++ osquery/sdk/.empty_register_foreign_tables.md | 21 ++++ osquery/sdk/.plugin_sdk.md | 15 +++ osquery/sdk/.sdk.md | 57 +++++++++ osquery/sdk/tests/.registry_tests.md | 49 ++++++++ osquery/sql/.dynamic_table_row.md | 46 +++++++ osquery/sql/.sql.md | 56 +++++++++ osquery/sql/.sqlite_encoding.md | 46 +++++++ osquery/sql/.sqlite_filesystem.md | 43 +++++++ osquery/sql/.sqlite_hashing.md | 48 +++++++ osquery/sql/.sqlite_math.md | 44 +++++++ osquery/sql/.sqlite_network.md | 41 ++++++ osquery/sql/.sqlite_operations.md | 42 +++++++ osquery/sql/.sqlite_string.md | 47 +++++++ osquery/sql/.sqlite_util.md | 62 +++++++++ osquery/sql/.sqlite_version.md | 42 +++++++ osquery/sql/.virtual_sqlite_table.md | 40 ++++++ osquery/sql/.virtual_table.md | 44 +++++++ osquery/sql/benchmarks/.sql_benchmarks.md | 45 +++++++ osquery/sql/tests/.sql.md | 36 ++++++ osquery/sql/tests/.sql_test_utils.md | 38 ++++++ osquery/sql/tests/.sqlite_hashing_tests.md | 39 ++++++ osquery/sql/tests/.sqlite_network_tests.md | 30 +++++ osquery/sql/tests/.sqlite_util_tests.md | 43 +++++++ osquery/sql/tests/.virtual_table.md | 45 +++++++ osquery/system/network/.hostname.md | 35 ++++++ .../system/network/tests/.host_identity.md | 35 ++++++ .../usersgroups/windows/.groups_service.md | 42 +++++++ .../windows/.users_groups_cache.md | 63 ++++++++++ .../usersgroups/windows/.users_service.md | 36 ++++++ .../windows/tests/.users_groups_cache.md | 45 +++++++ .../tables/applications/.browser_firefox.md | 40 ++++++ .../tables/applications/.jetbrains_plugins.md | 35 ++++++ .../tables/applications/.vscode_extensions.md | 41 ++++++ .../.chrome_extension_content_scripts.md | 38 ++++++ .../applications/chrome/.chrome_extensions.md | 44 +++++++ osquery/tables/applications/chrome/.utils.md | 54 ++++++++ .../applications/chrome/tests/.utils.md | 36 ++++++ .../applications/darwin/.browser_plugins.md | 38 ++++++ osquery/tables/applications/linux/.lxd.md | 58 +++++++++ .../applications/posix/.browser_opera.md | 30 +++++ .../applications/posix/.carbon_black.md | 39 ++++++ osquery/tables/applications/posix/.docker.md | 35 ++++++ .../applications/posix/.prometheus_metrics.md | 45 +++++++ .../posix/tests/.prometheus_metrics_tests.md | 41 ++++++ .../tests/.jetbrains_plugins_tests.md | 46 +++++++ .../applications/windows/.carbon_black.md | 26 ++++ .../applications/windows/.office_mru.md | 48 +++++++ .../cloud/aws/.ec2_instance_metadata.md | 41 ++++++ .../tables/cloud/aws/.ec2_instance_tags.md | 38 ++++++ .../cloud/azure/.azure_instance_metadata.md | 42 +++++++ .../cloud/azure/.azure_instance_tags.md | 38 ++++++ .../cloud/ycloud/.ycloud_instance_metadata.md | 40 ++++++ osquery/tables/events/.event_utils.md | 25 ++++ osquery/tables/events/darwin/.disk_events.md | 41 ++++++ .../events/darwin/.es_process_events.md | 42 +++++++ .../events/darwin/.es_process_file_events.md | 35 ++++++ osquery/tables/events/darwin/.file_events.md | 39 ++++++ .../tables/events/darwin/.hardware_events.md | 39 ++++++ .../tables/events/darwin/.openbsm_events.md | 44 +++++++ .../tables/events/darwin/.socket_events.md | 43 +++++++ .../events/darwin/.user_interaction_events.md | 30 +++++ .../tables/events/linux/.apparmor_events.md | 46 +++++++ .../events/linux/.bpf_process_events.md | 45 +++++++ .../tables/events/linux/.bpf_socket_events.md | 37 ++++++ osquery/tables/events/linux/.file_events.md | 34 +++++ .../tables/events/linux/.hardware_events.md | 61 +++++++++ .../tables/events/linux/.process_events.md | 38 ++++++ .../events/linux/.process_file_events.md | 48 +++++++ .../tables/events/linux/.seccomp_events.md | 51 ++++++++ .../tables/events/linux/.selinux_events.md | 43 +++++++ osquery/tables/events/linux/.socket_events.md | 46 +++++++ osquery/tables/events/linux/.syslog_events.md | 36 ++++++ osquery/tables/events/linux/.user_events.md | 42 +++++++ .../tables/events/tests/.file_events_tests.md | 37 ++++++ .../tests/linux/.bpf_process_events_tests.md | 45 +++++++ .../tests/linux/.bpf_socket_events_tests.md | 40 ++++++ .../tests/linux/.process_events_tests.md | 45 +++++++ .../tests/linux/.seccomp_events_tests.md | 34 +++++ .../tests/linux/.selinux_events_tests.md | 26 ++++ .../windows/.etw_process_events_tests.md | 43 +++++++ .../tests/windows/.powershell_events_tests.md | 40 ++++++ .../tests/windows/.windows_events_tests.md | 44 +++++++ .../events/windows/.dns_lookup_events.md | 40 ++++++ .../events/windows/.etw_process_events.md | 35 ++++++ .../events/windows/.ntfs_journal_events.md | 45 +++++++ .../events/windows/.powershell_events.md | 53 ++++++++ .../tables/events/windows/.windows_events.md | 41 ++++++ osquery/tables/forensic/.carves.md | 30 +++++ osquery/tables/networking/.curl.md | 44 +++++++ .../tables/networking/.curl_certificate.md | 32 +++++ osquery/tables/networking/.etc_hosts.md | 36 ++++++ osquery/tables/networking/.etc_protocols.md | 41 ++++++ osquery/tables/networking/.etc_services.md | 37 ++++++ osquery/tables/networking/.listening_ports.md | 48 +++++++ .../tables/networking/darwin/.interface_ip.md | 43 +++++++ osquery/tables/networking/darwin/.routes.md | 40 ++++++ .../tables/networking/darwin/.wifi_utils.md | 47 +++++++ osquery/tables/networking/linux/.arp_cache.md | 31 +++++ osquery/tables/networking/linux/.inet_diag.md | 56 +++++++++ .../tables/networking/linux/.interface_ip.md | 34 +++++ osquery/tables/networking/linux/.iptables.md | 37 ++++++ .../tables/networking/linux/.iptc_proxy.md | 68 ++++++++++ .../networking/linux/.process_open_sockets.md | 44 +++++++ osquery/tables/networking/linux/.routes.md | 37 ++++++ .../tables/networking/posix/.dns_resolvers.md | 34 +++++ .../tables/networking/posix/.interfaces.md | 34 +++++ osquery/tables/networking/posix/.utils.md | 37 ++++++ .../tests/.networking_tables_tests.md | 50 ++++++++ .../networking/tests/linux/.iptables_tests.md | 38 ++++++ .../windows/.windows_firewall_rules_tests.md | 37 ++++++ .../tables/networking/windows/.arp_cache.md | 42 +++++++ .../networking/windows/.connectivity.md | 42 +++++++ .../tables/networking/windows/.interfaces.md | 25 ++++ .../windows/.process_open_sockets.md | 36 ++++++ osquery/tables/networking/windows/.routes.md | 39 ++++++ .../tables/networking/windows/.win_sockets.md | 49 ++++++++ .../windows/.windows_firewall_rules.md | 52 ++++++++ osquery/tables/sleuthkit/.sleuthkit.md | 50 ++++++++ osquery/tables/system/.cpuid.md | 31 +++++ osquery/tables/system/.efi_misc.md | 39 ++++++ osquery/tables/system/.hash.md | 34 +++++ osquery/tables/system/.npm_packages.md | 32 +++++ osquery/tables/system/.python_packages.md | 39 ++++++ osquery/tables/system/.smbios_utils.md | 62 +++++++++ osquery/tables/system/.ssh_configs.md | 43 +++++++ osquery/tables/system/.ssh_keys.md | 41 ++++++ osquery/tables/system/.system_utils.md | 56 +++++++++ osquery/tables/system/.uptime.md | 36 ++++++ osquery/tables/system/.user_groups.md | 43 +++++++ osquery/tables/system/darwin/.acpi_tables.md | 42 +++++++ osquery/tables/system/darwin/.ad_config.md | 30 +++++ osquery/tables/system/darwin/.asl.md | 28 +++++ osquery/tables/system/darwin/.asl_utils.md | 39 ++++++ .../tables/system/darwin/.block_devices.md | 49 ++++++++ osquery/tables/system/darwin/.cpu_info.md | 41 ++++++ osquery/tables/system/darwin/.cpu_time.md | 36 ++++++ osquery/tables/system/darwin/.crashes.md | 38 ++++++ .../system/darwin/.cups_destinations.md | 48 +++++++ osquery/tables/system/darwin/.cups_jobs.md | 48 +++++++ .../system/darwin/.extended_attributes.md | 34 +++++ osquery/tables/system/darwin/.firewall.md | 54 ++++++++ osquery/tables/system/darwin/.gatekeeper.md | 34 +++++ .../system/darwin/.homebrew_packages.md | 41 ++++++ osquery/tables/system/darwin/.ibridge.md | 32 +++++ .../tables/system/darwin/.iokit_registry.md | 39 ++++++ .../system/darwin/.kernel_extensions.md | 42 +++++++ osquery/tables/system/darwin/.kernel_info.md | 35 ++++++ .../tables/system/darwin/.kernel_panics.md | 43 +++++++ osquery/tables/system/darwin/.keychain.md | 58 +++++++++ osquery/tables/system/darwin/.keychain_acl.md | 41 ++++++ .../tables/system/darwin/.keychain_items.md | 37 ++++++ .../tables/system/darwin/.keychain_utils.md | 58 +++++++++ osquery/tables/system/darwin/.launchd.md | 37 ++++++ .../tables/system/darwin/.managed_policies.md | 46 +++++++ osquery/tables/system/darwin/.mounts.md | 52 ++++++++ osquery/tables/system/darwin/.nfs_shares.md | 45 +++++++ osquery/tables/system/darwin/.nvram.md | 45 +++++++ osquery/tables/system/darwin/.packages.md | 49 ++++++++ .../tables/system/darwin/.password_policy.md | 46 +++++++ osquery/tables/system/darwin/.pci_devices.md | 32 +++++ osquery/tables/system/darwin/.preferences.md | 31 +++++ .../darwin/.process_open_descriptors.md | 42 +++++++ osquery/tables/system/darwin/.processes.md | 44 +++++++ .../tables/system/darwin/.quicklook_cache.md | 34 +++++ osquery/tables/system/darwin/.sandboxes.md | 32 +++++ .../system/darwin/.sharing_preferences.md | 52 ++++++++ osquery/tables/system/darwin/.sip_config.md | 35 ++++++ .../tables/system/darwin/.smbios_tables.md | 48 +++++++ osquery/tables/system/darwin/.smbios_utils.md | 39 ++++++ osquery/tables/system/darwin/.smc_keys.md | 56 +++++++++ .../tables/system/darwin/.startup_items.md | 43 +++++++ osquery/tables/system/darwin/.sysctl_utils.md | 39 ++++++ osquery/tables/system/darwin/.system_info.md | 39 ++++++ osquery/tables/system/darwin/.time_machine.md | 41 ++++++ osquery/tables/system/darwin/.usb_devices.md | 41 ++++++ .../system/darwin/.virtual_memory_info.md | 39 ++++++ osquery/tables/system/darwin/.xprotect.md | 40 ++++++ osquery/tables/system/freenux/.cpu_time.md | 42 +++++++ osquery/tables/system/linux/.acpi_tables.md | 39 ++++++ .../tables/system/linux/.apparmor_profiles.md | 30 +++++ osquery/tables/system/linux/.apt_sources.md | 41 ++++++ osquery/tables/system/linux/.block_devices.md | 57 +++++++++ osquery/tables/system/linux/.certificates.md | 37 ++++++ osquery/tables/system/linux/.cpu_info.md | 45 +++++++ osquery/tables/system/linux/.deb_packages.md | 41 ++++++ .../tables/system/linux/.disk_encryption.md | 43 +++++++ .../system/linux/.extended_attributes.md | 47 +++++++ osquery/tables/system/linux/.groups.md | 32 +++++ osquery/tables/system/linux/.intel_me.md | 46 +++++++ osquery/tables/system/linux/.kernel_info.md | 36 ++++++ osquery/tables/system/linux/.kernel_keys.md | 45 +++++++ .../tables/system/linux/.kernel_modules.md | 43 +++++++ osquery/tables/system/linux/.md_tables.md | 54 ++++++++ osquery/tables/system/linux/.memory_info.md | 33 +++++ osquery/tables/system/linux/.memory_map.md | 39 ++++++ .../system/linux/.model_specific_register.md | 53 ++++++++ osquery/tables/system/linux/.mounts.md | 43 +++++++ osquery/tables/system/linux/.os_version.md | 41 ++++++ osquery/tables/system/linux/.pci_devices.md | 54 ++++++++ osquery/tables/system/linux/.portage.md | 51 ++++++++ .../system/linux/.process_open_files.md | 41 ++++++ .../system/linux/.process_open_pipes.md | 43 +++++++ osquery/tables/system/linux/.processes.md | 22 ++++ osquery/tables/system/linux/.rpm_packages.md | 41 ++++++ osquery/tables/system/linux/.secureboot.md | 31 +++++ .../tables/system/linux/.selinux_settings.md | 37 ++++++ osquery/tables/system/linux/.shadow.md | 38 ++++++ osquery/tables/system/linux/.shared_memory.md | 43 +++++++ osquery/tables/system/linux/.smbios_tables.md | 59 +++++++++ osquery/tables/system/linux/.smbios_utils.md | 40 ++++++ osquery/tables/system/linux/.startup_items.md | 50 ++++++++ osquery/tables/system/linux/.sysctl_utils.md | 41 ++++++ osquery/tables/system/linux/.system_info.md | 42 +++++++ osquery/tables/system/linux/.systemd_units.md | 40 ++++++ osquery/tables/system/linux/.usb_devices.md | 44 +++++++ osquery/tables/system/linux/.user_groups.md | 31 +++++ osquery/tables/system/linux/.users.md | 34 +++++ osquery/tables/system/linux/.yum_sources.md | 35 ++++++ .../linux/dbus/.uniquedbusconnection.md | 49 ++++++++ .../system/linux/dbus/.uniquedbusmessage.md | 40 ++++++ .../system/linux/dbus/.uniqueresource.md | 53 ++++++++ .../system/linux/dbus/methods/.dbusmethod.md | 51 ++++++++ .../linux/dbus/methods/.getstringproperty.md | 40 ++++++ .../dbus/methods/.listunitsmethodhandler.md | 44 +++++++ osquery/tables/system/posix/.augeas.md | 39 ++++++ .../tables/system/posix/.authorized_keys.md | 39 ++++++ osquery/tables/system/posix/.crontab.md | 30 +++++ osquery/tables/system/posix/.known_hosts.md | 32 +++++ osquery/tables/system/posix/.last.md | 31 +++++ osquery/tables/system/posix/.load_average.md | 35 ++++++ .../tables/system/posix/.logged_in_users.md | 35 ++++++ osquery/tables/system/posix/.magic.md | 35 ++++++ osquery/tables/system/posix/.openssl_utils.md | 45 +++++++ osquery/tables/system/posix/.shell_history.md | 42 +++++++ osquery/tables/system/posix/.smbios_utils.md | 49 ++++++++ osquery/tables/system/posix/.ssh_keys.md | 38 ++++++ osquery/tables/system/posix/.sudoers.md | 32 +++++ osquery/tables/system/posix/.suid_bin.md | 33 +++++ osquery/tables/system/posix/.sysctl_utils.md | 44 +++++++ .../tables/system/posix/.system_controls.md | 38 ++++++ osquery/tables/system/posix/.ulimit_info.md | 42 +++++++ .../system/tests/.system_tables_tests.md | 50 ++++++++ .../tables/system/tests/darwin/.apps_tests.md | 41 ++++++ .../tables/system/tests/darwin/.asl_tests.md | 29 +++++ .../tests/darwin/.certificates_tests.md | 35 ++++++ .../darwin/.extended_attributes_tests.md | 39 ++++++ .../system/tests/darwin/.firewall_tests.md | 37 ++++++ .../system/tests/darwin/.keychain_test.md | 48 +++++++ .../system/tests/darwin/.launchd_tests.md | 40 ++++++ .../system/tests/darwin/.mdfind_tests.md | 42 +++++++ .../system/tests/darwin/.packages_tests.md | 36 ++++++ .../system/tests/darwin/.processes_tests.md | 39 ++++++ .../tables/system/tests/darwin/.smc_tests.md | 40 ++++++ .../tests/darwin/.startup_items_tests.md | 40 ++++++ .../tests/darwin/.system_extensions_test.md | 40 ++++++ .../system/tests/darwin/.unsigned_test.md | 24 ++++ .../system/tests/linux/.apt_sources_tests.md | 38 ++++++ .../tests/linux/.extended_attributes_tests.md | 41 ++++++ .../system/tests/linux/.md_tables_tests.md | 46 +++++++ .../system/tests/linux/.pci_devices_tests.md | 36 ++++++ .../tables/system/tests/linux/.pcidb_tests.md | 43 +++++++ .../system/tests/linux/.portage_tests.md | 32 +++++ .../system/tests/linux/.processes_tests.md | 37 ++++++ .../system/tests/linux/.rpm_packages_tests.md | 41 ++++++ .../tests/linux/.selinux_settings_tests.md | 35 ++++++ .../system/tests/linux/.yum_sources_tests.md | 39 ++++++ .../system/tests/posix/.augeas_tests.md | 35 ++++++ .../tests/posix/.authorized_keys_tests.md | 40 ++++++ .../system/tests/posix/.known_hosts_tests.md | 40 ++++++ .../tables/system/tests/posix/.last_tests.md | 45 +++++++ .../tests/posix/.shell_history_tests.md | 39 ++++++ .../system/tests/posix/.ssh_keys_tests.md | 41 ++++++ .../system/tests/posix/.sudoers_tests.md | 35 ++++++ .../tests/windows/.certificates_tests.md | 37 ++++++ .../system/tests/windows/.programs_tests.md | 31 +++++ .../system/tests/windows/.registry_tests.md | 56 +++++++++ .../tests/windows/.startup_items_tests.md | 35 ++++++ .../tests/windows/.windows_eventlog_tests.md | 42 +++++++ .../.windows_optional_features_tests.md | 36 ++++++ .../windows/.windows_update_history_tests.md | 34 +++++ .../tables/system/windows/.appcompat_shims.md | 43 +++++++ .../tables/system/windows/.authenticode.md | 47 +++++++ osquery/tables/system/windows/.autoexec.md | 44 +++++++ .../.background_activities_moderator.md | 33 +++++ osquery/tables/system/windows/.battery.md | 37 ++++++ .../tables/system/windows/.bitlocker_info.md | 47 +++++++ .../tables/system/windows/.certificates.md | 35 ++++++ .../tables/system/windows/.chassis_info.md | 45 +++++++ .../system/windows/.chocolatey_packages.md | 40 ++++++ osquery/tables/system/windows/.cpu_info.md | 43 +++++++ .../system/windows/.default_environment.md | 30 +++++ .../system/windows/.deviceguard_status.md | 46 +++++++ osquery/tables/system/windows/.disk_info.md | 45 +++++++ osquery/tables/system/windows/.dns_cache.md | 31 +++++ osquery/tables/system/windows/.drivers.md | 34 +++++ osquery/tables/system/windows/.groups.md | 50 ++++++++ .../tables/system/windows/.ie_extensions.md | 32 +++++ osquery/tables/system/windows/.intel_me.md | 49 ++++++++ osquery/tables/system/windows/.kernel_info.md | 38 ++++++ .../system/windows/.kva_speculative_info.md | 40 ++++++ .../tables/system/windows/.logged_in_users.md | 48 +++++++ .../tables/system/windows/.logical_drives.md | 32 +++++ .../tables/system/windows/.logon_sessions.md | 43 +++++++ osquery/tables/system/windows/.ntdomains.md | 39 ++++++ .../system/windows/.ntfs_acl_permissions.md | 37 ++++++ osquery/tables/system/windows/.objects.md | 40 ++++++ osquery/tables/system/windows/.os_version.md | 42 +++++++ osquery/tables/system/windows/.patches.md | 40 ++++++ .../windows/.physical_disk_performance.md | 47 +++++++ osquery/tables/system/windows/.pipes.md | 38 ++++++ osquery/tables/system/windows/.prefetch.md | 41 ++++++ osquery/tables/system/windows/.processes.md | 44 +++++++ osquery/tables/system/windows/.programs.md | 36 ++++++ osquery/tables/system/windows/.registry.md | 56 +++++++++ .../tables/system/windows/.scheduled_tasks.md | 39 ++++++ osquery/tables/system/windows/.secureboot.md | 39 ++++++ .../system/windows/.security_profile_info.md | 39 ++++++ .../windows/.security_profile_info_utils.md | 58 +++++++++ osquery/tables/system/windows/.services.md | 44 +++++++ .../system/windows/.shared_resources.md | 44 +++++++ osquery/tables/system/windows/.shellbags.md | 49 ++++++++ osquery/tables/system/windows/.shimcache.md | 33 +++++ .../tables/system/windows/.smbios_tables.md | 38 ++++++ .../tables/system/windows/.startup_items.md | 28 +++++ osquery/tables/system/windows/.system_info.md | 40 ++++++ osquery/tables/system/windows/.tpm_info.md | 41 ++++++ osquery/tables/system/windows/.user_groups.md | 39 ++++++ osquery/tables/system/windows/.userassist.md | 31 +++++ osquery/tables/system/windows/.users.md | 35 ++++++ osquery/tables/system/windows/.video_info.md | 46 +++++++ .../tables/system/windows/.windows_crashes.md | 53 ++++++++ .../system/windows/.windows_eventlog.md | 46 +++++++ .../windows/.windows_optional_features.md | 36 ++++++ .../tables/system/windows/.windows_search.md | 31 +++++ .../windows/.windows_security_center.md | 46 +++++++ .../windows/.windows_security_products.md | 31 +++++ .../system/windows/.windows_update_history.md | 47 +++++++ .../tables/system/windows/.wmi_bios_info.md | 30 +++++ .../windows/.wmi_cli_event_consumers.md | 37 ++++++ .../system/windows/.wmi_event_filters.md | 32 +++++ .../windows/.wmi_filter_consumer_binding.md | 30 +++++ .../windows/.wmi_script_event_consumers.md | 36 ++++++ osquery/tables/utility/.file.md | 33 +++++ osquery/tables/utility/.osquery.md | 38 ++++++ osquery/tables/utility/.time.md | 39 ++++++ osquery/tables/yara/.yara.md | 47 +++++++ osquery/tables/yara/.yara_events.md | 57 +++++++++ osquery/tables/yara/.yara_utils.md | 45 +++++++ osquery/tables/yara/tests/.yara_tests.md | 43 +++++++ osquery/tables/yara/windows/.yara_events.md | 42 +++++++ osquery/utils/.attribute.md | 29 +++++ osquery/utils/.base64.md | 25 ++++ osquery/utils/.chars.md | 41 ++++++ osquery/utils/.enum_class_hash.md | 29 +++++ osquery/utils/.map_take.md | 43 +++++++ osquery/utils/.mutex.md | 46 +++++++ osquery/utils/.only_movable.md | 47 +++++++ osquery/utils/.rot13.md | 19 +++ osquery/utils/.scope_guard.md | 31 +++++ osquery/utils/aws/.aws_util.md | 52 ++++++++ osquery/utils/aws/tests/.aws_util_tests.md | 34 +++++ osquery/utils/azure/.azure_util.md | 42 +++++++ osquery/utils/caches/.lru-impl.md | 32 +++++ osquery/utils/caches/.lru.md | 53 ++++++++ osquery/utils/caches/tests/.lru.md | 46 +++++++ osquery/utils/config/.default_paths.md | 40 ++++++ osquery/utils/conversions/.castvariant.md | 34 +++++ osquery/utils/conversions/.join.md | 30 +++++ osquery/utils/conversions/.split.md | 35 ++++++ osquery/utils/conversions/.to.md | 44 +++++++ osquery/utils/conversions/.trim.md | 21 ++++ osquery/utils/conversions/.tryto.md | 47 +++++++ osquery/utils/conversions/darwin/.cfdata.md | 27 ++++ .../utils/conversions/darwin/.cfdictionary.md | 37 ++++++ osquery/utils/conversions/darwin/.cfnumber.md | 32 +++++ osquery/utils/conversions/darwin/.cfstring.md | 25 ++++ osquery/utils/conversions/darwin/.cftime.md | 25 ++++ osquery/utils/conversions/darwin/.iokit.md | 51 ++++++++ .../conversions/darwin/tests/.cfdictionary.md | 39 ++++++ .../conversions/darwin/tests/.cfstring.md | 35 ++++++ osquery/utils/conversions/tests/.join.md | 22 ++++ osquery/utils/conversions/tests/.split.md | 35 ++++++ osquery/utils/conversions/tests/.to.md | 31 +++++ osquery/utils/conversions/tests/.trim.md | 38 ++++++ osquery/utils/conversions/tests/.tryto.md | 43 +++++++ osquery/utils/conversions/windows/.strings.md | 39 ++++++ .../conversions/windows/.windows_time.md | 38 ++++++ .../conversions/windows/tests/.strings.md | 41 ++++++ .../windows/tests/.windows_time.md | 38 ++++++ osquery/utils/darwin/.iokit_helpers.md | 54 ++++++++ osquery/utils/darwin/.plist.md | 42 +++++++ osquery/utils/darwin/.system_profiler.md | 34 +++++ osquery/utils/debug/.debug_only.md | 47 +++++++ osquery/utils/debug/tests/.debug_only.md | 43 +++++++ osquery/utils/error/.error.md | 36 ++++++ osquery/utils/error/tests/.error.md | 44 +++++++ osquery/utils/expected/.expected.md | 57 +++++++++ osquery/utils/expected/tests/.expected.md | 54 ++++++++ osquery/utils/info/.firmware.md | 42 +++++++ osquery/utils/info/.platform_type.md | 37 ++++++ osquery/utils/info/.tool_type.md | 45 +++++++ osquery/utils/info/.version.md | 40 ++++++ osquery/utils/info/firmware/.common.md | 34 +++++ osquery/utils/info/firmware/.linux.md | 31 +++++ osquery/utils/info/firmware/.macos.md | 42 +++++++ osquery/utils/info/firmware/.windows.md | 52 ++++++++ osquery/utils/json/.json.md | 50 ++++++++ osquery/utils/json/tests/.json.md | 42 +++++++ osquery/utils/linux/dpkg/.dpkgquery.md | 43 +++++++ osquery/utils/linux/dpkg/.idpkgquery.md | 61 +++++++++ osquery/utils/linux/dpkg/.modstatdb.md | 43 +++++++ osquery/utils/linux/dpkg/.pkgarray.md | 50 ++++++++ osquery/utils/macros/.macros.md | 33 +++++ osquery/utils/pidfile/.pidfile.md | 55 ++++++++ osquery/utils/pidfile/.pidfile_posix.md | 44 +++++++ osquery/utils/pidfile/.pidfile_windows.md | 44 +++++++ osquery/utils/pidfile/tests/.pidfile.md | 44 +++++++ osquery/utils/schemer/.schemer.md | 44 +++++++ osquery/utils/schemer/json/.schemer_json.md | 51 ++++++++ .../utils/schemer/json/.schemer_json_error.md | 45 +++++++ .../utils/schemer/json/.schemer_json_impl.md | 51 ++++++++ .../utils/schemer/json/tests/.schemer_json.md | 52 ++++++++ osquery/utils/schemer/tests/.schemer.md | 47 +++++++ osquery/utils/status/.status.md | 47 +++++++ osquery/utils/status/tests/.status.md | 45 +++++++ osquery/utils/system/.boottime.md | 25 ++++ osquery/utils/system/.env.md | 40 ++++++ osquery/utils/system/.errno.md | 40 ++++++ osquery/utils/system/.filepath.md | 24 ++++ osquery/utils/system/.system.md | 38 ++++++ osquery/utils/system/.time.md | 38 ++++++ osquery/utils/system/.uptime.md | 15 +++ osquery/utils/system/linux/.boottime.md | 26 ++++ osquery/utils/system/linux/.cpu.md | 46 +++++++ osquery/utils/system/linux/proc/.proc.md | 18 +++ .../utils/system/linux/proc/tests/.empty.md | 13 ++ .../utils/system/linux/proc/tests/.proc.md | 31 +++++ osquery/utils/system/linux/tests/.cpu.md | 39 ++++++ osquery/utils/system/posix/.env.md | 43 +++++++ osquery/utils/system/posix/.errno.md | 36 ++++++ osquery/utils/system/posix/.filepath.md | 33 +++++ osquery/utils/system/posix/.system.md | 49 ++++++++ osquery/utils/system/posix/.time.md | 31 +++++ osquery/utils/system/posix/tests/.errno.md | 28 +++++ osquery/utils/system/tests/.cpu.md | 20 +++ osquery/utils/system/tests/.errno.md | 25 ++++ osquery/utils/system/tests/.time.md | 26 ++++ osquery/utils/system/windows/.env.md | 48 +++++++ osquery/utils/system/windows/.errno.md | 31 +++++ osquery/utils/system/windows/.etw_helpers.md | 38 ++++++ osquery/utils/system/windows/.system.md | 39 ++++++ osquery/utils/system/windows/.time.md | 28 +++++ .../system/windows/.users_groups_helpers.md | 56 +++++++++ osquery/utils/tests/.base64.md | 26 ++++ osquery/utils/tests/.chars.md | 44 +++++++ osquery/utils/tests/.map_take.md | 42 +++++++ osquery/utils/tests/.rot13.md | 25 ++++ osquery/utils/tests/.scope_guard.md | 35 ++++++ osquery/utils/tests/windows/.env.md | 38 ++++++ osquery/utils/tests/windows/.filetime.md | 26 ++++ osquery/utils/tests/windows/.shellitems.md | 46 +++++++ osquery/utils/windows/.lzxpress.md | 34 +++++ osquery/utils/windows/.shellitem.md | 56 +++++++++ osquery/utils/ycloud/.ycloud_util.md | 36 ++++++ osquery/utils/ycloud/tests/.ycloud.md | 32 +++++ .../worker/ipc/.table_ipc_json_converter.md | 48 +++++++ .../include/.platform_table_container_ipc.md | 28 +++++ .../worker/ipc/include/.table_channel_base.md | 39 ++++++ .../include/.table_channel_factory_base.md | 53 ++++++++ osquery/worker/ipc/include/.table_ipc_base.md | 48 +++++++ .../ipc/include/.table_ipc_message_handler.md | 39 ++++++ .../ipc/linux/.linux_table_container_ipc.md | 53 ++++++++ osquery/worker/ipc/linux/.linux_table_ipc.md | 44 +++++++ .../linux/.platform_table_container_ipc.md | 18 +++ .../tests/.worker_table_container_tests.md | 37 ++++++ osquery/worker/ipc/posix/.pipe_channel.md | 36 ++++++ .../worker/ipc/posix/.pipe_channel_factory.md | 46 +++++++ .../posix/tests/.worker_ipc_channels_test.md | 45 +++++++ .../tests/.worker_json_conversions_test.md | 40 ++++++ osquery/worker/logging/glog/.glog_logger.md | 33 +++++ .../logging/include/.glog_logger_types.md | 22 ++++ osquery/worker/logging/include/.logger.md | 40 ++++++ osquery/worker/system/.memory.md | 25 ++++ osquery/worker/system/linux/.memory.md | 62 +++++++++ plugins/config/.filesystem_config.md | 34 +++++ plugins/config/.tls_config.md | 39 ++++++ plugins/config/.update.md | 23 ++++ .../parsers/.auto_constructed_tables.md | 45 +++++++ plugins/config/parsers/.decorators.md | 46 +++++++ plugins/config/parsers/.events_parser.md | 24 ++++ plugins/config/parsers/.feature_vectors.md | 27 ++++ plugins/config/parsers/.file_paths.md | 42 +++++++ plugins/config/parsers/.kafka_topics.md | 34 +++++ plugins/config/parsers/.logger.md | 29 +++++ plugins/config/parsers/.options.md | 38 ++++++ plugins/config/parsers/.prometheus_targets.md | 29 +++++ plugins/config/parsers/.views.md | 41 ++++++ .../config/parsers/tests/.decorators_tests.md | 37 ++++++ .../parsers/tests/.events_parser_tests.md | 35 ++++++ .../config/parsers/tests/.file_paths_tests.md | 40 ++++++ .../config/parsers/tests/.options_tests.md | 38 ++++++ plugins/config/parsers/tests/.views_tests.md | 36 ++++++ plugins/config/tests/.tls_config_tests.md | 32 +++++ plugins/database/.rocksdb.md | 59 +++++++++ plugins/database/.sqlite.md | 50 ++++++++ plugins/database/tests/.rocksdb.md | 38 ++++++ plugins/database/tests/.sqlite.md | 34 +++++ plugins/distributed/.tls_distributed.md | 44 +++++++ plugins/logger/.aws_firehose.md | 49 ++++++++ plugins/logger/.aws_kinesis.md | 47 +++++++ plugins/logger/.aws_log_forwarder.md | 81 ++++++++++++ plugins/logger/.buffered.md | 56 +++++++++ plugins/logger/.filesystem_logger.md | 37 ++++++ plugins/logger/.generated_wel.md | 35 ++++++ plugins/logger/.kafka_producer.md | 40 ++++++ plugins/logger/.logrotate.md | 48 +++++++ plugins/logger/.stdout.md | 40 ++++++ plugins/logger/.syslog_logger.md | 40 ++++++ plugins/logger/.tls_logger.md | 51 ++++++++ plugins/logger/.windows_event_log.md | 48 +++++++ .../logger/tests/.aws_kinesis_logger_tests.md | 42 +++++++ plugins/logger/tests/.buffered_tests.md | 36 ++++++ .../logger/tests/.filesystem_logger_tests.md | 41 ++++++ plugins/logger/tests/.kafka_producer_tests.md | 39 ++++++ plugins/logger/tests/.logrotate_tests.md | 39 ++++++ plugins/logger/tests/.syslog_logger_tests.md | 40 ++++++ plugins/logger/tests/.tls_logger_tests.md | 51 ++++++++ plugins/numeric_monitoring/.filesystem.md | 45 +++++++ .../numeric_monitoring/tests/.filesystem.md | 47 +++++++ plugins/remote/enroll/.tls_enroll.md | 31 +++++ .../remote/enroll/tests/.tls_enroll_tests.md | 48 +++++++ tests/.test_util.md | 48 +++++++ .../tables/.account_policy_data.md | 35 ++++++ tests/integration/tables/.acpi_tables.md | 24 ++++ tests/integration/tables/.ad_config.md | 27 ++++ tests/integration/tables/.alf.md | 48 +++++++ tests/integration/tables/.alf_exceptions.md | 40 ++++++ .../integration/tables/.alf_explicit_auths.md | 33 +++++ tests/integration/tables/.alf_services.md | 46 +++++++ tests/integration/tables/.app_schemes.md | 31 +++++ tests/integration/tables/.appcompat_shims.md | 36 ++++++ tests/integration/tables/.apps.md | 42 +++++++ tests/integration/tables/.apt_sources.md | 43 +++++++ tests/integration/tables/.arp_cache.md | 35 ++++++ tests/integration/tables/.asl.md | 32 +++++ tests/integration/tables/.augeas.md | 29 +++++ tests/integration/tables/.authenticode.md | 33 +++++ .../tables/.authorization_mechanisms.md | 32 +++++ tests/integration/tables/.authorizations.md | 38 ++++++ tests/integration/tables/.authorized_keys.md | 31 +++++ tests/integration/tables/.autoexec.md | 30 +++++ .../tables/.azure_instance_metadata.md | 35 ++++++ .../tables/.azure_instance_tags.md | 38 ++++++ .../.background_activities_moderator.md | 31 +++++ tests/integration/tables/.battery.md | 31 +++++ tests/integration/tables/.bitlocker_info.md | 34 +++++ tests/integration/tables/.block_devices.md | 36 ++++++ tests/integration/tables/.browser_plugins.md | 38 ++++++ .../integration/tables/.carbon_black_info.md | 32 +++++ tests/integration/tables/.carves.md | 34 +++++ tests/integration/tables/.certificates.md | 33 +++++ tests/integration/tables/.chassis_info.md | 42 +++++++ .../tables/.chocolatey_packages.md | 37 ++++++ .../.chrome_extension_content_scripts.md | 36 ++++++ .../integration/tables/.chrome_extensions.md | 31 +++++ .../integration/tables/.connected_displays.md | 43 +++++++ tests/integration/tables/.connectivity.md | 42 +++++++ tests/integration/tables/.cpu_info.md | 35 ++++++ tests/integration/tables/.cpu_time.md | 35 ++++++ tests/integration/tables/.cpuid.md | 37 ++++++ tests/integration/tables/.crashes.md | 36 ++++++ tests/integration/tables/.crontab.md | 31 +++++ .../integration/tables/.cups_destinations.md | 28 +++++ tests/integration/tables/.cups_jobs.md | 34 +++++ tests/integration/tables/.curl.md | 32 +++++ tests/integration/tables/.curl_certificate.md | 41 ++++++ tests/integration/tables/.deb_packages.md | 44 +++++++ .../tables/.default_environment.md | 41 ++++++ tests/integration/tables/.device_file.md | 42 +++++++ tests/integration/tables/.device_firmware.md | 27 ++++ tests/integration/tables/.device_hash.md | 34 +++++ .../integration/tables/.device_partitions.md | 42 +++++++ .../integration/tables/.deviceguard_status.md | 43 +++++++ tests/integration/tables/.disk_encryption.md | 34 +++++ tests/integration/tables/.disk_events.md | 36 ++++++ tests/integration/tables/.disk_info.md | 43 +++++++ .../integration/tables/.dns_lookup_events.md | 50 ++++++++ tests/integration/tables/.dns_resolvers.md | 35 ++++++ .../tables/.docker_container_envs.md | 28 +++++ .../tables/.docker_container_labels.md | 30 +++++ .../tables/.docker_container_mounts.md | 35 ++++++ .../tables/.docker_container_networks.md | 38 ++++++ .../tables/.docker_container_ports.md | 32 +++++ .../tables/.docker_container_processes.md | 36 ++++++ .../tables/.docker_container_stats.md | 41 ++++++ .../integration/tables/.docker_containers.md | 32 +++++ .../tables/.docker_image_history.md | 36 ++++++ .../tables/.docker_image_labels.md | 33 +++++ .../tables/.docker_image_layers.md | 43 +++++++ tests/integration/tables/.docker_images.md | 36 ++++++ tests/integration/tables/.docker_info.md | 37 ++++++ .../tables/.docker_network_labels.md | 29 +++++ tests/integration/tables/.docker_networks.md | 33 +++++ tests/integration/tables/.docker_version.md | 36 ++++++ .../tables/.docker_volume_labels.md | 33 +++++ tests/integration/tables/.docker_volumes.md | 28 +++++ tests/integration/tables/.drivers.md | 39 ++++++ .../tables/.ec2_instance_metadata.md | 34 +++++ .../integration/tables/.ec2_instance_tags.md | 29 +++++ tests/integration/tables/.etc_hosts.md | 29 +++++ tests/integration/tables/.etc_protocols.md | 36 ++++++ tests/integration/tables/.etc_services.md | 27 ++++ .../integration/tables/.etw_process_events.md | 38 ++++++ tests/integration/tables/.event_taps.md | 29 +++++ tests/integration/tables/.example.md | 41 ++++++ .../tables/.extended_attributes.md | 36 ++++++ .../integration/tables/.fan_speed_sensors.md | 29 +++++ tests/integration/tables/.fbsd_kmods.md | 34 +++++ tests/integration/tables/.file.md | 31 +++++ tests/integration/tables/.file_events.md | 37 ++++++ tests/integration/tables/.firefox_addons.md | 34 +++++ tests/integration/tables/.gatekeeper.md | 41 ++++++ .../tables/.gatekeeper_approved_apps.md | 29 +++++ tests/integration/tables/.groups.md | 36 ++++++ tests/integration/tables/.hardware_events.md | 38 ++++++ tests/integration/tables/.hash.md | 34 +++++ tests/integration/tables/.helper.md | 70 +++++++++++ .../integration/tables/.homebrew_packages.md | 28 +++++ tests/integration/tables/.ibridge.md | 41 ++++++ tests/integration/tables/.ie_extensions.md | 34 +++++ tests/integration/tables/.intel_me_info.md | 29 +++++ .../tables/.interface_addresses.md | 41 ++++++ .../integration/tables/.interface_details.md | 35 ++++++ tests/integration/tables/.interface_ipv6.md | 40 ++++++ tests/integration/tables/.iokit_devicetree.md | 36 ++++++ tests/integration/tables/.iokit_registry.md | 32 +++++ tests/integration/tables/.iptables.md | 40 ++++++ .../integration/tables/.jetbrains_plugins.md | 39 ++++++ .../integration/tables/.kernel_extensions.md | 33 +++++ tests/integration/tables/.kernel_info.md | 35 ++++++ tests/integration/tables/.kernel_keys.md | 23 ++++ tests/integration/tables/.kernel_modules.md | 43 +++++++ tests/integration/tables/.kernel_panics.md | 39 ++++++ tests/integration/tables/.keychain_acls.md | 42 +++++++ tests/integration/tables/.keychain_items.md | 37 ++++++ tests/integration/tables/.known_hosts.md | 27 ++++ .../tables/.kva_speculative_info.md | 43 +++++++ tests/integration/tables/.last.md | 44 +++++++ tests/integration/tables/.launchd.md | 37 ++++++ .../integration/tables/.launchd_overrides.md | 30 +++++ tests/integration/tables/.listening_ports.md | 35 ++++++ tests/integration/tables/.load_average.md | 26 ++++ .../integration/tables/.location_services.md | 33 +++++ tests/integration/tables/.logged_in_users.md | 40 ++++++ tests/integration/tables/.logical_drives.md | 42 +++++++ tests/integration/tables/.logon_sessions.md | 42 +++++++ tests/integration/tables/.magic.md | 30 +++++ tests/integration/tables/.managed_policies.md | 39 ++++++ tests/integration/tables/.md_devices.md | 35 ++++++ tests/integration/tables/.md_drives.md | 31 +++++ tests/integration/tables/.md_personalities.md | 26 ++++ tests/integration/tables/.mdfind.md | 46 +++++++ .../tables/.memory_array_mapped_addresses.md | 31 +++++ tests/integration/tables/.memory_arrays.md | 32 +++++ .../tables/.memory_device_mapped_addresses.md | 33 +++++ tests/integration/tables/.memory_devices.md | 33 +++++ .../integration/tables/.memory_error_info.md | 34 +++++ tests/integration/tables/.memory_info.md | 43 +++++++ tests/integration/tables/.memory_map.md | 33 +++++ tests/integration/tables/.mounts.md | 39 ++++++ tests/integration/tables/.msr.md | 44 +++++++ tests/integration/tables/.nfs_shares.md | 29 +++++ tests/integration/tables/.npm_packages.md | 35 ++++++ tests/integration/tables/.ntdomains.md | 40 ++++++ .../tables/.ntfs_acl_permissions.md | 33 +++++ tests/integration/tables/.nvram.md | 39 ++++++ tests/integration/tables/.oem_strings.md | 31 +++++ tests/integration/tables/.office_mru.md | 34 +++++ tests/integration/tables/.os_version.md | 41 ++++++ tests/integration/tables/.osquery_events.md | 32 +++++ .../integration/tables/.osquery_extensions.md | 32 +++++ tests/integration/tables/.osquery_flags.md | 34 +++++ tests/integration/tables/.osquery_info.md | 37 ++++++ tests/integration/tables/.osquery_packs.md | 33 +++++ tests/integration/tables/.osquery_registry.md | 39 ++++++ tests/integration/tables/.osquery_schedule.md | 43 +++++++ tests/integration/tables/.package_bom.md | 35 ++++++ .../tables/.package_install_history.md | 40 ++++++ tests/integration/tables/.package_receipts.md | 32 +++++ tests/integration/tables/.patches.md | 33 +++++ tests/integration/tables/.pci_devices.md | 40 ++++++ .../tables/.physical_disk_performance.md | 39 ++++++ tests/integration/tables/.pipes.md | 28 +++++ tests/integration/tables/.pkg_packages.md | 32 +++++ tests/integration/tables/.platform_info.md | 40 ++++++ tests/integration/tables/.plist.md | 32 +++++ tests/integration/tables/.portage_keywords.md | 40 ++++++ tests/integration/tables/.portage_packages.md | 33 +++++ tests/integration/tables/.portage_use.md | 28 +++++ tests/integration/tables/.power_sensors.md | 28 +++++ .../integration/tables/.powershell_events.md | 33 +++++ tests/integration/tables/.preferences.md | 38 ++++++ tests/integration/tables/.prefetch.md | 44 +++++++ tests/integration/tables/.process_envs.md | 42 +++++++ tests/integration/tables/.process_events.md | 34 +++++ .../tables/.process_file_events.md | 40 ++++++ .../integration/tables/.process_memory_map.md | 50 ++++++++ .../integration/tables/.process_namespaces.md | 33 +++++ .../integration/tables/.process_open_files.md | 40 ++++++ .../integration/tables/.process_open_pipes.md | 44 +++++++ .../tables/.process_open_sockets.md | 29 +++++ tests/integration/tables/.processes.md | 35 ++++++ tests/integration/tables/.programs.md | 51 ++++++++ .../integration/tables/.prometheus_metrics.md | 29 +++++ tests/integration/tables/.python_packages.md | 34 +++++ tests/integration/tables/.quicklook_cache.md | 36 ++++++ tests/integration/tables/.registry.md | 37 ++++++ tests/integration/tables/.routes.md | 43 +++++++ .../integration/tables/.rpm_package_files.md | 33 +++++ tests/integration/tables/.rpm_packages.md | 50 ++++++++ tests/integration/tables/.running_apps.md | 30 +++++ .../integration/tables/.safari_extensions.md | 32 +++++ tests/integration/tables/.sandboxes.md | 41 ++++++ tests/integration/tables/.scheduled_tasks.md | 35 ++++++ tests/integration/tables/.secureboot.md | 38 ++++++ .../tables/.security_profile_info.md | 34 +++++ tests/integration/tables/.selinux_events.md | 32 +++++ tests/integration/tables/.services.md | 35 ++++++ tests/integration/tables/.shadow.md | 31 +++++ tests/integration/tables/.shared_folders.md | 28 +++++ tests/integration/tables/.shared_memory.md | 39 ++++++ tests/integration/tables/.shared_resources.md | 34 +++++ .../tables/.sharing_preferences.md | 27 ++++ tests/integration/tables/.shell_history.md | 30 +++++ tests/integration/tables/.shellbags.md | 43 +++++++ tests/integration/tables/.shimcache.md | 33 +++++ tests/integration/tables/.signature.md | 30 +++++ tests/integration/tables/.sip_config.md | 25 ++++ tests/integration/tables/.smart_drive_info.md | 33 +++++ tests/integration/tables/.smbios_tables.md | 32 +++++ tests/integration/tables/.smc_keys.md | 31 +++++ tests/integration/tables/.socket_events.md | 38 ++++++ tests/integration/tables/.ssh_configs.md | 28 +++++ tests/integration/tables/.startup_items.md | 45 +++++++ tests/integration/tables/.sudoers.md | 36 ++++++ tests/integration/tables/.suid_bin.md | 39 ++++++ tests/integration/tables/.syslog_events.md | 34 +++++ tests/integration/tables/.system_controls.md | 36 ++++++ .../integration/tables/.system_extensions.md | 40 ++++++ tests/integration/tables/.system_info.md | 35 ++++++ tests/integration/tables/.systemd_units.md | 46 +++++++ .../tables/.temperature_sensors.md | 30 +++++ tests/integration/tables/.time.md | 38 ++++++ .../tables/.time_machine_backups.md | 30 +++++ .../tables/.time_machine_destinations.md | 31 +++++ tests/integration/tables/.tpm_info.md | 42 +++++++ tests/integration/tables/.ulimit_info.md | 34 +++++ tests/integration/tables/.unified_log.md | 42 +++++++ tests/integration/tables/.uptime.md | 35 ++++++ tests/integration/tables/.usb_devices.md | 39 ++++++ tests/integration/tables/.user_events.md | 37 ++++++ tests/integration/tables/.user_groups.md | 27 ++++ .../tables/.user_interaction_events.md | 25 ++++ tests/integration/tables/.user_ssh_keys.md | 40 ++++++ tests/integration/tables/.userassist.md | 46 +++++++ tests/integration/tables/.users.md | 60 +++++++++ tests/integration/tables/.video_info.md | 35 ++++++ .../tables/.virtual_memory_info.md | 42 +++++++ .../integration/tables/.vscode_extensions.md | 44 +++++++ tests/integration/tables/.wifi_networks.md | 40 ++++++ tests/integration/tables/.wifi_status.md | 39 ++++++ tests/integration/tables/.wifi_survey.md | 40 ++++++ tests/integration/tables/.winbaseobj.md | 35 ++++++ tests/integration/tables/.windows_crashes.md | 34 +++++ tests/integration/tables/.windows_eventlog.md | 38 ++++++ tests/integration/tables/.windows_events.md | 38 ++++++ .../tables/.windows_firewall_rules.md | 40 ++++++ tests/integration/tables/.windows_search.md | 44 +++++++ .../tables/.windows_security_products.md | 42 +++++++ .../tables/.windows_update_history.md | 42 +++++++ tests/integration/tables/.wmi_bios_info.md | 30 +++++ .../tables/.wmi_cli_event_consumers.md | 35 ++++++ .../integration/tables/.wmi_event_filters.md | 27 ++++ .../tables/.wmi_filter_consumer_binding.md | 28 +++++ .../tables/.wmi_script_event_consumers.md | 36 ++++++ tests/integration/tables/.xprotect_entries.md | 33 +++++ tests/integration/tables/.xprotect_meta.md | 29 +++++ tests/integration/tables/.xprotect_reports.md | 32 +++++ tests/integration/tables/.yara.md | 32 +++++ tests/integration/tables/.yara_events.md | 36 ++++++ .../tables/.ycloud_instance_metadata.md | 36 ++++++ tests/integration/tables/.yum_sources.md | 36 ++++++ tools/analysis/.profile.md | 41 ++++++ tools/ci/scripts/.check_copyright_headers.md | 41 ++++++ .../.third_party_libraries_cves_scanner.md | 51 ++++++++ .../.validate_manifest_libraries_versions.md | 43 +++++++ tools/ci/scripts/cve/osquery/.github_api.md | 46 +++++++ tools/ci/scripts/cve/osquery/.manifest_api.md | 43 +++++++ tools/cmake/.downloader.md | 37 ++++++ tools/codegen/.amalgamate.md | 37 ++++++ tools/codegen/.genapi.md | 43 +++++++ tools/codegen/.gentable.md | 35 ++++++ tools/codegen/.gentargets.md | 46 +++++++ tools/codegen/.genwebsitejson.md | 37 ++++++ tools/codegen/.substitute.md | 41 ++++++ tools/codegen/.templite.md | 45 +++++++ tools/deployment/.getfiles.md | 38 ++++++ tools/formatting/.format-check.md | 36 ++++++ tools/formatting/.git-clang-format.md | 49 ++++++++ tools/tests/.plist_benchmark.md | 44 +++++++ tools/tests/.test.md | 23 ++++ tools/tests/.test_additional.md | 38 ++++++ tools/tests/.test_base.md | 37 ++++++ ....test_docker_container_fs_changes_table.md | 36 ++++++ tools/tests/.test_example_queries.md | 28 +++++ tools/tests/.test_extensions.md | 42 +++++++ tools/tests/.test_http_server.md | 50 ++++++++ tools/tests/.test_osqueryd.md | 34 +++++ tools/tests/.test_osqueryi.md | 47 +++++++ tools/tests/.test_query_packs.md | 38 ++++++ tools/tests/.test_release.md | 35 ++++++ tools/tests/.test_windows_service.md | 49 ++++++++ tools/tests/.utils.md | 39 ++++++ tools/tests/.winexpect.md | 38 ++++++ 1365 files changed, 56125 insertions(+) create mode 100644 external/examples/config_plugin/.main.md create mode 100644 external/examples/read_only_table/.main.md create mode 100644 external/examples/string_batch/.main.md create mode 100644 external/examples/writable_table/.main.md create mode 100644 libraries/cmake/source/augeas/generated/linux/aarch64/code/.lexer.md create mode 100644 libraries/cmake/source/augeas/generated/linux/aarch64/code/.parser.md create mode 100644 libraries/cmake/source/augeas/generated/linux/aarch64/config/.config.md create mode 100644 libraries/cmake/source/augeas/generated/linux/x86_64/code/.lexer.md create mode 100644 libraries/cmake/source/augeas/generated/linux/x86_64/code/.parser.md create mode 100644 libraries/cmake/source/augeas/generated/linux/x86_64/config/.config.md create mode 100644 libraries/cmake/source/augeas/generated/macos/aarch64/code/.lexer.md create mode 100644 libraries/cmake/source/augeas/generated/macos/aarch64/code/.parser.md create mode 100644 libraries/cmake/source/augeas/generated/macos/aarch64/config/.config.md create mode 100644 libraries/cmake/source/augeas/generated/macos/x86_64/code/.lexer.md create mode 100644 libraries/cmake/source/augeas/generated/macos/x86_64/code/.parser.md create mode 100644 libraries/cmake/source/augeas/generated/macos/x86_64/config/.config.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/config/.config.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.alloca.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.ctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.fcntl.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.langinfo.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.limits.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.locale.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdint.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdio.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdlib.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.string.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.unistd.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wchar.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.context.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.selinux.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.stat.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.types.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.wait.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/config/.config.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.alloca.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.ctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fcntl.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fnmatch.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt-cdefs.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.langinfo.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.limits.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.locale.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdint.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdio.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdlib.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.string.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.unistd.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wchar.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.context.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.selinux.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.stat.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.types.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.wait.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/config/.config.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.alloca.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.argz.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.ctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fcntl.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fnmatch.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt-cdefs.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.langinfo.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.limits.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.locale.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdint.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdio.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdlib.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.string.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.unistd.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wchar.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.context.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.selinux.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.stat.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.types.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.wait.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/config/.config.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.alloca.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.argz.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.ctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fcntl.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fnmatch.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt-cdefs.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.langinfo.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.limits.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.locale.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdint.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdio.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdlib.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.string.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.unistd.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wchar.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wctype.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.context.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.selinux.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.stat.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.time.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.types.md create mode 100644 libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.wait.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/aarch64/include/aws/common/.config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/x86_64/include/aws/common/.config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/aarch64/include/aws/common/.config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/x86_64/include/aws/common/.config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/aarch64/include/aws/common/.config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/x86_64/include/aws/common/.config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.SDKConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.VersionConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.SDKConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.VersionConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.SDKConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.VersionConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.SDKConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.VersionConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.SDKConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.VersionConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.SDKConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.VersionConfig.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/aarch64/aws/crt/.Config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/x86_64/aws/crt/.Config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/aarch64/aws/crt/.Config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/x86_64/aws/crt/.Config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/aarch64/aws/crt/.Config.md create mode 100644 libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/x86_64/aws/crt/.Config.md create mode 100644 libraries/cmake/source/dbus/config/aarch64/.config.md create mode 100644 libraries/cmake/source/dbus/config/x86_64/.config.md create mode 100644 libraries/cmake/source/dbus/generated/aarch64/dbus/.dbus-arch-deps.md create mode 100644 libraries/cmake/source/dbus/generated/x86_64/dbus/.dbus-arch-deps.md create mode 100644 libraries/cmake/source/expat/config/aarch64/.expat_config.md create mode 100644 libraries/cmake/source/expat/config/x86_64/.expat_config.md create mode 100644 libraries/cmake/source/gflags/generated/gflags/.gflags_completions.md create mode 100644 libraries/cmake/source/gflags/generated/linux/aarch64/private/.defines.md create mode 100644 libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags.md create mode 100644 libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags_declare.md create mode 100644 libraries/cmake/source/gflags/generated/linux/x86_64/private/.defines.md create mode 100644 libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags.md create mode 100644 libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags_declare.md create mode 100644 libraries/cmake/source/gflags/generated/macos/aarch64/private/.defines.md create mode 100644 libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags.md create mode 100644 libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags_declare.md create mode 100644 libraries/cmake/source/gflags/generated/macos/x86_64/private/.defines.md create mode 100644 libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags.md create mode 100644 libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags_declare.md create mode 100644 libraries/cmake/source/gflags/generated/windows/aarch64/private/.defines.md create mode 100644 libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags.md create mode 100644 libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags_declare.md create mode 100644 libraries/cmake/source/gflags/generated/windows/x86_64/private/.defines.md create mode 100644 libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags.md create mode 100644 libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags_declare.md create mode 100644 libraries/cmake/source/glog/generated/linux/aarch64/private/.config.md create mode 100644 libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.export.md create mode 100644 libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.logging.md create mode 100644 libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.raw_logging.md create mode 100644 libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.stl_logging.md create mode 100644 libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.vlog_is_on.md create mode 100644 libraries/cmake/source/glog/generated/linux/x86_64/private/.config.md create mode 100644 libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.export.md create mode 100644 libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.logging.md create mode 100644 libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.raw_logging.md create mode 100644 libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.stl_logging.md create mode 100644 libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.vlog_is_on.md create mode 100644 libraries/cmake/source/glog/generated/macos/aarch64/private/.config.md create mode 100644 libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.export.md create mode 100644 libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.logging.md create mode 100644 libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.raw_logging.md create mode 100644 libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.stl_logging.md create mode 100644 libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.vlog_is_on.md create mode 100644 libraries/cmake/source/glog/generated/macos/x86_64/private/.config.md create mode 100644 libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.export.md create mode 100644 libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.logging.md create mode 100644 libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.raw_logging.md create mode 100644 libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.stl_logging.md create mode 100644 libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.vlog_is_on.md create mode 100644 libraries/cmake/source/glog/generated/windows/aarch64/private/.config.md create mode 100644 libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.export.md create mode 100644 libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.logging.md create mode 100644 libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.raw_logging.md create mode 100644 libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.stl_logging.md create mode 100644 libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.vlog_is_on.md create mode 100644 libraries/cmake/source/glog/generated/windows/x86_64/private/.config.md create mode 100644 libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.export.md create mode 100644 libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.logging.md create mode 100644 libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.raw_logging.md create mode 100644 libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.stl_logging.md create mode 100644 libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.vlog_is_on.md create mode 100644 libraries/cmake/source/krabsetw/config/.krabs.md create mode 100644 libraries/cmake/source/libarchive/config/linux/aarch64/.config.md create mode 100644 libraries/cmake/source/libarchive/config/linux/x86_64/.config.md create mode 100644 libraries/cmake/source/libarchive/config/macos/aarch64/.config.md create mode 100644 libraries/cmake/source/libarchive/config/macos/x86_64/.config.md create mode 100644 libraries/cmake/source/libarchive/config/windows/aarch64/.config.md create mode 100644 libraries/cmake/source/libarchive/config/windows/x86_64/.config.md create mode 100644 libraries/cmake/source/libaudit/config/aarch64/.config.md create mode 100644 libraries/cmake/source/libaudit/config/x86_64/.config.md create mode 100644 libraries/cmake/source/libaudit/generated/.aarch64_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.actiontabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.arm_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.errtabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.fieldtabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.flagtabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.ftypetabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.gen_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.i386_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.ia64_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.machinetabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.msg_typetabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.optabs.md create mode 100644 libraries/cmake/source/libaudit/generated/.ppc_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.s390_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.s390x_tables.md create mode 100644 libraries/cmake/source/libaudit/generated/.x86_64_tables.md create mode 100644 libraries/cmake/source/libcap/generated/.cap_names.list.md create mode 100644 libraries/cmake/source/libcap/generated/.cap_names.md create mode 100644 libraries/cmake/source/libcryptsetup/config/.config.md create mode 100644 libraries/cmake/source/libdevmapper/config/.config.md create mode 100644 libraries/cmake/source/libdevmapper/config/.configure.md create mode 100644 libraries/cmake/source/libdevmapper/include/.lvm-version.md create mode 100644 libraries/cmake/source/libdpkg/config/aarch64/.config.md create mode 100644 libraries/cmake/source/libdpkg/config/x86_64/.config.md create mode 100644 libraries/cmake/source/libgcrypt/config/aarch64/.config.md create mode 100644 libraries/cmake/source/libgcrypt/config/x86_64/.config.md create mode 100644 libraries/cmake/source/libgcrypt/generated/aarch64/cipher/.gost-sb.md create mode 100644 libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.asm-syntax.md create mode 100644 libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mod-source-info.md create mode 100644 libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpi-asm-defs.md create mode 100644 libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-lshift.md create mode 100644 libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-rshift.md create mode 100644 libraries/cmake/source/libgcrypt/generated/aarch64/src/.gcrypt.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/cipher/.gost-sb.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mod-source-info.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpi-asm-defs.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-add1.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-lshift.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul1.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul2.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul3.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-rshift.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-sub1.md create mode 100644 libraries/cmake/source/libgcrypt/generated/x86_64/src/.gcrypt.md create mode 100644 libraries/cmake/source/libgpg-error/config/aarch64/.config.md create mode 100644 libraries/cmake/source/libgpg-error/config/x86_64/.config.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.code-from-errno.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.code-to-errno.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes-sym.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources-sym.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.errnos-sym.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.gpg-error.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.gpgrt.md create mode 100644 libraries/cmake/source/libgpg-error/generated/aarch64/.mkerrcodes.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.code-from-errno.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.code-to-errno.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes-sym.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources-sym.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.errnos-sym.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.gpg-error.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.gpgrt.md create mode 100644 libraries/cmake/source/libgpg-error/generated/x86_64/.mkerrcodes.md create mode 100644 libraries/cmake/source/libiptables/config/.config.md create mode 100644 libraries/cmake/source/libmagic/config/linux/aarch64/.config.md create mode 100644 libraries/cmake/source/libmagic/config/linux/x86_64/.config.md create mode 100644 libraries/cmake/source/libmagic/config/macos/aarch64/.config.md create mode 100644 libraries/cmake/source/libmagic/config/macos/x86_64/.config.md create mode 100644 libraries/cmake/source/libmagic/include/.magic.md create mode 100644 libraries/cmake/source/librdkafka/config/linux/aarch64/.config.md create mode 100644 libraries/cmake/source/librdkafka/config/linux/x86_64/.config.md create mode 100644 libraries/cmake/source/librdkafka/config/macos/aarch64/.config.md create mode 100644 libraries/cmake/source/librdkafka/config/macos/x86_64/.config.md create mode 100644 libraries/cmake/source/librdkafka/config/windows/aarch64/.config.md create mode 100644 libraries/cmake/source/librdkafka/config/windows/x86_64/.config.md create mode 100644 libraries/cmake/source/librpm/config/aarch64/.config.md create mode 100644 libraries/cmake/source/librpm/config/x86_64/.config.md create mode 100644 libraries/cmake/source/libudev/config/.config.md create mode 100644 libraries/cmake/source/lzma/config/linux/aarch64/.config.md create mode 100644 libraries/cmake/source/lzma/config/linux/x86_64/.config.md create mode 100644 libraries/cmake/source/lzma/config/macos/aarch64/.config.md create mode 100644 libraries/cmake/source/lzma/config/macos/x86_64/.config.md create mode 100644 libraries/cmake/source/lzma/config/windows/aarch64/.config.md create mode 100644 libraries/cmake/source/lzma/config/windows/x86_64/.config.md create mode 100644 libraries/cmake/source/popt/config/linux/aarch64/.config.md create mode 100644 libraries/cmake/source/popt/config/linux/x86_64/.config.md create mode 100644 libraries/cmake/source/rocksdb/patches/.env_win.md create mode 100644 libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_config.md create mode 100644 libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_incs.md create mode 100644 libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_config.md create mode 100644 libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_incs.md create mode 100644 libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_config.md create mode 100644 libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_incs.md create mode 100644 libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_config.md create mode 100644 libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_incs.md create mode 100644 libraries/cmake/source/thrift/config/linux/aarch64/thrift/.config.md create mode 100644 libraries/cmake/source/thrift/config/linux/x86_64/thrift/.config.md create mode 100644 libraries/cmake/source/thrift/config/macos/aarch64/thrift/.config.md create mode 100644 libraries/cmake/source/thrift/config/macos/x86_64/thrift/.config.md create mode 100644 libraries/cmake/source/thrift/config/windows/aarch64/thrift/.config.md create mode 100644 libraries/cmake/source/thrift/config/windows/x86_64/thrift/.config.md create mode 100644 libraries/cmake/source/thrift/patches/.random_shuffle.md create mode 100644 libraries/cmake/source/util-linux/config/aarch64/.config.md create mode 100644 libraries/cmake/source/util-linux/config/x86_64/.config.md create mode 100644 libraries/cmake/source/util-linux/generated/aarch64/include/blkid/.blkid.md create mode 100644 libraries/cmake/source/util-linux/generated/x86_64/include/blkid/.blkid.md create mode 100644 openframe/.openframe_authorization_manager.md create mode 100644 openframe/.openframe_authorization_manager_provider.md create mode 100644 openframe/.openframe_encryption_service.md create mode 100644 openframe/.openframe_token_extractor.md create mode 100644 openframe/.openframe_token_refresher.md create mode 100644 osquery/.empty.md create mode 100644 osquery/carver/.carver.md create mode 100644 osquery/carver/.carver_utils.md create mode 100644 osquery/carver/tests/.carver_tests.md create mode 100644 osquery/config/.config.md create mode 100644 osquery/config/.packs.md create mode 100644 osquery/config/tests/.config_tests.md create mode 100644 osquery/config/tests/.packs.md create mode 100644 osquery/config/tests/.test_utils.md create mode 100644 osquery/core/.core.md create mode 100644 osquery/core/.flagalias.md create mode 100644 osquery/core/.flags.md create mode 100644 osquery/core/.init.md create mode 100644 osquery/core/.query.md create mode 100644 osquery/core/.shutdown.md create mode 100644 osquery/core/.system.md create mode 100644 osquery/core/.tables.md create mode 100644 osquery/core/.watcher.md create mode 100644 osquery/core/plugins/.logger.md create mode 100644 osquery/core/plugins/.plugin.md create mode 100644 osquery/core/plugins/.sql.md create mode 100644 osquery/core/posix/.platform.md create mode 100644 osquery/core/sql/.column.md create mode 100644 osquery/core/sql/.diff_results.md create mode 100644 osquery/core/sql/.query_data.md create mode 100644 osquery/core/sql/.query_performance.md create mode 100644 osquery/core/sql/.row.md create mode 100644 osquery/core/sql/.scheduled_query.md create mode 100644 osquery/core/sql/.table_row.md create mode 100644 osquery/core/sql/.table_rows.md create mode 100644 osquery/core/tests/.flags_tests.md create mode 100644 osquery/core/tests/.process_tests.md create mode 100644 osquery/core/tests/.query_performance_tests.md create mode 100644 osquery/core/tests/.query_tests.md create mode 100644 osquery/core/tests/.system_test.md create mode 100644 osquery/core/tests/.tables_tests.md create mode 100644 osquery/core/tests/.watcher_tests.md create mode 100644 osquery/core/tests/posix/.permissions_tests.md create mode 100644 osquery/core/tests/windows/.wmi_tests.md create mode 100644 osquery/core/windows/.global_users_groups_cache.md create mode 100644 osquery/core/windows/.handle.md create mode 100644 osquery/core/windows/.ntapi.md create mode 100644 osquery/core/windows/.platform.md create mode 100644 osquery/core/windows/.wmi.md create mode 100644 osquery/database/.database.md create mode 100644 osquery/database/.ephemeral.md create mode 100644 osquery/database/.idatabaseinterface.md create mode 100644 osquery/database/benchmarks/.database_benchmarks.md create mode 100644 osquery/database/tests/.database.md create mode 100644 osquery/database/tests/.results.md create mode 100644 osquery/database/tests/.test_utils.md create mode 100644 osquery/devtools/.devtools.md create mode 100644 osquery/devtools/.printer.md create mode 100644 osquery/devtools/.shell.md create mode 100644 osquery/devtools/tests/.printer_tests.md create mode 100644 osquery/dispatcher/.dispatcher.md create mode 100644 osquery/dispatcher/.distributed_runner.md create mode 100644 osquery/dispatcher/.scheduler.md create mode 100644 osquery/dispatcher/tests/.dispatcher.md create mode 100644 osquery/dispatcher/tests/.scheduler.md create mode 100644 osquery/distributed/.distributed.md create mode 100644 osquery/distributed/tests/.distributed_tests.md create mode 100644 osquery/events/.audit_flags.md create mode 100644 osquery/events/.eventer.md create mode 100644 osquery/events/.eventfactory.md create mode 100644 osquery/events/.eventpublisher.md create mode 100644 osquery/events/.eventpublisherplugin.md create mode 100644 osquery/events/.events.md create mode 100644 osquery/events/.eventsubscriber.md create mode 100644 osquery/events/.eventsubscriberplugin.md create mode 100644 osquery/events/.file_events_flags.md create mode 100644 osquery/events/.pathset.md create mode 100644 osquery/events/.subscription.md create mode 100644 osquery/events/.types.md create mode 100644 osquery/events/benchmarks/.events_benchmarks.md create mode 100644 osquery/events/darwin/.diskarbitration.md create mode 100644 osquery/events/darwin/.endpointsecurity.md create mode 100644 osquery/events/darwin/.endpointsecurity_fim.md create mode 100644 osquery/events/darwin/.es_utils.md create mode 100644 osquery/events/darwin/.event_taps.md create mode 100644 osquery/events/darwin/.fsevents.md create mode 100644 osquery/events/darwin/.iokit.md create mode 100644 osquery/events/darwin/.openbsm.md create mode 100644 osquery/events/darwin/.scnetwork.md create mode 100644 osquery/events/linux/.apparmor_events.md create mode 100644 osquery/events/linux/.auditdnetlink.md create mode 100644 osquery/events/linux/.auditeventpublisher.md create mode 100644 osquery/events/linux/.inotify.md create mode 100644 osquery/events/linux/.process_events.md create mode 100644 osquery/events/linux/.process_file_events.md create mode 100644 osquery/events/linux/.selinux_events.md create mode 100644 osquery/events/linux/.socket_events.md create mode 100644 osquery/events/linux/.syslog.md create mode 100644 osquery/events/linux/.udev.md create mode 100644 osquery/events/linux/bpf/.bpferrorstate.md create mode 100644 osquery/events/linux/bpf/.bpfeventpublisher.md create mode 100644 osquery/events/linux/bpf/.filesystem.md create mode 100644 osquery/events/linux/bpf/.ifilesystem.md create mode 100644 osquery/events/linux/bpf/.iprocesscontextfactory.md create mode 100644 osquery/events/linux/bpf/.isystemstatetracker.md create mode 100644 osquery/events/linux/bpf/.processcontextfactory.md create mode 100644 osquery/events/linux/bpf/.serializers.md create mode 100644 osquery/events/linux/bpf/.setrlimit.md create mode 100644 osquery/events/linux/bpf/.systemstatetracker.md create mode 100644 osquery/events/linux/bpf/.uniquedir.md create mode 100644 osquery/events/tests/.events_tests.md create mode 100644 osquery/events/tests/.eventsubscriberplugin.md create mode 100644 osquery/events/tests/.mockedosquerydatabase.md create mode 100644 osquery/events/tests/darwin/.fsevents_tests.md create mode 100644 osquery/events/tests/linux/.audit_tests.md create mode 100644 osquery/events/tests/linux/.inotify_tests.md create mode 100644 osquery/events/tests/linux/.process_file_events_tests.md create mode 100644 osquery/events/tests/linux/.socket_events.md create mode 100644 osquery/events/tests/linux/.syslog_tests.md create mode 100644 osquery/events/tests/linux/bpf/.bpfeventpublisher.md create mode 100644 osquery/events/tests/linux/bpf/.bpftestsmain.md create mode 100644 osquery/events/tests/linux/bpf/.mockedfilesystem.md create mode 100644 osquery/events/tests/linux/bpf/.mockedprocesscontextfactory.md create mode 100644 osquery/events/tests/linux/bpf/.processcontextfactory.md create mode 100644 osquery/events/tests/linux/bpf/.systemstatetracker.md create mode 100644 osquery/events/tests/linux/bpf/.utils.md create mode 100644 osquery/events/tests/windows/.usn_journal_reader_tests.md create mode 100644 osquery/events/windows/.evtsubscription.md create mode 100644 osquery/events/windows/.ntfs_event_publisher.md create mode 100644 osquery/events/windows/.usn_journal_reader.md create mode 100644 osquery/events/windows/.windowseventlogparser.md create mode 100644 osquery/events/windows/.windowseventlogparserservice.md create mode 100644 osquery/events/windows/.windowseventlogpublisher.md create mode 100644 osquery/events/windows/etw/.etw_concurrent_queue.md create mode 100644 osquery/events/windows/etw/.etw_controller.md create mode 100644 osquery/events/windows/etw/.etw_data_event.md create mode 100644 osquery/events/windows/etw/.etw_kernel_session.md create mode 100644 osquery/events/windows/etw/.etw_krabs.md create mode 100644 osquery/events/windows/etw/.etw_post_processing_pipeline.md create mode 100644 osquery/events/windows/etw/.etw_provider_config.md create mode 100644 osquery/events/windows/etw/.etw_publisher.md create mode 100644 osquery/events/windows/etw/.etw_publisher_dns.md create mode 100644 osquery/events/windows/etw/.etw_publisher_processes.md create mode 100644 osquery/events/windows/etw/.etw_user_session.md create mode 100644 osquery/experimental/events_stream/.events_stream.md create mode 100644 osquery/experimental/events_stream/.events_stream_registry.md create mode 100644 osquery/experimental/experiments/linuxevents/include/osquery/experiments/.linuxevents.md create mode 100644 osquery/experimental/experiments/linuxevents/src/.bpfprocesseventstable.md create mode 100644 osquery/experimental/experiments/linuxevents/src/.linuxevents.md create mode 100644 osquery/experimental/experiments/linuxevents/src/.linuxeventsservice.md create mode 100644 osquery/experimental/experiments/loader/include/osquery/experiments/.loader.md create mode 100644 osquery/experimental/experiments/loader/src/.loader.md create mode 100644 osquery/extensions/.extensions.md create mode 100644 osquery/extensions/.impl_thrift.md create mode 100644 osquery/extensions/.interface.md create mode 100644 osquery/extensions/tests/.extensions.md create mode 100644 osquery/extensions/thrift/gen/.Extension.md create mode 100644 osquery/extensions/thrift/gen/.ExtensionManager.md create mode 100644 osquery/extensions/thrift/gen/.ExtensionManager_server.skeleton.md create mode 100644 osquery/extensions/thrift/gen/.Extension_server.skeleton.md create mode 100644 osquery/extensions/thrift/gen/.osquery_constants.md create mode 100644 osquery/extensions/thrift/gen/.osquery_types.md create mode 100644 osquery/filesystem/.file_compression.md create mode 100644 osquery/filesystem/.fileops.md create mode 100644 osquery/filesystem/.filesystem.md create mode 100644 osquery/filesystem/.mock_file_structure.md create mode 100644 osquery/filesystem/darwin/.bsd_file_flags.md create mode 100644 osquery/filesystem/darwin/benchmarks/.plist_benchmarks.md create mode 100644 osquery/filesystem/linux/.mem.md create mode 100644 osquery/filesystem/linux/.mounts.md create mode 100644 osquery/filesystem/linux/.proc.md create mode 100644 osquery/filesystem/posix/.fileops.md create mode 100644 osquery/filesystem/posix/.xattrs.md create mode 100644 osquery/filesystem/tests/.fileops.md create mode 100644 osquery/filesystem/tests/.filesystem.md create mode 100644 osquery/filesystem/tests/darwin/.bsd_file_flags_tests.md create mode 100644 osquery/filesystem/tests/darwin/.plist_tests.md create mode 100644 osquery/filesystem/tests/linux/.proc_tests.md create mode 100644 osquery/filesystem/tests/posix/.filesystem_tests.md create mode 100644 osquery/filesystem/tests/posix/.xattrs.md create mode 100644 osquery/filesystem/tests/windows/.filesystem_tests.md create mode 100644 osquery/filesystem/windows/.fileops.md create mode 100644 osquery/hashing/.hashing.md create mode 100644 osquery/hashing/tests/.hashing.md create mode 100644 osquery/logger/.data_logger.md create mode 100644 osquery/logger/.logger.md create mode 100644 osquery/logger/benchmarks/.logger_benchmarks.md create mode 100644 osquery/logger/tests/.logger.md create mode 100644 osquery/main/.benchmarks.md create mode 100644 osquery/main/.empty.md create mode 100644 osquery/main/.main.md create mode 100644 osquery/main/.tests.md create mode 100644 osquery/main/harnesses/.fuzz_config.md create mode 100644 osquery/main/harnesses/.fuzz_sqlquery.md create mode 100644 osquery/main/harnesses/.fuzz_utils.md create mode 100644 osquery/main/posix/.main.md create mode 100644 osquery/main/windows/.main.md create mode 100644 osquery/numeric_monitoring/.numeric_monitoring.md create mode 100644 osquery/numeric_monitoring/.plugin_interface.md create mode 100644 osquery/numeric_monitoring/.pre_aggregation_cache.md create mode 100644 osquery/numeric_monitoring/tests/.numeric_monitoring.md create mode 100644 osquery/numeric_monitoring/tests/.pre_aggregation_cache.md create mode 100644 osquery/process/.process.md create mode 100644 osquery/process/posix/.process.md create mode 100644 osquery/process/posix/.process_ops.md create mode 100644 osquery/process/windows/.process.md create mode 100644 osquery/process/windows/.process_ops.md create mode 100644 osquery/profiler/.code_profiler.md create mode 100644 osquery/profiler/posix/.code_profiler.md create mode 100644 osquery/profiler/windows/.code_profiler.md create mode 100644 osquery/registry/.registry.md create mode 100644 osquery/registry/.registry_factory.md create mode 100644 osquery/registry/.registry_interface.md create mode 100644 osquery/registry/tests/.registry.md create mode 100644 osquery/remote/.http_client.md create mode 100644 osquery/remote/.requests.md create mode 100644 osquery/remote/.uri.md create mode 100644 osquery/remote/.utility.md create mode 100644 osquery/remote/enroll/.enroll.md create mode 100644 osquery/remote/enroll/tests/.enroll_tests.md create mode 100644 osquery/remote/serializers/.json.md create mode 100644 osquery/remote/serializers/tests/.json_serializers_tests.md create mode 100644 osquery/remote/tests/.requests_tests.md create mode 100644 osquery/remote/tests/.test_utils.md create mode 100644 osquery/remote/transports/.tls.md create mode 100644 osquery/remote/transports/tests/.tls_transports_tests.md create mode 100644 osquery/sdk/.empty_register_foreign_tables.md create mode 100644 osquery/sdk/.plugin_sdk.md create mode 100644 osquery/sdk/.sdk.md create mode 100644 osquery/sdk/tests/.registry_tests.md create mode 100644 osquery/sql/.dynamic_table_row.md create mode 100644 osquery/sql/.sql.md create mode 100644 osquery/sql/.sqlite_encoding.md create mode 100644 osquery/sql/.sqlite_filesystem.md create mode 100644 osquery/sql/.sqlite_hashing.md create mode 100644 osquery/sql/.sqlite_math.md create mode 100644 osquery/sql/.sqlite_network.md create mode 100644 osquery/sql/.sqlite_operations.md create mode 100644 osquery/sql/.sqlite_string.md create mode 100644 osquery/sql/.sqlite_util.md create mode 100644 osquery/sql/.sqlite_version.md create mode 100644 osquery/sql/.virtual_sqlite_table.md create mode 100644 osquery/sql/.virtual_table.md create mode 100644 osquery/sql/benchmarks/.sql_benchmarks.md create mode 100644 osquery/sql/tests/.sql.md create mode 100644 osquery/sql/tests/.sql_test_utils.md create mode 100644 osquery/sql/tests/.sqlite_hashing_tests.md create mode 100644 osquery/sql/tests/.sqlite_network_tests.md create mode 100644 osquery/sql/tests/.sqlite_util_tests.md create mode 100644 osquery/sql/tests/.virtual_table.md create mode 100644 osquery/system/network/.hostname.md create mode 100644 osquery/system/network/tests/.host_identity.md create mode 100644 osquery/system/usersgroups/windows/.groups_service.md create mode 100644 osquery/system/usersgroups/windows/.users_groups_cache.md create mode 100644 osquery/system/usersgroups/windows/.users_service.md create mode 100644 osquery/system/usersgroups/windows/tests/.users_groups_cache.md create mode 100644 osquery/tables/applications/.browser_firefox.md create mode 100644 osquery/tables/applications/.jetbrains_plugins.md create mode 100644 osquery/tables/applications/.vscode_extensions.md create mode 100644 osquery/tables/applications/chrome/.chrome_extension_content_scripts.md create mode 100644 osquery/tables/applications/chrome/.chrome_extensions.md create mode 100644 osquery/tables/applications/chrome/.utils.md create mode 100644 osquery/tables/applications/chrome/tests/.utils.md create mode 100644 osquery/tables/applications/darwin/.browser_plugins.md create mode 100644 osquery/tables/applications/linux/.lxd.md create mode 100644 osquery/tables/applications/posix/.browser_opera.md create mode 100644 osquery/tables/applications/posix/.carbon_black.md create mode 100644 osquery/tables/applications/posix/.docker.md create mode 100644 osquery/tables/applications/posix/.prometheus_metrics.md create mode 100644 osquery/tables/applications/posix/tests/.prometheus_metrics_tests.md create mode 100644 osquery/tables/applications/tests/.jetbrains_plugins_tests.md create mode 100644 osquery/tables/applications/windows/.carbon_black.md create mode 100644 osquery/tables/applications/windows/.office_mru.md create mode 100644 osquery/tables/cloud/aws/.ec2_instance_metadata.md create mode 100644 osquery/tables/cloud/aws/.ec2_instance_tags.md create mode 100644 osquery/tables/cloud/azure/.azure_instance_metadata.md create mode 100644 osquery/tables/cloud/azure/.azure_instance_tags.md create mode 100644 osquery/tables/cloud/ycloud/.ycloud_instance_metadata.md create mode 100644 osquery/tables/events/.event_utils.md create mode 100644 osquery/tables/events/darwin/.disk_events.md create mode 100644 osquery/tables/events/darwin/.es_process_events.md create mode 100644 osquery/tables/events/darwin/.es_process_file_events.md create mode 100644 osquery/tables/events/darwin/.file_events.md create mode 100644 osquery/tables/events/darwin/.hardware_events.md create mode 100644 osquery/tables/events/darwin/.openbsm_events.md create mode 100644 osquery/tables/events/darwin/.socket_events.md create mode 100644 osquery/tables/events/darwin/.user_interaction_events.md create mode 100644 osquery/tables/events/linux/.apparmor_events.md create mode 100644 osquery/tables/events/linux/.bpf_process_events.md create mode 100644 osquery/tables/events/linux/.bpf_socket_events.md create mode 100644 osquery/tables/events/linux/.file_events.md create mode 100644 osquery/tables/events/linux/.hardware_events.md create mode 100644 osquery/tables/events/linux/.process_events.md create mode 100644 osquery/tables/events/linux/.process_file_events.md create mode 100644 osquery/tables/events/linux/.seccomp_events.md create mode 100644 osquery/tables/events/linux/.selinux_events.md create mode 100644 osquery/tables/events/linux/.socket_events.md create mode 100644 osquery/tables/events/linux/.syslog_events.md create mode 100644 osquery/tables/events/linux/.user_events.md create mode 100644 osquery/tables/events/tests/.file_events_tests.md create mode 100644 osquery/tables/events/tests/linux/.bpf_process_events_tests.md create mode 100644 osquery/tables/events/tests/linux/.bpf_socket_events_tests.md create mode 100644 osquery/tables/events/tests/linux/.process_events_tests.md create mode 100644 osquery/tables/events/tests/linux/.seccomp_events_tests.md create mode 100644 osquery/tables/events/tests/linux/.selinux_events_tests.md create mode 100644 osquery/tables/events/tests/windows/.etw_process_events_tests.md create mode 100644 osquery/tables/events/tests/windows/.powershell_events_tests.md create mode 100644 osquery/tables/events/tests/windows/.windows_events_tests.md create mode 100644 osquery/tables/events/windows/.dns_lookup_events.md create mode 100644 osquery/tables/events/windows/.etw_process_events.md create mode 100644 osquery/tables/events/windows/.ntfs_journal_events.md create mode 100644 osquery/tables/events/windows/.powershell_events.md create mode 100644 osquery/tables/events/windows/.windows_events.md create mode 100644 osquery/tables/forensic/.carves.md create mode 100644 osquery/tables/networking/.curl.md create mode 100644 osquery/tables/networking/.curl_certificate.md create mode 100644 osquery/tables/networking/.etc_hosts.md create mode 100644 osquery/tables/networking/.etc_protocols.md create mode 100644 osquery/tables/networking/.etc_services.md create mode 100644 osquery/tables/networking/.listening_ports.md create mode 100644 osquery/tables/networking/darwin/.interface_ip.md create mode 100644 osquery/tables/networking/darwin/.routes.md create mode 100644 osquery/tables/networking/darwin/.wifi_utils.md create mode 100644 osquery/tables/networking/linux/.arp_cache.md create mode 100644 osquery/tables/networking/linux/.inet_diag.md create mode 100644 osquery/tables/networking/linux/.interface_ip.md create mode 100644 osquery/tables/networking/linux/.iptables.md create mode 100644 osquery/tables/networking/linux/.iptc_proxy.md create mode 100644 osquery/tables/networking/linux/.process_open_sockets.md create mode 100644 osquery/tables/networking/linux/.routes.md create mode 100644 osquery/tables/networking/posix/.dns_resolvers.md create mode 100644 osquery/tables/networking/posix/.interfaces.md create mode 100644 osquery/tables/networking/posix/.utils.md create mode 100644 osquery/tables/networking/tests/.networking_tables_tests.md create mode 100644 osquery/tables/networking/tests/linux/.iptables_tests.md create mode 100644 osquery/tables/networking/tests/windows/.windows_firewall_rules_tests.md create mode 100644 osquery/tables/networking/windows/.arp_cache.md create mode 100644 osquery/tables/networking/windows/.connectivity.md create mode 100644 osquery/tables/networking/windows/.interfaces.md create mode 100644 osquery/tables/networking/windows/.process_open_sockets.md create mode 100644 osquery/tables/networking/windows/.routes.md create mode 100644 osquery/tables/networking/windows/.win_sockets.md create mode 100644 osquery/tables/networking/windows/.windows_firewall_rules.md create mode 100644 osquery/tables/sleuthkit/.sleuthkit.md create mode 100644 osquery/tables/system/.cpuid.md create mode 100644 osquery/tables/system/.efi_misc.md create mode 100644 osquery/tables/system/.hash.md create mode 100644 osquery/tables/system/.npm_packages.md create mode 100644 osquery/tables/system/.python_packages.md create mode 100644 osquery/tables/system/.smbios_utils.md create mode 100644 osquery/tables/system/.ssh_configs.md create mode 100644 osquery/tables/system/.ssh_keys.md create mode 100644 osquery/tables/system/.system_utils.md create mode 100644 osquery/tables/system/.uptime.md create mode 100644 osquery/tables/system/.user_groups.md create mode 100644 osquery/tables/system/darwin/.acpi_tables.md create mode 100644 osquery/tables/system/darwin/.ad_config.md create mode 100644 osquery/tables/system/darwin/.asl.md create mode 100644 osquery/tables/system/darwin/.asl_utils.md create mode 100644 osquery/tables/system/darwin/.block_devices.md create mode 100644 osquery/tables/system/darwin/.cpu_info.md create mode 100644 osquery/tables/system/darwin/.cpu_time.md create mode 100644 osquery/tables/system/darwin/.crashes.md create mode 100644 osquery/tables/system/darwin/.cups_destinations.md create mode 100644 osquery/tables/system/darwin/.cups_jobs.md create mode 100644 osquery/tables/system/darwin/.extended_attributes.md create mode 100644 osquery/tables/system/darwin/.firewall.md create mode 100644 osquery/tables/system/darwin/.gatekeeper.md create mode 100644 osquery/tables/system/darwin/.homebrew_packages.md create mode 100644 osquery/tables/system/darwin/.ibridge.md create mode 100644 osquery/tables/system/darwin/.iokit_registry.md create mode 100644 osquery/tables/system/darwin/.kernel_extensions.md create mode 100644 osquery/tables/system/darwin/.kernel_info.md create mode 100644 osquery/tables/system/darwin/.kernel_panics.md create mode 100644 osquery/tables/system/darwin/.keychain.md create mode 100644 osquery/tables/system/darwin/.keychain_acl.md create mode 100644 osquery/tables/system/darwin/.keychain_items.md create mode 100644 osquery/tables/system/darwin/.keychain_utils.md create mode 100644 osquery/tables/system/darwin/.launchd.md create mode 100644 osquery/tables/system/darwin/.managed_policies.md create mode 100644 osquery/tables/system/darwin/.mounts.md create mode 100644 osquery/tables/system/darwin/.nfs_shares.md create mode 100644 osquery/tables/system/darwin/.nvram.md create mode 100644 osquery/tables/system/darwin/.packages.md create mode 100644 osquery/tables/system/darwin/.password_policy.md create mode 100644 osquery/tables/system/darwin/.pci_devices.md create mode 100644 osquery/tables/system/darwin/.preferences.md create mode 100644 osquery/tables/system/darwin/.process_open_descriptors.md create mode 100644 osquery/tables/system/darwin/.processes.md create mode 100644 osquery/tables/system/darwin/.quicklook_cache.md create mode 100644 osquery/tables/system/darwin/.sandboxes.md create mode 100644 osquery/tables/system/darwin/.sharing_preferences.md create mode 100644 osquery/tables/system/darwin/.sip_config.md create mode 100644 osquery/tables/system/darwin/.smbios_tables.md create mode 100644 osquery/tables/system/darwin/.smbios_utils.md create mode 100644 osquery/tables/system/darwin/.smc_keys.md create mode 100644 osquery/tables/system/darwin/.startup_items.md create mode 100644 osquery/tables/system/darwin/.sysctl_utils.md create mode 100644 osquery/tables/system/darwin/.system_info.md create mode 100644 osquery/tables/system/darwin/.time_machine.md create mode 100644 osquery/tables/system/darwin/.usb_devices.md create mode 100644 osquery/tables/system/darwin/.virtual_memory_info.md create mode 100644 osquery/tables/system/darwin/.xprotect.md create mode 100644 osquery/tables/system/freenux/.cpu_time.md create mode 100644 osquery/tables/system/linux/.acpi_tables.md create mode 100644 osquery/tables/system/linux/.apparmor_profiles.md create mode 100644 osquery/tables/system/linux/.apt_sources.md create mode 100644 osquery/tables/system/linux/.block_devices.md create mode 100644 osquery/tables/system/linux/.certificates.md create mode 100644 osquery/tables/system/linux/.cpu_info.md create mode 100644 osquery/tables/system/linux/.deb_packages.md create mode 100644 osquery/tables/system/linux/.disk_encryption.md create mode 100644 osquery/tables/system/linux/.extended_attributes.md create mode 100644 osquery/tables/system/linux/.groups.md create mode 100644 osquery/tables/system/linux/.intel_me.md create mode 100644 osquery/tables/system/linux/.kernel_info.md create mode 100644 osquery/tables/system/linux/.kernel_keys.md create mode 100644 osquery/tables/system/linux/.kernel_modules.md create mode 100644 osquery/tables/system/linux/.md_tables.md create mode 100644 osquery/tables/system/linux/.memory_info.md create mode 100644 osquery/tables/system/linux/.memory_map.md create mode 100644 osquery/tables/system/linux/.model_specific_register.md create mode 100644 osquery/tables/system/linux/.mounts.md create mode 100644 osquery/tables/system/linux/.os_version.md create mode 100644 osquery/tables/system/linux/.pci_devices.md create mode 100644 osquery/tables/system/linux/.portage.md create mode 100644 osquery/tables/system/linux/.process_open_files.md create mode 100644 osquery/tables/system/linux/.process_open_pipes.md create mode 100644 osquery/tables/system/linux/.processes.md create mode 100644 osquery/tables/system/linux/.rpm_packages.md create mode 100644 osquery/tables/system/linux/.secureboot.md create mode 100644 osquery/tables/system/linux/.selinux_settings.md create mode 100644 osquery/tables/system/linux/.shadow.md create mode 100644 osquery/tables/system/linux/.shared_memory.md create mode 100644 osquery/tables/system/linux/.smbios_tables.md create mode 100644 osquery/tables/system/linux/.smbios_utils.md create mode 100644 osquery/tables/system/linux/.startup_items.md create mode 100644 osquery/tables/system/linux/.sysctl_utils.md create mode 100644 osquery/tables/system/linux/.system_info.md create mode 100644 osquery/tables/system/linux/.systemd_units.md create mode 100644 osquery/tables/system/linux/.usb_devices.md create mode 100644 osquery/tables/system/linux/.user_groups.md create mode 100644 osquery/tables/system/linux/.users.md create mode 100644 osquery/tables/system/linux/.yum_sources.md create mode 100644 osquery/tables/system/linux/dbus/.uniquedbusconnection.md create mode 100644 osquery/tables/system/linux/dbus/.uniquedbusmessage.md create mode 100644 osquery/tables/system/linux/dbus/.uniqueresource.md create mode 100644 osquery/tables/system/linux/dbus/methods/.dbusmethod.md create mode 100644 osquery/tables/system/linux/dbus/methods/.getstringproperty.md create mode 100644 osquery/tables/system/linux/dbus/methods/.listunitsmethodhandler.md create mode 100644 osquery/tables/system/posix/.augeas.md create mode 100644 osquery/tables/system/posix/.authorized_keys.md create mode 100644 osquery/tables/system/posix/.crontab.md create mode 100644 osquery/tables/system/posix/.known_hosts.md create mode 100644 osquery/tables/system/posix/.last.md create mode 100644 osquery/tables/system/posix/.load_average.md create mode 100644 osquery/tables/system/posix/.logged_in_users.md create mode 100644 osquery/tables/system/posix/.magic.md create mode 100644 osquery/tables/system/posix/.openssl_utils.md create mode 100644 osquery/tables/system/posix/.shell_history.md create mode 100644 osquery/tables/system/posix/.smbios_utils.md create mode 100644 osquery/tables/system/posix/.ssh_keys.md create mode 100644 osquery/tables/system/posix/.sudoers.md create mode 100644 osquery/tables/system/posix/.suid_bin.md create mode 100644 osquery/tables/system/posix/.sysctl_utils.md create mode 100644 osquery/tables/system/posix/.system_controls.md create mode 100644 osquery/tables/system/posix/.ulimit_info.md create mode 100644 osquery/tables/system/tests/.system_tables_tests.md create mode 100644 osquery/tables/system/tests/darwin/.apps_tests.md create mode 100644 osquery/tables/system/tests/darwin/.asl_tests.md create mode 100644 osquery/tables/system/tests/darwin/.certificates_tests.md create mode 100644 osquery/tables/system/tests/darwin/.extended_attributes_tests.md create mode 100644 osquery/tables/system/tests/darwin/.firewall_tests.md create mode 100644 osquery/tables/system/tests/darwin/.keychain_test.md create mode 100644 osquery/tables/system/tests/darwin/.launchd_tests.md create mode 100644 osquery/tables/system/tests/darwin/.mdfind_tests.md create mode 100644 osquery/tables/system/tests/darwin/.packages_tests.md create mode 100644 osquery/tables/system/tests/darwin/.processes_tests.md create mode 100644 osquery/tables/system/tests/darwin/.smc_tests.md create mode 100644 osquery/tables/system/tests/darwin/.startup_items_tests.md create mode 100644 osquery/tables/system/tests/darwin/.system_extensions_test.md create mode 100644 osquery/tables/system/tests/darwin/.unsigned_test.md create mode 100644 osquery/tables/system/tests/linux/.apt_sources_tests.md create mode 100644 osquery/tables/system/tests/linux/.extended_attributes_tests.md create mode 100644 osquery/tables/system/tests/linux/.md_tables_tests.md create mode 100644 osquery/tables/system/tests/linux/.pci_devices_tests.md create mode 100644 osquery/tables/system/tests/linux/.pcidb_tests.md create mode 100644 osquery/tables/system/tests/linux/.portage_tests.md create mode 100644 osquery/tables/system/tests/linux/.processes_tests.md create mode 100644 osquery/tables/system/tests/linux/.rpm_packages_tests.md create mode 100644 osquery/tables/system/tests/linux/.selinux_settings_tests.md create mode 100644 osquery/tables/system/tests/linux/.yum_sources_tests.md create mode 100644 osquery/tables/system/tests/posix/.augeas_tests.md create mode 100644 osquery/tables/system/tests/posix/.authorized_keys_tests.md create mode 100644 osquery/tables/system/tests/posix/.known_hosts_tests.md create mode 100644 osquery/tables/system/tests/posix/.last_tests.md create mode 100644 osquery/tables/system/tests/posix/.shell_history_tests.md create mode 100644 osquery/tables/system/tests/posix/.ssh_keys_tests.md create mode 100644 osquery/tables/system/tests/posix/.sudoers_tests.md create mode 100644 osquery/tables/system/tests/windows/.certificates_tests.md create mode 100644 osquery/tables/system/tests/windows/.programs_tests.md create mode 100644 osquery/tables/system/tests/windows/.registry_tests.md create mode 100644 osquery/tables/system/tests/windows/.startup_items_tests.md create mode 100644 osquery/tables/system/tests/windows/.windows_eventlog_tests.md create mode 100644 osquery/tables/system/tests/windows/.windows_optional_features_tests.md create mode 100644 osquery/tables/system/tests/windows/.windows_update_history_tests.md create mode 100644 osquery/tables/system/windows/.appcompat_shims.md create mode 100644 osquery/tables/system/windows/.authenticode.md create mode 100644 osquery/tables/system/windows/.autoexec.md create mode 100644 osquery/tables/system/windows/.background_activities_moderator.md create mode 100644 osquery/tables/system/windows/.battery.md create mode 100644 osquery/tables/system/windows/.bitlocker_info.md create mode 100644 osquery/tables/system/windows/.certificates.md create mode 100644 osquery/tables/system/windows/.chassis_info.md create mode 100644 osquery/tables/system/windows/.chocolatey_packages.md create mode 100644 osquery/tables/system/windows/.cpu_info.md create mode 100644 osquery/tables/system/windows/.default_environment.md create mode 100644 osquery/tables/system/windows/.deviceguard_status.md create mode 100644 osquery/tables/system/windows/.disk_info.md create mode 100644 osquery/tables/system/windows/.dns_cache.md create mode 100644 osquery/tables/system/windows/.drivers.md create mode 100644 osquery/tables/system/windows/.groups.md create mode 100644 osquery/tables/system/windows/.ie_extensions.md create mode 100644 osquery/tables/system/windows/.intel_me.md create mode 100644 osquery/tables/system/windows/.kernel_info.md create mode 100644 osquery/tables/system/windows/.kva_speculative_info.md create mode 100644 osquery/tables/system/windows/.logged_in_users.md create mode 100644 osquery/tables/system/windows/.logical_drives.md create mode 100644 osquery/tables/system/windows/.logon_sessions.md create mode 100644 osquery/tables/system/windows/.ntdomains.md create mode 100644 osquery/tables/system/windows/.ntfs_acl_permissions.md create mode 100644 osquery/tables/system/windows/.objects.md create mode 100644 osquery/tables/system/windows/.os_version.md create mode 100644 osquery/tables/system/windows/.patches.md create mode 100644 osquery/tables/system/windows/.physical_disk_performance.md create mode 100644 osquery/tables/system/windows/.pipes.md create mode 100644 osquery/tables/system/windows/.prefetch.md create mode 100644 osquery/tables/system/windows/.processes.md create mode 100644 osquery/tables/system/windows/.programs.md create mode 100644 osquery/tables/system/windows/.registry.md create mode 100644 osquery/tables/system/windows/.scheduled_tasks.md create mode 100644 osquery/tables/system/windows/.secureboot.md create mode 100644 osquery/tables/system/windows/.security_profile_info.md create mode 100644 osquery/tables/system/windows/.security_profile_info_utils.md create mode 100644 osquery/tables/system/windows/.services.md create mode 100644 osquery/tables/system/windows/.shared_resources.md create mode 100644 osquery/tables/system/windows/.shellbags.md create mode 100644 osquery/tables/system/windows/.shimcache.md create mode 100644 osquery/tables/system/windows/.smbios_tables.md create mode 100644 osquery/tables/system/windows/.startup_items.md create mode 100644 osquery/tables/system/windows/.system_info.md create mode 100644 osquery/tables/system/windows/.tpm_info.md create mode 100644 osquery/tables/system/windows/.user_groups.md create mode 100644 osquery/tables/system/windows/.userassist.md create mode 100644 osquery/tables/system/windows/.users.md create mode 100644 osquery/tables/system/windows/.video_info.md create mode 100644 osquery/tables/system/windows/.windows_crashes.md create mode 100644 osquery/tables/system/windows/.windows_eventlog.md create mode 100644 osquery/tables/system/windows/.windows_optional_features.md create mode 100644 osquery/tables/system/windows/.windows_search.md create mode 100644 osquery/tables/system/windows/.windows_security_center.md create mode 100644 osquery/tables/system/windows/.windows_security_products.md create mode 100644 osquery/tables/system/windows/.windows_update_history.md create mode 100644 osquery/tables/system/windows/.wmi_bios_info.md create mode 100644 osquery/tables/system/windows/.wmi_cli_event_consumers.md create mode 100644 osquery/tables/system/windows/.wmi_event_filters.md create mode 100644 osquery/tables/system/windows/.wmi_filter_consumer_binding.md create mode 100644 osquery/tables/system/windows/.wmi_script_event_consumers.md create mode 100644 osquery/tables/utility/.file.md create mode 100644 osquery/tables/utility/.osquery.md create mode 100644 osquery/tables/utility/.time.md create mode 100644 osquery/tables/yara/.yara.md create mode 100644 osquery/tables/yara/.yara_events.md create mode 100644 osquery/tables/yara/.yara_utils.md create mode 100644 osquery/tables/yara/tests/.yara_tests.md create mode 100644 osquery/tables/yara/windows/.yara_events.md create mode 100644 osquery/utils/.attribute.md create mode 100644 osquery/utils/.base64.md create mode 100644 osquery/utils/.chars.md create mode 100644 osquery/utils/.enum_class_hash.md create mode 100644 osquery/utils/.map_take.md create mode 100644 osquery/utils/.mutex.md create mode 100644 osquery/utils/.only_movable.md create mode 100644 osquery/utils/.rot13.md create mode 100644 osquery/utils/.scope_guard.md create mode 100644 osquery/utils/aws/.aws_util.md create mode 100644 osquery/utils/aws/tests/.aws_util_tests.md create mode 100644 osquery/utils/azure/.azure_util.md create mode 100644 osquery/utils/caches/.lru-impl.md create mode 100644 osquery/utils/caches/.lru.md create mode 100644 osquery/utils/caches/tests/.lru.md create mode 100644 osquery/utils/config/.default_paths.md create mode 100644 osquery/utils/conversions/.castvariant.md create mode 100644 osquery/utils/conversions/.join.md create mode 100644 osquery/utils/conversions/.split.md create mode 100644 osquery/utils/conversions/.to.md create mode 100644 osquery/utils/conversions/.trim.md create mode 100644 osquery/utils/conversions/.tryto.md create mode 100644 osquery/utils/conversions/darwin/.cfdata.md create mode 100644 osquery/utils/conversions/darwin/.cfdictionary.md create mode 100644 osquery/utils/conversions/darwin/.cfnumber.md create mode 100644 osquery/utils/conversions/darwin/.cfstring.md create mode 100644 osquery/utils/conversions/darwin/.cftime.md create mode 100644 osquery/utils/conversions/darwin/.iokit.md create mode 100644 osquery/utils/conversions/darwin/tests/.cfdictionary.md create mode 100644 osquery/utils/conversions/darwin/tests/.cfstring.md create mode 100644 osquery/utils/conversions/tests/.join.md create mode 100644 osquery/utils/conversions/tests/.split.md create mode 100644 osquery/utils/conversions/tests/.to.md create mode 100644 osquery/utils/conversions/tests/.trim.md create mode 100644 osquery/utils/conversions/tests/.tryto.md create mode 100644 osquery/utils/conversions/windows/.strings.md create mode 100644 osquery/utils/conversions/windows/.windows_time.md create mode 100644 osquery/utils/conversions/windows/tests/.strings.md create mode 100644 osquery/utils/conversions/windows/tests/.windows_time.md create mode 100644 osquery/utils/darwin/.iokit_helpers.md create mode 100644 osquery/utils/darwin/.plist.md create mode 100644 osquery/utils/darwin/.system_profiler.md create mode 100644 osquery/utils/debug/.debug_only.md create mode 100644 osquery/utils/debug/tests/.debug_only.md create mode 100644 osquery/utils/error/.error.md create mode 100644 osquery/utils/error/tests/.error.md create mode 100644 osquery/utils/expected/.expected.md create mode 100644 osquery/utils/expected/tests/.expected.md create mode 100644 osquery/utils/info/.firmware.md create mode 100644 osquery/utils/info/.platform_type.md create mode 100644 osquery/utils/info/.tool_type.md create mode 100644 osquery/utils/info/.version.md create mode 100644 osquery/utils/info/firmware/.common.md create mode 100644 osquery/utils/info/firmware/.linux.md create mode 100644 osquery/utils/info/firmware/.macos.md create mode 100644 osquery/utils/info/firmware/.windows.md create mode 100644 osquery/utils/json/.json.md create mode 100644 osquery/utils/json/tests/.json.md create mode 100644 osquery/utils/linux/dpkg/.dpkgquery.md create mode 100644 osquery/utils/linux/dpkg/.idpkgquery.md create mode 100644 osquery/utils/linux/dpkg/.modstatdb.md create mode 100644 osquery/utils/linux/dpkg/.pkgarray.md create mode 100644 osquery/utils/macros/.macros.md create mode 100644 osquery/utils/pidfile/.pidfile.md create mode 100644 osquery/utils/pidfile/.pidfile_posix.md create mode 100644 osquery/utils/pidfile/.pidfile_windows.md create mode 100644 osquery/utils/pidfile/tests/.pidfile.md create mode 100644 osquery/utils/schemer/.schemer.md create mode 100644 osquery/utils/schemer/json/.schemer_json.md create mode 100644 osquery/utils/schemer/json/.schemer_json_error.md create mode 100644 osquery/utils/schemer/json/.schemer_json_impl.md create mode 100644 osquery/utils/schemer/json/tests/.schemer_json.md create mode 100644 osquery/utils/schemer/tests/.schemer.md create mode 100644 osquery/utils/status/.status.md create mode 100644 osquery/utils/status/tests/.status.md create mode 100644 osquery/utils/system/.boottime.md create mode 100644 osquery/utils/system/.env.md create mode 100644 osquery/utils/system/.errno.md create mode 100644 osquery/utils/system/.filepath.md create mode 100644 osquery/utils/system/.system.md create mode 100644 osquery/utils/system/.time.md create mode 100644 osquery/utils/system/.uptime.md create mode 100644 osquery/utils/system/linux/.boottime.md create mode 100644 osquery/utils/system/linux/.cpu.md create mode 100644 osquery/utils/system/linux/proc/.proc.md create mode 100644 osquery/utils/system/linux/proc/tests/.empty.md create mode 100644 osquery/utils/system/linux/proc/tests/.proc.md create mode 100644 osquery/utils/system/linux/tests/.cpu.md create mode 100644 osquery/utils/system/posix/.env.md create mode 100644 osquery/utils/system/posix/.errno.md create mode 100644 osquery/utils/system/posix/.filepath.md create mode 100644 osquery/utils/system/posix/.system.md create mode 100644 osquery/utils/system/posix/.time.md create mode 100644 osquery/utils/system/posix/tests/.errno.md create mode 100644 osquery/utils/system/tests/.cpu.md create mode 100644 osquery/utils/system/tests/.errno.md create mode 100644 osquery/utils/system/tests/.time.md create mode 100644 osquery/utils/system/windows/.env.md create mode 100644 osquery/utils/system/windows/.errno.md create mode 100644 osquery/utils/system/windows/.etw_helpers.md create mode 100644 osquery/utils/system/windows/.system.md create mode 100644 osquery/utils/system/windows/.time.md create mode 100644 osquery/utils/system/windows/.users_groups_helpers.md create mode 100644 osquery/utils/tests/.base64.md create mode 100644 osquery/utils/tests/.chars.md create mode 100644 osquery/utils/tests/.map_take.md create mode 100644 osquery/utils/tests/.rot13.md create mode 100644 osquery/utils/tests/.scope_guard.md create mode 100644 osquery/utils/tests/windows/.env.md create mode 100644 osquery/utils/tests/windows/.filetime.md create mode 100644 osquery/utils/tests/windows/.shellitems.md create mode 100644 osquery/utils/windows/.lzxpress.md create mode 100644 osquery/utils/windows/.shellitem.md create mode 100644 osquery/utils/ycloud/.ycloud_util.md create mode 100644 osquery/utils/ycloud/tests/.ycloud.md create mode 100644 osquery/worker/ipc/.table_ipc_json_converter.md create mode 100644 osquery/worker/ipc/include/.platform_table_container_ipc.md create mode 100644 osquery/worker/ipc/include/.table_channel_base.md create mode 100644 osquery/worker/ipc/include/.table_channel_factory_base.md create mode 100644 osquery/worker/ipc/include/.table_ipc_base.md create mode 100644 osquery/worker/ipc/include/.table_ipc_message_handler.md create mode 100644 osquery/worker/ipc/linux/.linux_table_container_ipc.md create mode 100644 osquery/worker/ipc/linux/.linux_table_ipc.md create mode 100644 osquery/worker/ipc/linux/.platform_table_container_ipc.md create mode 100644 osquery/worker/ipc/linux/tests/.worker_table_container_tests.md create mode 100644 osquery/worker/ipc/posix/.pipe_channel.md create mode 100644 osquery/worker/ipc/posix/.pipe_channel_factory.md create mode 100644 osquery/worker/ipc/posix/tests/.worker_ipc_channels_test.md create mode 100644 osquery/worker/ipc/tests/.worker_json_conversions_test.md create mode 100644 osquery/worker/logging/glog/.glog_logger.md create mode 100644 osquery/worker/logging/include/.glog_logger_types.md create mode 100644 osquery/worker/logging/include/.logger.md create mode 100644 osquery/worker/system/.memory.md create mode 100644 osquery/worker/system/linux/.memory.md create mode 100644 plugins/config/.filesystem_config.md create mode 100644 plugins/config/.tls_config.md create mode 100644 plugins/config/.update.md create mode 100644 plugins/config/parsers/.auto_constructed_tables.md create mode 100644 plugins/config/parsers/.decorators.md create mode 100644 plugins/config/parsers/.events_parser.md create mode 100644 plugins/config/parsers/.feature_vectors.md create mode 100644 plugins/config/parsers/.file_paths.md create mode 100644 plugins/config/parsers/.kafka_topics.md create mode 100644 plugins/config/parsers/.logger.md create mode 100644 plugins/config/parsers/.options.md create mode 100644 plugins/config/parsers/.prometheus_targets.md create mode 100644 plugins/config/parsers/.views.md create mode 100644 plugins/config/parsers/tests/.decorators_tests.md create mode 100644 plugins/config/parsers/tests/.events_parser_tests.md create mode 100644 plugins/config/parsers/tests/.file_paths_tests.md create mode 100644 plugins/config/parsers/tests/.options_tests.md create mode 100644 plugins/config/parsers/tests/.views_tests.md create mode 100644 plugins/config/tests/.tls_config_tests.md create mode 100644 plugins/database/.rocksdb.md create mode 100644 plugins/database/.sqlite.md create mode 100644 plugins/database/tests/.rocksdb.md create mode 100644 plugins/database/tests/.sqlite.md create mode 100644 plugins/distributed/.tls_distributed.md create mode 100644 plugins/logger/.aws_firehose.md create mode 100644 plugins/logger/.aws_kinesis.md create mode 100644 plugins/logger/.aws_log_forwarder.md create mode 100644 plugins/logger/.buffered.md create mode 100644 plugins/logger/.filesystem_logger.md create mode 100644 plugins/logger/.generated_wel.md create mode 100644 plugins/logger/.kafka_producer.md create mode 100644 plugins/logger/.logrotate.md create mode 100644 plugins/logger/.stdout.md create mode 100644 plugins/logger/.syslog_logger.md create mode 100644 plugins/logger/.tls_logger.md create mode 100644 plugins/logger/.windows_event_log.md create mode 100644 plugins/logger/tests/.aws_kinesis_logger_tests.md create mode 100644 plugins/logger/tests/.buffered_tests.md create mode 100644 plugins/logger/tests/.filesystem_logger_tests.md create mode 100644 plugins/logger/tests/.kafka_producer_tests.md create mode 100644 plugins/logger/tests/.logrotate_tests.md create mode 100644 plugins/logger/tests/.syslog_logger_tests.md create mode 100644 plugins/logger/tests/.tls_logger_tests.md create mode 100644 plugins/numeric_monitoring/.filesystem.md create mode 100644 plugins/numeric_monitoring/tests/.filesystem.md create mode 100644 plugins/remote/enroll/.tls_enroll.md create mode 100644 plugins/remote/enroll/tests/.tls_enroll_tests.md create mode 100644 tests/.test_util.md create mode 100644 tests/integration/tables/.account_policy_data.md create mode 100644 tests/integration/tables/.acpi_tables.md create mode 100644 tests/integration/tables/.ad_config.md create mode 100644 tests/integration/tables/.alf.md create mode 100644 tests/integration/tables/.alf_exceptions.md create mode 100644 tests/integration/tables/.alf_explicit_auths.md create mode 100644 tests/integration/tables/.alf_services.md create mode 100644 tests/integration/tables/.app_schemes.md create mode 100644 tests/integration/tables/.appcompat_shims.md create mode 100644 tests/integration/tables/.apps.md create mode 100644 tests/integration/tables/.apt_sources.md create mode 100644 tests/integration/tables/.arp_cache.md create mode 100644 tests/integration/tables/.asl.md create mode 100644 tests/integration/tables/.augeas.md create mode 100644 tests/integration/tables/.authenticode.md create mode 100644 tests/integration/tables/.authorization_mechanisms.md create mode 100644 tests/integration/tables/.authorizations.md create mode 100644 tests/integration/tables/.authorized_keys.md create mode 100644 tests/integration/tables/.autoexec.md create mode 100644 tests/integration/tables/.azure_instance_metadata.md create mode 100644 tests/integration/tables/.azure_instance_tags.md create mode 100644 tests/integration/tables/.background_activities_moderator.md create mode 100644 tests/integration/tables/.battery.md create mode 100644 tests/integration/tables/.bitlocker_info.md create mode 100644 tests/integration/tables/.block_devices.md create mode 100644 tests/integration/tables/.browser_plugins.md create mode 100644 tests/integration/tables/.carbon_black_info.md create mode 100644 tests/integration/tables/.carves.md create mode 100644 tests/integration/tables/.certificates.md create mode 100644 tests/integration/tables/.chassis_info.md create mode 100644 tests/integration/tables/.chocolatey_packages.md create mode 100644 tests/integration/tables/.chrome_extension_content_scripts.md create mode 100644 tests/integration/tables/.chrome_extensions.md create mode 100644 tests/integration/tables/.connected_displays.md create mode 100644 tests/integration/tables/.connectivity.md create mode 100644 tests/integration/tables/.cpu_info.md create mode 100644 tests/integration/tables/.cpu_time.md create mode 100644 tests/integration/tables/.cpuid.md create mode 100644 tests/integration/tables/.crashes.md create mode 100644 tests/integration/tables/.crontab.md create mode 100644 tests/integration/tables/.cups_destinations.md create mode 100644 tests/integration/tables/.cups_jobs.md create mode 100644 tests/integration/tables/.curl.md create mode 100644 tests/integration/tables/.curl_certificate.md create mode 100644 tests/integration/tables/.deb_packages.md create mode 100644 tests/integration/tables/.default_environment.md create mode 100644 tests/integration/tables/.device_file.md create mode 100644 tests/integration/tables/.device_firmware.md create mode 100644 tests/integration/tables/.device_hash.md create mode 100644 tests/integration/tables/.device_partitions.md create mode 100644 tests/integration/tables/.deviceguard_status.md create mode 100644 tests/integration/tables/.disk_encryption.md create mode 100644 tests/integration/tables/.disk_events.md create mode 100644 tests/integration/tables/.disk_info.md create mode 100644 tests/integration/tables/.dns_lookup_events.md create mode 100644 tests/integration/tables/.dns_resolvers.md create mode 100644 tests/integration/tables/.docker_container_envs.md create mode 100644 tests/integration/tables/.docker_container_labels.md create mode 100644 tests/integration/tables/.docker_container_mounts.md create mode 100644 tests/integration/tables/.docker_container_networks.md create mode 100644 tests/integration/tables/.docker_container_ports.md create mode 100644 tests/integration/tables/.docker_container_processes.md create mode 100644 tests/integration/tables/.docker_container_stats.md create mode 100644 tests/integration/tables/.docker_containers.md create mode 100644 tests/integration/tables/.docker_image_history.md create mode 100644 tests/integration/tables/.docker_image_labels.md create mode 100644 tests/integration/tables/.docker_image_layers.md create mode 100644 tests/integration/tables/.docker_images.md create mode 100644 tests/integration/tables/.docker_info.md create mode 100644 tests/integration/tables/.docker_network_labels.md create mode 100644 tests/integration/tables/.docker_networks.md create mode 100644 tests/integration/tables/.docker_version.md create mode 100644 tests/integration/tables/.docker_volume_labels.md create mode 100644 tests/integration/tables/.docker_volumes.md create mode 100644 tests/integration/tables/.drivers.md create mode 100644 tests/integration/tables/.ec2_instance_metadata.md create mode 100644 tests/integration/tables/.ec2_instance_tags.md create mode 100644 tests/integration/tables/.etc_hosts.md create mode 100644 tests/integration/tables/.etc_protocols.md create mode 100644 tests/integration/tables/.etc_services.md create mode 100644 tests/integration/tables/.etw_process_events.md create mode 100644 tests/integration/tables/.event_taps.md create mode 100644 tests/integration/tables/.example.md create mode 100644 tests/integration/tables/.extended_attributes.md create mode 100644 tests/integration/tables/.fan_speed_sensors.md create mode 100644 tests/integration/tables/.fbsd_kmods.md create mode 100644 tests/integration/tables/.file.md create mode 100644 tests/integration/tables/.file_events.md create mode 100644 tests/integration/tables/.firefox_addons.md create mode 100644 tests/integration/tables/.gatekeeper.md create mode 100644 tests/integration/tables/.gatekeeper_approved_apps.md create mode 100644 tests/integration/tables/.groups.md create mode 100644 tests/integration/tables/.hardware_events.md create mode 100644 tests/integration/tables/.hash.md create mode 100644 tests/integration/tables/.helper.md create mode 100644 tests/integration/tables/.homebrew_packages.md create mode 100644 tests/integration/tables/.ibridge.md create mode 100644 tests/integration/tables/.ie_extensions.md create mode 100644 tests/integration/tables/.intel_me_info.md create mode 100644 tests/integration/tables/.interface_addresses.md create mode 100644 tests/integration/tables/.interface_details.md create mode 100644 tests/integration/tables/.interface_ipv6.md create mode 100644 tests/integration/tables/.iokit_devicetree.md create mode 100644 tests/integration/tables/.iokit_registry.md create mode 100644 tests/integration/tables/.iptables.md create mode 100644 tests/integration/tables/.jetbrains_plugins.md create mode 100644 tests/integration/tables/.kernel_extensions.md create mode 100644 tests/integration/tables/.kernel_info.md create mode 100644 tests/integration/tables/.kernel_keys.md create mode 100644 tests/integration/tables/.kernel_modules.md create mode 100644 tests/integration/tables/.kernel_panics.md create mode 100644 tests/integration/tables/.keychain_acls.md create mode 100644 tests/integration/tables/.keychain_items.md create mode 100644 tests/integration/tables/.known_hosts.md create mode 100644 tests/integration/tables/.kva_speculative_info.md create mode 100644 tests/integration/tables/.last.md create mode 100644 tests/integration/tables/.launchd.md create mode 100644 tests/integration/tables/.launchd_overrides.md create mode 100644 tests/integration/tables/.listening_ports.md create mode 100644 tests/integration/tables/.load_average.md create mode 100644 tests/integration/tables/.location_services.md create mode 100644 tests/integration/tables/.logged_in_users.md create mode 100644 tests/integration/tables/.logical_drives.md create mode 100644 tests/integration/tables/.logon_sessions.md create mode 100644 tests/integration/tables/.magic.md create mode 100644 tests/integration/tables/.managed_policies.md create mode 100644 tests/integration/tables/.md_devices.md create mode 100644 tests/integration/tables/.md_drives.md create mode 100644 tests/integration/tables/.md_personalities.md create mode 100644 tests/integration/tables/.mdfind.md create mode 100644 tests/integration/tables/.memory_array_mapped_addresses.md create mode 100644 tests/integration/tables/.memory_arrays.md create mode 100644 tests/integration/tables/.memory_device_mapped_addresses.md create mode 100644 tests/integration/tables/.memory_devices.md create mode 100644 tests/integration/tables/.memory_error_info.md create mode 100644 tests/integration/tables/.memory_info.md create mode 100644 tests/integration/tables/.memory_map.md create mode 100644 tests/integration/tables/.mounts.md create mode 100644 tests/integration/tables/.msr.md create mode 100644 tests/integration/tables/.nfs_shares.md create mode 100644 tests/integration/tables/.npm_packages.md create mode 100644 tests/integration/tables/.ntdomains.md create mode 100644 tests/integration/tables/.ntfs_acl_permissions.md create mode 100644 tests/integration/tables/.nvram.md create mode 100644 tests/integration/tables/.oem_strings.md create mode 100644 tests/integration/tables/.office_mru.md create mode 100644 tests/integration/tables/.os_version.md create mode 100644 tests/integration/tables/.osquery_events.md create mode 100644 tests/integration/tables/.osquery_extensions.md create mode 100644 tests/integration/tables/.osquery_flags.md create mode 100644 tests/integration/tables/.osquery_info.md create mode 100644 tests/integration/tables/.osquery_packs.md create mode 100644 tests/integration/tables/.osquery_registry.md create mode 100644 tests/integration/tables/.osquery_schedule.md create mode 100644 tests/integration/tables/.package_bom.md create mode 100644 tests/integration/tables/.package_install_history.md create mode 100644 tests/integration/tables/.package_receipts.md create mode 100644 tests/integration/tables/.patches.md create mode 100644 tests/integration/tables/.pci_devices.md create mode 100644 tests/integration/tables/.physical_disk_performance.md create mode 100644 tests/integration/tables/.pipes.md create mode 100644 tests/integration/tables/.pkg_packages.md create mode 100644 tests/integration/tables/.platform_info.md create mode 100644 tests/integration/tables/.plist.md create mode 100644 tests/integration/tables/.portage_keywords.md create mode 100644 tests/integration/tables/.portage_packages.md create mode 100644 tests/integration/tables/.portage_use.md create mode 100644 tests/integration/tables/.power_sensors.md create mode 100644 tests/integration/tables/.powershell_events.md create mode 100644 tests/integration/tables/.preferences.md create mode 100644 tests/integration/tables/.prefetch.md create mode 100644 tests/integration/tables/.process_envs.md create mode 100644 tests/integration/tables/.process_events.md create mode 100644 tests/integration/tables/.process_file_events.md create mode 100644 tests/integration/tables/.process_memory_map.md create mode 100644 tests/integration/tables/.process_namespaces.md create mode 100644 tests/integration/tables/.process_open_files.md create mode 100644 tests/integration/tables/.process_open_pipes.md create mode 100644 tests/integration/tables/.process_open_sockets.md create mode 100644 tests/integration/tables/.processes.md create mode 100644 tests/integration/tables/.programs.md create mode 100644 tests/integration/tables/.prometheus_metrics.md create mode 100644 tests/integration/tables/.python_packages.md create mode 100644 tests/integration/tables/.quicklook_cache.md create mode 100644 tests/integration/tables/.registry.md create mode 100644 tests/integration/tables/.routes.md create mode 100644 tests/integration/tables/.rpm_package_files.md create mode 100644 tests/integration/tables/.rpm_packages.md create mode 100644 tests/integration/tables/.running_apps.md create mode 100644 tests/integration/tables/.safari_extensions.md create mode 100644 tests/integration/tables/.sandboxes.md create mode 100644 tests/integration/tables/.scheduled_tasks.md create mode 100644 tests/integration/tables/.secureboot.md create mode 100644 tests/integration/tables/.security_profile_info.md create mode 100644 tests/integration/tables/.selinux_events.md create mode 100644 tests/integration/tables/.services.md create mode 100644 tests/integration/tables/.shadow.md create mode 100644 tests/integration/tables/.shared_folders.md create mode 100644 tests/integration/tables/.shared_memory.md create mode 100644 tests/integration/tables/.shared_resources.md create mode 100644 tests/integration/tables/.sharing_preferences.md create mode 100644 tests/integration/tables/.shell_history.md create mode 100644 tests/integration/tables/.shellbags.md create mode 100644 tests/integration/tables/.shimcache.md create mode 100644 tests/integration/tables/.signature.md create mode 100644 tests/integration/tables/.sip_config.md create mode 100644 tests/integration/tables/.smart_drive_info.md create mode 100644 tests/integration/tables/.smbios_tables.md create mode 100644 tests/integration/tables/.smc_keys.md create mode 100644 tests/integration/tables/.socket_events.md create mode 100644 tests/integration/tables/.ssh_configs.md create mode 100644 tests/integration/tables/.startup_items.md create mode 100644 tests/integration/tables/.sudoers.md create mode 100644 tests/integration/tables/.suid_bin.md create mode 100644 tests/integration/tables/.syslog_events.md create mode 100644 tests/integration/tables/.system_controls.md create mode 100644 tests/integration/tables/.system_extensions.md create mode 100644 tests/integration/tables/.system_info.md create mode 100644 tests/integration/tables/.systemd_units.md create mode 100644 tests/integration/tables/.temperature_sensors.md create mode 100644 tests/integration/tables/.time.md create mode 100644 tests/integration/tables/.time_machine_backups.md create mode 100644 tests/integration/tables/.time_machine_destinations.md create mode 100644 tests/integration/tables/.tpm_info.md create mode 100644 tests/integration/tables/.ulimit_info.md create mode 100644 tests/integration/tables/.unified_log.md create mode 100644 tests/integration/tables/.uptime.md create mode 100644 tests/integration/tables/.usb_devices.md create mode 100644 tests/integration/tables/.user_events.md create mode 100644 tests/integration/tables/.user_groups.md create mode 100644 tests/integration/tables/.user_interaction_events.md create mode 100644 tests/integration/tables/.user_ssh_keys.md create mode 100644 tests/integration/tables/.userassist.md create mode 100644 tests/integration/tables/.users.md create mode 100644 tests/integration/tables/.video_info.md create mode 100644 tests/integration/tables/.virtual_memory_info.md create mode 100644 tests/integration/tables/.vscode_extensions.md create mode 100644 tests/integration/tables/.wifi_networks.md create mode 100644 tests/integration/tables/.wifi_status.md create mode 100644 tests/integration/tables/.wifi_survey.md create mode 100644 tests/integration/tables/.winbaseobj.md create mode 100644 tests/integration/tables/.windows_crashes.md create mode 100644 tests/integration/tables/.windows_eventlog.md create mode 100644 tests/integration/tables/.windows_events.md create mode 100644 tests/integration/tables/.windows_firewall_rules.md create mode 100644 tests/integration/tables/.windows_search.md create mode 100644 tests/integration/tables/.windows_security_products.md create mode 100644 tests/integration/tables/.windows_update_history.md create mode 100644 tests/integration/tables/.wmi_bios_info.md create mode 100644 tests/integration/tables/.wmi_cli_event_consumers.md create mode 100644 tests/integration/tables/.wmi_event_filters.md create mode 100644 tests/integration/tables/.wmi_filter_consumer_binding.md create mode 100644 tests/integration/tables/.wmi_script_event_consumers.md create mode 100644 tests/integration/tables/.xprotect_entries.md create mode 100644 tests/integration/tables/.xprotect_meta.md create mode 100644 tests/integration/tables/.xprotect_reports.md create mode 100644 tests/integration/tables/.yara.md create mode 100644 tests/integration/tables/.yara_events.md create mode 100644 tests/integration/tables/.ycloud_instance_metadata.md create mode 100644 tests/integration/tables/.yum_sources.md create mode 100644 tools/analysis/.profile.md create mode 100644 tools/ci/scripts/.check_copyright_headers.md create mode 100644 tools/ci/scripts/cve/.third_party_libraries_cves_scanner.md create mode 100644 tools/ci/scripts/cve/.validate_manifest_libraries_versions.md create mode 100644 tools/ci/scripts/cve/osquery/.github_api.md create mode 100644 tools/ci/scripts/cve/osquery/.manifest_api.md create mode 100644 tools/cmake/.downloader.md create mode 100644 tools/codegen/.amalgamate.md create mode 100644 tools/codegen/.genapi.md create mode 100644 tools/codegen/.gentable.md create mode 100644 tools/codegen/.gentargets.md create mode 100644 tools/codegen/.genwebsitejson.md create mode 100644 tools/codegen/.substitute.md create mode 100644 tools/codegen/.templite.md create mode 100644 tools/deployment/.getfiles.md create mode 100644 tools/formatting/.format-check.md create mode 100644 tools/formatting/.git-clang-format.md create mode 100644 tools/tests/.plist_benchmark.md create mode 100644 tools/tests/.test.md create mode 100644 tools/tests/.test_additional.md create mode 100644 tools/tests/.test_base.md create mode 100644 tools/tests/.test_docker_container_fs_changes_table.md create mode 100644 tools/tests/.test_example_queries.md create mode 100644 tools/tests/.test_extensions.md create mode 100644 tools/tests/.test_http_server.md create mode 100644 tools/tests/.test_osqueryd.md create mode 100644 tools/tests/.test_osqueryi.md create mode 100644 tools/tests/.test_query_packs.md create mode 100644 tools/tests/.test_release.md create mode 100644 tools/tests/.test_windows_service.md create mode 100644 tools/tests/.utils.md create mode 100644 tools/tests/.winexpect.md diff --git a/external/examples/config_plugin/.main.md b/external/examples/config_plugin/.main.md new file mode 100644 index 00000000000..895dbae060b --- /dev/null +++ b/external/examples/config_plugin/.main.md @@ -0,0 +1,37 @@ + +Minimal osquery extension entry point demonstrating how to register a custom `ConfigPlugin` and bootstrap an external extension process. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `ExampleConfigPlugin` | Class | Concrete `ConfigPlugin` that returns an empty query config map | +| `setUp()` | Method | Lifecycle hook called on plugin initialization; logs a warning | +| `genConfig()` | Method | Populates the config map with an empty JSON queries object | +| `REGISTER_EXTERNAL` | Macro | Registers the plugin under the `"config"` category with the name `"example"` | +| `main()` | Function | Initializes the osquery extension runner and blocks until shutdown | + +## Usage Example + +```cpp +// Build and run as a standalone osquery extension +// The extension connects to a running osqueryd via a socket + +int main(int argc, char* argv[]) { + // ToolType::EXTENSION tells the Initializer this is an extension process + osquery::Initializer runner(argc, argv, ToolType::EXTENSION); + + // Register with osqueryd; provide extension name and version + auto status = startExtension("example", "0.0.1"); + if (!status.ok()) { + LOG(ERROR) << status.getMessage(); + runner.requestShutdown(status.getCode()); + } + + // Block until SIGINT / SIGTERM or osqueryd disconnect + runner.waitForShutdown(); + return runner.shutdown(0); +} +``` + +> **Extend this pattern** by registering additional plugin types (`table`, `logger`, `distributed`) alongside or instead of `ConfigPlugin` using the same `REGISTER_EXTERNAL` macro before calling `startExtension`. \ No newline at end of file diff --git a/external/examples/read_only_table/.main.md b/external/examples/read_only_table/.main.md new file mode 100644 index 00000000000..7b55ab5e57a --- /dev/null +++ b/external/examples/read_only_table/.main.md @@ -0,0 +1,53 @@ + +Minimal reference implementation of an osquery external extension, demonstrating how to define a custom virtual table and register it with the osquery extension SDK. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `ExampleTable` | Class | Concrete `TablePlugin` subclass exposing a two-column virtual table | +| `columns()` | Method | Declares schema — `example_text` (TEXT) and `example_integer` (INTEGER) | +| `generate()` | Method | Returns a single hardcoded row used as a minimal working example | +| `REGISTER_EXTERNAL` | Macro | Registers `ExampleTable` with osquery as an external table named `"example"` | +| `main()` | Function | Bootstraps the extension runtime, connects to osquery, and blocks until shutdown | + +## Usage Example + +```cpp +// 1. Define your table plugin +class MyTable : public TablePlugin { + private: + TableColumns columns() const { + return { + std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("value", INTEGER_TYPE, ColumnOptions::DEFAULT), + }; + } + + TableRows generate(QueryContext& request) { + TableRows results; + auto r = make_table_row(); + r["name"] = "my_entry"; + r["value"] = INTEGER(42); + results.push_back(std::move(r)); + return results; + } +}; + +// 2. Register with osquery +REGISTER_EXTERNAL(MyTable, "table", "my_table"); + +// 3. Start the extension (mirrors the example main()) +int main(int argc, char* argv[]) { + osquery::Initializer runner(argc, argv, ToolType::EXTENSION); + auto status = startExtension("my_extension", "1.0.0"); + if (!status.ok()) { + LOG(ERROR) << status.getMessage(); + runner.requestShutdown(status.getCode()); + } + runner.waitForShutdown(); + return runner.shutdown(0); +} +``` + +> Once loaded, the table is queryable via standard SQL: `SELECT * FROM example;` \ No newline at end of file diff --git a/external/examples/string_batch/.main.md b/external/examples/string_batch/.main.md new file mode 100644 index 00000000000..fe1e4198236 --- /dev/null +++ b/external/examples/string_batch/.main.md @@ -0,0 +1,41 @@ + +Demonstrates how to use the osquery Extension SDK to batch log string messages to the filesystem logger via the osquery registry in groups of 10, with a 5-second delay between each batch. + +## Key Components + +### `StringBatch` (class) +An internal helper that accumulates strings into a JSON array and serializes them on demand using RapidJSON. + +| Method | Description | +|--------|-------------| +| `append(str)` | Adds a string entry to the internal JSON array | +| `take()` | Serializes the current batch to a JSON string and resets the internal state | + +### `main()` +Initializes the osquery extension, builds 10 batches of 10 strings each, and dispatches each batch to the `filesystem` logger via `Registry::call`. + +## Usage Example + +```cpp +// Initialize the extension runtime +osquery::Initializer runner(argc, argv, ToolType::EXTENSION); +auto status = startExtension("string_batch_example", "0.0.1"); + +// Build and send batches +StringBatch string_batch; +for (int k = 0; k < 10; ++k) { + string_batch.append("Log entry #" + std::to_string(k)); +} + +// Dispatch batch to the filesystem logger +auto status = Registry::call( + "logger", + "filesystem", + {{"string_batch", string_batch.take()}} +); + +// take() resets internal state — ready for next batch +string_batch.append("Next batch starts here"); +``` + +> **Note:** `take()` both serializes and resets the batch, so calling it twice without `append()` in between will return an empty JSON array `[]` on the second call. \ No newline at end of file diff --git a/external/examples/writable_table/.main.md b/external/examples/writable_table/.main.md new file mode 100644 index 00000000000..6427498e5b6 --- /dev/null +++ b/external/examples/writable_table/.main.md @@ -0,0 +1,45 @@ + +An osquery extension implementing a writable, in-memory SQLite virtual table that supports full INSERT, UPDATE, and DELETE operations with primary key constraint enforcement. + +## Key Components + +### `WritableTable : TablePlugin` +The core class extending osquery's `TablePlugin` with mutable data storage. + +**Private Members:** +- `data` — `unordered_map` storing all rows keyed by primary key (concatenation of `text` + `integer` columns) +- `rowid_to_primary_key` — reverse index mapping SQLite rowids to primary keys +- `mutex` — guards all data access for thread safety + +**Private Helpers:** +| Method | Purpose | +|---|---| +| `getPrimaryKey(row)` | Concatenates `text` + `integer` columns as the composite key | +| `isPrimaryKeyUnique(pk, ignored_rowid)` | Enforces uniqueness constraints | +| `generateRowId()` | Monotonically increasing rowid fallback when SQLite doesn't provide one | +| `saveRow(row, pk)` | Persists a row and updates the rowid index | +| `getRowData(row, json)` | Deserializes osquery's `json_value_array` into a `Row` | + +**Public Overrides:** +- `columns()` — declares `text` (TEXT) and `integer` (INTEGER) columns +- `generate()` — returns all stored rows for SELECT queries +- `insert()` — handles INSERT with NOT NULL and uniqueness checks +- `delete_()` — removes a row by rowid +- `update()` — replaces a row, handling primary key changes and optional new rowid from SQLite + +### `main()` +Bootstraps the osquery extension runtime and registers `WritableTable` under the name `"WritableTable"`. + +## Usage Example + +```cpp +// Build and run as an osquery extension +// Register macro wires the table into the osquery extension SDK +REGISTER_EXTERNAL(WritableTable, "table", "WritableTable"); + +// Then in SQL: +// INSERT INTO WritableTable (text, integer) VALUES ('hello', 42); +// SELECT * FROM WritableTable; +// UPDATE WritableTable SET integer = 99 WHERE text = 'hello'; +// DELETE FROM WritableTable WHERE text = 'hello'; +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/linux/aarch64/code/.lexer.md b/libraries/cmake/source/augeas/generated/linux/aarch64/code/.lexer.md new file mode 100644 index 00000000000..a4dd12c38c4 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/linux/aarch64/code/.lexer.md @@ -0,0 +1,50 @@ + +A flex-generated reentrant lexical scanner for the Augeas lens language (`augl` prefix), responsible for tokenizing input source files as part of the Augeas configuration parsing pipeline. + +## Key Components + +### Namespace Prefixing +All standard flex `yy_*` symbols are remapped to `augl_*` equivalents (e.g., `yylex` → `augl_lex`, `yy_create_buffer` → `augl__create_buffer`) to avoid symbol collisions when multiple scanners are linked together. + +### Core Types & Structures + +| Type | Description | +|------|-------------| +| `yyscan_t` | Opaque pointer for reentrant scanner state | +| `YY_BUFFER_STATE` | Pointer to `yy_buffer_state` struct managing input buffering | +| `flex_int8_t` / `flex_uint32_t` | Portable integer types (C99 `` or manual fallbacks) | + +### Buffer Management Macros + +| Macro | Purpose | +|-------|---------| +| `YY_BUF_SIZE` | Default input buffer size (16KB, 32KB on IA-64) | +| `YY_NEW_FILE` | Reset scanner to process a new input file | +| `yyless(n)` | Return all but first `n` matched characters to input | +| `unput(c)` | Push a character back into the input stream | + +### `yy_buffer_state` Fields + +- `yy_ch_buf` — raw input buffer +- `yy_buf_pos` — current read position +- `yy_bs_lineno` / `yy_bs_column` — line/column tracking +- `yy_is_interactive` — controls `getc()` vs `fread()` input strategy + +## Usage Example + +```c +/* Initialize and use the augl reentrant scanner */ +yyscan_t scanner; +augl_lex_init(&scanner); + +/* Optionally attach extra state */ +augl_set_extra(my_parse_state, scanner); + +/* Run the lexer (called internally by the parser) */ +int token = augl_lex(lvalp, llocp, scanner); + +/* Tear down */ +augl_lex_destroy(scanner); +``` + +> **Note:** This file is machine-generated by flex 2.6.4 and should not be edited directly. Modify the upstream `.l` grammar file and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/linux/aarch64/code/.parser.md b/libraries/cmake/source/augeas/generated/linux/aarch64/code/.parser.md new file mode 100644 index 00000000000..a9ebb2bd29b --- /dev/null +++ b/libraries/cmake/source/augeas/generated/linux/aarch64/code/.parser.md @@ -0,0 +1,65 @@ + +Auto-generated Bison parser interface header for the Augeas lens language (`augl`) parser, defining token types, semantic value types, and location tracking structures used by the generated LALR(1) parser. + +## Key Components + +### Token Enum (`yytokentype`) +Defines all terminal tokens recognized by the lexer: + +| Token | Value | Description | +|---|---|---| +| `DQUOTED` | 258 | Double-quoted string literal | +| `REGEXP` | 259 | Regular expression literal | +| `LIDENT` / `UIDENT` / `QIDENT` | 260–262 | Lower/Upper/Qualified identifiers | +| `ARROW` | 263 | `->` arrow operator | +| `KW_MODULE` … `KW_AFTER` | 264–275 | Language keywords | + +### Semantic Value Union (`YYSTYPE`) +Holds the value associated with each parsed token or grammar rule: + +- `struct term *term` — AST expression node +- `struct type *type` — Type annotation node +- `struct ident *ident` — Identifier reference +- `struct tree *tree` — Parse tree node +- `char *string` — String value +- `regexp` — Struct with `pattern` (string) and `nocase` (int) flag +- `int intval` — Integer literal +- `enum quant_tag quant` — Quantifier (`*`, `+`, `?`) + +### Location Type (`YYLTYPE`) +Tracks source positions with `first_line`, `first_column`, `last_line`, `last_column` for error reporting. + +### Parser Entry Point +```c +int augl_parse(struct term **term, yyscan_t scanner); +``` + +### Scanner State (`struct state`) +```c +struct state { + struct info *info; // Source file info + unsigned int comment_depth; // Nested comment tracking +}; +``` + +## Usage Example + +```c +#include "parser.h" +#include "info.h" + +struct term *result = NULL; +yyscan_t scanner; + +augl_lex_init(&scanner); +// configure scanner input ... + +int rc = augl_parse(&result, scanner); +if (rc == 0 && result != NULL) { + // process the parsed AST term +} + +augl_lex_destroy(scanner); +``` + +> **Note:** This file is auto-generated by GNU Bison 3.0.4 from `parser.y`. Do not edit directly — modify the grammar source and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/linux/aarch64/config/.config.md b/libraries/cmake/source/augeas/generated/linux/aarch64/config/.config.md new file mode 100644 index 00000000000..efa6084d29b --- /dev/null +++ b/libraries/cmake/source/augeas/generated/linux/aarch64/config/.config.md @@ -0,0 +1,43 @@ + +Auto-generated build configuration header for the Augeas configuration management library, produced by `autoconf`/`autoheader` from `configure.ac`. It captures compile-time feature detection results, platform capabilities, and gnulib module availability flags. + +## Key Components + +### Build & Debug Flags +- `ENABLE_DEBUG` — Enables debug mode at compile time +- `_FORTIFY_SOURCE` — Conditionally enables glibc buffer overflow protection when optimizations are active + +### Platform & Type Definitions +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — Double-precision float exponent bit layout +- `FLEXIBLE_ARRAY_MEMBER` — Struct flexible array member compatibility shim for pre-C99 compilers +- `C_LOCALE_MAYBE_EILSEQ` — Flags potential encoding errors in the C locale +- `GETTIMEOFDAY_TIMEZONE` — Defines the second argument type for `gettimeofday()` + +### POSIX/stdlib Feature Availability (`HAVE_*`) +Boolean flags indicating presence of headers and functions such as `alloca`, `canonicalize_file_name`, `btowc`, `arpa/inet.h`, and others detected at configure time. + +### Gnulib Module Flags (`GNULIB_*` / `GNULIB_TEST_*`) +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_FSCANF`, `GNULIB_LOCK`, etc. — Mark gnulib portability modules as present +- `GNULIB_TEST_*` — Enable test coverage for individual gnulib modules (e.g., `accept`, `realpath`, `strerror`, `mbrtowc`) + +## Usage Example + +```c +#include "config.h" + +#if ENABLE_DEBUG + fprintf(stderr, "[debug] starting up\n"); +#endif + +#if HAVE_ALLOCA_H + #include +#endif + +/* Use portable flexible array member */ +struct buffer { + size_t len; + char data[FLEXIBLE_ARRAY_MEMBER]; +}; +``` + +> **Note:** This file is auto-generated — edit `config.h.in` or `configure.ac` instead of modifying it directly. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/linux/x86_64/code/.lexer.md b/libraries/cmake/source/augeas/generated/linux/x86_64/code/.lexer.md new file mode 100644 index 00000000000..5885d6fda49 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/linux/x86_64/code/.lexer.md @@ -0,0 +1,44 @@ + +Flex-generated lexical scanner for the Augeas configuration language (`augl`), implementing a reentrant tokenizer used to parse `.aug` lens files. + +## Key Components + +### Scanner Infrastructure (Flex-generated) +- **`yy_buffer_state`** — Input buffer struct managing character buffering, position tracking, line/column counts, and EOF handling +- **`yyscan_t`** — Opaque pointer type for reentrant scanner state +- **`yy_accept[]`** — 95-entry accept state table (29 rules + EOF rule) +- **`yy_ec[]`** — 256-entry equivalence class table mapping input characters to scanner state transitions + +### Buffer Management Functions +- **`augl_restart()`** — Reinitialize scanner with a new input file +- **`augl__create_buffer()` / `augl__delete_buffer()`** — Allocate/free input buffers +- **`augl__switch_to_buffer()`** — Switch active input buffer +- **`augl_push_buffer_state()` / `augl_pop_buffer_state()`** — Buffer stack operations for nested input +- **`augl__scan_string()` / `augl__scan_bytes()`** — Scan from in-memory sources + +### Internal Helpers +- **`yy_get_previous_state()`** — Back-tracking state recovery +- **`yy_try_NUL_trans()`** — Handle NUL character transitions +- **`yy_get_next_buffer()`** — Refill input buffer from source + +## Usage Example + +```c +#include "lexer.h" + +/* Initialize reentrant scanner and scan a string */ +yyscan_t scanner; +augl_lex_init(&scanner); + +YY_BUFFER_STATE buf = augl__scan_string("let x = /foo/", scanner); + +int token; +while ((token = augl_lex(scanner)) != 0) { + /* process token */ +} + +augl__delete_buffer(buf, scanner); +augl_lex_destroy(scanner); +``` + +> **Note:** This file is auto-generated by Flex 2.5.35 from `lexer.l`. Edit `lexer.l` directly rather than modifying this file, as changes will be overwritten on regeneration. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/linux/x86_64/code/.parser.md b/libraries/cmake/source/augeas/generated/linux/x86_64/code/.parser.md new file mode 100644 index 00000000000..b54e83a94b8 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/linux/x86_64/code/.parser.md @@ -0,0 +1,74 @@ + +Auto-generated Bison parser interface header defining token types, semantic value union (`YYSTYPE`), location tracking (`YYLTYPE`), and scanner state for parsing Augeas lens definition files. + +## Key Components + +### Token Definitions (`yytokentype` enum) +| Token | Value | Description | +|---|---|---| +| `DQUOTED` | 258 | Double-quoted string literal | +| `REGEXP` | 259 | Regular expression literal | +| `LIDENT` / `UIDENT` / `QIDENT` | 260–262 | Lower/Upper/Qualified identifiers | +| `ARROW` | 263 | `->` arrow operator | +| `KW_MODULE` … `KW_AFTER` | 264–275 | Language keywords | + +### `YYSTYPE` — Semantic Value Union +Holds the value associated with each parsed token or grammar rule: + +```c +union YYSTYPE { + struct term *term; // Expression/term node + struct type *type; // Type annotation + struct ident *ident; // Identifier reference + struct tree *tree; // Parse tree node + char *string; // String value + struct { + int nocase; // Case-insensitive flag + char *pattern; // Regex pattern string + } regexp; + int intval; // Integer literal + enum quant_tag quant; // Quantifier (*, +, ?) +}; +``` + +### `YYLTYPE` — Location Tracking +Tracks source positions for error reporting: + +```c +typedef struct YYLTYPE { + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +``` + +### `state` — Custom Scanner State +Carries context between the lexer and parser: + +```c +struct state { + struct info *info; // Source file metadata + unsigned int comment_depth; // Nested comment tracking +}; +``` + +## Usage Example + +```c +#include "parser.h" + +// Access token location after parsing +void report_error(YYLTYPE *loc, const char *msg) { + fprintf(stderr, "Error at line %d, col %d: %s\n", + loc->first_line, loc->first_column, msg); +} + +// Initialize scanner state before invoking yyparse() +struct state scanner_state = { + .info = load_info("myfile.aug"), + .comment_depth = 0 +}; +``` + +> **Note:** This file is auto-generated by GNU Bison 2.4.1 from `parser.y`. Do not edit directly — modify the grammar source instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/linux/x86_64/config/.config.md b/libraries/cmake/source/augeas/generated/linux/x86_64/config/.config.md new file mode 100644 index 00000000000..e10e2b8994f --- /dev/null +++ b/libraries/cmake/source/augeas/generated/linux/x86_64/config/.config.md @@ -0,0 +1,49 @@ + +Auto-generated build configuration header for the Augeas configuration management library, produced by `autoconf`/`autoheader` from `configure.ac`. It captures platform capability detection results, gnulib module presence flags, and compile-time feature settings. + +## Key Components + +### Platform & Type Configuration +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — double-precision float exponent bit layout +- `FLEXIBLE_ARRAY_MEMBER` — C99 flexible array member compatibility shim +- `GETTIMEOFDAY_TIMEZONE` — second argument type for `gettimeofday()` (`struct timezone`) + +### Feature Flags +- `ENABLE_DEBUG` — enables debug build mode +- `C_LOCALE_MAYBE_EILSEQ` — C locale may produce encoding errors +- `_FORTIFY_SOURCE 2` — enables glibc buffer overflow hardening when optimizations are active +- `FUNC_REALPATH_WORKS` / `FUNC_NL_LANGINFO_YESEXPR_WORKS` — runtime behavior correctness flags + +### gnulib Module Presence (`GNULIB_*`) +Preprocessor expressions indicating which gnulib portability modules are active, e.g.: +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_LOCK`, `GNULIB_FSCANF`, `GNULIB_SCANF` +- `GNULIB_STRERROR`, `GNULIB_STRERROR_R_POSIX` — conditional on `IN_AUGEAS_GNULIB_TESTS` + +### gnulib Test Flags (`GNULIB_TEST_*`) +Enables unit testing for over 60 gnulib modules including socket APIs (`accept`, `bind`, `connect`), string functions (`stpcpy`, `strndup`, `strstr`), locale utilities, and POSIX wrappers. + +### System Capability Detection (`HAVE_*`) +Standard autoconf presence checks for headers and functions, including `HAVE_ALLOCA`, `HAVE_ALLOCA_H`, `HAVE_ARPA_INET_H`, `HAVE_BTOWC`, `HAVE_CANONICALIZE_FILE_NAME`, etc. + +## Usage Example + +```c +#include "config.h" + +/* Conditionally enable debug logging */ +#if ENABLE_DEBUG + fprintf(stderr, "[DEBUG] processing file\n"); +#endif + +/* Use flexible array member portably */ +struct buffer { + size_t len; + char data[FLEXIBLE_ARRAY_MEMBER]; +}; +size_t buf_size = offsetof(struct buffer, data) + 64; + +/* Guard gnulib-specific behavior */ +#if GNULIB_CANONICALIZE_LGPL + char *resolved = canonicalize_file_name(path); +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/macos/aarch64/code/.lexer.md b/libraries/cmake/source/augeas/generated/macos/aarch64/code/.lexer.md new file mode 100644 index 00000000000..2f52f86fcf9 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/macos/aarch64/code/.lexer.md @@ -0,0 +1,66 @@ + +A flex-generated reentrant lexical scanner for the Augeas lens language (`augl`), responsible for tokenizing source input into parser-consumable tokens with full location tracking. + +## Key Components + +### Scanner Namespace (`augl_*`) +All standard flex `yy_*` symbols are remapped to the `augl_` prefix to avoid conflicts when multiple scanners coexist in the same binary: + +| Flex Default | Augeas Alias | +|---|---| +| `yylex` | `augl_lex` | +| `yylex_init` | `augl_lex_init` | +| `yylex_destroy` | `augl_lex_destroy` | +| `yy_create_buffer` | `augl__create_buffer` | +| `yy_scan_string` | `augl__scan_string` | + +### Buffer State (`yy_buffer_state`) +Manages the active input window with fields for: +- `yy_ch_buf` / `yy_buf_pos` — raw character buffer and read cursor +- `yy_bs_lineno` / `yy_bs_column` — source location tracking +- `yy_is_interactive` — selects `getc()` vs `fread()` input strategy +- `yy_buffer_status` — lifecycle flags (`YY_BUFFER_NEW`, `YY_BUFFER_NORMAL`, `YY_EOF_PENDING`) + +### Reentrant Scanner State (`yyscan_t`) +An opaque `void*` handle passed to every API call, enabling thread-safe, multiple-instance scanning. Per-scanner state (input, output, extra data, debug flag) is accessed via macros like `yyin`, `yyout`, `yyextra`, and `yy_flex_debug`. + +### Key Macros +| Macro | Purpose | +|---|---| +| `BEGIN` | Enter a named start condition | +| `YY_START` / `YYSTATE` | Read the current start condition | +| `yyless(n)` | Push back all but the first *n* matched characters | +| `unput(c)` | Push a single character back to the input stream | +| `YY_LESS_LINENO(n)` | Adjust `yylineno` when characters are returned | + +## Usage Example + +```c +#include "lexer.h" /* generated header exposing augl_* prototypes */ +#include "parser.h" /* token definitions */ + +int scan_file(FILE *fp) { + yyscan_t scanner; + + /* Initialize a fresh, reentrant scanner instance */ + augl_lex_init(&scanner); + augl_set_in(fp, scanner); + + int token; + AUGL_LTYPE loc; + AUGL_STYPE val; + + while ((token = augl_lex(&val, &loc, scanner)) != 0) { + printf("token=%d line=%d col=%d text='%s'\n", + token, + augl_get_lineno(scanner), + augl_get_column(scanner), + augl_get_text(scanner)); + } + + augl_lex_destroy(scanner); + return 0; +} +``` + +> **Note:** `lexer.c` is auto-generated by flex 2.6.4 from a `.l` source file. Do not edit it directly — modify the upstream `.l` grammar and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/macos/aarch64/code/.parser.md b/libraries/cmake/source/augeas/generated/macos/aarch64/code/.parser.md new file mode 100644 index 00000000000..e4d32a0c30c --- /dev/null +++ b/libraries/cmake/source/augeas/generated/macos/aarch64/code/.parser.md @@ -0,0 +1,67 @@ + +Auto-generated Bison parser interface header for the `augl` grammar, defining token types, semantic value unions, and location tracking structures used by the Augeas lens language parser. + +## Key Components + +### Token Enum (`yytokentype`) +Defines all terminal tokens recognized by the lexer: + +| Token | Value | Description | +|---|---|---| +| `DQUOTED` | 258 | Double-quoted string literal | +| `REGEXP` | 259 | Regular expression literal | +| `LIDENT` / `UIDENT` / `QIDENT` | 260–262 | Lower/upper/qualified identifiers | +| `ARROW` | 263 | `->` arrow operator | +| `KW_MODULE` … `KW_AFTER` | 264–275 | Language keywords | + +### Semantic Value Union (`YYSTYPE`) +Holds the value of each parsed token/rule: + +| Field | Type | Purpose | +|---|---|---| +| `term` | `struct term *` | AST term node | +| `type` | `struct type *` | Type annotation | +| `ident` | `struct ident *` | Identifier reference | +| `tree` | `struct tree *` | Parse tree node | +| `regexp` | `struct { int nocase; char *pattern; }` | Regex with case flag | +| `quant` | `enum quant_tag` | Quantifier (`*`, `+`, `?`) | + +### Location Type (`YYLTYPE`) +Tracks source positions with `first_line`, `first_column`, `last_line`, `last_column` for error reporting. + +### Scanner State (`struct state`) +Custom lexer state carrying an `info` context pointer and `comment_depth` for nested comment handling. + +### Entry Point +```c +int augl_parse(struct term **term, yyscan_t scanner); +``` + +## Usage Example + +```c +#include "parser.h" +#include "info.h" + +int parse_lens_file(const char *filename) { + struct term *root = NULL; + yyscan_t scanner; + + /* Initialize scanner state with source info */ + struct state st = { + .info = make_info(filename), + .comment_depth = 0 + }; + + augl_lex_init(&scanner); + augl_set_extra(&st, scanner); + + /* Run the parser; result stored in root */ + int rc = augl_parse(&root, scanner); + + augl_lex_destroy(scanner); + return rc; /* 0 on success */ +} +``` + +> **Note:** Do not use any symbols prefixed with `yy_` or `YY_` — these are private Bison internals subject to change without notice. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/macos/aarch64/config/.config.md b/libraries/cmake/source/augeas/generated/macos/aarch64/config/.config.md new file mode 100644 index 00000000000..89e86545854 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/macos/aarch64/config/.config.md @@ -0,0 +1,49 @@ + +Auto-generated build configuration header produced by `autoconf`/`autoheader` for the Augeas configuration management library, defining platform capabilities, feature flags, and gnulib module settings detected at compile time. + +## Key Components + +### Build & Debug Flags +- `ENABLE_DEBUG` — Enables debug mode at compile/run time +- `_FORTIFY_SOURCE 2` — Activates glibc buffer overflow protection when optimizations are enabled + +### Platform & Type Definitions +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — Double-precision float exponent bit layout +- `FLEXIBLE_ARRAY_MEMBER` — Struct flexible array member compatibility shim +- `GETTIMEOFDAY_TIMEZONE` / `GETTIMEOFDAY_CLOBBERS_LOCALTIME` — Platform-specific `gettimeofday` behavior + +### Feature Detection (`HAVE_*`) +Indicates presence of system headers and functions detected by `configure`: +- `HAVE_ALLOCA` / `HAVE_ALLOCA_H` — Stack allocation support +- `HAVE_ARPA_INET_H` — Network header availability +- `HAVE_BTOWC`, `HAVE_CATGETS`, `HAVE_CFLOCALECOPYCURRENT` — Wide-char and locale function availability + +### Gnulib Module Flags (`GNULIB_*`) +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_FSCANF`, `GNULIB_LOCK`, `GNULIB_SCANF` — Active gnulib replacement modules +- `GNULIB_TEST_*` — Enables test coverage for ~60+ gnulib-wrapped POSIX functions (e.g., `accept`, `realpath`, `strerror`, `mbrtowc`) + +### Known Bug Workarounds +- `FUNC_MKDIR_DOT_BUG` — `mkdir` trailing-dot component bug +- `C_LOCALE_MAYBE_EILSEQ` — C locale encoding error possibility + +## Usage Example + +```c +#include "config.h" + +#if ENABLE_DEBUG + fprintf(stderr, "[debug] starting up\n"); +#endif + +#ifdef HAVE_ALLOCA_H + #include +#endif + +/* Use flexible array member portably */ +struct buffer { + size_t len; + char data[FLEXIBLE_ARRAY_MEMBER]; +}; +``` + +> This file is auto-generated — do not edit manually. Modify `configure.ac` or `config.h.in` and re-run `./configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/macos/x86_64/code/.lexer.md b/libraries/cmake/source/augeas/generated/macos/x86_64/code/.lexer.md new file mode 100644 index 00000000000..82535678bf0 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/macos/x86_64/code/.lexer.md @@ -0,0 +1,51 @@ + +Flex-generated reentrant lexical scanner for the Augeas lens language (`augl` prefix), tokenizing `.aug` lens files into parser-ready tokens. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `augl_lex` | Function | Main tokenization entry point (reentrant `yylex`) | +| `augl_lex_init` / `augl_lex_destroy` | Functions | Initialize and teardown per-scanner state (`yyscan_t`) | +| `augl_lex_init_extra` | Function | Initialize scanner with caller-supplied extra data | +| `yy_buffer_state` | Struct | Manages input buffer: position, size, interactivity, BOL flag, line/column tracking | +| `YY_BUFFER_STATE` | Typedef | Opaque handle to `yy_buffer_state*` | +| `yyscan_t` | Typedef | Opaque `void*` scanner handle enabling reentrancy | +| `augl__create_buffer` / `augl__delete_buffer` | Functions | Allocate/free input buffers | +| `augl__scan_string` / `augl__scan_bytes` | Functions | Scan from in-memory sources instead of `FILE*` | +| `augl_get_lineno` / `augl_set_lineno` | Functions | Line number accessors on the scanner state | +| `augl_get_lval` / `augl_set_lval` | Functions | Bison semantic value (`yylval`) accessors | +| `augl_get_lloc` / `augl_set_lloc` | Functions | Bison location (`yylloc`) accessors | + +## Usage Example + +```c +#include "lexer.h" +#include "parser.h" /* for AUGL_STYPE, AUGL_LTYPE */ + +int lex_file(FILE *fp) { + yyscan_t scanner; + AUGL_STYPE lval; + AUGL_LTYPE lloc; + int tok; + + /* Initialize reentrant scanner */ + augl_lex_init(&scanner); + + /* Point scanner at an open file */ + augl_restart(fp, scanner); + + /* Pull tokens until EOF */ + while ((tok = augl_lex(&lval, &lloc, scanner)) != 0) { + printf("token=%d text=%s line=%d\n", + tok, + augl_get_text(scanner), + augl_get_lineno(scanner)); + } + + augl_lex_destroy(scanner); + return 0; +} +``` + +> **Note:** This file is auto-generated by Flex 2.6.4 — edit the `.l` source grammar, not this file directly. All `yy*` symbols are prefixed `augl_` to avoid collisions when multiple scanners are linked together. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/macos/x86_64/code/.parser.md b/libraries/cmake/source/augeas/generated/macos/x86_64/code/.parser.md new file mode 100644 index 00000000000..aebbb9ace67 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/macos/x86_64/code/.parser.md @@ -0,0 +1,64 @@ + +Auto-generated Bison parser interface header for the Augeas lens language (`augl`) grammar, defining token types, semantic value union, and location tracking structures used by the LALR(1) parser. + +## Key Components + +### Token Enum (`yytokentype`) +Defines all terminal symbols recognized by the lexer: + +| Token | Value | Description | +|---|---|---| +| `DQUOTED` | 258 | Double-quoted string literal | +| `REGEXP` | 259 | Regular expression literal | +| `LIDENT` / `UIDENT` / `QIDENT` | 260–262 | Lower/upper/qualified identifiers | +| `ARROW` | 263 | `->` operator | +| `KW_MODULE` … `KW_AFTER` | 264–275 | Language keywords | + +### Semantic Value Union (`YYSTYPE`) +Holds the parsed value for each grammar symbol: + +- `struct term *term` — AST expression node +- `struct type *type` — type annotation node +- `struct ident *ident` — identifier node +- `struct tree *tree` — parse tree node +- `char *string` — raw string value +- `regexp` — struct with `nocase` flag and `pattern` string +- `int intval` — integer literal +- `enum quant_tag quant` — quantifier (`*`, `+`, `?`) + +### Location Type (`YYLTYPE`) +Tracks source positions with `first_line`, `first_column`, `last_line`, `last_column` for error reporting. + +### Scanner State (`struct state`) +Custom lexer state attached to the reentrant scanner, carrying an `info` context and `comment_depth` counter for nested comment handling. + +### Parser Entry Point +```c +int augl_parse(struct term **term, yyscan_t scanner); +``` + +## Usage Example + +```c +#include "parser.h" +#include "info.h" + +int parse_lens_file(const char *filename) { + struct term *result = NULL; + yyscan_t scanner; + + /* Initialize reentrant scanner, then invoke parser */ + augl_lex_init(&scanner); + + int rc = augl_parse(&result, scanner); + if (rc == 0 && result != NULL) { + /* Walk the resulting AST term */ + process_term(result); + } + + augl_lex_destroy(scanner); + return rc; +} +``` + +> **Note:** This file is auto-generated by GNU Bison 3.8.2 from `parser.y`. Do not edit directly — modify the grammar source instead and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/generated/macos/x86_64/config/.config.md b/libraries/cmake/source/augeas/generated/macos/x86_64/config/.config.md new file mode 100644 index 00000000000..45dbc795850 --- /dev/null +++ b/libraries/cmake/source/augeas/generated/macos/x86_64/config/.config.md @@ -0,0 +1,55 @@ + +Auto-generated build configuration header for the Augeas configuration management library, produced by `autoconf`/`autoheader` from `configure.ac`. It defines platform capabilities, feature flags, and gnulib module availability for the target system. + +## Key Components + +### Build & Debug Flags +- `ENABLE_DEBUG` — Enables debug mode at compile time +- `_FORTIFY_SOURCE 2` — Activates glibc buffer overflow protection when optimizations are enabled +- `FLEXIBLE_ARRAY_MEMBER` — Portable flexible array member support for pre-C99 compilers + +### Floating-Point Layout +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — Describes the memory layout of `double` exponent bits (IEEE 754 word index and bit offset) + +### System Capability Flags (`HAVE_*`) +Indicates availability of headers and functions detected at configure time: +- `HAVE_ALLOCA`, `HAVE_ALLOCA_H` — Stack allocation support +- `HAVE_ARPA_INET_H` — Network address conversion headers +- `HAVE_BTOWC`, `HAVE_CATGETS` — Wide character and message catalog functions +- `HAVE_CFLOCALECOPYCURRENT` — macOS CoreFoundation locale API + +### gnulib Module Flags (`GNULIB_*`) +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_FSCANF`, `GNULIB_LOCK`, `GNULIB_SCANF` — Marks active gnulib portability modules +- `GNULIB_TEST_*` — Enables test coverage for over 50 gnulib-wrapped POSIX functions (e.g., `accept`, `realpath`, `strerror`, `mbrtowc`) + +### Locale & Time +- `FUNC_NL_LANGINFO_YESEXPR_WORKS` — Confirms `nl_langinfo(YESEXPR)` returns valid data +- `GETTIMEOFDAY_TIMEZONE` — Defines the second argument type for `gettimeofday` as `void` + +## Usage Example + +```c +#include "config.h" + +#if ENABLE_DEBUG + fprintf(stderr, "Debug build active\n"); +#endif + +#if GNULIB_LOCK + /* Use gnulib thread-locking primitives */ + gl_lock_define(static, my_lock) +#endif + +#ifdef HAVE_ALLOCA_H + #include +#endif + +/* Safe stack allocation using detected alloca support */ +#if HAVE_ALLOCA + char *buf = alloca(256); +#else + char buf[256]; +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modify `configure.ac` and regenerate using `autoconf`/`autoheader`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/config/.config.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/config/.config.md new file mode 100644 index 00000000000..e8176d1d0b1 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/config/.config.md @@ -0,0 +1,51 @@ + +Auto-generated build configuration header for the Augeas configuration management library, produced by `autoconf`/`autoheader` from `configure.ac`. It captures platform capabilities, feature availability, and gnulib module presence detected at configure time. + +## Key Components + +### Build & Platform Flags +- `ENABLE_DEBUG` — Enables debug build mode +- `C_LOCALE_MAYBE_EILSEQ` — Signals potential encoding errors in C locale +- `FLEXIBLE_ARRAY_MEMBER` — Compatibility shim for pre-C99 flexible array members +- `_FORTIFY_SOURCE` — Enables glibc bounds-checking when optimizations are active + +### Floating-Point Layout +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — Describe the bit position of the `double` exponent, used for portable math operations + +### System Function Availability (`HAVE_*`) +- `HAVE_ALLOCA` / `HAVE_ALLOCA_H` — Stack allocation support +- `HAVE_CANONICALIZE_FILE_NAME` — POSIX path canonicalization +- `FUNC_REALPATH_WORKS` / `FUNC_NL_LANGINFO_YESEXPR_WORKS` — Runtime-verified POSIX function correctness + +### Gnulib Module Presence (`GNULIB_*`) +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_FSCANF`, `GNULIB_LOCK`, `GNULIB_SCANF` — Active gnulib replacement modules +- `GNULIB_MSVC_NOTHROW`, `GNULIB_STRERROR`, `GNULIB_STRERROR_R_POSIX` — Conditional on `IN_AUGEAS_GNULIB_TESTS` + +### Gnulib Test Guards (`GNULIB_TEST_*`) +Over 60 flags (e.g., `GNULIB_TEST_ACCEPT`, `GNULIB_TEST_REALPATH`, `GNULIB_TEST_STRERROR`) gate the inclusion of gnulib portability tests for individual POSIX functions. + +## Usage Example + +```c +#include "config.h" + +/* Conditionally enable debug logging */ +#if ENABLE_DEBUG + fprintf(stderr, "[DEBUG] Starting up\n"); +#endif + +/* Use gnulib realpath only when verified to work correctly */ +#if FUNC_REALPATH_WORKS + char *abs = realpath(path, NULL); +#else + /* fallback implementation */ +#endif + +/* Portable flexible array member in a struct */ +struct buffer { + size_t len; + char data[FLEXIBLE_ARRAY_MEMBER]; +}; +``` + +> **Note:** This file is auto-generated — do not edit manually. Re-run `./configure` to regenerate it for a different target platform. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.alloca.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.alloca.md new file mode 100644 index 00000000000..7eff8f79d55 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.alloca.md @@ -0,0 +1,41 @@ + +Portable compatibility header that provides a unified `alloca` macro for stack-based memory allocation across multiple compilers and platforms. + +## Key Components + +- **`alloca(N)`** macro — Allocates `N` bytes on the current stack frame, automatically freed when the enclosing function returns +- **Platform dispatch** — Maps `alloca` to the correct compiler built-in or system function: + +| Platform/Compiler | Mapping | +|---|---| +| GCC / MinGW | `__builtin_alloca` | +| AIX | `__alloca` | +| MSVC | `_alloca` (via ``) | +| DEC/VMS | `__ALLOCA` | +| Tandem/TNS-E | `_alloca` (intrinsic) | +| IBM MVS | `` | +| Fallback | `void *alloca(size_t)` declaration | + +## Usage Example + +```c +#include "alloca.h" +#include + +void process_buffer(size_t len) { + /* Allocate temporary buffer on the stack */ + char *buf = alloca(len); + memset(buf, 0, len); + /* buf is automatically freed when process_buffer() returns */ +} +``` + +## ⚠️ Known Limitations + +Avoid `alloca` in these scenarios: + +- **Inside function call arguments** — undefined behavior per C standard +- **Inside inline functions** — allocation may outlive the intended scope, persisting until the *caller* returns +- **Large allocations (`N >= 65536`)** — stack size is finite and platform-dependent; overflow causes a hard crash with no recoverable error + +> This file is auto-generated. Do not edit manually. Licensed under GNU Lesser General Public License v2.1+. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.ctype.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.ctype.md new file mode 100644 index 00000000000..727ccbc21b5 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.ctype.md @@ -0,0 +1,41 @@ + +A generated substitute header for ISO C99 ``, part of the GNU Portability Library (gnulib), providing compatibility shims and C++ namespace aliases for platforms where the standard ctype implementation is incomplete. + +## Key Components + +### Include Guard & System Header +- `_GL_CTYPE_H` — double-inclusion guard with `#include_next` to chain into the real system `` +- `#pragma GCC system_header` — suppresses warnings for GCC 3+ + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++, `extern` in C | +| `_GL_FUNCDECL_RPL(func, rettype, params)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, rettype, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, rettype, params)` | Creates a C++ namespace alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, rettype, params)` | Creates a C++ namespace alias redirecting to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to silence type-mismatch errors | + +## Usage Example + +```c +/* Providing a replacement for a broken platform function */ +#if GNULIB_ISBLANK +# if REPLACE_ISBLANK +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef isblank +# define isblank rpl_isblank +# endif + _GL_FUNCDECL_RPL (isblank, int, (int c)); + _GL_CXXALIAS_RPL (isblank, int, (int c)); +# else + _GL_CXXALIAS_SYS (isblank, int, (int c)); +# endif + _GL_CXXALIASWARN (isblank); +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. It is part of the gnulib portability layer used internally by GNU tools to normalize `` behavior across platforms (Linux, macOS, Windows, etc.). \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.fcntl.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.fcntl.md new file mode 100644 index 00000000000..9d2be5a02ad --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.fcntl.md @@ -0,0 +1,49 @@ + +A GNUlib-generated portable replacement for the system ``, normalizing file control flags and function declarations across platforms by defining unsupported flags to `0` and providing C/C++ compatible wrappers. + +## Key Components + +### Inclusion Guards & Platform Handling +- `_GL_FCNTL_H` — standard double-inclusion guard +- `__need_system_fcntl_h` — special invocation path for early inclusion +- `include_next ` — chains to the real system header after augmentation +- Conditional `` inclusion for non-glibc systems + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ | +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a `rpl_func` replacement function | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, ret, params)` | C++ alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | C++ alias redirecting to system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress conversion errors | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | + +### Platform-Specific Includes +- `` — included on non-glibc systems +- `` — included on native Windows (`_WIN32`) for `open()` / `creat()` + +## Usage Example + +```c +/* Gnulib consumers use it transparently — include as normal */ +#include + +/* The replacement macros are used internally in gnulib modules like: */ +#if GNULIB_OPEN +# if REPLACE_OPEN +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef open +# define open rpl_open +# endif + _GL_FUNCDECL_RPL (open, int, (const char *filename, int flags, ...)); + _GL_CXXALIAS_RPL (open, int, (const char *filename, int flags, ...)); +# else + _GL_CXXALIAS_SYS (open, int, (const char *filename, int flags, ...)); +# endif +#endif +``` + +> **Note:** This file is auto-generated by GNUlib's `gnulib-tool`. Do not edit manually — regenerate via the gnulib module system instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.langinfo.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.langinfo.md new file mode 100644 index 00000000000..a280fcdcabd --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.langinfo.md @@ -0,0 +1,51 @@ + +Auto-generated GNU portability wrapper that provides a complete, cross-platform substitute for the POSIX `` header on platforms where it is missing or incomplete. + +## Key Components + +### Type Definition +- **`nl_item`** — Defined as `int` on platforms lacking ``, used as the argument type for `nl_langinfo()`. + +### Locale Category Constants (`nl_item` values) + +| Category | Constants | +|---|---| +| `LC_CTYPE` | `CODESET` | +| `LC_NUMERIC` | `RADIXCHAR`, `DECIMAL_POINT`, `THOUSEP`, `THOUSANDS_SEP`, `GROUPING` | +| `LC_TIME` | `D_T_FMT`, `D_FMT`, `T_FMT`, `T_FMT_AMPM`, `AM_STR`, `PM_STR`, `DAY_1`–`DAY_7`, `ABDAY_1`–`ABDAY_7`, `MON_1`–`MON_12`, `ALTMON_1`–`ALTMON_12`, `ABMON_1`–`ABMON_12`, `ERA`, `ERA_D_FMT`, `ERA_D_T_FMT`, `ERA_T_FMT`, `ALT_DIGITS` | +| `LC_MONETARY` | `CRNCYSTR`, `INT_CURR_SYMBOL`, `MON_DECIMAL_POINT`, `MON_THOUSANDS_SEP`, `POSITIVE_SIGN`, `NEGATIVE_SIGN`, `FRAC_DIGITS`, `P_SIGN_POSN`, `N_SIGN_POSN` | +| `LC_MESSAGES` | `YESEXPR`, `NOEXPR` | + +### C++ Compatibility Macros +- **`_GL_FUNCDECL_RPL`** / **`_GL_FUNCDECL_SYS`** — Declare replacement or system functions with correct C linkage. +- **`_GL_CXXALIAS_RPL`** / **`_GL_CXXALIAS_SYS`** — Create `GNULIB_NAMESPACE`-scoped C++ aliases for portability. +- **`_GL_BEGIN_NAMESPACE`** / **`_GL_END_NAMESPACE`** — Wrap declarations in an optional C++ namespace. + +## Usage Example + +```c +#include +#include +#include + +int main(void) { + setlocale(LC_ALL, ""); + + /* Query the locale's character encoding */ + printf("Codeset: %s\n", nl_langinfo(CODESET)); + + /* Query decimal separator */ + printf("Decimal point: %s\n", nl_langinfo(DECIMAL_POINT)); + + /* Query abbreviated weekday names */ + printf("Day 1 (abbrev): %s\n", nl_langinfo(ABDAY_1)); + + /* Query yes/no regular expressions */ + printf("Yes expression: %s\n", nl_langinfo(YESEXPR)); + printf("No expression: %s\n", nl_langinfo(NOEXPR)); + + return 0; +} +``` + +> **Note:** This header is auto-generated by GNU `gnulib`. On platforms that already provide a complete ``, it delegates via `#include_next` and only fills in whichever constants are missing (e.g., `ALTMON_1`–`ALTMON_12` on macOS/BSD). \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.limits.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.limits.md new file mode 100644 index 00000000000..6cdb100c5a4 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.limits.md @@ -0,0 +1,49 @@ + +Auto-generated GNU compatibility wrapper that extends the system `` with portable definitions for `long long` integer bounds and ISO/IEC TS 18661-1:2014 width macros missing on older or non-standard platforms. + +## Key Components + +### `long long` Bounds Portability + +Defines `LLONG_MIN`, `LLONG_MAX`, and `ULLONG_MAX` when absent, with fallback chains covering: + +- `LONG_LONG_MIN/MAX` — HP-UX 11.31 +- `LONGLONG_MIN/MAX` — IRIX 6.5 +- GCC built-ins (`__LONG_LONG_MAX__`) + +### Integer Width Macros (`_GL_INTEGER_WIDTH`) + +A cascade of preprocessor macros that count usable bits in an integer type using its min/max values — safe for `#if` evaluation. Works by counting set bits in `MAX` across 128-bit decomposition: + +```text +_GL_INTEGER_WIDTH → _GL_COB128 → _GL_COB64 → _GL_COB32 → _GL_COB16 → _GL_COB8 → _GL_COB4 +``` + +### `WORD_BIT` / `LONG_BIT` + +Fallback assumptions when the system header omits them: +- `WORD_BIT` defaults to `32` +- `LONG_BIT` resolves to `32` or `64` based on `LONG_MAX` vs `INT_MAX` + +### ISO/IEC TS 18661-1 Width Macros + +Conditionally defines `_WIDTH` variants (`CHAR_WIDTH`, `INT_WIDTH`, `LLONG_WIDTH`, etc.) when `_GNU_SOURCE` or `__STDC_WANT_IEC_60559_BFP_EXT__` is set and `ULLONG_WIDTH` is not already provided. + +## Usage Example + +```c +#include /* Pulls in this gnulib wrapper automatically */ + +/* Check long long range portably */ +if (value > LLONG_MAX || value < LLONG_MIN) { + /* overflow */ +} + +/* Use width macros (requires _GNU_SOURCE or __STDC_WANT_IEC_60559_BFP_EXT__) */ +#define _GNU_SOURCE +#include + +static_assert(INT_WIDTH == 32, "Expected 32-bit int"); +``` + +> This header is auto-generated by gnulib — do not edit directly. Modifications should be made through the gnulib toolchain. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.locale.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.locale.md new file mode 100644 index 00000000000..c9406802c59 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.locale.md @@ -0,0 +1,39 @@ + +Auto-generated POSIX-compliant `locale.h` wrapper header from GNU gnulib, providing portable locale support with cross-platform compatibility fixes for systems where the system `locale.h` is missing or incomplete. + +## Key Components + +### Include Guard & Platform Handling +- `_GL_LOCALE_H` — main double-inclusion guard with special handling for MinGW and Solaris via `_GL_ALREADY_INCLUDING_LOCALE_H` +- Conditionally includes `` for `locale_t` type (required on macOS 10.5+) +- Uses `#include_next` to chain to the system's native `locale.h` + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the system function with given prototype | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to silence C++ conversion errors | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ builds, plain `extern` in C | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` namespace in C++ | + +## Usage Example + +```c +/* Using gnulib's portable locale wrapper */ +#include "locale.h" + +/* setlocale works portably across platforms */ +setlocale(LC_ALL, ""); + +/* In C++ with GNULIB_NAMESPACE defined, functions are + accessible via the namespace */ +#ifdef __cplusplus +// GNULIB_NAMESPACE::setlocale(LC_ALL, ""); +#endif +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit manually — regenerate via the gnulib module system when updates are needed. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdint.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdint.md new file mode 100644 index 00000000000..67ab6609ab6 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdint.md @@ -0,0 +1,58 @@ + +A gnulib-generated ISO C99 `stdint.h` compatibility header that provides fixed-width integer types and their limits for platforms that lack a native or compliant `` implementation. + +## Key Components + +### Exact-Width Integer Types (7.18.1.1) +| Type | Description | +|------|-------------| +| `int8_t` / `uint8_t` | 8-bit signed/unsigned | +| `int16_t` / `uint16_t` | 16-bit signed/unsigned | +| `int32_t` / `uint32_t` | 32-bit signed/unsigned | +| `int64_t` / `uint64_t` | 64-bit signed/unsigned (when available) | + +### Minimum-Width Types (7.18.1.2) +`int_least8_t`, `int_least16_t`, `int_least32_t`, `int_least64_t` (and unsigned variants) — aliased to the corresponding exact-width types on standard architectures. + +### Fast Minimum-Width Types (7.18.1.3) +`int_fast8_t`, `int_fast16_t`, `int_fast32_t`, `int_fast64_t` (and unsigned variants) — optimized for execution speed; **not recommended in public interfaces** due to compiler/platform variance. + +### Pointer-Sized Types (7.18.1.4) +- `intptr_t` / `uintptr_t` — integer types wide enough to hold a pointer (`long int` / `unsigned long int`) + +### Greatest-Width Types (7.18.1.5) +- `intmax_t` / `uintmax_t` — widest available integer types; prefers `long long` when available + +### Limit Macros (7.18.2) +`INT8_MIN`, `INT8_MAX`, `UINT8_MAX`, `INT16_MIN`, `INT16_MAX`, `UINT16_MAX`, and so on for all exact-width types. + +### Internal Helpers +- `_STDINT_MAX(signed, bits, zero)` — computes the maximum value for a given integer width +- `_STDINT_SIGNED_MIN(bits, zero)` — computes the minimum signed value +- `_verify_intmax_size` — compile-time assertion that `intmax_t` and `uintmax_t` have equal size + +## Usage Example + +```c +#include "stdint.h" /* gnulib replacement on platforms lacking C99 stdint */ + +int main(void) { + /* Exact-width types */ + int8_t byte_val = -128; + uint32_t counter = 4000000000U; + int64_t large_val = INT64_MAX; + + /* Pointer-sized integer */ + intptr_t ptr_as_int = (intptr_t) &byte_val; + + /* Fast types for performance-sensitive code (avoid in public APIs) */ + int_fast32_t fast_counter = 0; + + /* Greatest-width type for arbitrary precision accumulation */ + uintmax_t total = (uintmax_t) counter + (uintmax_t) large_val; + + return 0; +} +``` + +> **Note:** This file is auto-generated by gnulib and should not be edited manually. It transparently wraps or replaces the system `` using `include_next`, applying fixes only where the platform implementation is absent or non-conforming. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdio.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdio.md new file mode 100644 index 00000000000..9c4978bace3 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdio.md @@ -0,0 +1,43 @@ + +A GNU-compatible replacement/extension of the standard `` header, auto-generated by gnulib. It wraps the system `stdio.h` with portability fixes, missing function declarations, and C++ namespace aliases across platforms (Linux, Windows/MSVC, Solaris, Android, etc.). + +## Key Components + +### Format Attribute Macros +| Macro | Purpose | +|---|---| +| `_GL_ATTRIBUTE_FORMAT_PRINTF` | Marks functions using ISO C99/POSIX printf-style format strings | +| `_GL_ATTRIBUTE_FORMAT_PRINTF_SYSTEM` | Marks functions using system-native printf format strings | +| `_GL_ATTRIBUTE_FORMAT_SCANF` | Marks functions using ISO C99/POSIX scanf-style format strings | +| `_GL_ATTRIBUTE_FORMAT_SCANF_SYSTEM` | Marks functions using system-native scanf format strings | + +### Function Declaration Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, rettype, params)` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, rettype, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, rettype, params)` | Creates a C++ `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | + +### Include Guards & Platform Handling +- `_GL_STDIO_H` — main double-inclusion guard +- `_GL_ALREADY_INCLUDING_STDIO_H` — prevents re-entrant inclusion (e.g., OSF/1 nested include chains) +- Platform-specific includes pulled in conditionally for `renameat`, `perror`, `remove`, `rename` on MSVC/Android/Solaris + +## Usage Example + +```c +/* gnulib replacement functions are transparent to callers */ +#include + +int main(void) { + /* Uses rpl_printf internally if REPLACE_PRINTF is set, + otherwise delegates to the system printf */ + printf("Hello, %s\n", "world"); + + /* C++ code can use the GNULIB_NAMESPACE alias if configured */ + /* GNULIB_NAMESPACE::printf("Hello\n"); */ + return 0; +} +``` + +> **Note:** This header is auto-generated — do not edit directly. Modify the gnulib template and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdlib.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdlib.md new file mode 100644 index 00000000000..a29ba944a54 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.stdlib.md @@ -0,0 +1,47 @@ + +A GNU-compatible replacement/extension of the standard ``, auto-generated by gnulib to provide portable implementations of standard and POSIX functions across platforms where the system's native `stdlib.h` is missing, broken, or incomplete. + +## Key Components + +### Compatibility Guards +- `_GL_STDLIB_H` — double-inclusion guard with `include_next` for proper header chaining +- `__need_system_stdlib_h` / `__need_malloc_and_calloc` — special invocation modes for gnulib/glibc internal use + +### Compiler Attribute Macros +| Macro | Purpose | +|---|---| +| `_GL_ATTRIBUTE_PURE` | Maps to `__attribute__((__pure__))` on GCC ≥ 2.96 | +| `_Noreturn` | Portable no-return annotation (C11, `[[noreturn]]`, `__declspec`, or GCC attribute) | + +### Function Declaration Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias redirecting to the replacement | +| `_GL_CXXALIAS_RPL_CAST_1` | Same as above, with a cast to suppress type mismatch errors | +| `_GL_EXTERN_C` | Wraps declarations with `extern "C"` in C++ contexts | + +### Platform-Specific Includes +- `` — fixes `NULL` mis-definition on NetBSD 5.0 +- `` — provides `mkstemp`, `mkstemps`, `mkostemp`, `getsubopt` on macOS/Cygwin +- `` — provides `WEXITSTATUS` on MirBSD 10 (conditional) + +### `struct random_data` +Defined when the platform lacks it, providing state fields (`fptr`, `rptr`, `state`, `rand_type`, `rand_deg`, `rand_sep`, `end_ptr`) for reentrant random number generation. + +## Usage Example + +```c +/* gnulib handles this transparently — just include normally */ +#include + +int main(void) { + /* mkstemp, getsubopt, etc. are available portably via gnulib */ + char tmpl[] = "/tmp/myfileXXXXXX"; + int fd = mkstemp(tmpl); + return EXIT_SUCCESS; +} +``` + +> **Note:** This file is auto-generated by gnulib's `stdlib` module. Do not edit manually — regenerate via `gnulib-tool --import stdlib`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.string.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.string.md new file mode 100644 index 00000000000..5b3fb8f3671 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.string.md @@ -0,0 +1,43 @@ + +A auto-generated GNU-compatible `` header produced by Gnulib, providing portable string function declarations, cross-platform compatibility shims, and C++ namespace aliasing infrastructure for use across GNU/POSIX-compliant systems. + +## Key Components + +### Include Guard & Nested Include Handling +- `_GL_ALREADY_INCLUDING_STRING_H` — guards against recursive include loops (e.g., OS X/NetBSD `` → `` chain) +- `_GL_STRING_H` — standard double-inclusion guard +- `#include_next ` — delegates to the system `` before applying Gnulib overrides + +### Attribute Macros +- `_GL_ATTRIBUTE_PURE` — maps to `__attribute__((__pure__))` on GCC 2.96+; empty on older compilers + +### C/C++ Linkage Macros +- `_GL_EXTERN_C` — expands to `extern "C"` in C++ builds, `extern` in C builds + +### Function Declaration Macros +- `_GL_FUNCDECL_RPL(func, rettype, params)` — declares a replacement function prefixed `rpl_` +- `_GL_FUNCDECL_SYS(func, rettype, params)` — declares the system function as-is + +### C++ Namespace Alias Macros +- `_GL_CXXALIAS_RPL` — creates `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` +- `_GL_CXXALIAS_RPL_CAST_1` — same, with `reinterpret_cast` to suppress type mismatch errors +- `_GL_CXXALIAS_SYS` — creates `GNULIB_NAMESPACE::func` alias pointing to the system function +- `_GL_CXXALIAS_SYS_CAST` — same, with cast for declaration mismatches + +## Usage Example + +```c +/* Typical Gnulib pattern for a missing or broken platform function */ + +#if GNULIB_MEMRCHR +# if !HAVE_MEMRCHR + _GL_FUNCDECL_SYS (memrchr, void *, + (void const *s, int c, size_t n) + _GL_ATTRIBUTE_PURE); +# endif + _GL_CXXALIAS_SYS (memrchr, void *, + (void const *s, int c, size_t n)); +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Modify the Gnulib module configuration and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.time.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.time.md new file mode 100644 index 00000000000..6de3a3214ab --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.time.md @@ -0,0 +1,40 @@ + +A gnulib-generated compatibility shim that augments the system `` with more-standard POSIX declarations, portability fixes, and optional C++ namespace aliases via the `GNULIB_NAMESPACE` mechanism. + +## Key Components + +### Conditional Inclusion Guard +- `_GL_TIME_H` — prevents recursive inclusion; defers to the system header when only partial symbols (`__need_time_t`, `__need_clock_t`, `__need_timespec`) are requested +- Uses `#include_next ` to chain-include the real system header + +### Function Declaration Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a gnulib replacement as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the system function with correct prototype | +| `_GL_EXTERN_C` | Wraps declarations with `extern "C"` linkage in C++ | + +### C++ Namespace Alias Macros +| Macro | Purpose | +|---|---| +| `_GL_CXXALIAS_RPL` | Aliases `GNULIB_NAMESPACE::func` → `rpl_func` | +| `_GL_CXXALIAS_SYS` | Aliases `GNULIB_NAMESPACE::func` → system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Same as RPL variant but uses `reinterpret_cast` to silence type mismatch errors | +| `_GL_CXXALIAS_SYS_CAST` | Same as SYS variant with cast | + +## Usage Example + +```c +/* Typical gnulib usage pattern for a missing/broken function */ +#if GNULIB_STRFTIME +# if !HAVE_STRFTIME + _GL_FUNCDECL_SYS (strftime, size_t, + (char *, size_t, const char *, const struct tm *) + _GL_ARG_NONNULL ((1, 3, 4))); +# endif + _GL_CXXALIAS_SYS (strftime, size_t, + (char *, size_t, const char *, const struct tm *)); +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modifications should be made to the gnulib source templates and regenerated. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.unistd.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.unistd.md new file mode 100644 index 00000000000..7156c388dab --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.unistd.md @@ -0,0 +1,50 @@ + +A gnulib-generated portable substitute and wrapper for the POSIX `` header, providing cross-platform compatibility shims, replacement function declarations, and C++ namespace aliases for standard Unix API functions. + +## Key Components + +### Include Guards & Conditional Includes +- `_GL_UNISTD_H` — primary double-inclusion guard +- `_GL_INCLUDING_UNISTD_H` — re-entrancy guard for nested include cycles (e.g., macOS 10.3.9) +- Platform-specific includes pulled in as needed: ``, ``, ``, ``, `` + +### C++ Declaration Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates a `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1(...)` | Same as above but casts to silence conversion errors | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ | + +## Usage Example + +```c +/* The header is typically included transparently via gnulib module config. + Direct usage in a gnulib-enabled project: */ + +#include /* resolves to this wrapper on gnulib builds */ + +int main(void) { + /* ssize_t and off_t are guaranteed available across platforms */ + ssize_t n; + char buf[256]; + + /* getcwd, read, write, close etc. resolve to rpl_ replacements + on platforms where the system versions are broken */ + char *cwd = getcwd(buf, sizeof(buf)); + return 0; +} +``` + +```c +/* In C++ with GNULIB_NAMESPACE defined, functions are accessed via: */ +#define GNULIB_NAMESPACE gnulib +#include + +ssize_t n = gnulib::read(fd, buf, count); +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit manually — regenerate via `gnulib-tool` when updating module dependencies or targeting new platforms. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wchar.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wchar.md new file mode 100644 index 00000000000..40de9f503ed --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wchar.md @@ -0,0 +1,43 @@ + +Auto-generated gnulib substitute for ISO C99 ``, providing portable wide-character support and compatibility shims for platforms with broken or missing standard implementations. + +## Key Components + +### Include Guards & Platform Detection +- `_GL_WCHAR_H` — main double-inclusion guard +- `_GL_ALREADY_INCLUDING_WCHAR_H` — prevents re-entrant inclusion on platforms like IRIX 6.5 and HP-UX 11.00 +- Handles special invocation paths for glibc, uClibc, MinGW, HP-UX, and IRIX + +### Prerequisite Ordering Fixes +Forces correct inclusion order before delegating to the system ``: +- `` — for `wchar_t` on uClibc +- `` and `` — required by BSD/OS 4.0.1 and Tru64 + +### Function Declaration Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | C++ namespace alias pointing to replacement | +| `_GL_CXXALIAS_RPL_CAST_1` | Alias with cast for slightly mismatched signatures | +| `_GL_CXXALIAS_SYS` | C++ namespace alias pointing to system function | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | + +### Attributes +- `_GL_ATTRIBUTE_PURE` — maps to `__attribute__((__pure__))` on GCC 2.96+ + +## Usage Example + +```c +/* Standard inclusion — gnulib handles platform quirks transparently */ +#include + +/* wcwidth() is guaranteed to be declared after inclusion */ +int cols = wcwidth(L'A'); /* returns display width of wide character */ + +/* In C++ with GNULIB_NAMESPACE defined, replacements are accessible as: */ +// GNULIB_NAMESPACE::wcwidth(L'A'); +``` + +> This header is auto-generated by gnulib. Do not edit manually — modifications will be overwritten during the next gnulib update cycle. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wctype.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wctype.md new file mode 100644 index 00000000000..26b0e122062 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/.wctype.md @@ -0,0 +1,42 @@ + +Auto-generated gnulib substitute for ISO C99 ``, providing wide-character classification and conversion function declarations for platforms that lack a native implementation. + +## Key Components + +### Platform Compatibility Guards +- **MinGW special-case handling** — detects `__MINGW32__` + `__CTYPE_H_SOURCED__` to handle partial inclusion from `` +- **Prerequisite includes** — pulls in ``, ``, ``, and `` first to work around bugs in Solaris 2.5, Tru64, and BSD/OS 4.0.1 +- **Windows native path** — includes `` and `` on MSVC/MinGW to prevent `rpl_` prefix conflicts + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Same as above, but silences C++ cast errors | +| `_GL_CXXALIAS_SYS` | Creates `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++, plain `extern` in C | + +### Inline Control +- `_GL_WCTYPE_INLINE` — maps to `_GL_INLINE` for portable inline function definitions + +## Usage Example + +```c +/* config.h must be included before this header */ +#include +#include + +int main(void) { + wint_t ch = L'A'; + + if (iswalpha(ch)) + ch = towlower(ch); /* 'a' */ + + return 0; +} +``` + +> **Note:** `iswctype`, `towctrans`, `wctrans`, `wctype`, `wctrans_t`, and `wctype_t` are not yet implemented in this gnulib stub. Use platform headers where those are required. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.context.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.context.md new file mode 100644 index 00000000000..8f73bda98eb --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.context.md @@ -0,0 +1,41 @@ + +Auto-generated SELinux context API stub header that provides no-op inline implementations for systems where SELinux is not supported, ensuring compilation compatibility without the actual `libselinux` library. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `context_t` | `typedef int` | Opaque handle representing an SELinux security context | +| `context_new` | Inline function | Allocates a new context from a string; stubs return `ENOTSUP` | +| `context_str` | Inline function | Serializes a context back to a string | +| `context_free` | Inline function | Frees a context handle (no-op stub) | +| `context_user_set/get` | Inline functions | Get/set the user component of a context | +| `context_role_set/get` | Inline functions | Get/set the role component of a context | +| `context_type_set/get` | Inline functions | Get/set the type component of a context | +| `context_range_set/get` | Inline functions | Get/set the MLS/MCS range component of a context | +| `_GL_UNUSED_PARAMETER` | Macro | Suppresses `-Wunused-parameter` warnings via `__attribute__((unused))` | + +## Usage Example + +```c +#include "config.h" +#include "context.h" +#include + +void example(void) { + /* On non-SELinux systems, all calls set errno = ENOTSUP */ + context_t ctx = context_new("user_u:role_r:type_t"); + if (errno == ENOTSUP) { + /* SELinux not available — handle gracefully */ + return; + } + + context_user_set(ctx, "staff_u"); + context_role_set(ctx, "staff_r"); + + char *str = context_str(ctx); + context_free(ctx); +} +``` + +> **Note:** All functions in this stub unconditionally set `errno = ENOTSUP` and return failure values. This header is **auto-generated** — do not edit directly. Real SELinux functionality requires linking against `libselinux` with a proper `context.h` from that library. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.selinux.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.selinux.md new file mode 100644 index 00000000000..523ed21fe59 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/selinux/.selinux.md @@ -0,0 +1,48 @@ + +A auto-generated compatibility shim that replaces `` on platforms where SELinux is not available, providing stub implementations that return `ENOTSUP`. + +## Key Components + +### Types +- `security_class_t` — `unsigned short` alias for SELinux security classes +- `security_context_t` — `char *` alias for SELinux security context strings + +### Macros +- `is_selinux_enabled()` — always returns `0` (SELinux disabled) +- `SE_SELINUX_INLINE` — maps to `_GL_INLINE` for inline stub definitions +- `_GL_UNUSED_PARAMETER` — suppresses unused-parameter compiler warnings + +### Stub Functions (all return `-1` / set `errno = ENOTSUP`) + +| Function | Purpose | +|---|---| +| `getcon` / `freecon` | Get/free process security context | +| `getfscreatecon` / `setfscreatecon` | Get/set filesystem create context | +| `getfilecon` / `lgetfilecon` / `fgetfilecon` | Get file security context | +| `setfilecon` / `lsetfilecon` / `fsetfilecon` | Set file security context | +| `matchpathcon` | Match path to security context | +| `security_check_context` / `_raw` | Validate security context | +| `setexeccon` | Set exec security context | +| `security_compute_create` | Compute new security context | +| `string_to_security_class` | Convert string to class (returns `0`) | +| `matchpathcon_init_prefix` | Initialize path context matching | + +## Usage Example + +```c +/* No special usage needed — include transparently falls back to stubs */ +#include + +void check_selinux(void) { + if (!is_selinux_enabled()) { + /* Platform has no SELinux; stubs will be used */ + return; + } + + security_context_t ctx; + if (getcon(&ctx) == -1) { + /* errno == ENOTSUP on non-SELinux platforms */ + perror("getcon"); + } +} +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.stat.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.stat.md new file mode 100644 index 00000000000..cea18ae790a --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.stat.md @@ -0,0 +1,40 @@ + +A GNUlib-generated compatibility shim for `` that supplements incomplete platform implementations with portable, POSIX-compliant declarations and C++ namespace support. + +## Key Components + +### Include Guards & Platform Detection +- `_GL_SYS_STAT_H` — double-inclusion guard with `include_next` split pattern +- `__need_system_sys_stat_h` — special invocation path for raw system header access +- `#pragma GCC system_header` — suppresses warnings in GCC 3+ + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the system function with C linkage | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL_1` but silences type conversion errors via `reinterpret_cast` | +| `_GL_EXTERN_C` | Resolves to `extern "C"` in C++ or `extern` in C | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations in `GNULIB_NAMESPACE` when defined | + +## Usage Example + +```c +/* Platform missing fstatat()? GNUlib pattern used internally: */ + +#if GNULIB_FSTATAT +# if !HAVE_FSTATAT +_GL_FUNCDECL_SYS (fstatat, int, + (int fd, const char *name, struct stat *buf, int flags) + _GL_ARG_NONNULL ((2, 3))); +# endif +_GL_CXXALIAS_SYS (fstatat, int, + (int fd, const char *name, struct stat *buf, int flags)); +_GL_CXXALIASWARN (fstatat); +#endif +``` + +> **Note:** This file is auto-generated. Do not edit directly — modify the GNUlib template instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.time.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.time.md new file mode 100644 index 00000000000..39f5dcf14f7 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.time.md @@ -0,0 +1,46 @@ + +A gnulib-generated portability shim for `sys/time.h` that supplements missing or incomplete time-related declarations across platforms (POSIX, Cygwin, Windows/MSVC). + +## Key Components + +### Include Guards & Platform Detection +- `_GL_SYS_TIME_H` — double-inclusion guard with recursive-include protection for Cygwin/BSD systems +- Handles `include_next` chaining to the system's native `` +- Windows/MSVC path conditionally pulls in `` for `struct timeval` + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | +| `_GL_EXTERN_C` | Emits `extern "C"` linkage in C++, plain `extern` in C | +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the system-provided function | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS` | Creates a C++ namespace alias redirecting to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Alias variants using `reinterpret_cast` to suppress type-mismatch errors | + +## Usage Example + +```c +/* Typical gnulib pattern using macros from this header */ + +#if GNULIB_GETTIMEOFDAY +# if REPLACE_GETTIMEOFDAY +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef gettimeofday +# define gettimeofday rpl_gettimeofday +# endif + _GL_FUNCDECL_RPL (gettimeofday, int, + (struct timeval *tv, void *tz) + _GL_ARG_NONNULL ((1))); + _GL_CXXALIAS_RPL (gettimeofday, int, + (struct timeval *tv, void *tz)); +# else + _GL_CXXALIAS_SYS (gettimeofday, int, + (struct timeval *tv, void *tz)); +# endif +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modify the gnulib source templates instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.types.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.types.md new file mode 100644 index 00000000000..b102cc05e81 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.types.md @@ -0,0 +1,29 @@ + +Auto-generated gnulib compatibility header that supplements the system `sys/types.h` with portability fixes for GNU/POSIX types across platforms, particularly targeting Windows (MinGW/MSVC) and Large File Support scenarios. + +## Key Components + +| Macro / Type | Purpose | +|---|---| +| `off_t` | Overridden to 64-bit (`__int64` / `long long int`) when LFS is enabled on Windows | +| `dev_t` (`rpl_dev_t`) | Replaced with `unsigned long long int` (64-bit) for distinguishable inode support on Windows | +| `ino_t` (`rpl_ino_t`) | Replaced with 64-bit or 128-bit struct depending on build configuration | +| `_GL_WINDOWS_64_BIT_OFF_T` | Gnulib-internal indicator that 64-bit `off_t` override is active | +| `_GL_WINDOWS_STAT_INODES` | Gnulib-internal indicator for inode support level on Windows | +| `_GL_SYS_TYPES_H` | Double-inclusion guard protecting the wrapper | + +## Usage Example + +This header is consumed automatically by the gnulib build system — it is not included directly by application code. It wraps the system header transparently: + +```c +/* Gnulib replaces the system header via -I ordering. + Application code simply includes the standard header: */ +#include + +/* After inclusion, portable types are guaranteed: */ +off_t offset = 0; /* 64-bit on Windows when LFS enabled */ +ssize_t bytes_read; /* Portable signed size type */ +``` + +> **Note:** This file is auto-generated — do not edit manually. Changes should be made to the gnulib template and regenerated via the gnulib toolchain. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.wait.md b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.wait.md new file mode 100644 index 00000000000..221dc9f4576 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/aarch64/lib/sys/.wait.md @@ -0,0 +1,50 @@ + +Auto-generated POSIX-compatible `` replacement header from GNU gnulib, providing cross-platform process wait status macros and declarations, with special handling for Windows (non-Cygwin) environments where the native header is absent. + +## Key Components + +### Platform Guards +- `_GL_SYS_WAIT_H` — Split double-inclusion guard supporting the `include_next` pattern +- Windows/Cygwin detection — Skips `include_next ` on native Win32 + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations in `GNULIB_NAMESPACE` when defined | +| `_GL_EXTERN_C` | Applies `extern "C"` linkage in C++ builds | +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the system-provided function | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS` | Creates a C++ namespace alias redirecting to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress C++ conversion errors when signatures differ slightly | + +## Usage Example + +```c +/* Standard usage — include as a drop-in for */ +#include /* resolves to this gnulib header on patched builds */ + +pid_t child = fork(); +if (child == 0) { + /* child process */ + _exit(0); +} else { + int status; + waitpid(child, &status, 0); + + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + } +} +``` + +```c +/* C++ with GNULIB_NAMESPACE — aliases resolve transparently */ +#define GNULIB_NAMESPACE gnulib +#include + +pid_t pid = gnulib::waitpid(-1, &status, WNOHANG); +``` + +> **Note:** This file is auto-generated by gnulib — do not edit manually. Modifications should be made to the gnulib module source and regenerated. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/config/.config.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/config/.config.md new file mode 100644 index 00000000000..928d4cb3d8b --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/config/.config.md @@ -0,0 +1,51 @@ + +Auto-generated build configuration header for the Augeas configuration management library, produced by `autoconf`/`autoheader` from `configure.ac`. It captures platform capabilities, feature flags, and gnulib module availability detected at configure time. + +## Key Components + +### Build & Debug Flags +- `ENABLE_DEBUG` — Enables debug builds (`1` when active) +- `_FORTIFY_SOURCE` — Conditionally enables glibc buffer overflow protection when optimizations are on +- `FLEXIBLE_ARRAY_MEMBER` — Compatibility shim for C99 flexible array members on older compilers + +### Floating Point Layout +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — Describes the memory layout of `double` exponent bits (platform-specific) + +### POSIX/System Function Availability (`HAVE_*`) +Indicates presence of system headers and functions detected at configure time, including: +- `HAVE_ALLOCA` / `HAVE_ALLOCA_H` — Stack allocation support +- `HAVE_CANONICALIZE_FILE_NAME` — Absolute path resolution +- `HAVE_BTOWC` — Wide-character conversion +- `HAVE_BUG_BIG_NANOSLEEP` — Known nanosleep bug workaround flag + +### gnulib Module Flags (`GNULIB_*`) +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_FSCANF`, `GNULIB_LOCK`, etc. — Mark which gnulib portability modules are active +- `GNULIB_TEST_*` — Enable gnulib unit tests for individual POSIX functions (e.g., `accept`, `bind`, `realpath`, `strerror`) + +### Locale & I18N +- `C_LOCALE_MAYBE_EILSEQ` — Signals potential encoding errors in the C locale +- `FUNC_NL_LANGINFO_YESEXPR_WORKS` — Validates `nl_langinfo` behavior +- `GETTIMEOFDAY_TIMEZONE` — Type alias for the `gettimeofday` timezone argument + +## Usage Example + +```c +#include "config.h" + +#if ENABLE_DEBUG + fprintf(stderr, "[debug] initializing module\n"); +#endif + +#ifdef HAVE_ALLOCA_H + #include +#endif + +/* Use gnulib realpath if FUNC_REALPATH_WORKS is not defined */ +#if !FUNC_REALPATH_WORKS + char *resolved = gnulib_realpath(path, NULL); +#else + char *resolved = realpath(path, NULL); +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Re-run `./configure` to regenerate it for your target platform. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.alloca.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.alloca.md new file mode 100644 index 00000000000..edd0eb1203a --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.alloca.md @@ -0,0 +1,42 @@ + +Portable compatibility header that provides a unified `alloca` macro/function declaration for stack-based memory allocation across multiple compilers and platforms. + +## Key Components + +- **`alloca(N)`** macro/function — Allocates `N` bytes on the call stack, automatically reclaimed when the enclosing function returns. + +### Platform Mappings + +| Platform/Compiler | Resolution | +|---|---| +| GCC / MinGW | `__builtin_alloca` | +| AIX | `__alloca` | +| MSVC | `_alloca` (via ``) | +| DEC/VMS | `__ALLOCA` | +| Tandem TNS/E | `_alloca` intrinsic | +| IBM MVS | `` | +| Fallback | `void *alloca(size_t)` declaration | + +## Usage Example + +```c +#include "alloca.h" +#include + +void process_data(size_t len) { + /* Allocate a temporary buffer on the stack */ + char *buf = alloca(len); + memset(buf, 0, len); + /* buf is automatically freed when process_data() returns */ +} +``` + +## ⚠️ Known Limitations + +Avoid `alloca` in these scenarios: + +- **Inside function call arguments** — undefined behaviour +- **Inside inline functions** — allocation may outlive the intended scope +- **Large allocations (`N >= 65536`)** — stack size is not guaranteed; crash risk with no recoverable error + +> This file is auto-generated — do not edit manually. Licensed under GNU Lesser General Public License v2.1+. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.ctype.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.ctype.md new file mode 100644 index 00000000000..f1a8c30e9a0 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.ctype.md @@ -0,0 +1,48 @@ + +A generated substitute header for ISO C99 ``, part of the GNU gnulib portability library. It wraps the system `` using `#include_next` and provides compatibility macros for platforms where the standard header is incomplete. + +## Key Components + +### Include Guard & System Header Wrapping +- Uses a split double-inclusion guard pattern to safely wrap the system `` via `#include_next` +- Marks itself as a GCC system header (`#pragma GCC system_header`) to suppress warnings + +### C++/Namespace Support Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | +| `_GL_EXTERN_C` | Declares functions with C linkage in C++ | +| `_GL_FUNCDECL_RPL` | Declares a replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias pointing to the replacement | +| `_GL_CXXALIAS_SYS` | Creates a C++ namespace alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL` but uses `reinterpret_cast` for mismatched signatures | +| `_GL_CXXALIAS_SYS_CAST` | Like `_GL_CXXALIAS_SYS` but silences invalid conversion errors via cast | + +## Usage Example + +```c +/* Using the gnulib ctype substitute in a portable program */ +#include /* resolves to this gnulib wrapper on incomplete platforms */ + +int main(void) { + /* Standard ctype functions work normally */ + int c = 'A'; + if (isalpha(c)) { + /* safe on all platforms, even those with incomplete ctype.h */ + } + return 0; +} +``` + +```c +/* How gnulib internally declares a replacement function */ +#if GNULIB_ISBLANK +# if !HAVE_ISBLANK + _GL_FUNCDECL_SYS (isblank, int, (int c)); +# endif + _GL_CXXALIAS_SYS (isblank, int, (int c)); +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. It is part of the GNU Portability Library (gnulib) and targets platforms where ISO C99 `` functions like `isblank` may be missing or broken. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fcntl.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fcntl.md new file mode 100644 index 00000000000..43856edd238 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fcntl.md @@ -0,0 +1,47 @@ + +A gnulib-generated portability wrapper around the system ``, normalizing file control flags and function declarations across platforms by defining unsupported flags to `0` and providing POSIX-compatible replacements. + +## Key Components + +### Include Guards & Prerequisite Headers +- `_GL_FCNTL_H` — standard double-inclusion guard +- Conditionally includes ``, ``, ``, and `` (Windows) before delegating to the system `` via `#include_next` + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | +| `_GL_EXTERN_C` | Applies `extern "C"` linkage in C++ | +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the system function with a given prototype | +| `_GL_CXXALIAS_RPL(func, ...)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress C++ type conversion errors | + +### Special Invocation Mode +- `__need_system_fcntl_h` — triggers a minimal include path (prerequisite headers + raw system ``) used internally before `` + +## Usage Example + +```c +/* Standard usage — just include as you would */ +#include + +int fd = open("file.txt", O_RDONLY); + +/* In a gnulib module providing open() replacement */ +#if GNULIB_OPEN +# if REPLACE_OPEN +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef open +# define open rpl_open +# endif + _GL_FUNCDECL_RPL(open, int, (const char *filename, int flags, ...)); + _GL_CXXALIAS_RPL(open, int, (const char *filename, int flags, ...)); +# else + _GL_CXXALIAS_SYS(open, int, (const char *filename, int flags, ...)); +# endif +#endif +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit manually — regenerate via `gnulib-tool` when updating the gnulib submodule. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fnmatch.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fnmatch.md new file mode 100644 index 00000000000..ab1dcfc2e3f --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.fnmatch.md @@ -0,0 +1,50 @@ + +Auto-generated GNU portability wrapper header that provides a cross-platform substitute for the standard ``, part of the GNU C Library (gnulib) portability layer. + +## Key Components + +### Flag Constants +- `FNM_PATHNAME` — Match only within path components (don't let `*` cross `/`) +- `FNM_NOESCAPE` — Treat backslash as a literal character +- `FNM_PERIOD` — Require explicit match for leading `.` +- `FNM_NOMATCH` — Return value when pattern does not match + +### Core Function +- `fnmatch(pattern, string, flags)` — Matches a filename string against a shell-style glob pattern + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias pointing to the replacement | +| `_GL_CXXALIAS_SYS` | Creates a C++ namespace alias pointing to the system function | +| `_GL_CXXALIASWARN` | Emits a warning if the non-namespaced form is used in C++ | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++ builds, `extern` otherwise | + +## Usage Example + +```c +#include "fnmatch.h" +#include + +int main(void) { + const char *pattern = "*.txt"; + const char *filename = "report.txt"; + + if (fnmatch(pattern, filename, FNM_PATHNAME) == 0) { + printf("Match found: %s\n", filename); + } else { + printf("No match: %s\n", filename); + } + + /* Match with period protection */ + if (fnmatch("*.conf", ".hidden.conf", FNM_PERIOD) != 0) { + printf("Leading dot not matched\n"); + } + + return 0; +} +``` + +> **Note:** This is an auto-generated gnulib portability shim. Do not edit manually — regenerate via gnulib's `gnulib-tool`. On platforms where a compliant `fnmatch` already exists, this header delegates directly to the system implementation via `#include_next`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt-cdefs.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt-cdefs.md new file mode 100644 index 00000000000..5b98824845a --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt-cdefs.md @@ -0,0 +1,47 @@ + +Compatibility shim providing fallback definitions for internal glibc C definition macros required by the gnulib `getopt` implementation when building outside of GNU libc environments. + +## Key Components + +| Macro | Purpose | +|---|---| +| `__BEGIN_DECLS` | Opens `extern "C"` linkage block for C++ compatibility | +| `__END_DECLS` | Closes `extern "C"` linkage block | +| `__GNUC_PREREQ(maj, min)` | Tests whether GCC version meets a minimum `major.minor` requirement | +| `__THROW` | Marks functions as `throw()` (no-exception) when using GCC 2.8+ with C++ | + +## Usage Example + +This header is auto-generated and **not intended for direct inclusion**. Use `getopt.h` or `unistd.h` instead: + +```c +/* Correct usage — include the public header */ +#include + +int main(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "hv")) != -1) { + switch (opt) { + case 'h': /* handle help */ break; + case 'v': /* handle verbose */ break; + } + } + return 0; +} +``` + +Internally, the macros resolve as follows depending on environment: + +```c +/* On non-glibc / non-C++ builds, macros collapse to nothing */ +__BEGIN_DECLS /* → (empty) */ +void my_func __THROW; /* → void my_func */ +__END_DECLS /* → (empty) */ + +/* On C++ with GCC ≥ 2.8 */ +extern "C" { +void my_func throw (); +} +``` + +> **Note:** This file is part of [gnulib](https://www.gnu.org/software/gnulib/) and is licensed under LGPL 2.1+. It is **not** shared with the GNU C Library, unlike `getopt-core.h` and `getopt-ext.h`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt.md new file mode 100644 index 00000000000..edfd6084c4b --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.getopt.md @@ -0,0 +1,45 @@ + +Auto-generated gnulib replacement header for POSIX `getopt` command-line option parsing, providing portable declarations across platforms where the system `getopt.h` may be absent or non-conformant. + +## Key Components + +| Element | Description | +|---|---| +| `_GL_SYSTEM_GETOPT` | Guard macro used during `include_next` to prevent recursive inclusion of the system `getopt.h` | +| `__GETOPT_PREFIX` | Optional prefix macro for standalone apps needing namespaced getopt symbols | +| `_GL_ARG_NONNULL(params)` | GCC `__nonnull__` attribute wrapper; enforces non-NULL argument checking at compile time (GCC 3.3+), no-op on other compilers | +| `` | Core type/constant definitions for getopt | +| `` | Core getopt function declarations (e.g., `getopt`) | +| `` | Extended getopt declarations (e.g., `getopt_long`, `getopt_long_only`) | + +## Usage Example + +```c +#include + +int main(int argc, char *argv[]) { + int opt; + + /* Short options */ + while ((opt = getopt(argc, argv, "hv")) != -1) { + switch (opt) { + case 'h': printf("Usage: ...\n"); break; + case 'v': printf("Verbose mode\n"); break; + default: break; + } + } + + /* Long options with getopt_long */ + static struct option long_opts[] = { + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + int idx; + while ((opt = getopt_long(argc, argv, "hv", long_opts, &idx)) != -1) { /* ... */ } + return 0; +} +``` + +> **Note:** This header is auto-generated by gnulib. Do not edit manually — changes will be overwritten during project regeneration. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.langinfo.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.langinfo.md new file mode 100644 index 00000000000..33e06e6ec7c --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.langinfo.md @@ -0,0 +1,50 @@ + +A GNUlib-generated portability wrapper for the POSIX `` header, providing locale information constants and `nl_langinfo` support for platforms that lack or have an incomplete implementation. + +## Key Components + +### Type Definition +- **`nl_item`** — Defined as `int` on platforms missing ``, used as the item selector type for `nl_langinfo()` + +### Locale Category Constants (`nl_item` values) + +| Category | Constants | +|---|---| +| `LC_CTYPE` | `CODESET` | +| `LC_NUMERIC` | `RADIXCHAR`, `DECIMAL_POINT`, `THOUSEP`, `THOUSANDS_SEP`, `GROUPING` | +| `LC_TIME` | `D_T_FMT`, `D_FMT`, `T_FMT`, `T_FMT_AMPM`, `AM_STR`, `PM_STR`, `DAY_1`–`DAY_7`, `ABDAY_1`–`ABDAY_7`, `MON_1`–`MON_12`, `ALTMON_1`–`ALTMON_12`, `ABMON_1`–`ABMON_12`, `ERA*`, `ALT_DIGITS` | +| `LC_MONETARY` | `CRNCYSTR`, `INT_CURR_SYMBOL`, `MON_DECIMAL_POINT`, `P_SIGN_POSN`, `N_SIGN_POSN`, and related | +| `LC_MESSAGES` | `YESEXPR`, `NOEXPR` | + +### C++/GNUlib Compatibility Macros +- **`_GL_FUNCDECL_RPL`** — Declares a GNUlib replacement function (`rpl_func`) +- **`_GL_FUNCDECL_SYS`** — Declares the system function with C linkage +- **`_GL_CXXALIAS_RPL`** — Creates a C++ namespace alias pointing to the replacement +- **`_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE`** — Wraps declarations in `GNULIB_NAMESPACE` when building with C++ + +## Usage Example + +```c +#include "langinfo.h" +#include +#include + +int main(void) { + setlocale(LC_ALL, ""); + + /* Query the locale's character encoding */ + char *encoding = nl_langinfo(CODESET); + printf("Encoding: %s\n", encoding); + + /* Query decimal point character */ + char *decimal = nl_langinfo(RADIXCHAR); + printf("Decimal separator: %s\n", decimal); + + /* Query abbreviated day names */ + printf("Monday abbrev: %s\n", nl_langinfo(ABDAY_2)); + + return 0; +} +``` + +> **Note:** This header is auto-generated by GNUlib's `gnulib-tool`. Do not edit manually — regenerate via GNUlib if updates are needed. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.limits.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.limits.md new file mode 100644 index 00000000000..c92f8b296ed --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.limits.md @@ -0,0 +1,34 @@ + +A GNU-compatible replacement/extension of the standard ``, auto-generated as part of the Gnulib portability layer. It wraps the system `` via `#include_next` and backfills missing constants for `long long` and integer-width macros across non-standard platforms (HP-UX, IRIX, older GCC). + +## Key Components + +| Macro / Group | Purpose | +|---|---| +| `LLONG_MIN` / `LLONG_MAX` / `ULLONG_MAX` | Portable `long long` limits; falls back to platform-specific names (`LONG_LONG_MAX`, `LONGLONG_MAX`) or GCC built-ins | +| `_GL_INTEGER_WIDTH(min, max)` | Computes usable bit-width of an integer type from its min/max values | +| `_GL_COB128` → `_GL_COB4` | Recursive bit-counting helper chain (Count Of Bits), safe for use in `#if` expressions | +| `WORD_BIT` | Bit-width of `int`; defaults to `32` if not defined | +| `LONG_BIT` | Bit-width of `long`; auto-detected as `32` or `64` via `LONG_MAX == INT_MAX` | +| `*_WIDTH` macros | ISO/IEC TS 18661-1 width macros (`CHAR_WIDTH`, `INT_WIDTH`, `LLONG_WIDTH`, etc.), enabled under `_GNU_SOURCE` or `__STDC_WANT_IEC_60559_BFP_EXT__` | + +## Usage Example + +```c +#include /* resolves to this gnulib wrapper on supported builds */ + +/* Portable long long bounds */ +long long lower = LLONG_MIN; +long long upper = LLONG_MAX; + +/* Compute bit-width of int at preprocessing time */ +#define MY_INT_BITS _GL_INTEGER_WIDTH(INT_MIN, INT_MAX) + +/* Use TS 18661 width macros (requires _GNU_SOURCE) */ +#ifdef _GNU_SOURCE + printf("int is %d bits wide\n", INT_WIDTH); + printf("long long is %d bits wide\n", LLONG_WIDTH); +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modifications should be made to the upstream Gnulib template. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.locale.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.locale.md new file mode 100644 index 00000000000..7fdc2b4cb45 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.locale.md @@ -0,0 +1,46 @@ + +Auto-generated POSIX-compatible `locale.h` header from the GNU portability library (gnulib), providing cross-platform locale support with C++ namespace compatibility macros. + +## Key Components + +### Include Guards & Platform Handling +- `_GL_LOCALE_H` — Main include guard preventing double inclusion +- `_GL_ALREADY_INCLUDING_LOCALE_H` — Split guard for handling `include_next` chains +- Special invocation paths for MinGW/Win32 and Solaris/gettext combinations + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations in `GNULIB_NAMESPACE` | +| `_GL_EXTERN_C` | Applies `extern "C"` linkage in C++ builds | +| `_GL_FUNCDECL_RPL(func, rettype, params)` | Declares a replacement function prefixed `rpl_` | +| `_GL_FUNCDECL_SYS(func, rettype, params)` | Declares the system function with given prototype | +| `_GL_CXXALIAS_RPL(func, rettype, params)` | Creates C++ alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, rettype, params)` | Creates C++ alias redirecting to system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants for mismatched declarations | + +### Platform Includes +- Pulls in `` to fix NULL mis-definition on NetBSD 5.0 +- Conditionally includes `` for `locale_t` type on macOS 10.5+ + +## Usage Example + +```c +/* Declaring a gnulib replacement for a locale function */ +#if GNULIB_SETLOCALE +# if REPLACE_SETLOCALE +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef setlocale +# define setlocale rpl_setlocale +# endif + _GL_FUNCDECL_RPL (setlocale, char *, (int category, const char *locale)); + _GL_CXXALIAS_RPL (setlocale, char *, (int category, const char *locale)); +# else + _GL_CXXALIAS_SYS (setlocale, char *, (int category, const char *locale)); +# endif + _GL_CXXALIASWARN (setlocale); +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. It is part of gnulib's POSIX portability layer, normalizing locale API availability across Linux, macOS, Windows (MinGW), and Solaris. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdint.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdint.md new file mode 100644 index 00000000000..6c54b60eaa7 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdint.md @@ -0,0 +1,52 @@ + +A gnulib-generated portable replacement for ISO C99 ``, providing fixed-width integer types and their limits on platforms that lack a compliant standard implementation. + +## Key Components + +### Exact-Width Integer Types (§7.18.1.1) +| Type | Underlying C Type | +|------|------------------| +| `int8_t` / `uint8_t` | `signed char` / `unsigned char` | +| `int16_t` / `uint16_t` | `short int` / `unsigned short int` | +| `int32_t` / `uint32_t` | `int` / `unsigned int` | +| `int64_t` / `uint64_t` | `long long int` / `unsigned long long int` (platform-dependent) | + +### Minimum-Width Types (§7.18.1.2) +Aliases mapping `int_leastN_t` / `uint_leastN_t` to their exact-width equivalents. + +### Fastest Minimum-Width Types (§7.18.1.3) +`int_fastN_t` / `uint_fastN_t` — note: implementation differs per platform (e.g., SunOS uses `int` for 32-bit fast types; others use `long int`). + +### Pointer-Sized Types (§7.18.1.4) +`intptr_t` / `uintptr_t` — defined as `long int` / `unsigned long int` unless overridden by the system (e.g., kLIBC). + +### Greatest-Width Types (§7.18.1.5) +`intmax_t` / `uintmax_t` — preferring `long long int` when available; a compile-time size equality check is enforced via: + +```c +typedef int _verify_intmax_size[sizeof(intmax_t) == sizeof(uintmax_t) ? 1 : -1]; +``` + +### Integer Limit Macros (§7.18.2) +Constants such as `INT8_MIN`, `INT8_MAX`, `UINT8_MAX`, etc. + +--- + +## Usage Example + +```c +#include + +/* Portable fixed-width variables */ +uint8_t byte_val = 255; +int32_t counter = -1000000; +uint64_t timestamp = 1700000000ULL; + +/* Pointer-sized integer */ +intptr_t addr = (intptr_t)&byte_val; + +/* Greatest-width accumulator */ +intmax_t total = (intmax_t)counter + (intmax_t)timestamp; +``` + +> **Note:** This file is auto-generated by gnulib and should not be edited manually. It is conditionally compiled only when the host platform's native `` is absent or non-conformant. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdio.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdio.md new file mode 100644 index 00000000000..e4d8b944019 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdio.md @@ -0,0 +1,51 @@ + +A GNU-compatible replacement/augmentation of the standard `` header, auto-generated by gnulib to provide portable I/O declarations across platforms (Linux, macOS, Windows/MSVC, Solaris, Android, etc.). + +## Key Components + +### Inclusion Guards & Split-Include Logic +- `_GL_STDIO_H` / `_GL_ALREADY_INCLUDING_STDIO_H` — prevents recursive inclusion (handles nested include chains on OSF/1, glibc internals, etc.) +- `#include_next ` — wraps the system `stdio.h` before adding gnulib extensions + +### Format Attribute Macros +| Macro | Purpose | +|---|---| +| `_GL_ATTRIBUTE_FORMAT(spec)` | Base GCC `__format__` attribute wrapper | +| `_GL_ATTRIBUTE_FORMAT_PRINTF` | Marks ISO C99/POSIX printf-style functions | +| `_GL_ATTRIBUTE_FORMAT_PRINTF_SYSTEM` | Marks system-native printf-style functions | +| `_GL_ATTRIBUTE_FORMAT_SCANF` | Marks ISO C99/POSIX scanf-style functions | +| `_GL_ATTRIBUTE_FORMAT_SCANF_SYSTEM` | Marks system-native scanf-style functions | + +### C++ Namespace / Replacement Function Macros +- `_GL_FUNCDECL_RPL(func, rettype, params)` — declares a `rpl_func` replacement with C linkage +- `_GL_FUNCDECL_SYS(func, rettype, params)` — declares the original system function +- `_GL_CXXALIAS_RPL(func, rettype, params)` — creates a `GNULIB_NAMESPACE::func` alias pointing to the replacement +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in the gnulib C++ namespace when defined + +### Platform-Specific Includes +Conditionally pulls in ``, ``, ``, or `` on Solaris, Android, and MSVC to resolve declaration gaps (e.g., `renameat`, `perror`, `remove`). + +## Usage Example + +```c +/* gnulib automatically redirects printf to its replacement when needed */ +#include /* resolves to this gnulib-generated header */ + +/* Format attribute is applied transparently to custom wrappers */ +void my_log(const char *fmt, ...) + _GL_ATTRIBUTE_FORMAT_PRINTF(1, 2); + +void my_log(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +int main(void) { + my_log("Value: %d\n", 42); + return 0; +} +``` + +> **Note:** This file is auto-generated — do not edit directly. Modify the gnulib module configuration and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdlib.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdlib.md new file mode 100644 index 00000000000..2879a40a3b7 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.stdlib.md @@ -0,0 +1,40 @@ + +A GNU-compatible replacement/extension of the standard `` header, auto-generated by gnulib to provide portable standard library declarations across platforms where the system's own `stdlib.h` is missing, incomplete, or non-conformant. + +## Key Components + +| Component | Description | +|---|---| +| `_GL_ATTRIBUTE_PURE` | Macro marking functions as pure (no side effects) for GCC optimization | +| `_Noreturn` | Cross-compiler macro for functions that never return (maps to `[[noreturn]]`, `__attribute__((noreturn))`, or `__declspec(noreturn)`) | +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) with a given prototype | +| `_GL_FUNCDECL_SYS` | Declares the original system function with a given prototype | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias redirecting to the replacement function | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL` but uses `reinterpret_cast` to silence type mismatch errors | +| `struct random_data` | Conditionally defined struct for reentrant random number state (when not provided by the platform) | +| `GNULIB_NAMESPACE` | Optional C++ namespace wrapping for all gnulib-provided declarations | + +## Usage Example + +This header is consumed indirectly — gnulib inserts it before the system `` via `include_next`: + +```c +/* Standard include — gnulib intercepts and augments */ +#include + +/* Using a gnulib-provided replacement transparently */ +char *tmp = mkstemp(template); /* rpl_mkstemp used if system version is broken */ + +/* _Noreturn usage across compilers */ +_Noreturn void fatal_error(const char *msg); + +/* Pure function annotation */ +int _GL_ATTRIBUTE_PURE compute_hash(const char *s, size_t len); +``` + +## Notes + +- **Do not edit** — this file is auto-generated by gnulib's `gnulib-tool`. +- Uses `include_next` to wrap the system ``, so both must be on the include path. +- Platform-specific guards handle differences on Solaris, macOS, Cygwin, NetBSD, MirBSD, and native Windows (`_WIN32`). +- Licensed under **GNU LGPL v2.1+**. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.string.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.string.md new file mode 100644 index 00000000000..e2ab0847dfd --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.string.md @@ -0,0 +1,39 @@ + +Auto-generated GNU-compatible `` header produced by Gnulib, extending the system's native string header with portable replacements, missing function declarations, and C++ namespace aliasing support. + +## Key Components + +### Include Guard & Nesting Logic +- `_GL_ALREADY_INCLUDING_STRING_H` — handles recursive include chains (e.g., OS X/NetBSD `` → `` → `"string.h"`) +- `_GL_STRING_H` — standard double-inclusion guard wrapping all declarations + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` if defined | +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, ...)` | Creates a C++ namespace alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | Creates a C++ namespace alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants for mismatched C function signatures | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | + +### Attributes +- `_GL_ATTRIBUTE_PURE` — maps to `__attribute__((__pure__))` on GCC 2.96+ + +## Usage Example + +```c +/* Typical Gnulib usage pattern for a portable function replacement */ + +#if GNULIB_STRNDUP +# if !HAVE_STRNDUP + _GL_FUNCDECL_SYS (strndup, char *, (const char *s, size_t n)); +# endif + _GL_CXXALIAS_SYS (strndup, char *, (const char *s, size_t n)); + _GL_CXXALIASWARN (strndup); +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modify the Gnulib module configuration and regenerate via `gnulib-tool`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.time.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.time.md new file mode 100644 index 00000000000..76de986bfae --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.time.md @@ -0,0 +1,39 @@ + +Auto-generated gnulib compatibility header that provides a more standards-compliant `` by wrapping the system header and supplementing missing or broken POSIX/C99 declarations across platforms. + +## Key Components + +### Guard Macros +- `_GL_TIME_H` — prevents recursive inclusion; the header conditionally defers to the system `` when internal glibc partial-inclusion guards (`__need_time_t`, `__need_clock_t`, `__need_timespec`) are active + +### C/C++ Linkage Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ mode, `extern` otherwise | +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the system function with a portable prototype | +| `_GL_CXXALIAS_RPL(func, ...)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_*_CAST` variants | Same as above but with `reinterpret_cast` to silence C++ conversion warnings | +| `_GL_BEGIN/END_NAMESPACE` | Opens/closes the `GNULIB_NAMESPACE` C++ namespace when defined | + +## Usage Example + +```c +/* Typical gnulib pattern — substitute a missing or broken platform function */ + +#if GNULIB_STRPTIME +# if !HAVE_STRPTIME + _GL_FUNCDECL_SYS (strptime, char *, + (const char *input, const char *format, struct tm *result)); +# endif + _GL_CXXALIAS_SYS (strptime, char *, + (const char *input, const char *format, struct tm *result)); + _GL_CXXALIASWARN (strptime); +#endif + +/* In C++ with GNULIB_NAMESPACE defined, callers use: */ +/* GNULIB_NAMESPACE::strptime(input, fmt, &tm_val); */ +``` + +> **Note:** This file is auto-generated by gnulib's `gnulib-tool`. Do not edit it directly — changes belong in the gnulib module definition or the project's `configure.ac`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.unistd.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.unistd.md new file mode 100644 index 00000000000..26c058730a1 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.unistd.md @@ -0,0 +1,38 @@ + +A GNUlib-generated substitute and wrapper for the POSIX `` header, providing portable declarations of standard Unix API functions across platforms (Linux, macOS, Windows/MinGW, Cygwin, Android, etc.). + +## Key Components + +### Platform Compatibility Guards +- Handles nested include chains (e.g., macOS 10.3.9 `` → `` → `` → ``) +- Conditionally includes ``, ``, ``, ``, ``, ``, `` to fill in missing declarations per platform + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a GNUlib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1(...)` | Same as above but silences invalid-conversion errors via `reinterpret_cast` | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | + +## Usage Example + +```c +/* GNUlib automatically selects this header over the system . + No direct user action needed — just include normally: */ +#include + +/* Replacement functions are transparently aliased, e.g.: */ +ssize_t n = read(fd, buf, sizeof(buf)); /* may route to rpl_read on Windows */ +int ret = chdir("/tmp"); /* may route to rpl_chdir on MinGW */ + +/* In C++ with GNULIB_NAMESPACE defined: */ +#define GNULIB_NAMESPACE gnulib +#include +gnulib::read(fd, buf, 128); /* uses the GNUlib-wrapped version */ +``` + +> **Note:** This file is auto-generated by GNUlib's `gnulib-tool`. Do not edit manually — regenerate via the GNUlib module system instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wchar.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wchar.md new file mode 100644 index 00000000000..51d3ff9b457 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wchar.md @@ -0,0 +1,46 @@ + +A generated substitute for the ISO C99 `` header, part of the GNU portability library (gnulib), providing wide-character function declarations and compatibility fixes for platforms with incomplete or broken `wchar.h` implementations. + +## Key Components + +### Include Guards & Special Invocation +- `_GL_WCHAR_H` / `_GL_ALREADY_INCLUDING_WCHAR_H` — double-inclusion guards handling re-entrant include scenarios (HP-UX, MinGW, IRIX, uClibc) +- Conditional `#include_next ` — falls through to the system header when nested inclusion is detected + +### Platform Compatibility Includes +- Conditionally pulls in ``, ``, and `` before `` to satisfy platform-specific ordering requirements (Tru64, BSD/OS 4.0.1) +- `` included for glibc detection via `__GLIBC__` + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a replacement function `rpl_func` | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates `GNULIB_NAMESPACE::func` alias to `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Same as above, with a cast for mismatched signatures | +| `_GL_CXXALIAS_SYS` | Creates `GNULIB_NAMESPACE::func` alias to the system function | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++, `extern` in C | +| `_GL_ATTRIBUTE_PURE` | Maps to `__attribute__((__pure__))` on GCC 2.96+ | + +## Usage Example + +```c +/* Typical gnulib override pattern used internally in this header */ + +#if GNULIB_WCWIDTH +# if REPLACE_WCWIDTH +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef wcwidth +# define wcwidth rpl_wcwidth +# endif + _GL_FUNCDECL_RPL (wcwidth, int, (wchar_t wc)); + _GL_CXXALIAS_RPL (wcwidth, int, (wchar_t wc)); +# else + _GL_CXXALIAS_SYS (wcwidth, int, (wchar_t wc)); +# endif + _GL_CXXALIASWARN (wcwidth); +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modifications should be made to the gnulib source templates. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wctype.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wctype.md new file mode 100644 index 00000000000..5a5691e9f45 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/.wctype.md @@ -0,0 +1,44 @@ + +A auto-generated gnulib substitute for the ISO C99 `` header, providing wide-character classification and conversion functions for platforms that lack a native implementation. + +## Key Components + +### Platform Compatibility Guards +- **MinGW special-case**: Handles `__MINGW32__` + `__CTYPE_H_SOURCED__` to support partial inclusion from `` +- **Prerequisite includes**: Forces ``, ``, ``, `` inclusion order to work around bugs in Solaris 2.5, Tru64, and BSD/OS 4.0.1 +- **Native Windows path**: Includes `` and `` early to prevent `rpl_` prefix conflicts on MSVC/MinGW + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a `rpl_func` replacement function | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_SYS` | Creates `GNULIB_NAMESPACE::func` alias → system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL` but silences cast errors | +| `_GL_CXXALIAS_SYS_CAST` | Like `_GL_CXXALIAS_SYS` but silences cast errors | +| `_GL_EXTERN_C` | Resolves to `extern "C"` in C++, `extern` in C | + +### Inline Control +- `_GL_WCTYPE_INLINE` — defaults to `_GL_INLINE`; controls inlining of wrapper functions + +## Usage Example + +```c +/* config.h must be included before this header */ +#include +#include "wctype.h" + +int main(void) { + wint_t c = L'A'; + + /* Wide character classification */ + if (iswalpha(c)) { + wint_t lower = towlower(c); /* Returns L'a' */ + } + + return 0; +} +``` + +> **Note:** `iswctype`, `towctrans`, `wctrans`, `wctype`, `wctrans_t`, and `wctype_t` are not yet implemented in this substitute header. This file is auto-generated — do not edit directly. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.context.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.context.md new file mode 100644 index 00000000000..3f9bdc6335d --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.context.md @@ -0,0 +1,42 @@ + +Auto-generated SELinux context API stub header that provides no-op inline implementations for systems where SELinux is not supported, ensuring portable compilation without requiring the actual `libselinux` library. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `context_t` | `typedef int` | Opaque handle representing a SELinux security context | +| `context_new()` | Inline function | Allocates a new context from a string (stub: sets `ENOTSUP`) | +| `context_free()` | Inline function | Frees a context handle (stub: no-op) | +| `context_str()` | Inline function | Converts context to string representation (stub: sets `ENOTSUP`) | +| `context_user_set/get()` | Inline functions | Get/set the user component of a context | +| `context_role_set/get()` | Inline functions | Get/set the role component of a context | +| `context_type_set/get()` | Inline functions | Get/set the type component of a context | +| `context_range_set/get()` | Inline functions | Get/set the MLS/MCS range component of a context | +| `_GL_UNUSED_PARAMETER` | Macro | Suppresses GCC `-Wunused-parameter` warnings on stub parameters | + +## Usage Example + +```c +#include "config.h" +#include "context.h" +#include + +int main(void) { + /* On non-SELinux systems, all calls set errno = ENOTSUP */ + context_t ctx = context_new("user_u:role_r:type_t:s0"); + if (ctx == 0) { + perror("context_new"); /* prints: ENOTSUP */ + return 1; + } + + /* These setters return -1 with errno = ENOTSUP on stub builds */ + if (context_type_set(ctx, "new_type_t") < 0) + perror("context_type_set"); + + context_free(ctx); + return 0; +} +``` + +> **Note:** All functions in this header are stubs. On systems with SELinux support, this header is replaced by the real `` from `libselinux`, where these functions perform actual context manipulation. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.selinux.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.selinux.md new file mode 100644 index 00000000000..619c50c9d4f --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/selinux/.selinux.md @@ -0,0 +1,53 @@ + +A auto-generated stub header that replaces `` on platforms where SELinux is not available, providing no-op inline implementations that return `ENOTSUP`. + +## Key Components + +### Types +- `security_class_t` — `unsigned short` alias for SELinux security class identifiers +- `security_context_t` — `char*` alias for SELinux security context strings + +### Macros +- `is_selinux_enabled()` — always returns `0` (SELinux disabled) +- `_GL_UNUSED_PARAMETER` — GCC attribute to suppress unused-parameter warnings +- `SE_SELINUX_INLINE` — maps to `_GL_INLINE` for inline function definitions + +### Stub Functions (all return `-1`, set `errno = ENOTSUP`) + +| Function | Purpose | +|---|---| +| `getcon` / `freecon` | Get/free current process security context | +| `getfscreatecon` / `setfscreatecon` | Get/set filesystem create context | +| `getfilecon` / `lgetfilecon` / `fgetfilecon` | Get file security context (path/symlink/fd) | +| `setfilecon` / `lsetfilecon` / `fsetfilecon` | Set file security context (path/symlink/fd) | +| `matchpathcon` | Match path to expected context | +| `matchpathcon_init_prefix` | Initialize path context matching | +| `security_check_context` / `security_check_context_raw` | Validate a security context string | +| `setexeccon` | Set exec security context | +| `security_compute_create` | Compute new object security context | +| `string_to_security_class` | Convert class name string to ID | + +## Usage Example + +```c +/* No explicit include needed — build system substitutes this header + automatically on non-SELinux platforms via gnulib. */ +#include + +int main(void) { + /* On non-SELinux systems, always returns 0 */ + if (!is_selinux_enabled()) { + return 0; + } + + security_context_t ctx = NULL; + /* Returns -1, sets errno = ENOTSUP on stub platforms */ + if (getcon(&ctx) == -1) { + perror("getcon"); + } + freecon(ctx); + return 0; +} +``` + +> **Note:** This header is auto-generated by gnulib. Do not edit it manually. On platforms with native SELinux support (`HAVE_SELINUX_SELINUX_H` defined), the real system header is included instead via `#include_next`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.stat.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.stat.md new file mode 100644 index 00000000000..0fec808ee56 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.stat.md @@ -0,0 +1,45 @@ + +Auto-generated gnulib compatibility header that supplements incomplete `sys/stat.h` implementations on platforms where the system-provided header is missing standard POSIX declarations or file stat functions. + +## Key Components + +### Include Guards & Platform Detection +- `_GL_SYS_STAT_H` — primary double-inclusion guard +- `__need_system_sys_stat_h` — special invocation mode that passes through directly to the system header +- `#include_next ` — chains to the real system header after injecting gnulib additions + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations in `GNULIB_NAMESPACE` when building C++ | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ builds, plain `extern` in C | +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the system function with gnulib prototype | +| `_GL_CXXALIAS_RPL(func, ...)` | Creates a C++ alias routing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | Creates a C++ alias routing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress C++ conversion errors | + +## Usage Example + +```c +/* Platform missing fstatat()? gnulib provides it via this header */ +#include /* resolves to this gnulib shim on incomplete platforms */ + +struct stat sb; +if (stat("/etc/hosts", &sb) == 0) { + /* sb.st_size, sb.st_mtime, etc. are reliably available */ +} +``` + +```c +/* Replacement function pattern used internally by gnulib */ +#if GNULIB_STAT +# if REPLACE_STAT +# undef stat +# define stat rpl_stat /* _GL_FUNCDECL_RPL routes here */ +# endif +_GL_CXXALIAS_SYS(stat, int, (const char *path, struct stat *buf)); +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. It is part of the gnulib portability layer used by the Flamingo/OpenFrame build toolchain to ensure consistent POSIX `stat` semantics across Linux, macOS, and Windows (MinGW/MSVC) targets. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.time.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.time.md new file mode 100644 index 00000000000..b30579d3686 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.time.md @@ -0,0 +1,45 @@ + +A gnulib-generated compatibility header that supplements the system's `sys/time.h`, providing portable time-related declarations and C++ namespace aliases across platforms (Linux, Cygwin, Windows/MSVC, BSD variants). + +## Key Components + +### Include Guards & Platform Handling +- `_GL_SYS_TIME_H` — primary double-inclusion guard with split logic to handle recursive includes on Cygwin/BSD +- Delegates to system `` via `include_next`, falling back to `` if unavailable +- Windows/MSVC path includes `` for `struct timeval` + +### C++ Declaration Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the system function with C linkage | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants for mismatched C declarations | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations in `GNULIB_NAMESPACE` when defined | + +## Usage Example + +```c +/* Using gnulib's gettimeofday replacement transparently */ +#include /* resolves to this gnulib header on patched builds */ + +int main(void) { + struct timeval tv; + gettimeofday(&tv, NULL); /* routes to rpl_gettimeofday if REPLACE_GETTIMEOFDAY=1 */ + return 0; +} +``` + +```c +/* In C++ with GNULIB_NAMESPACE defined */ +#define GNULIB_NAMESPACE gnulib +#include + +// Calls system or replacement via namespace alias +gnulib::gettimeofday(&tv, NULL); +``` + +> **Note:** This file is auto-generated by gnulib — do not edit directly. Modify the gnulib module configuration and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.types.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.types.md new file mode 100644 index 00000000000..4c4c94fa903 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.types.md @@ -0,0 +1,29 @@ + +Auto-generated gnulib compatibility header that supplements the system `sys/types.h` with portable type definitions, primarily targeting Windows/MinGW environments and Large File Support (LFS) scenarios. + +## Key Components + +| Component | Purpose | +|---|---| +| `off_t` override | Redefines `off_t` as a 64-bit integer (`__int64` or `long long`) when LFS is requested on native Windows | +| `dev_t` override | Optionally redefines `dev_t` as `unsigned long long int` for Windows distinguishable inode support | +| `ino_t` override | Optionally redefines `ino_t` as 64-bit or 128-bit type (`rpl_ino_t`) for Windows inode support | +| `_GL_WINDOWS_64_BIT_OFF_T` | Internal gnulib indicator flagging 64-bit `off_t` override is active | +| `_GL_WINDOWS_STAT_INODES` | Internal gnulib indicator for Windows inode mode level | +| `stddef.h` include | Pulls in `size_t` on non-Cygwin Windows where MSVC defines it outside `sys/types.h` | + +## Usage Example + +This file is not used directly — it is automatically picked up via `include_next` wrapping: + +```c +/* gnulib wraps the system header transparently */ +#include + +/* After inclusion, portable types are available: */ +off_t file_offset = 0; /* 64-bit on Windows with LFS enabled */ +ssize_t bytes_read = -1; +size_t buffer_len = 1024; +``` + +> **Note:** This file is auto-generated by gnulib and should not be edited manually. Conditional compilation blocks (`#if 0`) are intentionally disabled — enable them by changing the condition at the gnulib configuration level, not by editing this file directly. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.wait.md b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.wait.md new file mode 100644 index 00000000000..803657d6ceb --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/linux/x86_64/lib/sys/.wait.md @@ -0,0 +1,47 @@ + +Auto-generated POSIX-compatible `` portability shim from GNU gnulib, providing cross-platform process wait status macros and C++/namespace compatibility infrastructure for non-POSIX systems (primarily Windows). + +## Key Components + +### Guard Macros & Platform Detection +- `_GL_SYS_WAIT_H` — double-inclusion guard with split structure to support `include_next` +- Win32/Cygwin detection — skips `include_next ` on native Windows where the header doesn't exist + +### C++ Namespace Infrastructure (`_GL_CXXDEFS_H`) +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in `GNULIB_NAMESPACE` when building under C++ +- `_GL_EXTERN_C` — emits `extern "C"` in C++ builds, plain `extern` in C + +### Function Declaration Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, rettype, params)` | Declares a gnulib replacement as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, rettype, params)` | Declares the system-provided function | + +### C++ Alias Macros +| Macro | Purpose | +|---|---| +| `_GL_CXXALIAS_RPL` | Aliases `GNULIB_NAMESPACE::func` → `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Same, with `reinterpret_cast` for signature mismatches | +| `_GL_CXXALIAS_SYS` | Aliases `GNULIB_NAMESPACE::func` → system `func` | +| `_GL_CXXALIAS_SYS_CAST` | Same, with cast for slight signature differences | +| `_GL_CXXALIAS_SYS_CAST2` | Selects among overloaded C functions via double cast | + +## Usage Example + +```c +/* Typical gnulib module pattern using these macros */ +#if GNULIB_WAITPID +# if REPLACE_WAITPID +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef waitpid +# define waitpid rpl_waitpid +# endif + _GL_FUNCDECL_RPL (waitpid, pid_t, (pid_t pid, int *statusp, int opts)); + _GL_CXXALIAS_RPL (waitpid, pid_t, (pid_t pid, int *statusp, int opts)); +# else + _GL_CXXALIAS_SYS (waitpid, pid_t, (pid_t pid, int *statusp, int opts)); +# endif +#endif +``` + +> **Note:** This file is auto-generated by gnulib — do not edit manually. Modify the gnulib module configuration and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/config/.config.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/config/.config.md new file mode 100644 index 00000000000..6eeab658e31 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/config/.config.md @@ -0,0 +1,47 @@ + +Auto-generated build configuration header produced by `autoconf`/`autoheader` for the Augeas library, capturing platform capabilities, feature flags, and gnulib module availability detected at configure time. + +## Key Components + +### Build & Debug Flags +- `ENABLE_DEBUG` — Enables debug mode when set to `1` +- `_FORTIFY_SOURCE` — Conditionally enables glibc bounds-checking when optimizations are active + +### Platform/ABI Settings +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — Double-precision float exponent bit layout +- `FLEXIBLE_ARRAY_MEMBER` — Struct flexible array member compatibility across C standards +- `GETTIMEOFDAY_TIMEZONE` / `GETTIMEOFDAY_CLOBBERS_LOCALTIME` — Platform-specific `gettimeofday` behavior + +### Feature Detection (`HAVE_*`) +Presence flags for system headers and functions including `alloca`, `arpa/inet.h`, `btowc`, `catgets`, `CFLocaleCopyCurrent`, and many more POSIX/libc symbols. + +### Gnulib Module Flags (`GNULIB_*` / `GNULIB_TEST_*`) +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_FSCANF`, `GNULIB_LOCK`, `GNULIB_SCANF` — Mark gnulib replacement modules as active +- `GNULIB_TEST_*` — Enable test coverage for individual gnulib portability shims (e.g., `GNULIB_TEST_REALPATH`, `GNULIB_TEST_STRERROR`) + +### Known Bug Workarounds +- `FUNC_MKDIR_DOT_BUG` — Flags a `mkdir` trailing-dot bug +- `FUNC_NL_LANGINFO_YESEXPR_WORKS` — Confirms `nl_langinfo(YESEXPR)` is functional +- `C_LOCALE_MAYBE_EILSEQ` — Signals possible encoding errors in the C locale + +## Usage Example + +```c +#include "config.h" + +#if ENABLE_DEBUG + fprintf(stderr, "[debug] Starting initialization\n"); +#endif + +#ifdef HAVE_ALLOCA_H + #include +#endif + +/* Use flexible array member portably */ +struct buffer { + size_t len; + char data[FLEXIBLE_ARRAY_MEMBER]; +}; +``` + +> **Note:** This file is auto-generated by `./configure` — do not edit manually. Regenerate by running `autoconf` followed by `./configure` with the appropriate flags. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.alloca.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.alloca.md new file mode 100644 index 00000000000..51b6c2ef741 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.alloca.md @@ -0,0 +1,40 @@ + +Portable compatibility header that provides a unified `alloca` macro for stack-based memory allocation across multiple compilers and platforms. + +## Key Components + +- **`alloca(N)` macro** — Allocates `N` bytes on the current stack frame, automatically freed when the enclosing function returns. Mapped to the appropriate compiler/platform intrinsic: + +| Platform/Compiler | Mapping | +|---|---| +| GCC / MinGW | `__builtin_alloca` | +| AIX | `__alloca` | +| MSVC | `_alloca` (via ``) | +| DEC/VMS | `__ALLOCA` | +| Tandem TNS/E | `_alloca` (intrinsic) | +| MVS | via `` | +| Fallback | `void *alloca(size_t)` | + +## Usage Example + +```c +#include "alloca.h" +#include + +void process_data(size_t n) { + /* Allocate a temporary buffer on the stack */ + char *buf = (char *) alloca(n); + memset(buf, 0, n); + /* buf is automatically released when process_data() returns */ +} +``` + +## ⚠️ Known Limitations + +Avoid `alloca` in these scenarios: + +- **Function call arguments** — behavior is undefined +- **Inline functions** — allocation may outlive the intended scope, persisting until the *calling* function returns +- **Large allocations** (`N >= 65536`) — stack size is finite and platform-dependent; overflow causes a hard crash with no recoverable error + +> **Note:** This header is auto-generated. Do not edit manually. Guard symbol `_GL_ALLOCA_H` is intentionally distinct from `_ALLOCA_H` to avoid conflicts with Bison's alloca detection logic. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.argz.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.argz.md new file mode 100644 index 00000000000..5b9f1adfd07 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.argz.md @@ -0,0 +1,53 @@ + +GNU C Library header providing utilities for managing **argz vectors** — null-byte (`\0`) separated string arrays used for efficient argument list manipulation. + +## Key Components + +| Function | Description | +|---|---| +| `argz_create` | Build argz vector from a Unix `argv` array | +| `argz_create_sep` | Build argz vector from a delimiter-separated string | +| `argz_count` | Count the number of strings in an argz vector | +| `argz_extract` | Extract pointers from argz into a standard `argv` array | +| `argz_stringify` | Convert argz back to a printable string by replacing `\0` with a separator | +| `argz_append` | Append a raw buffer to an existing argz vector | +| `argz_add` | Append a single string to an argz vector | +| `argz_add_sep` | Append a delimited string list to an argz vector | +| `argz_delete` | Remove a specific entry from an argz vector | +| `argz_insert` | Insert an entry before a given position | +| `argz_replace` | Replace all occurrences of a string within an argz vector | +| `argz_next` | Iterate over entries in an argz vector | + +## Usage Example + +```c +#include "argz.h" +#include +#include + +int main(void) { + char *argz = NULL; + size_t argz_len = 0; + + /* Build argz from a colon-separated string */ + argz_create_sep("/usr/bin:/usr/local/bin:/opt/bin", ':', &argz, &argz_len); + + /* Iterate over entries */ + char *entry = NULL; + while ((entry = argz_next(argz, argz_len, entry))) { + printf("Path: %s\n", entry); + } + + /* Replace an entry */ + argz_replace(&argz, &argz_len, "/opt/bin", "/opt/custom/bin", NULL); + + free(argz); + return 0; +} +``` + +## Notes + +- All mutating functions may **reallocate** the argz buffer; always pass `&argz` and `&argz_len` by pointer. +- On memory allocation failure, functions return `ENOMEM`; always check return values. +- The vector is freed with standard `free()`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.ctype.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.ctype.md new file mode 100644 index 00000000000..e4f2e4e3dfd --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.ctype.md @@ -0,0 +1,42 @@ + +Auto-generated GNU Portability Library (gnulib) substitute header that extends the standard ISO C99 `` for platforms where the system implementation is incomplete or broken. + +## Key Components + +### Include Guard & System Header +- Uses a split double-inclusion guard pattern (`_GL_CTYPE_H`) to safely wrap `#include_next `, pulling in the platform's native ctype header before applying gnulib fixes. + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) +Core macro infrastructure for cross-platform function declarations: + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias redirecting to the replacement | +| `_GL_CXXALIAS_SYS` | Creates a C++ namespace alias redirecting to the system function | +| `_GL_CXXALIAS_SYS_CAST` | Same as `_GL_CXXALIAS_SYS` but suppresses type-mismatch errors via `reinterpret_cast` | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ contexts, plain `extern` otherwise | + +### GNULIB_NAMESPACE Support +When `GNULIB_NAMESPACE` is defined in C++ builds, replacement and system functions are wrapped in an inline conversion-operator struct, preventing symbol references unless the namespaced function is actually used. + +## Usage Example + +```c +/* Typical gnulib pattern for replacing a broken platform function */ +#if GNULIB_ISBLANK +# if REPLACE_ISBLANK +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef isblank +# define isblank rpl_isblank +# endif + _GL_FUNCDECL_RPL (isblank, int, (int c)); + _GL_CXXALIAS_RPL (isblank, int, (int c)); +# else + _GL_CXXALIAS_SYS (isblank, int, (int c)); +# endif +#endif +``` + +> **Note:** This file is auto-generated by gnulib — do not edit manually. Modifications should be made to the gnulib module templates and regenerated. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fcntl.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fcntl.md new file mode 100644 index 00000000000..c1111c9c205 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fcntl.md @@ -0,0 +1,42 @@ + +A gnulib-generated portable wrapper around the system `` that normalizes file control flags and function declarations across platforms, mapping unsupported flags to `0` and providing cross-platform C/C++ compatible declarations. + +## Key Components + +### Inclusion Guards & Platform Handling +- `_GL_FCNTL_H` — standard double-inclusion guard +- `__need_system_fcntl_h` — special invocation path for direct system header access +- Conditionally includes ``, ``, and `` (Windows) based on platform + +### C++/Namespace Compatibility Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress invalid-conversion errors | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes the `GNULIB_NAMESPACE` C++ namespace | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++ or `extern` in C | + +## Usage Example + +```c +/* Typical gnulib usage pattern for a portable open() replacement */ +#if GNULIB_OPEN +# if REPLACE_OPEN +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef open +# define open rpl_open +# endif + _GL_FUNCDECL_RPL(open, int, (const char *filename, int flags, ...) + _GL_ARG_NONNULL((1))); + _GL_CXXALIAS_RPL(open, int, (const char *filename, int flags, ...)); +# else + _GL_CXXALIAS_SYS(open, int, (const char *filename, int flags, ...)); +# endif + _GL_CXXALIASWARN(open); +#endif +``` + +> **Note:** This file is auto-generated by gnulib — do not edit manually. Unsupported `fcntl` flags on the target platform are defined as `0` to allow portable code to compile without `#ifdef` guards around every flag reference. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fnmatch.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fnmatch.md new file mode 100644 index 00000000000..358a930d4b4 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.fnmatch.md @@ -0,0 +1,49 @@ + +Auto-generated GNU portability wrapper that provides a cross-platform substitute for the standard `` header, part of the GNU C Library (gnulib) compatibility layer. + +## Key Components + +### Flag Constants +- `FNM_PATHNAME` — Slash must be matched by slash only +- `FNM_NOESCAPE` — Disable backslash escaping +- `FNM_PERIOD` — Period must be matched explicitly +- `FNM_CASEFOLD` — Case-insensitive matching (GNU extension) + +### Return Values +- `FNM_NOMATCH` — Pattern did not match the string + +### Core Function +- `fnmatch(pattern, string, flags)` — Matches a filename string against a shell-style wildcard pattern + +### C++ Compatibility Macros +- `_GL_FUNCDECL_RPL` / `_GL_FUNCDECL_SYS` — Declare replacement or system functions +- `_GL_CXXALIAS_RPL` / `_GL_CXXALIAS_SYS` — Create C++ namespace aliases for gnulib functions +- `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` — Cast variants to suppress type-conversion errors +- `GNULIB_NAMESPACE` — Optional namespace wrapper for C++ builds + +## Usage Example + +```c +#include "fnmatch.h" +#include + +int main(void) { + const char *pattern = "*.c"; + const char *filename = "main.c"; + + if (fnmatch(pattern, filename, FNM_PATHNAME) == 0) { + printf("Match: %s matches %s\n", filename, pattern); + } else { + printf("No match\n"); + } + + /* Case-insensitive match using GNU extension */ + if (fnmatch("*.H", "header.h", FNM_CASEFOLD) == 0) { + printf("Case-insensitive match succeeded\n"); + } + + return 0; +} +``` + +> **Note:** This header is auto-generated by gnulib's `gnulib-tool`. Do not edit manually — changes will be overwritten. It transparently falls back to the system `` when available, or provides a portable replacement implementation otherwise. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt-cdefs.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt-cdefs.md new file mode 100644 index 00000000000..cddd96e5293 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt-cdefs.md @@ -0,0 +1,35 @@ + +Compatibility header that provides fallback definitions for internal glibc macros required by the gnulib `getopt` implementation on non-glibc platforms. + +## Key Components + +| Macro | Purpose | +|---|---| +| `__BEGIN_DECLS` | Opens `extern "C"` linkage block for C++ compatibility | +| `__END_DECLS` | Closes `extern "C"` linkage block | +| `__GNUC_PREREQ(maj, min)` | Tests whether the GCC version meets a minimum requirement | +| `__THROW` | Annotates functions with `throw()` on qualifying C++ compilers | + +## Usage Example + +This header is **not meant to be included directly**. Use `getopt.h` or `unistd.h` instead: + +```c +/* Correct usage */ +#include + +int main(int argc, char *argv[]) { + int opt; + while ((opt = getopt(argc, argv, "hv")) != -1) { + switch (opt) { + case 'h': /* help */ break; + case 'v': /* verbose */ break; + } + } + return 0; +} +``` + +> **Note:** This file is auto-generated (`DO NOT EDIT`) and is part of [gnulib](https://www.gnu.org/software/gnulib/). It is **not** shared with the GNU C Library, unlike most of the `getopt` implementation. + +The macros are defined only if not already provided — either by `` (included when available) or by the compiler/platform headers — making the definitions safe fallbacks on non-glibc systems such as macOS, musl libc, or MSVC environments. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt.md new file mode 100644 index 00000000000..17acadf82a0 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.getopt.md @@ -0,0 +1,41 @@ + +Auto-generated gnulib replacement header for POSIX `getopt` that provides portable command-line option parsing declarations, bridging system and gnulib implementations. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `_GL_GETOPT_H` | Guard macro | Double-inclusion guard for this header | +| `_GL_SYSTEM_GETOPT` | Internal macro | Prevents recursive inclusion when forwarding to system `` | +| `__GETOPT_PREFIX` | Config macro | Optional prefix for external symbols in standalone applications | +| `_GL_ARG_NONNULL(params)` | Compiler macro | Marks function arguments as non-NULL using GCC's `__nonnull__` attribute | +| `getopt-cdefs.h` | Include | Core type/constant definitions | +| `getopt-pfx-core.h` | Include | Core `getopt` function declarations | +| `getopt-pfx-ext.h` | Include | Extended `getopt_long` / `getopt_long_only` declarations | + +## Usage Example + +```c +#include + +int main(int argc, char *argv[]) { + int opt; + /* Short options: -v (flag), -o (requires argument) */ + while ((opt = getopt(argc, argv, "vo:")) != -1) { + switch (opt) { + case 'v': + verbose = 1; + break; + case 'o': + output = optarg; /* optarg points to the option argument */ + break; + default: + fprintf(stderr, "Usage: %s [-v] [-o output]\n", argv[0]); + return 1; + } + } + return 0; +} +``` + +> **Note:** This header is auto-generated by gnulib and should not be edited manually. On systems with a native ``, it transparently forwards to that header via `include_next` before layering gnulib's portable declarations. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.langinfo.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.langinfo.md new file mode 100644 index 00000000000..c8b1855c379 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.langinfo.md @@ -0,0 +1,50 @@ + +A GNUlib-generated portability wrapper that substitutes for and extends the POSIX `` header on platforms where it is missing or incomplete. + +## Key Components + +### Type Definition +- **`nl_item`** — Defined as `int` on platforms lacking ``, used as the argument type for `nl_langinfo()`. + +### Locale Category Constants (`nl_item` values) + +| Category | Constants | +|---|---| +| `LC_CTYPE` | `CODESET` | +| `LC_NUMERIC` | `RADIXCHAR`, `DECIMAL_POINT`, `THOUSEP`, `THOUSANDS_SEP`, `GROUPING` | +| `LC_TIME` | `D_T_FMT`, `D_FMT`, `T_FMT`, `T_FMT_AMPM`, `AM_STR`/`PM_STR`, `DAY_1`–`DAY_7`, `ABDAY_1`–`ABDAY_7`, `MON_1`–`MON_12`, `ALTMON_1`–`ALTMON_12`, `ABMON_1`–`ABMON_12`, `ERA*`, `ALT_DIGITS` | +| `LC_MONETARY` | `CRNCYSTR`, `CURRENCY_SYMBOL`, `INT_CURR_SYMBOL`, `MON_DECIMAL_POINT`, `MON_THOUSANDS_SEP`, `MON_GROUPING`, `POSITIVE_SIGN`, `NEGATIVE_SIGN`, `FRAC_DIGITS`, `INT_FRAC_DIGITS`, `P/N_CS_PRECEDES`, `P/N_SEP_BY_SPACE`, `P/N_SIGN_POSN` | +| `LC_MESSAGES` | `YESEXPR`, `NOEXPR` | + +### C++ Interop Macros +- **`_GL_FUNCDECL_RPL`** / **`_GL_FUNCDECL_SYS`** — Declare replacement or system functions with proper C linkage. +- **`_GL_CXXALIAS_RPL`** / **`_GL_CXXALIAS_SYS`** — Create `GNULIB_NAMESPACE`-scoped C++ aliases for wrapped functions. +- **`_GL_BEGIN_NAMESPACE`** / **`_GL_END_NAMESPACE`** — Bracket declarations inside the optional `GNULIB_NAMESPACE`. + +## Usage Example + +```c +#include "langinfo.h" +#include +#include + +int main(void) { + setlocale(LC_ALL, ""); + + /* Query the locale's character encoding */ + printf("Codeset: %s\n", nl_langinfo(CODESET)); + + /* Query the decimal point character */ + printf("Decimal point: %s\n", nl_langinfo(DECIMAL_POINT)); + + /* Query abbreviated day names */ + printf("Monday abbrev: %s\n", nl_langinfo(ABDAY_2)); + + /* Query alternate (standalone) month name */ + printf("January (alt): %s\n", nl_langinfo(ALTMON_1)); + + return 0; +} +``` + +> **Note:** This header is auto-generated by GNUlib. Do not edit it directly. Constants are only defined when the underlying platform's `` does not already provide them, preventing redefinition conflicts. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.limits.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.limits.md new file mode 100644 index 00000000000..24574e77c25 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.limits.md @@ -0,0 +1,50 @@ + +Auto-generated GNU gnulib replacement for the system ``, extending it with portable definitions for `long long` integer bounds and ISO/IEC TS 18661-1 width macros on platforms where they may be missing. + +## Key Components + +### `long long` Bounds Portability +Conditionally defines missing standard macros by mapping from platform-specific variants: + +| Macro | Description | +|---|---| +| `LLONG_MIN` | Minimum `long long` value | +| `LLONG_MAX` | Maximum `long long` value | +| `ULLONG_MAX` | Maximum `unsigned long long` value | + +Handles HP-UX 11.31 (`LONG_LONG_*`), IRIX 6.5 (`LONGLONG_*`), and GCC (`__LONG_LONG_MAX__`). + +### Integer Width Bit-Counting Macros +A chain of preprocessor macros computing usable bit width from min/max values at compile time: + +| Macro | Purpose | +|---|---| +| `_GL_INTEGER_WIDTH(min, max)` | Top-level width calculator for any integer type | +| `_GL_COB128` → `_GL_COB4` | Hierarchical bit-counting helpers (count-of-bits) | + +### Platform Width Constants +- `WORD_BIT` — defaults to `32` if undefined +- `LONG_BIT` — set to `32` or `64` based on `LONG_MAX` vs `INT_MAX` + +### ISO/IEC TS 18661-1 Width Macros +Defined when `ULLONG_WIDTH` is absent and `_GNU_SOURCE` or `__STDC_WANT_IEC_60559_BFP_EXT__` is active: +`CHAR_WIDTH`, `SCHAR_WIDTH`, `UCHAR_WIDTH`, `SHRT_WIDTH`, `USHRT_WIDTH`, `INT_WIDTH`, `UINT_WIDTH`, `LONG_WIDTH`, `ULONG_WIDTH`, `LLONG_WIDTH`, `ULLONG_WIDTH` + +## Usage Example + +```c +/* Typical use: just include limits.h; gnulib fills in the gaps */ +#include + +/* Now safely portable across HP-UX, IRIX, GCC environments */ +long long min = LLONG_MIN; /* e.g. -9223372036854775808 */ +long long max = LLONG_MAX; /* e.g. 9223372036854775807 */ + +/* Compute bit width of int at preprocessing time */ +#define MY_INT_BITS _GL_INTEGER_WIDTH(INT_MIN, INT_MAX) /* 32 */ + +/* IEC 60559 width macros (requires _GNU_SOURCE) */ +#ifdef _GNU_SOURCE + int w = INT_WIDTH; /* 32 on most platforms */ +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.locale.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.locale.md new file mode 100644 index 00000000000..ad871af4fa7 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.locale.md @@ -0,0 +1,52 @@ + +Auto-generated POSIX-compliant `locale.h` header from GNU gnulib, providing cross-platform locale support with C++ namespace compatibility macros and replacement function infrastructure. + +## Key Components + +### Include Guards & Platform Handling +- `_GL_LOCALE_H` — Main double-inclusion guard +- `_GL_ALREADY_INCLUDING_LOCALE_H` — Prevents recursive inclusion on Solaris/mingw +- Conditionally includes `` for `locale_t` type (Mac OS X 10.5+) + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | +| `_GL_EXTERN_C` | Declares symbols with C linkage in C++ contexts | +| `_GL_FUNCDECL_RPL(func, rettype, params)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, rettype, params)` | Declares the native system function | +| `_GL_CXXALIAS_RPL(func, rettype, params)` | Creates a C++ alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, rettype, params)` | Creates a C++ alias redirecting to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to silence invalid conversion errors | + +## Usage Example + +```c +/* Using gnulib locale replacement pattern */ + +#include "locale.h" + +/* Example: replacement function declaration for a locale utility */ +#if GNULIB_NEWLOCALE +# if REPLACE_NEWLOCALE +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef newlocale +# define newlocale rpl_newlocale +# endif +_GL_FUNCDECL_RPL(newlocale, locale_t, + (int category_mask, const char *locale, locale_t base)); +_GL_CXXALIAS_RPL(newlocale, locale_t, + (int category_mask, const char *locale, locale_t base)); +# else +_GL_CXXALIAS_SYS(newlocale, locale_t, + (int category_mask, const char *locale, locale_t base)); +# endif +#endif +``` + +## Notes + +- **Do not edit** — file is auto-generated by gnulib's build system +- Licensed under **GNU LGPL v2.1+** +- Wraps the system `` via `#include_next`, adding portability shims for platforms where locale functions are missing or broken \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdint.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdint.md new file mode 100644 index 00000000000..e2be3063f88 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdint.md @@ -0,0 +1,64 @@ + +A gnulib-generated portable replacement for ISO C99 ``, providing fixed-width integer types and their limits on platforms that lack a native or complete implementation. + +## Key Components + +### Exact-Width Integer Types (7.18.1.1) +| Type | Description | +|------|-------------| +| `int8_t` / `uint8_t` | 8-bit signed/unsigned | +| `int16_t` / `uint16_t` | 16-bit signed/unsigned | +| `int32_t` / `uint32_t` | 32-bit signed/unsigned | +| `int64_t` / `uint64_t` | 64-bit signed/unsigned (when available) | + +### Minimum-Width Types (7.18.1.2) +`int_least8_t`, `uint_least8_t`, `int_least16_t`, `uint_least16_t`, `int_least32_t`, `uint_least32_t`, `int_least64_t`, `uint_least64_t` + +### Fast Minimum-Width Types (7.18.1.3) +`int_fast8_t`, `uint_fast8_t`, `int_fast16_t`, `uint_fast16_t`, `int_fast32_t`, `uint_fast32_t`, `int_fast64_t`, `uint_fast64_t` + +> **Note:** Fast types may differ across compilers. Avoid using them in public interfaces. + +### Pointer-Sized Types (7.18.1.4) +`intptr_t`, `uintptr_t` — Integer types wide enough to hold a pointer. + +### Greatest-Width Types (7.18.1.5) +`intmax_t`, `uintmax_t` — Largest available signed/unsigned integer types. + +### Limit Macros (7.18.2) +`INT8_MIN`, `INT8_MAX`, `UINT8_MAX`, `INT16_MIN`, `INT16_MAX`, `UINT16_MAX`, and equivalents for 32/64-bit ranges. + +### Internal Helpers +`_STDINT_MAX`, `_STDINT_SIGNED_MIN`, `_STDINT_UNSIGNED_MIN` — Preprocessor macros for computing type limits portably. + +--- + +## Usage Example + +```c +#include "stdint.h" + +int main(void) { + int8_t byte_val = INT8_MAX; /* 127 */ + uint32_t counter = UINT32_MAX; /* 4294967295 */ + int64_t timestamp = INT64_MIN; /* -9223372036854775808 */ + intptr_t ptr_sized = (intptr_t) &byte_val; /* pointer-width integer */ + + /* Safe fixed-width arithmetic */ + uint16_t a = 1000, b = 2000; + uint16_t sum = (uint16_t)(a + b); /* 3000, guaranteed 16-bit */ + + return 0; +} +``` + +### Platform Behavior + +```text +Platform int64_t backing type +───────────────────────────────────── +Linux/macOS long long int +Windows MSVC __int64 +64-bit LP64 long int (if LONG_MAX covers 63 bits) +No 64-bit HW int64_t undefined / omitted +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdio.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdio.md new file mode 100644 index 00000000000..90038d60d69 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdio.md @@ -0,0 +1,56 @@ + +A GNU-compatible replacement/extension of the standard `` header, auto-generated by gnulib to provide portable, POSIX-compliant stdio declarations across platforms (Linux, macOS, Windows/MSVC, Solaris, Android, etc.). + +## Key Components + +### Inclusion Guards & Split-Include Logic +- `_GL_STDIO_H` / `_GL_ALREADY_INCLUDING_STDIO_H` — prevents recursive inclusion and handles nested include edge cases (e.g., OSF/1 chains) +- `#include_next ` — delegates to the system stdio, then augments it + +### Format Attribute Macros +| Macro | Purpose | +|---|---| +| `_GL_ATTRIBUTE_FORMAT` | Base GCC `__format__` wrapper | +| `_GL_ATTRIBUTE_FORMAT_PRINTF` | Marks ISO C99/POSIX printf-style functions | +| `_GL_ATTRIBUTE_FORMAT_PRINTF_SYSTEM` | Marks system-native printf-style functions | +| `_GL_ATTRIBUTE_FORMAT_SCANF` | Marks ISO C99/POSIX scanf-style functions | +| `_GL_ATTRIBUTE_FORMAT_SCANF_SYSTEM` | Marks system-native scanf-style functions | + +### C++ Namespace / Replacement Declaration Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Same as above but allows casting for signature mismatches | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++, plain `extern` in C | + +### Platform-Specific Includes +Conditionally pulls in ``, ``, ``, or `` on Solaris, Android, and MSVC to surface declarations (e.g., `renameat`, `perror`, `remove`) that those platforms place outside ``. + +## Usage Example + +```c +/* This header is consumed automatically — include it like standard stdio */ +#include + +/* gnulib overrides become transparent; use standard calls normally */ +int main(void) { + printf("Hello, portable world!\n"); + + FILE *f = fopen("data.txt", "r"); + if (!f) { + perror("fopen"); /* works correctly on MSVC via gnulib shim */ + return 1; + } + + char buf[256]; + while (fgets(buf, sizeof(buf), f)) + fputs(buf, stdout); + + fclose(f); + return 0; +} +``` + +> **Note:** This file is auto-generated — do not edit directly. Modify the gnulib source templates and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdlib.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdlib.md new file mode 100644 index 00000000000..d204bc9a188 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.stdlib.md @@ -0,0 +1,49 @@ + +A GNU-compatible replacement/extension of the standard C ``, auto-generated by gnulib to provide portable, POSIX-compliant standard library declarations across platforms where the system's native `stdlib.h` is missing, broken, or incomplete. + +## Key Components + +### Portability Guards +- `_GL_STDLIB_H` — split double-inclusion guard supporting `include_next` chaining +- `__need_system_stdlib_h` / `__need_malloc_and_calloc` — special invocation bypass modes for internal gnulib/glibc use + +### Compiler Attribute Macros +- `_GL_ATTRIBUTE_PURE` — maps to `__attribute__((__pure__))` on GCC ≥ 2.96, empty otherwise +- `_Noreturn` — portable no-return annotation across C11, C++11, GCC, MSVC, and Sun Studio + +### Function Declaration Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declare a gnulib replacement as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declare the system function with C linkage | +| `_GL_CXXALIAS_RPL(func, ret, params)` | C++ namespace alias redirecting to `rpl_func` | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | + +### Platform-Specific Includes +Conditionally pulls in ``, ``, ``, ``, and `` based on target platform to ensure declarations like `mkstemp`, `mkstemps`, `getsubopt`, and `WEXITSTATUS` are available. + +### `struct random_data` +Conditionally defined when the platform lacks it, providing the reentrant random number generator state struct used by `initstate_r` / `random_r`. + +## Usage Example + +```c +/* gnulib-generated stdlib.h is used transparently via include_next. + Consumer code simply includes stdlib.h as normal: */ +#include + +/* Portable no-return function using the gnulib-defined _Noreturn: */ +_Noreturn void fatal_error(const char *msg) { + fprintf(stderr, "Fatal: %s\n", msg); + exit(EXIT_FAILURE); +} + +/* Replacement functions (e.g. rpl_mkstemp) are aliased automatically + when GNULIB_NAMESPACE is defined in C++ builds: */ +#ifdef __cplusplus +#define GNULIB_NAMESPACE gnulib +// gnulib::mkstemp(...) now resolves to rpl_mkstemp if replacement is active +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modify the gnulib source templates and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.string.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.string.md new file mode 100644 index 00000000000..0c94db2be6c --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.string.md @@ -0,0 +1,49 @@ + +A GNU-compatible `` replacement header, auto-generated by Gnulib, that provides portable string function declarations, C++ namespace aliases, and cross-platform compatibility shims for systems where the system `` is missing or non-conformant. + +## Key Components + +### Include Guard & Nesting Protection +- `_GL_STRING_H` — primary include guard +- `_GL_ALREADY_INCLUDING_STRING_H` — handles nested include chains (e.g., OS X/NetBSD: `` → `` → `"string.h"`) +- Uses `#include_next ` to chain to the system header + +### C++ Namespace Support (`_GL_CXXDEFS_H`) +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in `GNULIB_NAMESPACE` when building with C++ +- `_GL_EXTERN_C` — applies `extern "C"` linkage in C++ builds + +### Function Declaration Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a Gnulib replacement (`rpl_func`) | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the system function as-is | +| `_GL_CXXALIAS_RPL(func, ...)` | C++ alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | C++ alias redirecting to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Variants using `reinterpret_cast` to silence type mismatch errors | + +### Attributes +- `_GL_ATTRIBUTE_PURE` — maps to `__attribute__((__pure__))` on GCC ≥ 2.96, empty otherwise + +## Usage Example + +```c +/* Typical Gnulib usage pattern for a portable function replacement */ + +#if GNULIB_STRDUP +# if REPLACE_STRDUP +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strdup +# define strdup rpl_strdup +# endif + _GL_FUNCDECL_RPL (strdup, char *, (const char *s)); + _GL_CXXALIAS_RPL (strdup, char *, (const char *s)); +# else +# if !HAVE_DECL_STRDUP + _GL_FUNCDECL_SYS (strdup, char *, (const char *s)); +# endif + _GL_CXXALIAS_SYS (strdup, char *, (const char *s)); +# endif +#endif +``` + +> **Note:** This file is auto-generated — do not edit directly. Modifications should be made to the Gnulib source templates and regenerated. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.time.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.time.md new file mode 100644 index 00000000000..addf99d073d --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.time.md @@ -0,0 +1,50 @@ + +A gnulib-generated compatibility shim that augments the system `` with more-standard POSIX declarations, portability fixes, and optional C++ namespace aliasing via the gnulib macro framework. + +## Key Components + +### Guard Macros +- `_GL_TIME_H` — prevents recursive inclusion; delegates to system `` when only partial declarations (e.g., `__need_time_t`) are needed +- `_GL_CXXDEFS_H` — one-time guard for the C++/gnulib declaration helper macros + +### Function Declaration Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares `rpl_func` replacement with given signature | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the original system function | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ builds, plain `extern` otherwise | + +### C++ Namespace Alias Macros +| Macro | Purpose | +|---|---| +| `_GL_CXXALIAS_RPL` | Aliases `GNULIB_NAMESPACE::func` → `rpl_func` | +| `_GL_CXXALIAS_SYS` | Aliases `GNULIB_NAMESPACE::func` → system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress C++ conversion errors when signatures differ slightly | + +## Usage Example + +```c +/* Standard C usage — include normally; gnulib transparently fills gaps */ +#include + +int main(void) { + struct timespec ts; + /* clock_gettime declared/replaced by gnulib if missing on the platform */ + clock_gettime(CLOCK_REALTIME, &ts); + return 0; +} +``` + +```cpp +// C++ with GNULIB_NAMESPACE: access fixed functions via namespace +#define GNULIB_NAMESPACE gnulib +#include + +void example() { + struct timespec ts; + // Calls rpl_clock_gettime or system clock_gettime via alias + gnulib::clock_gettime(CLOCK_REALTIME, &ts); +} +``` + +> **Note:** This file is auto-generated by gnulib — do not edit manually. Portability behaviour is controlled by `./configure`-time substitution variables (e.g., `REPLACE_GMTIME`, `HAVE_NANOSLEEP`). \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.unistd.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.unistd.md new file mode 100644 index 00000000000..a57b5196e77 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.unistd.md @@ -0,0 +1,32 @@ + +A generated gnulib wrapper and portability shim for the standard POSIX `` header, providing cross-platform function declarations, C++ namespace aliases, and replacement function scaffolding for systems where the native header is missing, incomplete, or broken. + +## Key Components + +- **Double-inclusion guard** — Uses `_GL_UNISTD_H` with a split guard pattern to handle nested include chains (e.g., macOS 10.3.9's `` → `` → `` → `` cycle) +- **Platform-specific includes** — Conditionally pulls in ``, ``, ``, ``, ``, ``, ``, and `` to patch gaps on MinGW, MSVC, Cygwin, Android, AIX, and others +- **`_GL_FUNCDECL_RPL`** — Macro to declare a gnulib replacement function named `rpl_` with a given return type and parameter list +- **`_GL_FUNCDECL_SYS`** — Macro to declare the original system function with a portable prototype +- **`_GL_CXXALIAS_RPL` / `_GL_CXXALIAS_RPL_CAST_1`** — Macros that inject zero-overhead C++ wrapper objects into `GNULIB_NAMESPACE` so replacement functions are accessible via the namespace without unconditional symbol references +- **`_GL_EXTERN_C`** — Expands to `extern "C"` in C++ builds, plain `extern` otherwise + +## Usage Example + +```c +/* Typical gnulib pattern for replacing a broken system function */ + +#if GNULIB_CHDIR +# if REPLACE_CHDIR +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef chdir +# define chdir rpl_chdir +# endif +_GL_FUNCDECL_RPL (chdir, int, (const char *path)); +_GL_CXXALIAS_RPL (chdir, int, (const char *path)); +# else +_GL_FUNCDECL_SYS (chdir, int, (const char *path)); +# endif +#endif +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit directly — regenerate via `gnulib-tool` when updating the gnulib module set. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wchar.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wchar.md new file mode 100644 index 00000000000..c514e59cee4 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wchar.md @@ -0,0 +1,41 @@ + +Auto-generated substitute for ISO C99 ``, part of the GNU portability library (gnulib). It ensures correct prerequisite inclusion order and provides portable wide-character function declarations across platforms with known `` deficiencies. + +## Key Components + +### Inclusion Guards & Platform Handling +- `_GL_WCHAR_H` — primary double-inclusion guard +- `_GL_ALREADY_INCLUDING_WCHAR_H` — prevents recursive inclusion loops on HP-UX, IRIX, MinGW, and uClibc +- Conditionally includes ``, ``, and `` before the system `` to satisfy platform-specific ordering bugs (Tru64, BSD/OS 4.0.1) + +### Function Declaration Macros (`_GL_CXXDEFS_H`) +- `_GL_FUNCDECL_RPL(func, rettype, params)` — declares a replacement function as `rpl_func` +- `_GL_FUNCDECL_SYS(func, rettype, params)` — declares the original system function +- `_GL_CXXALIAS_RPL(func, rettype, params)` — creates a `GNULIB_NAMESPACE::func` C++ alias pointing to `rpl_func` +- `_GL_CXXALIAS_RPL_CAST_1(...)` — same as above with a `reinterpret_cast` to suppress type mismatch errors +- `_GL_CXXALIAS_SYS(func, rettype, params)` — creates a `GNULIB_NAMESPACE::func` alias to the system function +- `_GL_EXTERN_C` — expands to `extern "C"` in C++ or `extern` in C +- `_GL_ATTRIBUTE_PURE` — maps to `__attribute__((__pure__))` on GCC 2.96+ + +### Namespace Support +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in `GNULIB_NAMESPACE` when building under C++ + +## Usage Example + +```c +/* Typical gnulib module pattern using these macros */ + +#if GNULIB_WCWIDTH +# if REPLACE_WCWIDTH +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef wcwidth +# define wcwidth rpl_wcwidth +# endif + _GL_FUNCDECL_RPL (wcwidth, int, (wchar_t)); + _GL_CXXALIAS_RPL (wcwidth, int, (wchar_t)); +# else + _GL_FUNCDECL_SYS (wcwidth, int, (wchar_t)); + _GL_CXXALIAS_SYS (wcwidth, int, (wchar_t)); +# endif +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wctype.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wctype.md new file mode 100644 index 00000000000..af3cdc19d75 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/.wctype.md @@ -0,0 +1,40 @@ + +Auto-generated GNU portability substitute for ISO C99 ``, providing wide-character classification and conversion functions on platforms that lack a native implementation. + +## Key Components + +### Include Guards & Platform Handling +- `_GL_WCTYPE_H` — standard idempotency guard +- Special MinGW path via `__MINGW32__ && __CTYPE_H_SOURCED__` to handle partial inclusion from `` +- Prerequisite includes (``, ``, ``, ``) to work around known bugs on Solaris 2.5, Tru64, and BSD/OS 4.0.1 + +### C++/GNULIB Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the system-native function | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress C++ "invalid conversion" errors | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++, plain `extern` in C | + +### Inline Control +- `_GL_WCTYPE_INLINE` — expands to `_GL_INLINE` for portable inline function definitions + +## Usage Example + +```c +/* Including this header transparently replaces a missing system wctype.h */ +#include +#include /* resolves to this substitute on deficient platforms */ + +int main(void) { + wint_t ch = L'A'; + if (iswalpha(ch)) + putwchar(towlower(ch)); /* outputs 'a' */ + return 0; +} +``` + +> **Note:** `iswctype`, `towctrans`, `wctrans`, `wctype`, `wctrans_t`, and `wctype_t` are not yet implemented in this substitute. Requires `config.h` to be included before this header. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.context.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.context.md new file mode 100644 index 00000000000..fd2971d5d18 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.context.md @@ -0,0 +1,39 @@ + +Auto-generated SELinux context API stub header that provides no-op inline replacements for SELinux context functions when SELinux support is unavailable or disabled at build time. All functions set `errno = ENOTSUP` and return failure/null values. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `context_t` | `typedef int` | Opaque SELinux context handle type | +| `context_new()` | Inline function | Create a new context from a string — always fails | +| `context_str()` | Inline function | Convert context to string — always returns NULL | +| `context_free()` | Inline function | Free a context — no-op | +| `context_user_set/get()` | Inline functions | Get/set the user component of a context | +| `context_role_set/get()` | Inline functions | Get/set the role component of a context | +| `context_type_set/get()` | Inline functions | Get/set the type component of a context | +| `context_range_set/get()` | Inline functions | Get/set the range/MLS component of a context | +| `_GL_UNUSED_PARAMETER` | Macro | Suppresses `-Wunused-parameter` warnings (GCC 2.7+) | + +## Usage Example + +```c +#include "config.h" /* Must be included first */ +#include "context.h" + +void example(void) { + /* On systems without SELinux, all calls fail with ENOTSUP */ + context_t ctx = context_new("system_u:object_r:tmp_t:s0"); + if (ctx == 0) { + /* errno == ENOTSUP — SELinux not supported */ + return; + } + + context_type_set(ctx, "httpd_t"); + char *type = context_type_get(ctx); + + context_free(ctx); +} +``` + +> **Note:** This header is auto-generated and must not be edited manually. It acts as a portability shim — on systems with real SELinux support, the actual `libselinux` implementation replaces these stubs. Always include `config.h` before this header. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.selinux.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.selinux.md new file mode 100644 index 00000000000..787fb2c1162 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/selinux/.selinux.md @@ -0,0 +1,52 @@ + +A auto-generated stub header providing a no-op replacement for `` on platforms where SELinux is unavailable, part of the GNU gnulib portability layer. + +## Key Components + +### Types +- `security_class_t` — Typedef for `unsigned short` representing a security class +- `security_context_t` — Typedef for `char *` representing a security context string + +### Macros +- `is_selinux_enabled()` — Always returns `0` (SELinux disabled) +- `_GL_UNUSED_PARAMETER` — Suppresses unused-parameter compiler warnings +- `SE_SELINUX_INLINE` — Inline hint mapped to `_GL_INLINE` + +### Stub Functions (all return `-1` with `errno = ENOTSUP`) + +| Function | Purpose | +|---|---| +| `getcon` / `freecon` | Get/free process security context | +| `getfscreatecon` / `setfscreatecon` | Get/set filesystem creation context | +| `getfilecon` / `lgetfilecon` / `fgetfilecon` | Get file security context (path/symlink/fd) | +| `setfilecon` / `lsetfilecon` / `fsetfilecon` | Set file security context (path/symlink/fd) | +| `matchpathcon` | Match path to default context | +| `matchpathcon_init_prefix` | Initialize path context matching | +| `security_check_context` / `security_check_context_raw` | Validate a security context string | +| `setexeccon` | Set exec security context | +| `security_compute_create` | Compute a new security context | +| `string_to_security_class` | Convert string to security class (returns `0`) | + +## Usage Example + +```c +#include "selinux.h" + +int main(void) { + /* On non-SELinux platforms, is_selinux_enabled() always returns 0 */ + if (!is_selinux_enabled()) { + return 0; /* SELinux not active, skip enforcement */ + } + + security_context_t ctx = NULL; + if (getcon(&ctx) == -1) { + /* errno == ENOTSUP on stub platforms */ + perror("getcon"); + return 1; + } + freecon(ctx); + return 0; +} +``` + +> **Note:** When `HAVE_SELINUX_SELINUX_H` is defined at build time, this stub is bypassed entirely via `#include_next` and the real system SELinux header is used instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.stat.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.stat.md new file mode 100644 index 00000000000..08e728875db --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.stat.md @@ -0,0 +1,49 @@ + +A gnulib-generated compatibility shim that extends the system `` header with missing declarations, POSIX-compliant prototypes, and portable file status macros for platforms where the native header is incomplete. + +## Key Components + +### Include Guard & Split Inclusion +- `_GL_SYS_STAT_H` — double-inclusion guard supporting `include_next` chaining +- `__need_system_sys_stat_h` — special invocation path for raw system header access +- Pulls in `` (for `nlink_t`, `off_t`) and `` (for `struct timespec`) + +### C++/Namespace Compatibility Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a gnulib replacement (`rpl_func`) | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the system function with given prototype | +| `_GL_CXXALIAS_RPL(func, ...)` | Creates `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | Creates `GNULIB_NAMESPACE::func` alias → system `func` | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants for slightly mismatched signatures | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations in `GNULIB_NAMESPACE` when defined | + +## Usage Example + +```c +/* Using gnulib's portable stat wrapper in a project */ +#include /* resolves to this shim on incomplete platforms */ + +int check_file(const char *path) { + struct stat st; + + /* Works portably — gnulib backfills missing prototypes/macros */ + if (stat(path, &st) == 0) { + if (S_ISREG(st.st_mode)) { + return 1; /* regular file */ + } + } + return 0; +} +``` + +```c +/* With GNULIB_NAMESPACE in C++ */ +#define GNULIB_NAMESPACE gnulib +#include + +// Calls rpl_stat or system stat via gnulib::stat alias +gnulib::stat(path, &st); +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit manually — regenerate via `gnulib-tool` when updating module dependencies. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.time.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.time.md new file mode 100644 index 00000000000..49e295f3761 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.time.md @@ -0,0 +1,33 @@ + +A gnulib-generated compatibility header that augments or replaces the system `sys/time.h`, providing a more complete and portable `gettimeofday` interface across platforms (Linux, BSD, Cygwin, MSVC/Windows). + +## Key Components + +| Macro / Guard | Purpose | +|---|---| +| `_GL_SYS_TIME_H` | Double-inclusion guard for the gnulib wrapper layer | +| `_GL_CXXDEFS_H` | Guards for C++/`GNULIB_NAMESPACE` alias machinery | +| `_GL_FUNCDECL_RPL` | Declares a replacement function (`rpl_func`) with given prototype | +| `_GL_FUNCDECL_SYS` | Declares the native system function with given prototype | +| `_GL_CXXALIAS_RPL` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL_1` but silences C++ conversion errors via `reinterpret_cast` | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++ builds, plain `extern` otherwise | +| `include_next` | Delegates to the real system `` after the wrapper layer | + +## Usage Example + +```c +/* This header is consumed automatically by the build system. + Do not include it directly — include as usual. */ + +#include /* resolves to this gnulib wrapper on affected platforms */ + +struct timeval tv; +gettimeofday(&tv, NULL); + +/* In a C++ build with GNULIB_NAMESPACE defined, the alias resolves cleanly: */ +// GNULIB_NAMESPACE::gettimeofday(&tv, NULL); +``` + +> **Note:** This file is auto-generated by gnulib's `sys_time` module. Do not edit manually — regenerate via `gnulib-tool --update` when upgrading gnulib. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.types.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.types.md new file mode 100644 index 00000000000..08da91c167d --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.types.md @@ -0,0 +1,30 @@ + +Auto-generated gnulib compatibility header that supplements the system `sys/types.h` with more complete POSIX type definitions, particularly for Windows/MinGW environments and Large File Support (LFS) scenarios. + +## Key Components + +| Macro / Type | Purpose | +|---|---| +| `off_t` | Overridden to `__int64` / `long long int` when 64-bit LFS is enabled on Windows | +| `dev_t` (`rpl_dev_t`) | Replacement 64-bit device type for Windows distinguishable inode support | +| `ino_t` (`rpl_ino_t`) | Replacement 64-bit (or 128-bit experimental) inode type for Windows | +| `_GL_WINDOWS_64_BIT_OFF_T` | Internal gnulib indicator that 64-bit `off_t` override is active | +| `_GL_WINDOWS_STAT_INODES` | Internal gnulib indicator for Windows inode mode level | +| `_GL_INCLUDING_SYS_TYPES_H` | Split double-inclusion guard used during `include_next` delegation | + +## Usage Example + +This header is not included directly by application code. It is injected automatically by gnulib's module system ahead of the system header: + +```c +/* gnulib wraps sys/types.h transparently — no change needed in your code */ +#include + +/* off_t, ino_t, dev_t, size_t are now available with correct widths + even on Windows/MSVC/MinGW targets */ +off_t file_offset = 0; +ino_t inode_number; +dev_t device_id; +``` + +> **Note:** All active override blocks are currently disabled (`#if 0`). The file delegates to the platform `sys/types.h` via `include_next`, only activating replacements when gnulib's configure step detects they are needed. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.wait.md b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.wait.md new file mode 100644 index 00000000000..43c322d4647 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/aarch64/lib/sys/.wait.md @@ -0,0 +1,50 @@ + +Auto-generated POSIX-compatible `` header from GNU gnulib, providing cross-platform process wait status macros and function declarations — particularly filling gaps on Windows (non-Cygwin) where the native header is absent. + +## Key Components + +### Macros & Guards +- `_GL_SYS_WAIT_H` — split double-inclusion guard enabling `include_next` chaining on POSIX platforms +- Platform detection: skips `include_next` on `_WIN32` (non-Cygwin) environments + +### C++ Compatibility Layer (`_GL_CXXDEFS_H`) +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in `GNULIB_NAMESPACE` when building with C++ +- `_GL_EXTERN_C` — ensures C linkage in C++ translation units + +### Function Declaration Macros +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | C++ namespace alias redirecting to replacement | +| `_GL_CXXALIAS_SYS` | C++ namespace alias redirecting to system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants that silence invalid-conversion errors | + +## Usage Example + +```c +/* Using gnulib's wait.h in a portable process-management context */ +#include /* resolves to this gnulib header on patched builds */ +#include + +int main(void) { + pid_t pid; + int status; + + pid = fork(); + if (pid == 0) { + /* child process work */ + _exit(0); + } + + /* waitpid declared via _GL_FUNCDECL_SYS or _GL_FUNCDECL_RPL */ + waitpid(pid, &status, 0); + + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + } + return 0; +} +``` + +> **Note:** This file is auto-generated by gnulib — do not edit directly. Modify the gnulib module configuration and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/config/.config.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/config/.config.md new file mode 100644 index 00000000000..e167a9bf82f --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/config/.config.md @@ -0,0 +1,49 @@ + +Auto-generated build configuration header for the Augeas configuration management library, produced by `autoconf`/`configure` from `config.h.in`. It captures platform capabilities, feature flags, and gnulib module availability for conditional compilation. + +## Key Components + +### Build & Debug Flags +- `ENABLE_DEBUG` — Enables debug mode (`1` by default) +- `_FORTIFY_SOURCE` — Conditionally enables glibc buffer overflow protection when optimizations are active + +### Floating Point Layout +- `DBL_EXPBIT0_BIT` / `DBL_EXPBIT0_WORD` — Defines the bit/word position of the `double` exponent for portable IEEE 754 arithmetic + +### Platform Feature Detections (`HAVE_*`) +Indicates availability of system headers and functions, e.g.: +- `HAVE_ALLOCA`, `HAVE_ALLOCA_H` — Stack allocation support +- `HAVE_ARPA_INET_H` — Network address header +- `HAVE_BTOWC`, `HAVE_CATGETS` — Wide-character and locale functions +- `HAVE_CFLOCALECOPYCURRENT` — macOS CoreFoundation locale API + +### Gnulib Module Flags (`GNULIB_*`) +- `GNULIB_CANONICALIZE_LGPL`, `GNULIB_FSCANF`, `GNULIB_LOCK`, etc. — Mark which gnulib portability modules are active +- `GNULIB_TEST_*` — Enable unit tests for individual gnulib replacements (e.g., `GNULIB_TEST_STRERROR`, `GNULIB_TEST_REALPATH`) + +### Behavioral Quirk Flags +- `FUNC_NL_LANGINFO_YESEXPR_WORKS` — Confirms `nl_langinfo(YESEXPR)` returns a non-empty string +- `FLEXIBLE_ARRAY_MEMBER` — Struct hack compatibility for pre-C99 compilers +- `GETTIMEOFDAY_TIMEZONE` — Type alias for the second argument of `gettimeofday` + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile debug output */ +#ifdef ENABLE_DEBUG + fprintf(stderr, "[debug] entering parser\n"); +#endif + +/* Use portable flexible array member */ +struct Node { + int count; + char data[FLEXIBLE_ARRAY_MEMBER]; +}; + +/* Guard gnulib-provided function usage */ +#if GNULIB_CANONICALIZE_LGPL + char *resolved = canonicalize_file_name(path); +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.alloca.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.alloca.md new file mode 100644 index 00000000000..01f8c9bdba4 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.alloca.md @@ -0,0 +1,43 @@ + +Portable compatibility header that provides a unified `alloca` macro/function declaration for stack-based memory allocation across multiple compilers and platforms. + +## Key Components + +- **`alloca(N)` macro/function** — Allocates `N` bytes on the stack, valid until the enclosing function returns. Resolves to the appropriate compiler/platform intrinsic: + +| Platform/Compiler | Resolution | +|---|---| +| GCC | `__builtin_alloca` | +| AIX | `__alloca` | +| MSVC | `_alloca` (via ``) | +| HP DEC/VMS | `__ALLOCA` | +| Tandem/TNS-E | `_alloca` intrinsic | +| IBM MVS | `` | +| Fallback | `void *alloca(size_t)` | + +## Usage Example + +```c +#include "alloca.h" +#include + +void process_data(size_t len) { + /* Allocate a temporary buffer on the stack */ + char *buf = alloca(len); + memset(buf, 0, len); + /* buf is automatically freed when process_data() returns */ +} +``` + +## Important Caveats + +> **Avoid `alloca` in these contexts:** +> - Inside function call argument lists — undefined behavior +> - Inside inline functions — allocation may outlive the call site +> - For large `N` (≥ 65536 bytes) — stack overflow risk with no recoverable error + +## Notes + +- This header is **auto-generated** — do not edit manually. +- The guard macro `_GL_ALLOCA_H` (not `_ALLOCA_H`) is intentional; Bison uses `_ALLOCA_H` to detect a real `alloca` implementation. +- Licensed under **GNU LGPL v2.1+**. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.argz.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.argz.md new file mode 100644 index 00000000000..c0f2e464287 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.argz.md @@ -0,0 +1,49 @@ + +Auto-generated GNU C Library header providing utilities for manipulating **argz vectors** — contiguous arrays of null-terminated strings separated by `'\0'` characters, commonly used for argument and environment processing. + +## Key Components + +| Function | Description | +|---|---| +| `argz_create` | Build an argz vector from a Unix `argv[]` array | +| `argz_create_sep` | Build an argz vector from a delimiter-separated string | +| `argz_count` | Count the number of strings in an argz vector | +| `argz_extract` | Extract pointers to each string into a standard `argv` array | +| `argz_stringify` | Convert argz back to a printable string by replacing `'\0'` with a separator | +| `argz_append` | Append a raw buffer to an argz vector | +| `argz_add` | Append a single string to an argz vector | +| `argz_add_sep` | Append a delimiter-separated string to an argz vector | +| `argz_delete` | Remove an entry from an argz vector | +| `argz_insert` | Insert an entry before a given position | +| `argz_replace` | Replace all occurrences of a string within an argz vector | +| `argz_next` | Iterate over entries in an argz vector | + +## Usage Example + +```c +#include +#include +#include + +int main(void) { + char *argz = NULL; + size_t argz_len = 0; + + /* Build argz from a colon-separated string */ + argz_create_sep("/usr/bin:/usr/local/bin:/home/user/bin", ':', &argz, &argz_len); + + /* Iterate over each entry */ + char *entry = NULL; + while ((entry = argz_next(argz, argz_len, entry))) { + printf("Path: %s\n", entry); + } + + /* Replace an entry */ + argz_replace(&argz, &argz_len, "/usr/bin", "/opt/bin", NULL); + + free(argz); + return 0; +} +``` + +> **Note:** All modifying functions that reallocate (`argz_add`, `argz_append`, `argz_insert`, `argz_replace`) return `ENOMEM` on allocation failure. Always check return values. The resulting argz vector must be freed with `free()`. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.ctype.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.ctype.md new file mode 100644 index 00000000000..29a8dd95adf --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.ctype.md @@ -0,0 +1,64 @@ + +A GNUlib-generated substitute header for ISO C99 ``, providing compatibility shims and C++ namespace aliasing macros for platforms where the standard implementation is incomplete. + +## Key Components + +### Include Guard & Chaining +- Uses `#include_next ` to wrap the system's original header, extending rather than replacing it +- Split double-inclusion guard pattern (`_GL_CTYPE_H`) handles the two-pass include + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a replacement function prefixed with `rpl_` | +| `_GL_FUNCDECL_SYS` | Declares the original system function with C linkage | +| `_GL_CXXALIAS_RPL` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL` but silences C++ conversion errors via `reinterpret_cast` | +| `_GL_CXXALIAS_SYS_CAST` | Like `_GL_CXXALIAS_SYS` but with cast for mismatched declarations | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++ or `extern` in C | + +### Three Portability Patterns Supported + +```c +/* Pattern 1: Missing function — provide substitute */ +#if !HAVE_FOO +_GL_FUNCDECL_SYS(foo, int, (const char *s)); +#endif +_GL_CXXALIAS_SYS(foo, int, (const char *s)); + +/* Pattern 2: Broken function — always replace */ +#if REPLACE_FOO +_GL_FUNCDECL_RPL(foo, int, (const char *s)); +_GL_CXXALIAS_RPL(foo, int, (const char *s)); +#else +_GL_CXXALIAS_SYS(foo, int, (const char *s)); +#endif + +/* Pattern 3: Missing on some, broken on others */ +#if REPLACE_FOO +_GL_FUNCDECL_RPL(foo, int, (const char *s)); +_GL_CXXALIAS_RPL(foo, int, (const char *s)); +#elif !HAVE_FOO +_GL_FUNCDECL_SYS(foo, int, (const char *s)); +_GL_CXXALIAS_SYS(foo, int, (const char *s)); +#endif +``` + +## Usage Example + +```c +/* Consuming code — portable ctype usage with GNULIB_NAMESPACE */ +#define GNULIB_NAMESPACE gnulib +#include "ctype.h" + +int main(void) { + /* Resolves to either rpl_isblank or system isblank, + transparently via GNULIB_NAMESPACE alias */ + int result = gnulib::isblank(' '); + return result ? 0 : 1; +} +``` + +> **Note:** This file is auto-generated by GNUlib's `gnulib-tool`. Do not edit manually — regenerate via the GNUlib module system when updates are needed. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fcntl.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fcntl.md new file mode 100644 index 00000000000..f229032f384 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fcntl.md @@ -0,0 +1,50 @@ + +A gnulib-generated compatibility wrapper for the standard `` header, normalizing file control flags and function declarations across platforms by defining unsupported flags as `0` and providing POSIX-compliant replacements. + +## Key Components + +| Macro / Guard | Purpose | +|---|---| +| `_GL_FCNTL_H` | Double-inclusion guard for the gnulib wrapper | +| `__need_system_fcntl_h` | Special invocation path that directly includes the system header | +| `_GL_FUNCDECL_RPL` | Declares a replacement function prefixed with `rpl_` | +| `_GL_FUNCDECL_SYS` | Declares the system function with a compatible prototype | +| `_GL_CXXALIAS_RPL` | Creates a `GNULIB_NAMESPACE::func` C++ alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS` | Creates a `GNULIB_NAMESPACE::func` C++ alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL` but silences invalid-conversion errors via `reinterpret_cast` | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++ mode, plain `extern` in C mode | + +## Usage Example + +```c +/* System headers are pulled in via include_next, then gnulib overrides + are applied transparently. Typical consumer code needs no changes: */ +#include + +int main(void) { + /* O_NOFOLLOW may be 0 on platforms that lack it — safe to pass */ + int fd = open("file.txt", O_RDONLY | O_NOFOLLOW); + if (fd < 0) { + perror("open"); + return 1; + } + close(fd); + return 0; +} +``` + +```c +/* When GNULIB_NAMESPACE is defined (C++ usage), functions are aliased: */ +#define GNULIB_NAMESPACE gnulib +#include + +// Calls gnulib's replacement open() if REPLACE_OPEN is set, +// otherwise delegates to the system open() +int fd = gnulib::open("file.txt", O_WRONLY | O_CREAT, 0644); +``` + +## Notes + +- **Auto-generated** — do not edit manually; regenerate via gnulib's `gnulib-tool`. +- Non-working flags on the target platform are defined to `0`, making them safe to use in flag expressions without `#ifdef` guards. +- On native Windows (`_WIN32`), `` is included to pick up `open()` and `creat()` declarations. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fnmatch.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fnmatch.md new file mode 100644 index 00000000000..f813ebf7943 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.fnmatch.md @@ -0,0 +1,44 @@ + +A GNU portability wrapper header that provides a cross-platform substitute for the standard ``, part of the GNU Portability Library (gnulib). It ensures consistent `fnmatch` behavior across platforms where the native implementation may be missing or broken. + +## Key Components + +### Macros — Function Declaration + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, rettype, params)` | Declares a replacement function prefixed with `rpl_` | +| `_GL_FUNCDECL_SYS(func, rettype, params)` | Declares the system-provided function | +| `_GL_EXTERN_C` | Ensures C linkage in C++ translation units | + +### Macros — C++ Namespace Aliasing + +| Macro | Purpose | +|---|---| +| `_GL_CXXALIAS_RPL(func, rettype, params)` | Aliases `rpl_func` into `GNULIB_NAMESPACE` | +| `_GL_CXXALIAS_SYS(func, rettype, params)` | Aliases the system function into `GNULIB_NAMESPACE` | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress type-mismatch warnings | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes the `GNULIB_NAMESPACE` C++ namespace | + +## Usage Example + +```c +/* Basic fnmatch usage after including this wrapper */ +#include "fnmatch.h" +#include + +int main(void) { + const char *pattern = "*.txt"; + const char *filename = "readme.txt"; + + /* FNM_PATHNAME: slash only matches slash in pattern */ + if (fnmatch(pattern, filename, 0) == 0) { + printf("'%s' matches pattern '%s'\n", filename, pattern); + } else { + printf("No match.\n"); + } + return 0; +} +``` + +> **Note:** This header is auto-generated by gnulib's `gnulib-tool`. Do not edit manually — regenerate via gnulib if updates are needed. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt-cdefs.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt-cdefs.md new file mode 100644 index 00000000000..34b22847448 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt-cdefs.md @@ -0,0 +1,42 @@ + +Compatibility shim that provides fallback definitions for GNU libc internal macros required by `getopt-core.h` and `getopt-ext.h` on non-glibc platforms. Auto-generated as part of gnulib's portable `getopt` implementation. + +## Key Components + +| Macro | Purpose | +|---|---| +| `__BEGIN_DECLS` | Opens `extern "C"` linkage block in C++ contexts | +| `__END_DECLS` | Closes `extern "C"` linkage block in C++ contexts | +| `__GNUC_PREREQ(maj, min)` | Tests if GCC version meets a minimum `major.minor` requirement | +| `__THROW` | Expands to `throw()` on GCC 2.8+ C++, empty otherwise | + +## Usage Example + +> **Do not include this header directly.** Use `getopt.h` or `unistd.h` instead. + +These macros are consumed internally by `getopt-core.h` and `getopt-ext.h`: + +```c +/* Internal usage within getopt-core.h (simplified) */ +#include "getopt-cdefs.h" + +__BEGIN_DECLS + +extern int getopt (int argc, char *const *argv, const char *shortopts) + __THROW; + +__END_DECLS +``` + +```c +/* Checking GCC version compatibility */ +#if __GNUC_PREREQ(4, 8) + /* Use GCC 4.8+ specific feature */ +#endif +``` + +## Notes + +- When `sys/cdefs.h` is available (controlled by the `#if 1` guard), it is preferred over the fallback definitions. +- All macros are guarded with `#ifndef`, so existing platform definitions are never overridden. +- Licensed under **GNU LGPL v2.1+** as part of gnulib. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt.md new file mode 100644 index 00000000000..8799110bf5e --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.getopt.md @@ -0,0 +1,42 @@ + +GNU `getopt` portability wrapper header, auto-generated by gnulib, providing cross-platform command-line option parsing declarations with system header fallback and non-null argument annotations. + +## Key Components + +| Element | Description | +|---|---| +| `_GL_SYSTEM_GETOPT` | Guard macro preventing recursive inclusion when delegating to the system `` via `include_next` | +| `__GETOPT_PREFIX` | Optional prefix macro for standalone apps to namespace `getopt` symbols and avoid conflicts | +| `_GL_ARG_NONNULL(params)` | Compiler hint macro mapping to GCC `__nonnull__` attribute (GCC ≥ 3.3); no-op on unsupported compilers | +| `` | Core type/constant definitions | +| `` | Core `getopt` function declarations (possibly prefixed) | +| `` | Extended `getopt_long` / `getopt_long_only` declarations (possibly prefixed) | + +## Usage Example + +```c +#include +#include + +int main(int argc, char *argv[]) { + int opt; + + /* Short options: -v, -o */ + while ((opt = getopt(argc, argv, "vo:")) != -1) { + switch (opt) { + case 'v': + printf("Verbose mode\n"); + break; + case 'o': + printf("Output file: %s\n", optarg); + break; + default: + fprintf(stderr, "Usage: %s [-v] [-o file]\n", argv[0]); + return 1; + } + } + return 0; +} +``` + +> **Note:** This file is **auto-generated** — do not edit directly. It is part of [gnulib](https://www.gnu.org/software/gnulib/) and licensed under LGPL 2.1+. On systems with a native ``, it transparently delegates via `include_next` before applying gnulib extensions. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.langinfo.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.langinfo.md new file mode 100644 index 00000000000..309fbbad7fd --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.langinfo.md @@ -0,0 +1,51 @@ + +A gnulib-generated portability wrapper for the POSIX `` header, providing locale information constants and `nl_langinfo` support on platforms where the header is absent or incomplete. + +## Key Components + +### Type Definition +- **`nl_item`** — Defined as `int` on platforms lacking ``, used as the argument type for `nl_langinfo()`. + +### Locale Category Constants + +| Category | Constants | +|---|---| +| `LC_CTYPE` | `CODESET` | +| `LC_NUMERIC` | `RADIXCHAR`/`DECIMAL_POINT`, `THOUSEP`/`THOUSANDS_SEP`, `GROUPING` | +| `LC_TIME` | `D_T_FMT`, `D_FMT`, `T_FMT`, `T_FMT_AMPM`, `AM_STR`, `PM_STR`, `DAY_1`–`DAY_7`, `ABDAY_1`–`ABDAY_7`, `MON_1`–`MON_12`, `ALTMON_1`–`ALTMON_12`, `ABMON_1`–`ABMON_12`, `ERA*`, `ALT_DIGITS` | +| `LC_MONETARY` | `CRNCYSTR`, `INT_CURR_SYMBOL`, `MON_DECIMAL_POINT`, `MON_THOUSANDS_SEP`, sign/position constants | +| `LC_MESSAGES` | `YESEXPR`, `NOEXPR` | + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) +- **`_GL_FUNCDECL_RPL`** — Declares a replacement function prefixed `rpl_`. +- **`_GL_FUNCDECL_SYS`** — Declares the original system function. +- **`_GL_CXXALIAS_RPL`** — Creates a `GNULIB_NAMESPACE` C++ alias pointing to the replacement. +- **`_GL_CXXALIAS_RPL_CAST_1`** — Variant with cast for slightly mismatched signatures. +- **`_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE`** — Wraps declarations in `GNULIB_NAMESPACE` when defined. + +## Usage Example + +```c +#include "langinfo.h" +#include +#include + +int main(void) { + setlocale(LC_ALL, ""); + + /* Query the locale's character encoding */ + char *encoding = nl_langinfo(CODESET); + printf("Encoding: %s\n", encoding); + + /* Query the decimal point character */ + char *decimal = nl_langinfo(DECIMAL_POINT); + printf("Decimal point: %s\n", decimal); + + /* Query abbreviated day names */ + printf("Monday abbrev: %s\n", nl_langinfo(ABDAY_2)); + + return 0; +} +``` + +> **Note:** This header is auto-generated by gnulib. Do not edit it directly — modify the gnulib module configuration and regenerate. On platforms with a complete ``, it delegates via `#include_next`; only missing constants are backfilled. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.limits.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.limits.md new file mode 100644 index 00000000000..f819d09a365 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.limits.md @@ -0,0 +1,39 @@ + +Auto-generated GNU-compatible replacement for the system ``, extending it with portable definitions for `long long` integer bounds and ISO/IEC TS 18661-1 width macros across non-standard platforms. + +## Key Components + +### `long long` Bounds Portability +Conditionally defines missing `LLONG_MIN`, `LLONG_MAX`, and `ULLONG_MAX` by mapping from platform-specific names (`LONG_LONG_MAX` on HP-UX, `LONGLONG_MAX` on IRIX) or falling back to GCC built-ins. + +### `_GL_INTEGER_WIDTH` Macro Family +A recursive bit-counting macro cascade that computes the usable bit width of any integer type at preprocessor time, given its min/max values. Works by counting set bits in `MAX` across 4-to-128-bit ranges via `_GL_COB4` through `_GL_COB128`. + +### `WORD_BIT` / `LONG_BIT` +Fallback definitions assuming `int` is 32 bits and `long` is 32 or 64 bits, guarded to avoid redefining platform values. + +### ISO/IEC TS 18661-1 Width Macros +Defines `CHAR_WIDTH`, `INT_WIDTH`, `LLONG_WIDTH`, etc. when `_GNU_SOURCE` or `__STDC_WANT_IEC_60559_BFP_EXT__` is set and `ULLONG_WIDTH` is not already defined. + +## Usage Example + +```c +#include /* pulls in this gnulib wrapper via include_next */ + +/* Portable long long bounds */ +long long floor_val = LLONG_MIN; +long long ceil_val = LLONG_MAX; + +/* Compute bit width of int at preprocessor level */ +#define MY_INT_WIDTH _GL_INTEGER_WIDTH(INT_MIN, INT_MAX) +#if MY_INT_WIDTH < 32 +# error "int must be at least 32 bits" +#endif + +/* ISO TS 18661 width macros (requires _GNU_SOURCE) */ +#define _GNU_SOURCE +#include +static_assert(INT_WIDTH == 32, "unexpected int width"); +``` + +> **Note:** This file is auto-generated by [gnulib](https://www.gnu.org/software/gnulib/) and should not be edited manually. It uses `#include_next` to layer on top of the system ``. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.locale.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.locale.md new file mode 100644 index 00000000000..35e11ac8614 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.locale.md @@ -0,0 +1,53 @@ + +Auto-generated POSIX-compliant `locale.h` header (via Gnulib) that provides portable locale support across platforms, including Windows/MinGW, macOS, Solaris, and NetBSD, with C++ namespace compatibility macros. + +## Key Components + +### Platform Compatibility Guards +- `_GL_ALREADY_INCLUDING_LOCALE_H` — prevents recursive inclusion conflicts (Solaris + libintl edge case) +- `__need_locale_t` on MinGW — triggers a special pass-through inclusion path +- Includes `` on macOS 10.5+ for the `locale_t` type + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | +| `_GL_EXTERN_C` | Declares functions with C linkage in C++ | +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, ...)` | Creates a C++ alias routing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | Creates a C++ alias routing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to silence type mismatch errors | + +## Usage Example + +```c +/* Using Gnulib locale replacements in a portable C program */ +#include "locale.h" + +int main(void) { + /* locale_t is available cross-platform via this header */ + locale_t loc = newlocale(LC_ALL_MASK, "en_US.UTF-8", (locale_t)0); + if (loc == (locale_t)0) { + /* handle error */ + return 1; + } + uselocale(loc); + /* ... locale-aware operations ... */ + freelocale(loc); + return 0; +} +``` + +```c +/* In C++ with GNULIB_NAMESPACE defined, functions are accessed as: */ +#define GNULIB_NAMESPACE gnulib +#include "locale.h" + +void example() { + gnulib::newlocale(LC_ALL_MASK, "fr_FR.UTF-8", (locale_t)0); +} +``` + +> **Note:** This file is auto-generated — do not edit directly. It wraps the system `` via `#include_next` and layers portability fixes on top. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdint.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdint.md new file mode 100644 index 00000000000..dc6d3a22aba --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdint.md @@ -0,0 +1,46 @@ + +A gnulib-generated portable replacement for ISO C99 ``, providing fixed-width integer types and their limits on platforms that lack a compliant implementation. + +## Key Components + +### Exact-Width Integer Types (`7.18.1.1`) +| Type | Underlying C Type | +|------|------------------| +| `int8_t` / `uint8_t` | `signed char` / `unsigned char` | +| `int16_t` / `uint16_t` | `short int` / `unsigned short int` | +| `int32_t` / `uint32_t` | `int` / `unsigned int` | +| `int64_t` / `uint64_t` | `long long` / `unsigned long long` (platform-dependent) | + +### Minimum-Width Integer Types (`7.18.1.2`) +Aliases to their exact-width counterparts (e.g., `int_least8_t` → `int8_t`). + +### Fastest Minimum-Width Types (`7.18.1.3`) +- `int_fast8_t` / `uint_fast8_t` — map to `signed char` / `unsigned char` +- `int_fast16/32_t` — map to `long int` (or `int` on SunOS for ABI compatibility) + +### Pointer-Sized Types (`7.18.1.4`) +- `intptr_t` → `long int` +- `uintptr_t` → `unsigned long int` + +### Greatest-Width Types (`7.18.1.5`) +- `intmax_t` / `uintmax_t` — resolved to `long long` or `int64_t` as available + +### Limit Macros (`7.18.2`) +Standard min/max constants: `INT8_MIN`, `INT8_MAX`, `UINT8_MAX`, `INT16_MIN`, `INT16_MAX`, etc. + +## Usage Example + +```c +#include "stdint.h" + +/* Portable fixed-width arithmetic */ +uint32_t counter = 0; +int64_t large_value = INT64_MAX; +intptr_t ptr_val = (intptr_t) some_pointer; + +/* Safe size checking */ +uint8_t byte = UINT8_MAX; /* 255 */ +int16_t small = INT16_MIN; /* -32768 */ +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit manually. It handles Android Bionic, IRIX, SunOS, MSVC (`__int64`), and kLIBC edge cases transparently. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdio.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdio.md new file mode 100644 index 00000000000..ffc8015faf2 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdio.md @@ -0,0 +1,53 @@ + +A GNU-like replacement header for ``, auto-generated by gnulib to provide portable, standards-compliant I/O declarations across platforms (Linux, macOS, Windows/MSVC, Android, Solaris, etc.). + +## Key Components + +### Include Guards & Inclusion Strategy +- Uses a split double-inclusion guard (`_GL_STDIO_H`, `_GL_ALREADY_INCLUDING_STDIO_H`) to safely wrap `#include_next `, layering gnulib extensions on top of the system header. + +### Format Attribute Macros +| Macro | Purpose | +|---|---| +| `_GL_ATTRIBUTE_FORMAT_PRINTF` | Marks functions taking ISO C99/POSIX `printf`-style format strings | +| `_GL_ATTRIBUTE_FORMAT_PRINTF_SYSTEM` | Marks functions using system `printf` format directives | +| `_GL_ATTRIBUTE_FORMAT_SCANF` | Marks functions taking ISO C99/POSIX `scanf`-style format strings | +| `_GL_ATTRIBUTE_FORMAT_SCANF_SYSTEM` | Marks functions using system `scanf` format directives | + +### Function Declaration Macros (`_GL_CXXDEFS_H`) +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates a C++ namespace alias redirecting to the replacement | +| `_GL_EXTERN_C` | Ensures C linkage in C++ translation units | + +### Platform Compatibility Includes +Conditionally pulls in ``, ``, ``, and `` on platforms (Solaris, Android, MSVC/Windows) where declarations like `renameat`, `perror`, and `remove` are not in the standard ``. + +## Usage Example + +```c +/* This header is used transparently — include it like the standard header. + gnulib's build system places it earlier in the include path. */ +#include + +/* GCC will now warn on mismatched format strings thanks to + _GL_ATTRIBUTE_FORMAT_PRINTF applied to printf/fprintf wrappers. */ + +int main(void) { + /* Standard calls work as normal; gnulib provides + portable replacements where the system is deficient. */ + printf("Hello, portable world!\n"); + + FILE *f = fopen("data.txt", "r"); + if (!f) { + perror("fopen"); /* Works on MSVC too via gnulib shim */ + return 1; + } + fclose(f); + return 0; +} +``` + +> **Note:** This file is auto-generated — do not edit directly. Modifications should be made to the gnulib source templates and regenerated. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdlib.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdlib.md new file mode 100644 index 00000000000..453e52dfb82 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.stdlib.md @@ -0,0 +1,45 @@ + +A GNU-compatible replacement/extension of the standard ``, auto-generated by gnulib to provide portable, POSIX-compliant standard library declarations across platforms where the system header is missing or broken. + +## Key Components + +### Guard Macros & Inclusion Strategy +- `_GL_STDLIB_H` — double-inclusion guard with `include_next` to chain to the real system header +- `__need_system_stdlib_h` / `__need_malloc_and_calloc` — special invocation bypass for internal gnulib/glibc use + +### Compiler Attribute Macros +| Macro | Purpose | +|---|---| +| `_GL_ATTRIBUTE_PURE` | Maps to `__attribute__((__pure__))` on GCC ≥ 2.96 | +| `_Noreturn` | Portable no-return annotation (C11, `[[noreturn]]`, `__declspec`, or GCC attribute) | + +### C++ Namespace / Aliasing Infrastructure (`_GL_CXXDEFS_H`) +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in `GNULIB_NAMESPACE` when defined +- `_GL_FUNCDECL_RPL` — declares a replacement function prefixed `rpl_` +- `_GL_FUNCDECL_SYS` — re-declares the system function with corrected prototype +- `_GL_CXXALIAS_RPL` / `_GL_CXXALIAS_RPL_CAST_1` — create C++ namespace aliases redirecting to replacement implementations + +### Platform-Specific Includes +Conditionally pulls in ``, ``, ``, ``, `` to cover missing declarations on macOS, Solaris, Cygwin, and Windows. + +### `struct random_data` +Conditionally defined when the platform lacks it, providing the state structure for `initstate_r` / `random_r`. + +## Usage Example + +```c +/* gnulib-generated stdlib.h is transparent — use stdlib normally */ +#include + +int main(void) { + /* mkstemp, getsubopt, etc. are guaranteed declared cross-platform */ + char tmpl[] = "/tmp/fileXXXXXX"; + int fd = mkstemp(tmpl); + + void *buf = malloc(1024); + free(buf); + return EXIT_SUCCESS; +} +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit manually — regenerate via `gnulib-tool` when updating module dependencies. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.string.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.string.md new file mode 100644 index 00000000000..cd57c418551 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.string.md @@ -0,0 +1,48 @@ + +Auto-generated GNU-compatible `` header from the Gnulib portability layer, providing cross-platform string function declarations with replacements and extensions for systems where the native implementation is missing or non-conformant. + +## Key Components + +### Include Guard & Nesting Protection +- `_GL_ALREADY_INCLUDING_STRING_H` — Handles recursive include chains (e.g., OS X/NetBSD: `` → `` → `"string.h"`) +- `_GL_STRING_H` — Primary double-inclusion guard +- `#include_next ` — Chains to the system's native header + +### C++/Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` in C++ builds | +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the system function with given prototype | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates a C++ namespace alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | Creates a C++ namespace alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress `invalid conversion` errors | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, plain `extern` in C | + +### Portability Attributes +- `_GL_ATTRIBUTE_PURE` — Maps to `__attribute__((__pure__))` on GCC ≥ 2.96, empty otherwise + +## Usage Example + +```c +/* Typical Gnulib pattern for a portable string function replacement */ + +#if GNULIB_STRDUP +# if REPLACE_STRDUP +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strdup +# define strdup rpl_strdup +# endif + _GL_FUNCDECL_RPL (strdup, char *, (const char *s)); + _GL_CXXALIAS_RPL (strdup, char *, (const char *s)); +# else + _GL_CXXALIAS_SYS (strdup, char *, (const char *s)); +# endif +#endif + +/* In C++ with GNULIB_NAMESPACE defined, call via: */ +/* GNULIB_NAMESPACE::strdup(s); */ +``` + +> **Note:** This file is auto-generated — do not edit directly. Modifications should be made to the Gnulib source templates. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.time.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.time.md new file mode 100644 index 00000000000..29ce882ded4 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.time.md @@ -0,0 +1,54 @@ + +A auto-generated gnulib compatibility header that extends the system `` with more-standard POSIX/C99 declarations, portable function replacements, and C++ namespace aliasing macros. + +## Key Components + +### Guard & Include Logic +- `_GL_TIME_H` — include guard preventing recursive inclusion +- Conditional `include_next ` — passes through to the real system header while layering gnulib additions on top +- Handles edge cases for glibc partial includes (`__need_time_t`, `__need_clock_t`, `__need_timespec`) and MinGW/Solaris quirks + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL(func, ret, params)` | Declares a replacement function `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ret, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ret, params)` | Creates `GNULIB_NAMESPACE::func` alias → system func | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Same as above with `reinterpret_cast` for type mismatches | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++, `extern` in C | + +### Namespace Support +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in `GNULIB_NAMESPACE` when building as C++ + +## Usage Example + +```c +/* Typical gnulib pattern for a missing/broken function (e.g. strptime) */ + +#if GNULIB_STRPTIME +# if !HAVE_STRPTIME + _GL_FUNCDECL_SYS (strptime, char *, + (const char *s, const char *fmt, struct tm *tp)); +# endif + _GL_CXXALIAS_SYS (strptime, char *, + (const char *s, const char *fmt, struct tm *tp)); + _GL_CXXALIASWARN (strptime); +#endif + +/* Replacement pattern for a broken function (e.g. mktime) */ + +#if GNULIB_MKTIME +# if REPLACE_MKTIME +# undef mktime +# define mktime rpl_mktime + _GL_FUNCDECL_RPL (mktime, time_t, (struct tm *tp)); + _GL_CXXALIAS_RPL (mktime, time_t, (struct tm *tp)); +# else + _GL_CXXALIAS_SYS (mktime, time_t, (struct tm *tp)); +# endif +#endif +``` + +> **Note:** This file is auto-generated by gnulib — do not edit manually. Modifications should be made to the gnulib module source and regenerated. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.unistd.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.unistd.md new file mode 100644 index 00000000000..8b41f60284c --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.unistd.md @@ -0,0 +1,49 @@ + +A auto-generated gnulib wrapper and portability shim for the POSIX `` header, providing cross-platform declarations, replacements, and C++ namespace aliases for standard Unix API functions. + +## Key Components + +| Component | Description | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function prefixed with `rpl_` | +| `_GL_FUNCDECL_SYS` | Declares the original system function with a given prototype | +| `_GL_CXXALIAS_RPL` | Creates a C++ alias in `GNULIB_NAMESPACE` pointing to `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Same as `_GL_CXXALIAS_RPL` but uses `reinterpret_cast` to silence type mismatch errors | +| `_GL_CXXALIAS_SYS` | Creates a C++ alias pointing to the original system function | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations inside `GNULIB_NAMESPACE` when building C++ | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ builds, plain `extern` otherwise | + +### Platform Compatibility Guards + +The header conditionally includes platform-specific headers to fill gaps: + +- **Windows/MinGW**: pulls in `` and `` for `access`, `chdir`, `dup`, etc. +- **Cygwin / Android**: pulls in `` or `` for missing declarations +- **MSVC**: relies on `` for `off_t` +- **Non-glibc systems**: selectively includes `` for `environ`, `getcwd`, `_exit` + +## Usage Example + +```c +/* Typical gnulib pattern for replacing a broken system function */ + +#if GNULIB_CHOWN +# if REPLACE_CHOWN +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef chown +# define chown rpl_chown +# endif +_GL_FUNCDECL_RPL (chown, int, + (const char *file, uid_t uid, gid_t gid) + _GL_ARG_NONNULL ((1))); +_GL_CXXALIAS_RPL (chown, int, + (const char *file, uid_t uid, gid_t gid)); +# else +_GL_CXXALIAS_SYS (chown, int, + (const char *file, uid_t uid, gid_t gid)); +# endif +_GL_CXXALIASWARN (chown); +#endif +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit it directly — modify the gnulib module configuration and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wchar.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wchar.md new file mode 100644 index 00000000000..482ddb23113 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wchar.md @@ -0,0 +1,41 @@ + +A generated substitute for ISO C99 ``, part of the GNU Portability Library (gnulib), providing cross-platform wide character support with fixes for platforms that have missing or broken implementations. + +## Key Components + +### Include Guards & Platform Detection +- `_GL_WCHAR_H` — main include guard preventing duplicate inclusion +- `_GL_ALREADY_INCLUDING_WCHAR_H` — re-entrancy guard for nested include scenarios +- Handles special invocation conventions for glibc, uClibc, MinGW, HP-UX, and IRIX + +### C++ Compatibility Macros (`_GL_CXXDEFS_H`) +- `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` — wraps declarations in `GNULIB_NAMESPACE` when building with C++ +- `_GL_EXTERN_C` — applies `extern "C"` linkage in C++ contexts + +### Function Declaration Macros +- `_GL_FUNCDECL_RPL(func, rettype, params)` — declares a replacement function as `rpl_func` +- `_GL_FUNCDECL_SYS(func, rettype, params)` — declares the original system function +- `_GL_CXXALIAS_RPL(func, rettype, params)` — creates a C++ namespace alias redirecting to `rpl_func` +- `_GL_CXXALIAS_RPL_CAST_1(...)` — same as above but uses `reinterpret_cast` for mismatched signatures +- `_GL_CXXALIAS_SYS(func, rettype, params)` — creates a C++ namespace alias for the system function + +### Attributes +- `_GL_ATTRIBUTE_PURE` — maps to `__attribute__((__pure__))` on GCC 2.96+, empty otherwise + +## Usage Example + +```c +/* Typical gnulib pattern for replacing a broken system function */ +#if GNULIB_WCWIDTH +# if REPLACE_WCWIDTH +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef wcwidth +# define wcwidth rpl_wcwidth +# endif +_GL_FUNCDECL_RPL (wcwidth, int, (wchar_t wc)); +_GL_CXXALIAS_RPL (wcwidth, int, (wchar_t wc)); +# else +_GL_CXXALIAS_SYS (wcwidth, int, (wchar_t wc)); +# endif +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wctype.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wctype.md new file mode 100644 index 00000000000..38b7476dc34 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/.wctype.md @@ -0,0 +1,45 @@ + +Auto-generated GNU portability substitute for ISO C99 ``, providing wide character classification and conversion function declarations for platforms that lack a native implementation. + +## Key Components + +### Inclusion Guards & Platform Handling + +- `_GL_WCTYPE_H` — Standard idempotency guard +- MinGW/MSVC special-case handling via `__MINGW32__` and `_WIN32` checks +- Prerequisite header ordering fixes for Solaris 2.5, Tru64, and BSD/OS 4.0.1 + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a replacement function prefixed `rpl_` | +| `_GL_FUNCDECL_SYS` | Declares the original system function | +| `_GL_CXXALIAS_RPL` | Creates `GNULIB_NAMESPACE::func` alias → `rpl_func` | +| `_GL_CXXALIAS_RPL_CAST_1` | Same as above with a cast for mismatched signatures | +| `_GL_CXXALIAS_SYS` | Creates `GNULIB_NAMESPACE::func` alias → system `func` | +| `_GL_EXTERN_C` | Wraps declarations with `extern "C"` linkage in C++ | + +### Inline Helpers + +- `_GL_WCTYPE_INLINE` — Resolves to `_GL_INLINE` for portable inline definitions +- `_GL_INLINE_HEADER_BEGIN` — Requires `config.h` to be included first + +## Usage Example + +```c +/* Ensure config.h is included before this header */ +#include +#include + +int example(wint_t ch) { + /* Standard wide-char classification functions */ + if (iswalpha(ch)) + return towupper(ch); + if (iswdigit(ch)) + return towlower(ch); + return ch; +} +``` + +> **Note:** `iswctype`, `towctrans`, `wctrans`, and `wctype` are **not yet implemented** in this substitute. Only the core `isw*` and `tow*` family functions are covered. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.context.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.context.md new file mode 100644 index 00000000000..cf19c713a32 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.context.md @@ -0,0 +1,41 @@ + +Auto-generated SELinux context API stub header that provides no-op inline implementations for systems where SELinux is not supported, setting `errno = ENOTSUP` for all operations. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `context_t` | `typedef int` | Opaque context handle type | +| `context_new` | Inline function | Creates a new context from string (stub: returns 0) | +| `context_str` | Inline function | Converts context to string representation (stub: returns NULL) | +| `context_free` | Inline function | Frees a context handle (stub: no-op) | +| `context_user_set/get` | Inline functions | Get/set user component of context (stub) | +| `context_role_set/get` | Inline functions | Get/set role component of context (stub) | +| `context_type_set/get` | Inline functions | Get/set type component of context (stub) | +| `context_range_set/get` | Inline functions | Get/set range/MLS component of context (stub) | +| `_GL_UNUSED_PARAMETER` | Macro | Suppresses unused parameter warnings via `__attribute__((unused))` | + +## Usage Example + +```c +#include "config.h" +#include "context.h" +#include + +void example(void) { + /* On non-SELinux systems, all calls set errno = ENOTSUP */ + context_t ctx = context_new("user_u:role_r:type_t"); + if (ctx == 0) { + /* Expected on non-SELinux builds */ + perror("context_new"); + return; + } + + context_type_set(ctx, "new_type_t"); + char *label = context_str(ctx); + + context_free(ctx); +} +``` + +> **Note:** This header is auto-generated and should not be edited manually. All functions are stubs that immediately return failure (`errno = ENOTSUP`). On systems with actual SELinux support, the real `libselinux` implementation replaces these stubs at link time. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.selinux.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.selinux.md new file mode 100644 index 00000000000..c36f824ab1a --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/selinux/.selinux.md @@ -0,0 +1,52 @@ + +A generated stub header that provides a no-op replacement for `` on platforms where SELinux is not available, part of the GNU portability library (gnulib). + +## Key Components + +### Types +- `security_class_t` — `unsigned short` alias for SELinux security class identifiers +- `security_context_t` — `char*` alias for SELinux security context strings + +### Macros +- `is_selinux_enabled()` — Always returns `0` (SELinux disabled) +- `SE_SELINUX_INLINE` — Inline function qualifier mapped to `_GL_INLINE` +- `_GL_UNUSED_PARAMETER` — GCC attribute to suppress unused parameter warnings + +### Stub Functions (all return `-1` with `errno = ENOTSUP`) + +| Function | Purpose | +|---|---| +| `getcon` / `freecon` | Get/free process security context | +| `getfscreatecon` / `setfscreatecon` | Get/set filesystem creation context | +| `getfilecon` / `lgetfilecon` / `fgetfilecon` | Get file security context (path, symlink, fd) | +| `setfilecon` / `lsetfilecon` / `fsetfilecon` | Set file security context (path, symlink, fd) | +| `matchpathcon` | Match path to expected context | +| `security_check_context` / `security_check_context_raw` | Validate a context string | +| `setexeccon` | Set exec security context | +| `security_compute_create` | Compute a new context for object creation | +| `string_to_security_class` | Convert class name to `security_class_t` | +| `matchpathcon_init_prefix` | Initialize path context matching | + +## Usage Example + +This header is used automatically via the build system — no direct inclusion is needed: + +```c +/* config.h must be included before this header */ +#include +#include /* resolves to this stub on non-SELinux platforms */ + +void label_file(const char *path) { + security_context_t ctx; + + if (!is_selinux_enabled()) + return; /* stub always takes this path */ + + if (getfilecon(path, &ctx) == -1) + perror("getfilecon"); /* stub always sets errno = ENOTSUP */ + else + freecon(ctx); +} +``` + +> **Note:** `config.h` must be included before this header, as it depends on `_GL_INLINE_HEADER_BEGIN` being defined. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.stat.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.stat.md new file mode 100644 index 00000000000..7e79e3d4de5 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.stat.md @@ -0,0 +1,47 @@ + +A gnulib-generated compatibility shim that supplements incomplete `sys/stat.h` headers on non-POSIX or partial platforms (e.g., native Windows), providing missing file status constants, function declarations, and C++ namespace aliases. + +## Key Components + +| Component | Description | +|---|---| +| `_GL_FUNCDECL_RPL` | Declares a gnulib replacement function (`rpl_func`) with the given prototype | +| `_GL_FUNCDECL_SYS` | Declares the original system function with the given prototype | +| `_GL_CXXALIAS_RPL` | Creates a `GNULIB_NAMESPACE::func` C++ alias redirecting to `rpl_func` | +| `_GL_CXXALIAS_SYS` | Creates a `GNULIB_NAMESPACE::func` C++ alias redirecting to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` | Like `_GL_CXXALIAS_RPL` but uses `reinterpret_cast` for mismatched signatures | +| `_GL_CXXALIAS_SYS_CAST` | Like `_GL_CXXALIAS_SYS` but uses `reinterpret_cast` for mismatched signatures | +| `_GL_EXTERN_C` | Expands to `extern "C"` in C++ contexts, plain `extern` otherwise | +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes the `GNULIB_NAMESPACE` namespace in C++ builds | + +## Usage Example + +This header is consumed automatically by the build system — not included directly. The macros follow three standard patterns: + +```c +/* Pattern 1: Substitute for a missing function */ +#if GNULIB_FCHMODAT +# if !HAVE_FCHMODAT +_GL_FUNCDECL_SYS (fchmodat, int, + (int fd, const char *file, mode_t mode, int flag)); +# endif +_GL_CXXALIAS_SYS (fchmodat, int, + (int fd, const char *file, mode_t mode, int flag)); +_GL_CXXALIASWARN (fchmodat); +#endif + +/* Pattern 2: Replace a broken system function */ +#if GNULIB_LSTAT +# if REPLACE_LSTAT +# undef lstat +# define lstat rpl_lstat +_GL_FUNCDECL_RPL (lstat, int, (const char *name, struct stat *buf)); +_GL_CXXALIAS_RPL (lstat, int, (const char *name, struct stat *buf)); +# else +_GL_CXXALIAS_SYS (lstat, int, (const char *name, struct stat *buf)); +# endif +_GL_CXXALIASWARN (lstat); +#endif +``` + +> **Note:** This file is auto-generated by gnulib. Do not edit it manually — regenerate via the gnulib `update-copyright` / `gnulib-tool` workflow instead. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.time.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.time.md new file mode 100644 index 00000000000..3dafea2d1c9 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.time.md @@ -0,0 +1,46 @@ + +A gnulib-generated compatibility header that supplements the system `sys/time.h` with a more complete, portable interface — handling platform gaps on Cygwin, MSVC/Windows, and BSD systems. + +## Key Components + +### Include Guard & Platform Handling +- `_GL_SYS_TIME_H` — double-inclusion guard with split structure to support `include_next` +- Guards for Cygwin (`_CYGWIN_SYS_TIME_H`), BSD (`_SYS_TIME_H`), and MSVC (`_MSC_VER`) platforms +- Conditionally pulls in `` on Windows for `struct timeval` + +### C++/Namespace Compatibility Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Wraps declarations in `GNULIB_NAMESPACE` when defined | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ builds, `extern` in C | +| `_GL_FUNCDECL_RPL(func, rettype, params)` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, rettype, params)` | Declares the original system function | +| `_GL_CXXALIAS_RPL(func, rettype, params)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, rettype, params)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_RPL_CAST_1` / `_GL_CXXALIAS_SYS_CAST` | Cast variants to suppress C++ conversion errors | + +## Usage Example + +```c +/* Typical gnulib pattern for replacing a broken platform function */ + +#if GNULIB_GETTIMEOFDAY +# if REPLACE_GETTIMEOFDAY +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef gettimeofday +# define gettimeofday rpl_gettimeofday +# endif + _GL_FUNCDECL_RPL (gettimeofday, int, + (struct timeval *tv, void *tz)); + _GL_CXXALIAS_RPL (gettimeofday, int, + (struct timeval *tv, void *tz)); +# else + _GL_CXXALIAS_SYS (gettimeofday, int, + (struct timeval *tv, void *tz)); +# endif + _GL_CXXALIASWARN (gettimeofday); +#endif +``` + +> **Note:** This file is auto-generated by gnulib — do not edit directly. Modifications should be made to the gnulib module configuration and regenerated. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.types.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.types.md new file mode 100644 index 00000000000..2d1eaa13115 --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.types.md @@ -0,0 +1,30 @@ + +Auto-generated gnulib compatibility header that supplements the system `sys/types.h` with more complete POSIX type definitions, particularly for Windows/MinGW environments and Large File Support (LFS). + +## Key Components + +| Macro / Type | Purpose | +|---|---| +| `off_t` | Overridden to 64-bit (`__int64` or `long long`) when LFS is enabled on Windows | +| `dev_t` / `rpl_dev_t` | Optional 64-bit device type replacement for Windows distinguishable inode support | +| `ino_t` / `rpl_ino_t` | Optional 64-bit (or 128-bit struct) inode type replacement for Windows | +| `_GL_WINDOWS_64_BIT_OFF_T` | Internal gnulib indicator that 64-bit `off_t` override is active | +| `_GL_WINDOWS_STAT_INODES` | Internal gnulib indicator for Windows inode mode | +| `_GL_SYS_TYPES_H` | Double-inclusion guard | + +## Usage Example + +This header is consumed transparently by the build system — not included directly by application code. It wraps the system `sys/types.h` via `include_next`: + +```c +/* The preprocessor resolves this to the gnulib wrapper automatically + when gnulib is present in the include path. */ +#include + +/* After inclusion, off_t behaves as 64-bit on Windows with LFS enabled, + matching POSIX semantics on Linux/macOS. */ +off_t file_offset = 0; +ssize_t bytes_read = read(fd, buf, sizeof(buf)); +``` + +> **Note:** This file is auto-generated by gnulib — do not edit manually. Configuration is controlled by the gnulib module selection at build time, not by modifying this header. \ No newline at end of file diff --git a/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.wait.md b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.wait.md new file mode 100644 index 00000000000..180e5dc9a5b --- /dev/null +++ b/libraries/cmake/source/augeas/gnulib/generated/macos/x86_64/lib/sys/.wait.md @@ -0,0 +1,43 @@ + +Auto-generated POSIX-compatible `` replacement header from GNU gnulib, providing cross-platform process waiting macros and C++/namespace compatibility infrastructure on systems (particularly Windows) where the native header is absent or incomplete. + +## Key Components + +### Platform Compatibility Guard +- Conditionally includes the native `` via `include_next` on non-Windows platforms (excludes Win32 unless Cygwin) +- Pulls in `` to ensure `pid_t` availability + +### C++ Namespace Macros (`_GL_CXXDEFS_H`) + +| Macro | Purpose | +|---|---| +| `_GL_BEGIN_NAMESPACE` / `_GL_END_NAMESPACE` | Opens/closes `GNULIB_NAMESPACE` when defined | +| `_GL_EXTERN_C` | Emits `extern "C"` in C++ builds, `extern` otherwise | +| `_GL_FUNCDECL_RPL(func, ...)` | Declares a gnulib replacement function as `rpl_func` | +| `_GL_FUNCDECL_SYS(func, ...)` | Declares the system function with a given prototype | +| `_GL_CXXALIAS_RPL(func, ...)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to `rpl_func` | +| `_GL_CXXALIAS_SYS(func, ...)` | Creates a `GNULIB_NAMESPACE::func` alias pointing to the system function | +| `_GL_CXXALIAS_*_CAST` variants | Same as above but silences C++ type-conversion errors via `reinterpret_cast` | + +## Usage Example + +```c +/* Using gnulib waitpid replacement (if configured) */ +#include /* resolves to this gnulib header */ +#include + +int main(void) { + pid_t pid; + int status; + + /* waitpid declared via _GL_FUNCDECL_SYS or _GL_FUNCDECL_RPL */ + pid = waitpid(-1, &status, WNOHANG); + + if (WIFEXITED(status)) { + int exit_code = WEXITSTATUS(status); + } + return 0; +} +``` + +> **Note:** This file is auto-generated by gnulib — do not edit directly. Modify the gnulib module configuration instead and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/aarch64/include/aws/common/.config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/aarch64/include/aws/common/.config.md new file mode 100644 index 00000000000..1719ce494c5 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/aarch64/include/aws/common/.config.md @@ -0,0 +1,33 @@ + +Compile-time configuration header that exposes compiler and platform feature detection results generated by CMake during the configure phase. + +## Key Components + +| Macro | Status | Description | +|---|---|---| +| `AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS` | ✅ Enabled | GCC built-in overflow-checked arithmetic | +| `AWS_HAVE_GCC_INLINE_ASM` | ✅ Enabled | GCC inline assembly support | +| `AWS_HAVE_MSVC_MULX` | ❌ Disabled | MSVC `_mulx_u64` intrinsic (Windows only) | +| `AWS_HAVE_POSIX_LARGE_FILE_SUPPORT` | ✅ Enabled | POSIX large file (`off64_t`, `fopen64`) support | +| `AWS_HAVE_EXECINFO` | ✅ Enabled | `execinfo.h` stack trace support | +| `AWS_HAVE_WINAPI_DESKTOP` | ❌ Disabled | Windows Desktop API availability | + +## Usage Example + +These macros are consumed internally by `aws-c-common` inline functions — not intended for direct use. Typical internal pattern: + +```c +#include + +#ifdef AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS + /* Use __builtin_mul_overflow for safe arithmetic */ + if (__builtin_mul_overflow(a, b, &result)) { + return AWS_OP_ERR; + } +#else + /* Fallback portable overflow check */ + result = a * b; +#endif +``` + +> **Note:** This file is auto-generated by CMake and will be overwritten on reconfigure. Do not edit manually — modify `CMakeLists.txt` feature detection logic instead. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/x86_64/include/aws/common/.config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/x86_64/include/aws/common/.config.md new file mode 100644 index 00000000000..20a52406184 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/linux/x86_64/include/aws/common/.config.md @@ -0,0 +1,36 @@ + +Exposes compile-time feature detection results generated by CMake during the configure phase for the `aws-common` library. + +## Key Components + +| Macro | Status | Description | +|---|---|---| +| `AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS` | ✅ Enabled | GCC built-in overflow-checked arithmetic (`__builtin_*_overflow`) | +| `AWS_HAVE_GCC_INLINE_ASM` | ✅ Enabled | GCC inline assembly (`asm` keyword) support | +| `AWS_HAVE_MSVC_MULX` | ❌ Disabled | MSVC `_mulx_u64` intrinsic for wide multiplication | +| `AWS_HAVE_POSIX_LARGE_FILE_SUPPORT` | ✅ Enabled | POSIX large file (`_FILE_OFFSET_BITS=64`) support | +| `AWS_HAVE_EXECINFO` | ✅ Enabled | `execinfo.h` stack trace support | +| `AWS_HAVE_WINAPI_DESKTOP` | ❌ Disabled | Windows Desktop API availability | + +## Usage Example + +These macros gate platform-specific code paths in `aws-c-common` internals: + +```c +#include + +#ifdef AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS + /* Use GCC built-in overflow-safe addition */ + if (__builtin_add_overflow(a, b, &result)) { + return AWS_OP_ERR; + } +#else + /* Fallback: manual overflow check */ + if (b > (SIZE_MAX - a)) { + return AWS_OP_ERR; + } + result = a + b; +#endif +``` + +> **Note:** These macros are an **implementation detail** of the aws-c-common build system. Do not rely on them in application code — they can change between releases without notice. Values are set at CMake configure time and will differ across platforms and toolchains. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/aarch64/include/aws/common/.config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/aarch64/include/aws/common/.config.md new file mode 100644 index 00000000000..373bd0d6995 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/aarch64/include/aws/common/.config.md @@ -0,0 +1,32 @@ + +Exposes compile-time feature detection results generated by CMake, indicating which compiler and platform capabilities are available for use by the `aws-common` library internals. + +## Key Components + +| Macro | Status | Description | +|---|---|---| +| `AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS` | Disabled | GCC overflow math extension support | +| `AWS_HAVE_GCC_INLINE_ASM` | **Enabled** | GCC inline assembly support | +| `AWS_HAVE_MSVC_MULX` | Disabled | MSVC `_mulx_u64` intrinsic support | +| `AWS_HAVE_POSIX_LARGE_FILE_SUPPORT` | **Enabled** | POSIX large file (`LFS`) support | +| `AWS_HAVE_EXECINFO` | **Enabled** | `execinfo.h` stack trace support | +| `AWS_HAVE_WINAPI_DESKTOP` | Disabled | Windows Desktop API support | + +## Usage Example + +```c +#include + +#ifdef AWS_HAVE_GCC_INLINE_ASM + /* Use GCC inline assembly code path */ + __asm__ volatile ("nop"); +#else + /* Fallback implementation */ +#endif + +#ifdef AWS_HAVE_POSIX_LARGE_FILE_SUPPORT + /* Safe to use off64_t, fopen64, etc. */ +#endif +``` + +> **Note:** These macros are considered implementation details of the `aws-common` library. Do not depend on them directly in application code — they may change between builds or versions based on the detected host platform and compiler. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/x86_64/include/aws/common/.config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/x86_64/include/aws/common/.config.md new file mode 100644 index 00000000000..29787524ba1 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/macos/x86_64/include/aws/common/.config.md @@ -0,0 +1,40 @@ + +Exposes compile-time feature detection results generated by CMake, indicating which compiler and platform capabilities are available for the `aws-common` library. + +## Key Components + +| Macro | Status | Description | +|---|---|---| +| `AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS` | ✅ Enabled | GCC built-in overflow-checked arithmetic | +| `AWS_HAVE_GCC_INLINE_ASM` | ✅ Enabled | GCC inline assembly support | +| `AWS_HAVE_MSVC_MULX` | ❌ Disabled | MSVC `_mulx_u64` intrinsic | +| `AWS_HAVE_POSIX_LARGE_FILE_SUPPORT` | ✅ Enabled | POSIX large file (64-bit offset) support | +| `AWS_HAVE_EXECINFO` | ✅ Enabled | `execinfo.h` stack trace support | +| `AWS_HAVE_WINAPI_DESKTOP` | ❌ Disabled | Windows Desktop API availability | + +## Usage Example + +These macros gate platform-specific code paths in `aws-c-common` internals: + +```c +#include + +#ifdef AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS + /* Use GCC overflow-safe addition */ + if (__builtin_add_overflow(a, b, &result)) { + return AWS_OP_ERR; + } +#else + /* Fallback to manual overflow check */ + if (b > (SIZE_MAX - a)) { + return AWS_OP_ERR; + } + result = a + b; +#endif + +#ifdef AWS_HAVE_GCC_INLINE_ASM + /* Use inline assembly for optimized path */ +#endif +``` + +> **Note:** This file is auto-generated by CMake at configure time. Do not edit manually — changes will be overwritten on the next `cmake` run. Treat all macros here as implementation details subject to change. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/aarch64/include/aws/common/.config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/aarch64/include/aws/common/.config.md new file mode 100644 index 00000000000..e003b5cd08f --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/aarch64/include/aws/common/.config.md @@ -0,0 +1,33 @@ + +Compile-time configuration header that exposes CMake feature detection results as preprocessor macros for the `aws-common` library. + +## Key Components + +| Macro | Description | +|---|---| +| `AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS` | GCC overflow math extension support | +| `AWS_HAVE_GCC_INLINE_ASM` | GCC inline assembly support | +| `AWS_HAVE_MSVC_MULX` | MSVC `_mulx_u64` intrinsic support | +| `AWS_HAVE_POSIX_LARGE_FILE_SUPPORT` | POSIX large file (64-bit offset) support | +| `AWS_HAVE_EXECINFO` | `execinfo.h` / backtrace support | +| `AWS_HAVE_WINAPI_DESKTOP` | Windows Desktop API availability (enabled) | + +## Usage Example + +```c +#include + +#ifdef AWS_HAVE_GCC_INLINE_ASM + /* Use GCC inline assembly path */ + __asm__ volatile("" ::: "memory"); +#elif defined(AWS_HAVE_WINAPI_DESKTOP) + /* Use Windows API path */ + MemoryBarrier(); +#endif +``` + +## Notes + +- **Do not modify manually** — this file is auto-generated by CMake at configure time; edits will be overwritten on reconfigure. +- Only `AWS_HAVE_WINAPI_DESKTOP` is active in this build; all other capability macros are disabled (commented-out `#undef`). +- Macros are intentionally treated as **implementation details** and may change between releases without notice. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/x86_64/include/aws/common/.config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/x86_64/include/aws/common/.config.md new file mode 100644 index 00000000000..895f3e70fe8 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-c-common/windows/x86_64/include/aws/common/.config.md @@ -0,0 +1,35 @@ + +Exposes compile-time feature detection results generated by CMake during the configuration phase for the AWS Common C library. + +## Key Components + +| Macro | Description | +|---|---| +| `AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS` | GCC overflow math builtins available | +| `AWS_HAVE_GCC_INLINE_ASM` | GCC inline assembly support available | +| `AWS_HAVE_MSVC_MULX` | MSVC `_mulx_u64` intrinsic available | +| `AWS_HAVE_POSIX_LARGE_FILE_SUPPORT` | POSIX large file (`LFS`) support available | +| `AWS_HAVE_EXECINFO` | `execinfo.h` backtracing support available | +| `AWS_HAVE_WINAPI_DESKTOP` | Windows Desktop API available *(enabled in this build)* | + +## Usage Example + +```c +#include + +#ifdef AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS + // Use GCC overflow-safe arithmetic builtins + int result; + if (__builtin_add_overflow(a, b, &result)) { + // handle overflow + } +#elif defined(AWS_HAVE_WINAPI_DESKTOP) + // Fall back to Windows-specific implementation + result = windows_safe_add(a, b); +#else + // Generic fallback + result = a + b; +#endif +``` + +> **Note:** These macros are an **implementation detail** of the AWS C Common library. Do not rely on their names or values remaining stable across builds or versions — they are set automatically by CMake and reflect the capabilities of the current build environment. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.SDKConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.SDKConfig.md new file mode 100644 index 00000000000..a1a477a734b --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.SDKConfig.md @@ -0,0 +1,22 @@ + +Configuration header that controls AWS SDK memory management behavior by defining or undefining the `USE_AWS_MEMORY_MANAGEMENT` preprocessor macro. + +## Key Components + +- **`USE_AWS_MEMORY_MANAGEMENT`** — Preprocessor macro (currently undefined/disabled) that toggles custom AWS memory management. When enabled, the SDK uses its own allocator hooks instead of standard `malloc`/`free`. + +## Usage Example + +```c +#include "SDKConfig.h" + +// When USE_AWS_MEMORY_MANAGEMENT is defined, use AWS allocators +#ifdef USE_AWS_MEMORY_MANAGEMENT + Aws::Utils::Memory::InitializeAWSMemorySystem(allocator); +#endif +``` + +## Notes + +- This file is typically **auto-generated** by CMake during the build configuration step. +- To enable custom memory management, change `#undef` to `#define` or set the corresponding CMake flag (`-DUSE_AWS_MEMORY_MANAGEMENT=ON`). \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.VersionConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.VersionConfig.md new file mode 100644 index 00000000000..064f072ffba --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/aarch64/aws/core/.VersionConfig.md @@ -0,0 +1,25 @@ + +Defines the AWS C++ SDK version constants used for compile-time version identification and compatibility checks. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_SDK_VERSION_STRING` | `"1.9.116"` | Full version as a string literal | +| `AWS_SDK_VERSION_MAJOR` | `1` | Major version number | +| `AWS_SDK_VERSION_MINOR` | `9` | Minor version number | +| `AWS_SDK_VERSION_PATCH` | `116` | Patch version number | + +## Usage Example + +```c +#include "VersionConfig.h" + +/* Compile-time version check */ +#if AWS_SDK_VERSION_MAJOR == 1 && AWS_SDK_VERSION_MINOR >= 9 + /* Use features available in 1.9+ */ +#endif + +/* Runtime version logging */ +printf("AWS SDK Version: %s\n", AWS_SDK_VERSION_STRING); +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.SDKConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.SDKConfig.md new file mode 100644 index 00000000000..08f50843f28 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.SDKConfig.md @@ -0,0 +1,28 @@ + +Compile-time configuration header for the AWS C SDK, generated during the CMake build process to define memory management and other SDK-wide feature flags. + +## Key Components + +| Macro | Default | Description | +|---|---|---| +| `USE_AWS_MEMORY_MANAGEMENT` | Undefined | When defined, enables custom AWS memory management hooks instead of standard `malloc`/`free` | + +## Usage Example + +```c +#include + +#ifdef USE_AWS_MEMORY_MANAGEMENT + // Custom allocator path + aws_mem_acquire(allocator, size); +#else + // Standard allocation path + malloc(size); +#endif +``` + +> This file is auto-generated by CMake — do not edit manually. To enable `USE_AWS_MEMORY_MANAGEMENT`, set the corresponding CMake option at build time: + +```bash +cmake -DUSE_AWS_MEMORY_MANAGEMENT=ON .. +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.VersionConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.VersionConfig.md new file mode 100644 index 00000000000..52d06ed9968 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/linux/x86_64/aws/core/.VersionConfig.md @@ -0,0 +1,33 @@ + +Defines compile-time version constants for the AWS C++ SDK, providing both a human-readable version string and individual numeric components. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_SDK_VERSION_STRING` | `"1.9.116"` | Full version as a string literal | +| `AWS_SDK_VERSION_MAJOR` | `1` | Major version number | +| `AWS_SDK_VERSION_MINOR` | `9` | Minor version number | +| `AWS_SDK_VERSION_PATCH` | `116` | Patch version number | + +## Usage Example + +```c +#include "VersionConfig.h" +#include + +/* Print full version string */ +printf("AWS SDK Version: %s\n", AWS_SDK_VERSION_STRING); + +/* Conditional compilation based on version */ +#if AWS_SDK_VERSION_MAJOR == 1 && AWS_SDK_VERSION_MINOR >= 9 + /* Use features available in 1.9+ */ +#endif + +/* Runtime version check */ +if (AWS_SDK_VERSION_PATCH >= 116) { + /* Patch-specific logic */ +} +``` + +> **Note:** These macros are set at build time and reflect SDK version `1.9.116`. When upgrading the SDK, verify this header is regenerated to match the new release. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.SDKConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.SDKConfig.md new file mode 100644 index 00000000000..ea08b6d5e90 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.SDKConfig.md @@ -0,0 +1,20 @@ + +Defines compile-time configuration for the AWS C SDK, specifically controlling whether custom AWS memory management is enabled. + +## Key Components + +- `USE_AWS_MEMORY_MANAGEMENT` — Preprocessor macro that, when defined, enables AWS custom memory management hooks. Currently undefined (disabled) in this configuration. + +## Usage Example + +```c +#include + +#ifdef USE_AWS_MEMORY_MANAGEMENT + // Custom allocator path + aws_mem_acquire(allocator, size); +#else + // Standard malloc path + malloc(size); +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.VersionConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.VersionConfig.md new file mode 100644 index 00000000000..aca738ffe16 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/aarch64/aws/core/.VersionConfig.md @@ -0,0 +1,25 @@ + +Defines the AWS SDK for C++ version constants used for compile-time version identification and compatibility checks. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_SDK_VERSION_STRING` | `"1.9.116"` | Full version as a string literal | +| `AWS_SDK_VERSION_MAJOR` | `1` | Major version number | +| `AWS_SDK_VERSION_MINOR` | `9` | Minor version number | +| `AWS_SDK_VERSION_PATCH` | `116` | Patch version number | + +## Usage Example + +```c +#include "VersionConfig.h" + +// Runtime version check +printf("AWS SDK Version: %s\n", AWS_SDK_VERSION_STRING); + +// Compile-time compatibility guard +#if AWS_SDK_VERSION_MAJOR != 1 || AWS_SDK_VERSION_MINOR < 9 + #error "Unsupported AWS SDK version. Requires >= 1.9.x" +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.SDKConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.SDKConfig.md new file mode 100644 index 00000000000..52eaa7c6229 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.SDKConfig.md @@ -0,0 +1,27 @@ + +Compile-time configuration header for the AWS C SDK, controlling optional build-time features via preprocessor defines. + +## Key Components + +| Macro | Default | Description | +|---|---|---| +| `USE_AWS_MEMORY_MANAGEMENT` | Undefined | Enables custom AWS memory management hooks when defined | + +## Usage Example + +```c +/* Check if custom memory management is active */ +#ifdef USE_AWS_MEMORY_MANAGEMENT + /* AWS allocator overrides are in effect */ + aws_mem_acquire(allocator, size); +#else + /* Standard malloc/free in use */ + malloc(size); +#endif +``` + +## Notes + +- Generated by CMake at build time — do not edit manually +- To enable `USE_AWS_MEMORY_MANAGEMENT`, pass `-DUSE_AWS_MEMORY_MANAGEMENT` during the CMake configure step +- Consumed internally by the AWS C SDK; rarely referenced directly in application code \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.VersionConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.VersionConfig.md new file mode 100644 index 00000000000..3e83c9bfd57 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/macos/x86_64/aws/core/.VersionConfig.md @@ -0,0 +1,26 @@ + +Defines version constants for the AWS C++ SDK, providing both a human-readable version string and individual numeric components for programmatic version checks. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_SDK_VERSION_STRING` | `"1.9.116"` | Full version as a string literal | +| `AWS_SDK_VERSION_MAJOR` | `1` | Major version number | +| `AWS_SDK_VERSION_MINOR` | `9` | Minor version number | +| `AWS_SDK_VERSION_PATCH` | `116` | Patch version number | + +## Usage Example + +```c +#include "VersionConfig.h" +#include + +// Print full version string +printf("AWS SDK Version: %s\n", AWS_SDK_VERSION_STRING); + +// Compile-time version guard +#if AWS_SDK_VERSION_MAJOR == 1 && AWS_SDK_VERSION_MINOR >= 9 + // Use features available in 1.9+ +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.SDKConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.SDKConfig.md new file mode 100644 index 00000000000..515ec6ab033 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.SDKConfig.md @@ -0,0 +1,21 @@ + +Compile-time configuration header for the AWS C SDK that controls memory management behavior via preprocessor definitions. + +## Key Components + +| Macro | Description | +|---|---| +| `USE_AWS_MEMORY_MANAGEMENT` | When defined, enables custom AWS memory management. Currently disabled (`#undef`). | + +## Usage Example + +```c +#include + +// The macro is undefined by default, meaning standard allocators are used. +// To enable custom memory management, define the macro before including SDK headers: +#define USE_AWS_MEMORY_MANAGEMENT +#include +``` + +> **Note:** This file is typically auto-generated by CMake during the AWS SDK build process. Modify the CMake configuration rather than editing this file directly. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.VersionConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.VersionConfig.md new file mode 100644 index 00000000000..4e7672c302d --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/aarch64/aws/core/.VersionConfig.md @@ -0,0 +1,25 @@ + +Defines the AWS C++ SDK version constants used across the SDK for version identification and compatibility checks. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_SDK_VERSION_STRING` | `"1.9.116"` | Full version as a string literal | +| `AWS_SDK_VERSION_MAJOR` | `1` | Major version number | +| `AWS_SDK_VERSION_MINOR` | `9` | Minor version number | +| `AWS_SDK_VERSION_PATCH` | `116` | Patch version number | + +## Usage Example + +```c +#include "VersionConfig.h" + +/* Check full version string */ +printf("AWS SDK Version: %s\n", AWS_SDK_VERSION_STRING); + +/* Compile-time version guard */ +#if AWS_SDK_VERSION_MAJOR == 1 && AWS_SDK_VERSION_MINOR >= 9 + /* Use features available in 1.9+ */ +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.SDKConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.SDKConfig.md new file mode 100644 index 00000000000..d38f4d8447d --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.SDKConfig.md @@ -0,0 +1,25 @@ + +Defines the compile-time configuration flags for the AWS C SDK, controlling optional features like custom memory management. + +## Key Components + +| Macro | Default | Description | +|---|---|---| +| `USE_AWS_MEMORY_MANAGEMENT` | Undefined (disabled) | Enables AWS custom memory allocator hooks when defined | + +## Usage Example + +```c +// Check if custom memory management is active +#include + +#ifdef USE_AWS_MEMORY_MANAGEMENT + // Custom allocator path + aws_mem_acquire(allocator, size); +#else + // Standard malloc path + malloc(size); +#endif +``` + +> **Note:** This file is auto-generated by CMake during the build process. Edit `CMakeLists.txt` or pass `-DUSE_AWS_MEMORY_MANAGEMENT=ON` at configure time rather than modifying this header directly. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.VersionConfig.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.VersionConfig.md new file mode 100644 index 00000000000..ca249806318 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-cpp-sdk-core/windows/x86_64/aws/core/.VersionConfig.md @@ -0,0 +1,27 @@ + +Defines the AWS C++ SDK version constants for compile-time version identification and compatibility checks. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_SDK_VERSION_STRING` | `"1.9.116"` | Full version as a string literal | +| `AWS_SDK_VERSION_MAJOR` | `1` | Major version number | +| `AWS_SDK_VERSION_MINOR` | `9` | Minor version number | +| `AWS_SDK_VERSION_PATCH` | `116` | Patch version number | + +## Usage Example + +```c +#include "VersionConfig.h" + +/* Runtime version check */ +printf("AWS SDK Version: %s\n", AWS_SDK_VERSION_STRING); + +/* Compile-time compatibility guard */ +#if AWS_SDK_VERSION_MAJOR != 1 || AWS_SDK_VERSION_MINOR < 9 + #error "Unsupported AWS SDK version. Requires >= 1.9.x" +#endif +``` + +> **Note:** This file is auto-generated during the AWS SDK build process. Do not edit manually — version values are updated via the SDK's CMake configuration pipeline. \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/aarch64/aws/crt/.Config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/aarch64/aws/crt/.Config.md new file mode 100644 index 00000000000..57077beb921 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/aarch64/aws/crt/.Config.md @@ -0,0 +1,17 @@ + +Defines the AWS CRT C++ SDK version string as a compile-time constant, used for version identification and compatibility checks. + +## Key Components + +| Symbol | Type | Value | +|--------|------|-------| +| `AWS_CRT_CPP_VERSION` | `#define` macro | `"1.9.116"` | + +## Usage Example + +```c +#include + +// Access the version string at compile time +printf("AWS CRT C++ Version: %s\n", AWS_CRT_CPP_VERSION); +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/x86_64/aws/crt/.Config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/x86_64/aws/crt/.Config.md new file mode 100644 index 00000000000..ac5b629e888 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/linux/x86_64/aws/crt/.Config.md @@ -0,0 +1,18 @@ + +Defines the AWS CRT C++ library version string as a compile-time constant. + +## Key Components + +| Macro | Value | Description | +|-------|-------|-------------| +| `AWS_CRT_CPP_VERSION` | `"1.9.116"` | Current semantic version of the AWS CRT C++ SDK | + +## Usage Example + +```c +#include + +// Access the version string at compile time +const char* version = AWS_CRT_CPP_VERSION; +printf("AWS CRT C++ Version: %s\n", version); +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/aarch64/aws/crt/.Config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/aarch64/aws/crt/.Config.md new file mode 100644 index 00000000000..f2dedfd3bdf --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/aarch64/aws/crt/.Config.md @@ -0,0 +1,18 @@ + +Defines the AWS CRT C++ SDK version string as a compile-time constant, used for version identification and compatibility checks across the SDK. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_CRT_CPP_VERSION` | `"1.9.116"` | Current SDK version string | + +## Usage Example + +```c +#include + +// Access the version string at compile time +const char* version = AWS_CRT_CPP_VERSION; +printf("AWS CRT C++ SDK Version: %s\n", version); +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/x86_64/aws/crt/.Config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/x86_64/aws/crt/.Config.md new file mode 100644 index 00000000000..2cb1864b25e --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/macos/x86_64/aws/crt/.Config.md @@ -0,0 +1,23 @@ + +Defines the AWS CRT C++ library version string as a preprocessor macro, providing a single source of truth for the SDK version at compile time. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `AWS_CRT_CPP_VERSION` | `"1.9.116"` | Current version string of the AWS CRT C++ library | + +## Usage Example + +```c +#include "Config.h" + +// Access the version string at compile time +const char* version = AWS_CRT_CPP_VERSION; +printf("AWS CRT C++ Version: %s\n", version); + +// Use in conditional compilation +#ifdef AWS_CRT_CPP_VERSION + // Version-specific logic +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/aarch64/aws/crt/.Config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/aarch64/aws/crt/.Config.md new file mode 100644 index 00000000000..e49f38128f1 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/aarch64/aws/crt/.Config.md @@ -0,0 +1,18 @@ + +Defines the AWS CRT C++ SDK version string as a compile-time constant macro. + +## Key Components + +| Symbol | Type | Value | +|---|---|---| +| `AWS_CRT_CPP_VERSION` | Preprocessor macro | `"1.9.116"` | + +## Usage Example + +```c +#include "Config.h" + +// Access the version string at compile time +const char *version = AWS_CRT_CPP_VERSION; +printf("AWS CRT C++ Version: %s\n", version); +``` \ No newline at end of file diff --git a/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/x86_64/aws/crt/.Config.md b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/x86_64/aws/crt/.Config.md new file mode 100644 index 00000000000..2a9135cc378 --- /dev/null +++ b/libraries/cmake/source/aws-sdk-cpp/config/aws-crt-cpp/windows/x86_64/aws/crt/.Config.md @@ -0,0 +1,19 @@ + +Defines the AWS CRT C++ library version string as a compile-time constant. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `AWS_CRT_CPP_VERSION` | `#define` | Semantic version string for the AWS CRT C++ library | + +## Usage Example + +```c +#include "Config.h" + +// Access the version string at compile time +const char* version = AWS_CRT_CPP_VERSION; +printf("AWS CRT C++ Version: %s\n", version); +// Output: AWS CRT C++ Version: 1.9.116 +``` \ No newline at end of file diff --git a/libraries/cmake/source/dbus/config/aarch64/.config.md b/libraries/cmake/source/dbus/config/aarch64/.config.md new file mode 100644 index 00000000000..ffcbfe6b77c --- /dev/null +++ b/libraries/cmake/source/dbus/config/aarch64/.config.md @@ -0,0 +1,57 @@ + +CMake-generated configuration header for the D-Bus IPC library (version 1.12.24), providing compile-time feature detection, platform identification, and path/symbol availability macros. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"dbus"` | +| `PACKAGE_VERSION` | `"1.12.24"` | +| `PACKAGE_BUGREPORT` | freedesktop.org bug tracker URL | + +### Installation Paths +| Macro | Default Value | +|---|---| +| `DBUS_BINDIR` | `/usr/bin` | +| `DBUS_DATADIR` | `/usr/share` | +| `DBUS_RUNSTATEDIR` | `/var/run` | +| `DBUS_SYSTEM_BUS_DEFAULT_ADDRESS` | `unix:path=/var/run/dbus/system_bus_socket` | +| `DBUS_MACHINE_UUID_FILE` | `/var/lib/dbus/machine-id` | + +### Platform Detection +- `DBUS_UNIX` — defined on Linux/macOS +- `DBUS_WIN` / `DBUS_WIN32` / `DBUS_WINCE` — defined on Windows variants +- MinGW ANSI stdio mode controlled via `__USE_MINGW_ANSI_STDIO` + +### Feature Flags +- `DBUS_ENABLE_ASSERT` — runtime assertions enabled (unless `DBUS_DISABLE_ASSERT` is set) +- `DBUS_ENABLE_CHECKS` — API parameter checks enabled (unless `DBUS_DISABLE_CHECKS` is set) +- `DBUS_VA_COPY` — maps to `va_copy` when available +- `HAVE_UNIX_FD_PASSING` — Unix file descriptor passing supported + +### Available System Headers & Symbols +Presence macros for standard headers (`HAVE_UNISTD_H`, `HAVE_SYS_INOTIFY_H`, etc.) and POSIX symbols (`HAVE_SOCKETPAIR`, `HAVE_WRITEV`, `HAVE_NANOSLEEP`, `HAVE_BACKTRACE`, etc.). + +## Usage Example + +```c +#include "config.h" + +/* Conditional compilation based on platform */ +#ifdef DBUS_UNIX + /* Use Unix domain socket path */ + const char *bus_addr = DBUS_SYSTEM_BUS_DEFAULT_ADDRESS; +#endif + +/* Guard assertions with feature flag */ +#ifdef DBUS_ENABLE_ASSERT + assert(connection != NULL); +#endif + +/* Use portable va_copy */ +#ifdef DBUS_VA_COPY + va_list copy; + DBUS_VA_COPY(copy, args); +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/dbus/config/x86_64/.config.md b/libraries/cmake/source/dbus/config/x86_64/.config.md new file mode 100644 index 00000000000..87c4e35255f --- /dev/null +++ b/libraries/cmake/source/dbus/config/x86_64/.config.md @@ -0,0 +1,54 @@ + +CMake-generated configuration header for the D-Bus IPC library (version 1.12.24), defining build-time package metadata, filesystem paths, platform detection macros, and feature/symbol availability flags. + +## Key Components + +### Package Metadata +- `PACKAGE_NAME`, `PACKAGE_VERSION`, `PACKAGE_STRING` — Identifies the build as `dbus 1.12.24` +- `PACKAGE_BUGREPORT`, `PACKAGE_URL` — Project contact and homepage URLs + +### Filesystem & Runtime Paths +| Macro | Value | +|---|---| +| `DBUS_DATADIR` | `/usr/share` | +| `DBUS_BINDIR` / `DBUS_DAEMONDIR` | `/usr/bin` | +| `DBUS_SYSTEM_BUS_DEFAULT_ADDRESS` | `unix:path=/var/run/dbus/system_bus_socket` | +| `DBUS_MACHINE_UUID_FILE` | `/var/lib/dbus/machine-id` | +| `DBUS_RUNSTATEDIR` | `/var/run` | + +### Platform Detection +- `DBUS_UNIX` — Defined on POSIX systems (Linux/macOS) +- `DBUS_WIN` / `DBUS_WIN32` / `DBUS_WINCE` — Defined on Windows variants +- Windows MSVC compatibility shims: `snprintf → _snprintf`, `strtoll → _strtoi64`, `mode_t` typedef + +### Feature Flags +- `DBUS_ENABLE_ASSERT` — Runtime assertions enabled (unless `DBUS_DISABLE_ASSERT` is set) +- `DBUS_ENABLE_CHECKS` — API parameter checks enabled (unless `DBUS_DISABLE_CHECKS` is set) +- `DBUS_VA_COPY` — Maps to `va_copy` when available +- Disabled: verbose mode, ANSI mode, GLib integration, SELinux, kqueue, X11, gcov + +### Header & Symbol Availability +Standard POSIX headers confirmed present: `unistd.h`, `signal.h`, `sys/poll.h`, `syslog.h`, `sys/inotify.h`, etc. +Key symbols available: `backtrace`, `socketpair`, `writev`, `nanosleep`, `setenv`, `pipe2`, `accept4`, `inotify_init1` + +## Usage Example + +```c +#include "config.h" + +/* Conditional compilation based on platform */ +#ifdef DBUS_UNIX + connect(sock, (struct sockaddr *)&addr, sizeof(addr)); +#endif + +#ifdef DBUS_WIN + /* Windows-specific socket handling */ +#endif + +/* Use resolved paths at runtime */ +const char *system_socket = DBUS_SYSTEM_BUS_DEFAULT_ADDRESS; +/* → "unix:path=/var/run/dbus/system_bus_socket" */ + +/* Assert/check guards are active since neither + * DBUS_DISABLE_ASSERT nor DBUS_DISABLE_CHECKS is set */ +``` \ No newline at end of file diff --git a/libraries/cmake/source/dbus/generated/aarch64/dbus/.dbus-arch-deps.md b/libraries/cmake/source/dbus/generated/aarch64/dbus/.dbus-arch-deps.md new file mode 100644 index 00000000000..162044d620a --- /dev/null +++ b/libraries/cmake/source/dbus/generated/aarch64/dbus/.dbus-arch-deps.md @@ -0,0 +1,49 @@ + +Architecture and compiler-specific type definitions and version constants for the D-Bus IPC library, generated at build time and installed alongside the library headers. + +## Key Components + +### Integer Type Definitions + +| Type | Underlying C Type | +|---|---| +| `dbus_int64_t` | `long` | +| `dbus_uint64_t` | `unsigned long` | +| `dbus_int32_t` | `int` | +| `dbus_uint32_t` | `unsigned int` | +| `dbus_int16_t` | `short` | +| `dbus_uint16_t` | `unsigned short` | + +### 64-bit Integer Macros + +- **`DBUS_HAVE_INT64`** — Always `1`; D-Bus no longer supports platforms without 64-bit integer support. +- **`DBUS_INT64_CONSTANT(val)`** — Appends `L` suffix for signed 64-bit literals. +- **`DBUS_UINT64_CONSTANT(val)`** — Appends `UL` suffix for unsigned 64-bit literals. + +### Version Constants + +- **`DBUS_MAJOR_VERSION`** — `1` +- **`DBUS_MINOR_VERSION`** — `12` +- **`DBUS_MICRO_VERSION`** — `24` +- **`DBUS_VERSION_STRING`** — `"1.12.24"` +- **`DBUS_VERSION`** — Packed integer `(1 << 16) | (12 << 8) | (24)` for compile-time version comparisons. + +## Usage Example + +```c +#include + +/* Compile-time version guard */ +#if DBUS_VERSION < ((1 << 16) | (12 << 8) | (0)) +#error "D-Bus 1.12.0 or newer required" +#endif + +/* Using portable fixed-width types */ +dbus_uint64_t id = DBUS_UINT64_CONSTANT(123456789012345); +dbus_int32_t status = -1; + +/* Runtime version string */ +printf("D-Bus version: %s\n", DBUS_VERSION_STRING); +``` + +> **Note:** Never include this file directly. It is only valid when included transitively via ``, enforced by the `DBUS_INSIDE_DBUS_H` guard at the top of the file. \ No newline at end of file diff --git a/libraries/cmake/source/dbus/generated/x86_64/dbus/.dbus-arch-deps.md b/libraries/cmake/source/dbus/generated/x86_64/dbus/.dbus-arch-deps.md new file mode 100644 index 00000000000..8ced3808800 --- /dev/null +++ b/libraries/cmake/source/dbus/generated/x86_64/dbus/.dbus-arch-deps.md @@ -0,0 +1,48 @@ + +Architecture and compiler-specific type definitions and version constants for the D-Bus IPC library, generated at build time and installed alongside the library headers. + +## Key Components + +### Integer Type Definitions + +| Type | Underlying C Type | +|---|---| +| `dbus_int64_t` | `long` | +| `dbus_uint64_t` | `unsigned long` | +| `dbus_int32_t` | `int` | +| `dbus_uint32_t` | `unsigned int` | +| `dbus_int16_t` | `short` | +| `dbus_uint16_t` | `unsigned short` | + +### Macros + +| Macro | Value / Purpose | +|---|---| +| `DBUS_HAVE_INT64` | `1` — confirms 64-bit integer support | +| `DBUS_INT64_CONSTANT(val)` | Appends `L` suffix for 64-bit signed literals | +| `DBUS_UINT64_CONSTANT(val)` | Appends `UL` suffix for 64-bit unsigned literals | +| `DBUS_MAJOR_VERSION` | `1` | +| `DBUS_MINOR_VERSION` | `12` | +| `DBUS_MICRO_VERSION` | `24` | +| `DBUS_VERSION_STRING` | `"1.12.24"` | +| `DBUS_VERSION` | Packed integer `(1 << 16) | (12 << 8) | 24` | + +## Usage Example + +```c +#include + +/* Compile-time version guard */ +#if DBUS_VERSION < ((1 << 16) | (12 << 8) | (0)) +#error "D-Bus 1.12.0 or newer required" +#endif + +/* Using portable integer types */ +dbus_uint64_t timestamp = DBUS_UINT64_CONSTANT(1234567890123); +dbus_int32_t status = -1; + +/* Runtime version check string */ +printf("D-Bus version: %s\n", DBUS_VERSION_STRING); +``` + +> **Note:** Never include this header directly. It is guarded to only be included via ``. Direct inclusion will trigger a compile-time error. \ No newline at end of file diff --git a/libraries/cmake/source/expat/config/aarch64/.expat_config.md b/libraries/cmake/source/expat/config/aarch64/.expat_config.md new file mode 100644 index 00000000000..b7fa7c890c8 --- /dev/null +++ b/libraries/cmake/source/expat/config/aarch64/.expat_config.md @@ -0,0 +1,57 @@ + +Compile-time configuration header for the **expat 2.6.0** XML parsing library, generated via CMake to capture platform capabilities and feature flags for the current build target. + +## Key Components + +### Byte Order +- `BYTEORDER 1234` — Little-endian target platform (`4321` = big-endian) +- `WORDS_BIGENDIAN` — Undefined (little-endian confirmed) + +### Available System Headers & Functions +| Macro | Description | +|---|---| +| `HAVE_DLFCN_H` | Dynamic linking support | +| `HAVE_FCNTL_H` | File control operations | +| `HAVE_MMAP` | Memory-mapped file I/O | +| `HAVE_GETPAGESIZE` | Page size query function | +| `HAVE_SYSCALL_GETRANDOM` | Kernel entropy via `SYS_getrandom` | +| `HAVE_STDINT_H` / `HAVE_INTTYPES_H` | Standard integer types | + +### Entropy Sources +- `HAVE_SYSCALL_GETRANDOM` — Enabled (kernel syscall path) +- `HAVE_ARC4RANDOM`, `HAVE_GETRANDOM`, `XML_DEV_URANDOM` — All disabled/undefined + +### XML Feature Flags +| Macro | Value | Description | +|---|---|---| +| `XML_CONTEXT_BYTES` | `1024` | Parse error context window size | +| `XML_GE` | `1` | General entity support enabled | +| `XML_DTD` | undefined | DTD/parameter entity parsing disabled | +| `XML_NS` | undefined | XML Namespace support disabled | +| `XML_ATTR_INFO` | undefined | Attribute byte-offset retrieval disabled | + +### Package Metadata +- `PACKAGE_STRING` — `"expat 2.6.0"` +- `PACKAGE_BUGREPORT` — `https://github.com/libexpat/libexpat/issues` + +## Usage Example + +```c +#include "expat_config.h" +#include + +/* Conditional compilation based on detected capabilities */ +#ifdef HAVE_MMAP + /* Use memory-mapped I/O for large XML files */ + parse_with_mmap(file_path); +#else + parse_with_read(file_path); +#endif + +#if XML_GE + /* General entity handling is available */ + XML_SetDefaultHandler(parser, default_handler); +#endif +``` + +> **Note:** This file is auto-generated by CMake — do not edit manually. Modify `expat_config.h.cmake` and re-run the build configuration step to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/expat/config/x86_64/.expat_config.md b/libraries/cmake/source/expat/config/x86_64/.expat_config.md new file mode 100644 index 00000000000..30c885b0c2b --- /dev/null +++ b/libraries/cmake/source/expat/config/x86_64/.expat_config.md @@ -0,0 +1,55 @@ + +Configuration header for the Expat XML parser library (v2.6.0), generated via CMake. Defines platform capabilities, system header availability, byte order, and XML feature flags for the build environment. + +## Key Components + +### Byte Order & Endianness +- `BYTEORDER 1234` — Little-endian target platform +- `WORDS_BIGENDIAN` — Undefined (big-endian mode disabled) + +### System Header Availability +| Macro | Header | +|---|---| +| `HAVE_DLFCN_H` | `` | +| `HAVE_FCNTL_H` | `` | +| `HAVE_STDINT_H` | `` | +| `HAVE_UNISTD_H` | `` | +| `HAVE_SYS_STAT_H` | `` | + +### Entropy / Randomness Sources +- `HAVE_SYSCALL_GETRANDOM` — Enabled (Linux `getrandom` syscall) +- `HAVE_ARC4RANDOM` / `HAVE_ARC4RANDOM_BUF` — Disabled +- `XML_DEV_URANDOM` — Disabled (non-Windows only guard included) + +### XML Feature Flags +- `XML_CONTEXT_BYTES 1024` — Parse error context window size +- `XML_GE 1` — General entity support enabled +- `XML_DTD` — DTD/parameter entity parsing disabled +- `XML_NS` — XML Namespace support disabled +- `XML_ATTR_INFO` — Attribute byte-offset retrieval disabled + +### Package Metadata +- `PACKAGE_STRING "expat 2.6.0"` +- `PACKAGE_BUGREPORT` — Points to GitHub Issues + +## Usage Example + +```c +#include "expat_config.h" +#include + +/* Conditional compilation based on config flags */ +#ifdef HAVE_UNISTD_H +# include +#endif + +#if XML_GE + /* General entity processing is available */ + XML_Parser parser = XML_ParserCreate(NULL); +#endif + +/* Check context bytes for error reporting */ +printf("Context window: %d bytes\n", XML_CONTEXT_BYTES); +``` + +> **Note:** This file is auto-generated by CMake and should not be edited manually. To change feature flags, modify the CMake build configuration and regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/gflags/.gflags_completions.md b/libraries/cmake/source/gflags/generated/gflags/.gflags_completions.md new file mode 100644 index 00000000000..3a30a39de5d --- /dev/null +++ b/libraries/cmake/source/gflags/generated/gflags/.gflags_completions.md @@ -0,0 +1,41 @@ + +Provides the interface for bash-style tab completion of command-line flags in programs using the gflags library. + +## Key Components + +- **`HandleCommandLineCompletions()`** — Core function that intercepts program startup to process the `--tab_completion_word` flag. If the flag is set, it resolves completions and terminates the process (similar to `--helpshort` behavior). + +## Usage Example + +```c +#include + +int main(int argc, char* argv[]) { + google::ParseCommandLineFlags(&argc, &argv, true); + + // Call early after flag initialization + gflags::HandleCommandLineCompletions(); + + // ... rest of program startup +} +``` + +**Bash setup** (add to `.bashrc`): + +```bash +complete -o bashdefault -o default -o nospace -C \ + '/path/to/gflags_completions.sh --tab_completion_columns $COLUMNS' \ + my_binary another_binary +``` + +## Completion Search Modes + +| Suffix | Behavior | +|--------|----------| +| `--foo` | Flags prefixed with `foo` | +| `--foo?` | Flags containing `foo` in name | +| `--foo??` | Also searches module definition paths | +| `--foo???` | Also searches flag descriptions | +| `--foo+` | Force exhaustive match list | + +> **Note:** Output is trimmed by default for readability. Each completion line includes the flag's default value and a truncated description, width-controlled by `--tab_completion_columns`. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/linux/aarch64/private/.defines.md b/libraries/cmake/source/gflags/generated/linux/aarch64/private/.defines.md new file mode 100644 index 00000000000..9d8bbd51f9d --- /dev/null +++ b/libraries/cmake/source/gflags/generated/linux/aarch64/private/.defines.md @@ -0,0 +1,47 @@ + +Internal build configuration header generated by CMake that captures platform feature detection results for the `gflags` library. Not part of the public API — consumed only by internal translation units. + +## Key Components + +| Define | Status | Purpose | +|---|---|---| +| `OS_WINDOWS` | ❌ undefined | Windows OS target detection | +| `HAVE_STDINT_H` | ✅ defined | `` availability | +| `HAVE_SYS_TYPES_H` | ✅ defined | `` availability | +| `HAVE_INTTYPES_H` | ✅ defined | `` availability | +| `HAVE_SYS_STAT_H` | ✅ defined | `` availability | +| `HAVE_UNISTD_H` | ✅ defined | `` availability | +| `HAVE_FNMATCH_H` | ✅ defined | `` availability | +| `HAVE_SHLWAPI_H` | ❌ undefined | Windows `` availability | +| `HAVE_STRTOLL` | ✅ defined | `strtoll()` function availability | +| `HAVE_STRTOQ` | ❌ undefined | BSD `strtoq()` function availability | +| `HAVE_PTHREAD` | ✅ defined | POSIX thread support | +| `HAVE_RWLOCK` | ✅ defined | `pthread_rwlock_t` type availability | + +## Usage Example + +Conditional compilation using the generated defines: + +```c +#include "defines.h" + +#ifdef HAVE_STDINT_H + #include +#endif + +#ifdef HAVE_PTHREAD + #include + #ifdef HAVE_RWLOCK + pthread_rwlock_t my_lock; + #endif +#endif + +// Portable 64-bit string parsing +#ifdef HAVE_STRTOLL + long long val = strtoll(str, NULL, 10); +#elif defined(HAVE_STRTOQ) + long long val = strtoq(str, NULL, 10); +#endif +``` + +> **Bazel note:** When building with Bazel, these defines are injected via `-D` compiler flags instead. This file is intentionally excluded from the Bazel build to avoid private files appearing in `$(GENDIR)`. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags.md b/libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags.md new file mode 100644 index 00000000000..d933257a73b --- /dev/null +++ b/libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags.md @@ -0,0 +1,65 @@ + +Brief header defining the Google gflags command-line flag parsing library interface, providing macros and functions to declare, define, validate, and programmatically manipulate typed command-line flags in C++ programs. + +## Key Components + +### Flag Definition Macros +- `DEFINE_bool`, `DEFINE_int32`, `DEFINE_string`, etc. — Declares a named flag with a default value and help text, exposing it as `FLAGS_` +- `DECLARE_bool`, `DECLARE_int32`, etc. — Forward-declares a flag defined elsewhere (imported from `gflags_declare.h`) +- `DEFINE_validator(name, fn)` — Convenience macro to register a validation function for a flag + +### Validation +- `RegisterFlagValidator()` — Overloaded for all supported types (`bool`, `int32`, `uint32`, `int64`, `uint64`, `double`, `string`). Attaches a validator called on parse or `SetCommandLineOption` + +### Flag Introspection +- `CommandLineFlagInfo` — Struct holding flag metadata: name, type, description, current/default value, filename, validator presence, and raw pointer +- `GetAllFlags()` — Returns all registered flags sorted by file +- `GetCommandLineOption()` / `GetCommandLineFlagInfo()` / `GetCommandLineFlagInfoOrDie()` — Programmatic flag lookup by name + +### Argv & Program Info +- `SetArgv()`, `GetArgv()`, `GetArgv0()`, `GetArgvs()` — Store/retrieve program arguments +- `ProgramInvocationName()`, `ProgramInvocationShortName()` — Return argv0 or its basename +- `ProgramUsage()`, `VersionString()` — Return strings set by `SetUsageMessage()` / `SetVersionString()` + +### Flag Setting Modes +- `FlagSettingMode` enum — Controls update behavior: `SET_FLAGS_VALUE`, `SET_FLAG_IF_DEFAULT`, `SET_FLAGS_DEFAULT` + +## Usage Example + +```c +#include "gflags/gflags.h" + +// Define flags with defaults and help text +DEFINE_int32(port, 8080, "Port to listen on"); +DEFINE_string(host, "localhost", "Host address"); +DEFINE_bool(verbose, false, "Enable verbose output"); + +// Optional: attach a validator +static bool ValidatePort(const char* name, int32 value) { + return value > 0 && value < 65536; +} +DEFINE_validator(port, &ValidatePort); + +int main(int argc, char** argv) { + // Parse flags from argv; modifies argc/argv to remove recognized flags + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_verbose) { + printf("Connecting to %s:%d\n", FLAGS_host.c_str(), FLAGS_port); + } + + // Programmatic flag lookup + std::string val; + if (gflags::GetCommandLineOption("host", &val)) { + printf("host = %s\n", val.c_str()); + } + return 0; +} +``` + +```bash +# Runtime usage +./myapp --host=example.com --port=9090 --verbose +./myapp --noverbose --port=443 +./myapp --help # prints all registered flags +``` \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags_declare.md b/libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags_declare.md new file mode 100644 index 00000000000..9675570906a --- /dev/null +++ b/libraries/cmake/source/gflags/generated/linux/aarch64/public/gflags/.gflags_declare.md @@ -0,0 +1,49 @@ + +Provides forward declarations for gflags command-line flag variables, enabling flag access across translation units without including the full gflags library. + +## Key Components + +### Macros — DLL Import/Export +| Macro | Purpose | +|---|---| +| `GFLAGS_IS_A_DLL` | Toggles DLL vs. static linkage (default: `0`) | +| `GFLAGS_DLL_DECL` | Applies `dllimport` / GCC visibility to library symbols | +| `GFLAGS_DLL_DECLARE_FLAG` | Applies `dllimport` / GCC visibility to user flag variables | + +### Type Aliases (`namespace gflags`) +| Alias | Underlying Type | +|---|---| +| `int32` / `uint32` | `int32_t` / `uint32_t` | +| `int64` / `uint64` | `int64_t` / `uint64_t` | + +### Declaration Macros +| Macro | Type Declared | +|---|---| +| `DECLARE_bool(name)` | `bool` | +| `DECLARE_int32(name)` | `gflags::int32` | +| `DECLARE_uint32(name)` | `gflags::uint32` | +| `DECLARE_int64(name)` | `gflags::int64` | +| `DECLARE_uint64(name)` | `gflags::uint64` | +| `DECLARE_double(name)` | `double` | +| `DECLARE_string(name)` | `std::string` (via `fLS::clstring`) | + +## Usage Example + +Declare flags defined elsewhere and use them in the current translation unit: + +```cpp +#include + +// Declare flags defined in another .cc file +DECLARE_bool(verbose); +DECLARE_int32(port); +DECLARE_string(host); + +void connect() { + if (FLAGS_verbose) { + printf("Connecting to %s:%d\n", FLAGS_host.c_str(), FLAGS_port); + } +} +``` + +> **Note:** `DECLARE_*` only makes a flag visible — use `DEFINE_*` (from `gflags.h`) in exactly one `.cc` file to define and register the flag with its default value and help text. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/linux/x86_64/private/.defines.md b/libraries/cmake/source/gflags/generated/linux/x86_64/private/.defines.md new file mode 100644 index 00000000000..6b5289efed1 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/linux/x86_64/private/.defines.md @@ -0,0 +1,47 @@ + +Build-time generated configuration header that captures platform capability detection results from CMake for internal use by the gflags library. + +## Key Components + +### Platform Detection + +| Define | Status | Purpose | +|--------|--------|---------| +| `OS_WINDOWS` | ❌ undefined | MS Windows build target | +| `HAVE_STDINT_H` | ✅ defined | `` availability | +| `HAVE_SYS_TYPES_H` | ✅ defined | `` availability | +| `HAVE_INTTYPES_H` | ✅ defined | `` availability | +| `HAVE_SYS_STAT_H` | ✅ defined | `` availability | +| `HAVE_UNISTD_H` | ✅ defined | `` availability | +| `HAVE_FNMATCH_H` | ✅ defined | `` availability | +| `HAVE_SHLWAPI_H` | ❌ undefined | Windows `` availability | +| `HAVE_STRTOLL` | ✅ defined | `strtoll` function availability | +| `HAVE_STRTOQ` | ❌ undefined | `strtoq` function availability | +| `HAVE_PTHREAD` | ✅ defined | POSIX threads support | +| `HAVE_RWLOCK` | ✅ defined | `pthread_rwlock_t` type availability | + +## Usage Example + +```c +// Internal conditional compilation based on detected capabilities +#include "defines.h" + +#ifdef HAVE_PTHREAD + #include + #ifdef HAVE_RWLOCK + pthread_rwlock_t flags_lock; + #endif +#endif + +#ifdef HAVE_STRTOLL + long long val = strtoll(str, &end, 10); +#elif defined(HAVE_STRTOQ) + long long val = strtoq(str, &end, 10); +#endif +``` + +## Notes + +- **Internal only** — not part of the public gflags API +- **CMake only** — when building with Bazel, these defines are passed via `-D` compiler flags instead; this file is excluded to avoid artifacts in `$(GENDIR)` +- Generated from `defines.h.in` during the CMake configure step; do not edit manually \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags.md b/libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags.md new file mode 100644 index 00000000000..498d06b2e44 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags.md @@ -0,0 +1,61 @@ + +Google's gflags (Google Flags) command-line flag parsing library header. Provides macros and APIs for declaring, defining, parsing, and validating command-line flags in C++ programs. + +## Key Components + +### Flag Definition Macros +- `DEFINE_bool`, `DEFINE_int32`, `DEFINE_uint32`, `DEFINE_int64`, `DEFINE_uint64`, `DEFINE_double`, `DEFINE_string` — define typed command-line flags with default values and help text +- `DECLARE_*` variants — forward-declare flags defined in other translation units +- `DEFINE_validator(name, fn)` — convenience macro to register a flag validator + +### Validator Registration +- `RegisterFlagValidator(flag*, validate_fn)` — overloaded for all supported types; called automatically during `ParseCommandLineFlags` and `SetCommandLineOption` + +### Flag Introspection +- `CommandLineFlagInfo` — struct holding name, type, description, current/default values, filename, and validation state +- `GetAllFlags(vector*)` — retrieve metadata for all registered flags +- `GetCommandLineOption(name, string*)` — programmatically read a flag value +- `GetCommandLineFlagInfo(name, CommandLineFlagInfo*)` — retrieve full flag metadata +- `GetCommandLineFlagInfoOrDie(name)` — same, exits if flag not found + +### Program Info Utilities +- `SetArgv` / `GetArgv` / `GetArgvs` — store and retrieve raw argv +- `ProgramInvocationName` / `ProgramInvocationShortName` — argv0 helpers +- `ProgramUsage` / `VersionString` — usage/version string accessors + +### Flag Setting Modes (`FlagSettingMode`) +- `SET_FLAGS_VALUE` — unconditional update +- `SET_FLAG_IF_DEFAULT` — update only if not previously set +- `SET_FLAGS_DEFAULT` — update the default value + +## Usage Example + +```c +#include "gflags/gflags.h" + +// Define flags +DEFINE_int32(port, 8080, "Port to listen on"); +DEFINE_string(host, "localhost", "Host to bind to"); +DEFINE_bool(verbose, false, "Enable verbose output"); + +// Register a validator +static bool ValidatePort(const char* name, int32_t value) { + if (value > 0 && value < 65536) return true; + fprintf(stderr, "Invalid --%s: %d\n", name, value); + return false; +} +DEFINE_validator(port, &ValidatePort); + +int main(int argc, char** argv) { + gflags::SetUsageMessage("My server application"); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_verbose) { + printf("Starting on %s:%d\n", FLAGS_host.c_str(), FLAGS_port); + } + return 0; +} +// Run: ./server --host=0.0.0.0 --port=9090 --verbose +``` + +> **Thread Safety Note:** Flag definition macros and `SetArgv` are thread-hostile and must be called before threads spawn. Programmatic getters (`GetCommandLineOption`, etc.) are thread-safe. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags_declare.md b/libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags_declare.md new file mode 100644 index 00000000000..5fc174dd991 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/linux/x86_64/public/gflags/.gflags_declare.md @@ -0,0 +1,47 @@ + +Header file for declaring (not defining) gflags command-line flags in external translation units, enabling cross-file flag access without redefinition. + +## Key Components + +### Macros + +| Macro | Purpose | +|---|---| +| `DECLARE_bool(name)` | Declares an external `bool` flag | +| `DECLARE_int32(name)` | Declares an external `int32` flag | +| `DECLARE_uint32(name)` | Declares an external `uint32` flag | +| `DECLARE_int64(name)` | Declares an external `int64` flag | +| `DECLARE_uint64(name)` | Declares an external `uint64` flag | +| `DECLARE_double(name)` | Declares an external `double` flag | +| `DECLARE_string(name)` | Declares an external `string` flag | +| `GFLAGS_DLL_DECL` | DLL import/export visibility decorator | +| `GFLAGS_DLL_DECLARE_FLAG` | DLL decorator for flag variable declarations | + +### Type Aliases (inside `gflags` namespace) + +Platform-portable fixed-width integers: `int32`, `uint32`, `int64`, `uint64` — resolved via C99 ``, BSD ``, or Windows intrinsics. + +## Usage Example + +```cpp +// file_a.cc — defines the flag +#include +DEFINE_bool(verbose, false, "Enable verbose output"); + +// file_b.cc — declares and uses the flag defined in file_a.cc +#include + +DECLARE_bool(verbose); +DECLARE_int32(max_retries); +DECLARE_string(config_path); + +void run() { + if (FLAGS_verbose) { + printf("Config: %s, Retries: %d\n", + FLAGS_config_path.c_str(), + FLAGS_max_retries); + } +} +``` + +> **Note:** Use `DECLARE_*` only to reference flags defined elsewhere with `DEFINE_*`. Including `gflags_declare.h` is lighter than the full `gflags.h` — prefer it in headers to minimize compilation dependencies. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/macos/aarch64/private/.defines.md b/libraries/cmake/source/gflags/generated/macos/aarch64/private/.defines.md new file mode 100644 index 00000000000..ca2f24910ce --- /dev/null +++ b/libraries/cmake/source/gflags/generated/macos/aarch64/private/.defines.md @@ -0,0 +1,45 @@ + +Internal build configuration header generated by CMake that provides platform and feature detection macros for the gflags library. + +## Key Components + +| Macro | Status | Purpose | +|---|---|---| +| `OS_WINDOWS` | undefined | Windows OS targeting | +| `HAVE_STDINT_H` | defined | `` availability | +| `HAVE_SYS_TYPES_H` | defined | `` availability | +| `HAVE_INTTYPES_H` | defined | `` availability | +| `HAVE_SYS_STAT_H` | defined | `` availability | +| `HAVE_UNISTD_H` | defined | `` availability | +| `HAVE_FNMATCH_H` | defined | `` availability | +| `HAVE_SHLWAPI_H` | undefined | Windows `` availability | +| `HAVE_STRTOLL` | defined | `strtoll()` function availability | +| `HAVE_STRTOQ` | undefined | `strtoq()` function availability | +| `HAVE_PTHREAD` | defined | POSIX threads availability | +| `HAVE_RWLOCK` | defined | `pthread_rwlock_t` type availability | + +## Usage Example + +```c +// Typical internal conditional compilation pattern within gflags +#include "defines.h" + +#ifdef HAVE_STDINT_H + #include +#endif + +#ifdef HAVE_PTHREAD + #include + #ifdef HAVE_RWLOCK + static pthread_rwlock_t flags_lock = PTHREAD_RWLOCK_INITIALIZER; + #endif +#endif + +#ifdef OS_WINDOWS + #include +#else + #include +#endif +``` + +> **Note:** This file is **internal only** — not part of the public gflags API. When building with Bazel, these macros are passed via `-D` compiler flags instead, and this file is not used. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags.md b/libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags.md new file mode 100644 index 00000000000..de050389ca3 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags.md @@ -0,0 +1,68 @@ + +Google's gflags (Google Flags) command-line flag parsing library header. Provides macros and APIs for declaring, defining, parsing, and validating typed command-line flags in C++ programs. + +## Key Components + +### Flag Definition Macros +- `DEFINE_bool`, `DEFINE_int32`, `DEFINE_uint32`, `DEFINE_int64`, `DEFINE_uint64`, `DEFINE_double`, `DEFINE_string` — define a named flag with default value and help text +- `DECLARE_*` — declare a flag defined in another translation unit + +### Validator Registration +- `RegisterFlagValidator()` — overloaded for all supported types; registers a validation function called on parse or `SetCommandLineOption` +- `DEFINE_validator(name, fn)` — convenience macro for validator registration + +### Flag Inspection +- `CommandLineFlagInfo` — struct holding name, type, description, current/default value, filename, validator status, and raw pointer +- `GetAllFlags()` — retrieves all registered flags sorted by file +- `GetCommandLineOption()` / `GetCommandLineFlagInfo()` / `GetCommandLineFlagInfoOrDie()` — programmatic flag lookup + +### Program Metadata +- `SetArgv()`, `GetArgv()`, `GetArgv0()`, `ProgramInvocationName()`, `ProgramInvocationShortName()` +- `ProgramUsage()`, `VersionString()` + +### Flag Setting Modes (`FlagSettingMode`) +- `SET_FLAGS_VALUE` — always update +- `SET_FLAG_IF_DEFAULT` — update only if not yet explicitly set +- `SET_FLAGS_DEFAULT` — update the default value + +## Usage Example + +```c +#include "gflags/gflags.h" + +// Validate that port is in a valid range +static bool ValidatePort(const char* name, int32_t value) { + return value > 0 && value < 65536; +} + +// Define flags +DEFINE_int32(port, 8080, "Port to listen on"); +DEFINE_string(host, "localhost", "Host to connect to"); +DEFINE_bool(verbose, false, "Enable verbose output"); + +// Register validator +DEFINE_validator(port, &ValidatePort); + +int main(int argc, char** argv) { + // Parse flags from command line + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_verbose) { + printf("Connecting to %s:%d\n", FLAGS_host.c_str(), FLAGS_port); + } + + // Programmatic flag access + std::string value; + if (gflags::GetCommandLineOption("host", &value)) { + printf("Host flag: %s\n", value.c_str()); + } + + return 0; +} +``` + +```bash +# Command-line usage +./myapp --port=9090 --host=example.com --verbose +./myapp --noverbose --port=443 +``` \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags_declare.md b/libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags_declare.md new file mode 100644 index 00000000000..89bc620aa2c --- /dev/null +++ b/libraries/cmake/source/gflags/generated/macos/aarch64/public/gflags/.gflags_declare.md @@ -0,0 +1,49 @@ + +Header file for declaring (but not defining) gflags command-line flag variables, enabling cross-translation-unit access to flags defined elsewhere. + +## Key Components + +### Macros — DLL Import/Export +| Macro | Purpose | +|---|---| +| `GFLAGS_IS_A_DLL` | Indicates if gflags is built as a shared library (DLL) | +| `GFLAGS_DLL_DECL` | Visibility attribute for gflags library symbols | +| `GFLAGS_DLL_DECLARE_FLAG` | Visibility attribute for user-declared flag variables | + +### Type Definitions (`GFLAGS_NAMESPACE`) +| Typedef | Underlying Type | +|---|---| +| `int32` / `uint32` | `int32_t` / `uint32_t` | +| `int64` / `uint64` | `int64_t` / `uint64_t` | + +### Declaration Macros +| Macro | Type Declared | +|---|---| +| `DECLARE_bool(name)` | `bool` | +| `DECLARE_int32(name)` | `int32` | +| `DECLARE_uint32(name)` | `uint32` | +| `DECLARE_int64(name)` | `int64` | +| `DECLARE_uint64(name)` | `uint64` | +| `DECLARE_double(name)` | `double` | +| `DECLARE_string(name)` | `std::string` (via `fLS::clstring`) | + +Each macro expands via `DECLARE_VARIABLE`, placing the extern symbol into a type-specific namespace (`fLB`, `fLI`, `fLD`, `fLS`, etc.) and importing it with `using`. + +## Usage Example + +```cpp +// file_a.cc — defines the flag +#include +DEFINE_int32(port, 8080, "Server port number"); + +// file_b.cc — declares (references) the flag defined elsewhere +#include +DECLARE_int32(port); + +void StartServer() { + // FLAGS_port is now accessible in this translation unit + Listen(FLAGS_port); +} +``` + +> **Note:** Use `DECLARE_*` macros (this header) when you need to *reference* a flag across files. Use `DEFINE_*` macros (from `gflags.h`) only once where the flag is *defined*. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/macos/x86_64/private/.defines.md b/libraries/cmake/source/gflags/generated/macos/x86_64/private/.defines.md new file mode 100644 index 00000000000..9cd1ce0b894 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/macos/x86_64/private/.defines.md @@ -0,0 +1,44 @@ + +Internal CMake-generated configuration header that records platform capability detection results for the gflags library build system. + +## Key Components + +### Platform Detection Macros + +| Macro | Status | Purpose | +|---|---|---| +| `OS_WINDOWS` | Undefined | MS Windows build target | +| `HAVE_STDINT_H` | ✅ Defined | `` availability | +| `HAVE_SYS_TYPES_H` | ✅ Defined | `` availability | +| `HAVE_INTTYPES_H` | ✅ Defined | `` availability | +| `HAVE_SYS_STAT_H` | ✅ Defined | `` availability | +| `HAVE_UNISTD_H` | ✅ Defined | `` availability | +| `HAVE_FNMATCH_H` | ✅ Defined | `` availability | +| `HAVE_SHLWAPI_H` | Undefined | Windows `` | +| `HAVE_STRTOLL` | ✅ Defined | `strtoll()` function present | +| `HAVE_STRTOQ` | Undefined | BSD `strtoq()` function | +| `HAVE_PTHREAD` | ✅ Defined | POSIX threads available | +| `HAVE_RWLOCK` | ✅ Defined | `pthread_rwlock_t` type present | + +## Usage Example + +These macros are consumed internally by gflags source files to conditionally compile platform-specific code: + +```c +// Internal gflags usage pattern +#include "defines.h" + +#ifdef HAVE_PTHREAD + #include + // use pthread_rwlock_t for thread safety + #ifdef HAVE_RWLOCK + pthread_rwlock_t flags_lock; + #endif +#endif + +#ifdef HAVE_STDINT_H + #include +#endif +``` + +> **Note:** This file is **not part of the public API**. It is generated from `defines.h.in` by CMake at configure time. When building with Bazel, equivalent definitions are passed via `-D` compiler flags instead, and this file is not used. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags.md b/libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags.md new file mode 100644 index 00000000000..5a3565042f8 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags.md @@ -0,0 +1,67 @@ + +Google's gflags (Google Flags) command-line flag parsing library header. Provides macros and functions for declaring, defining, parsing, and validating command-line flags in C++ programs. + +## Key Components + +### Flag Definition & Declaration Macros +- `DEFINE_bool`, `DEFINE_int32`, `DEFINE_string`, etc. — Define typed flags with default values and descriptions +- `DECLARE_bool`, `DECLARE_int32`, etc. — Declare flags defined in other translation units +- `DEFINE_validator(name, fn)` — Register a validator function for a named flag + +### Validator Registration +- `RegisterFlagValidator(flag*, validate_fn)` — Overloaded for all supported types (`bool`, `int32`, `uint32`, `int64`, `uint64`, `double`, `std::string`). Returns `false` if the flag pointer is invalid or a validator is already registered. + +### Flag Introspection +- `CommandLineFlagInfo` — Struct holding flag metadata: name, type, description, current/default values, filename, validator presence, and raw pointer +- `GetAllFlags(vector*)` — Retrieve metadata for all registered flags +- `GetCommandLineOption(name, output*)` — Fetch a flag's string value by name +- `GetCommandLineFlagInfo(name, output*)` — Fetch full metadata by name +- `GetCommandLineFlagInfoOrDie(name)` — Same but calls `exit()` if not found + +### Program Info Utilities +- `SetArgv()`, `GetArgv()`, `GetArgv0()`, `GetArgvs()` — Store and retrieve program argument data +- `ProgramInvocationName()`, `ProgramInvocationShortName()` — Get argv0 variants +- `ProgramUsage()`, `VersionString()` — Retrieve usage/version strings + +### Flag Setting Modes +`FlagSettingMode` enum controls `SetCommandLineOption` behavior: +- `SET_FLAGS_VALUE` — Always update +- `SET_FLAG_IF_DEFAULT` — Update only if not yet explicitly set +- `SET_FLAGS_DEFAULT` — Update the default value + +## Usage Example + +```c +#include "gflags/gflags.h" + +// Define flags +DEFINE_int32(port, 8080, "Port to listen on"); +DEFINE_string(host, "localhost", "Host address"); +DEFINE_bool(verbose, false, "Enable verbose output"); + +// Optional: register a validator +static bool ValidatePort(const char* name, int32 value) { + return value > 0 && value < 65536; +} +DEFINE_validator(port, &ValidatePort); + +// Declare a flag from another file +DECLARE_bool(debug_mode); + +int main(int argc, char** argv) { + // Parse all command-line flags + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_verbose) { + printf("Listening on %s:%d\n", FLAGS_host.c_str(), FLAGS_port); + } + return 0; +} +``` + +```bash +# Runtime usage +./myapp --port=9090 --host=0.0.0.0 --verbose +./myapp --noverbose --port=443 +./myapp --help +``` \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags_declare.md b/libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags_declare.md new file mode 100644 index 00000000000..b30b86f2e93 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/macos/x86_64/public/gflags/.gflags_declare.md @@ -0,0 +1,53 @@ + +Provides forward declarations for gflags command-line flag variables, allowing translation units to reference flags defined elsewhere without including the full gflags library. + +## Key Components + +### Macros — DLL Visibility +| Macro | Purpose | +|---|---| +| `GFLAGS_DLL_DECL` | Controls symbol export/import for the gflags library itself | +| `GFLAGS_DLL_DECLARE_FLAG` | Controls symbol visibility for user-declared flag variables | +| `GFLAGS_IS_A_DLL` | Toggle (0/1) indicating whether gflags was built as a shared library | + +### Type Aliases (`namespace gflags`) +| Alias | Underlying Type | +|---|---| +| `int32` / `uint32` | `int32_t` / `uint32_t` | +| `int64` / `uint64` | `int64_t` / `uint64_t` | + +### Declaration Macros +| Macro | Declares flag of type | +|---|---| +| `DECLARE_bool(name)` | `bool` | +| `DECLARE_int32(name)` | `gflags::int32` | +| `DECLARE_uint32(name)` | `gflags::uint32` | +| `DECLARE_int64(name)` | `gflags::int64` | +| `DECLARE_uint64(name)` | `gflags::uint64` | +| `DECLARE_double(name)` | `double` | +| `DECLARE_string(name)` | `std::string` (via `fLS::clstring`) | + +Each macro expands to an `extern` declaration inside a typed namespace (`fLB`, `fLI`, `fLS`, etc.) and pulls the symbol into the current scope via `using`. + +## Usage Example + +```cpp +// file_a.cc — defines the flag +#include +DEFINE_bool(verbose, false, "Enable verbose output"); + +// file_b.cc — references the flag defined in file_a.cc +#include + +DECLARE_bool(verbose); // extern declaration; no ODR violation +DECLARE_int32(num_workers); // reference an int32 flag from another TU +DECLARE_string(config_path); + +void run() { + if (FLAGS_verbose) { + // use FLAGS_num_workers, FLAGS_config_path ... + } +} +``` + +> **Best practice:** Include `gflags_declare.h` in headers that reference flags, and reserve `gflags.h` (with `DEFINE_*`) for `.cc` files only. This minimises compilation dependencies. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/windows/aarch64/private/.defines.md b/libraries/cmake/source/gflags/generated/windows/aarch64/private/.defines.md new file mode 100644 index 00000000000..982c3b1c88f --- /dev/null +++ b/libraries/cmake/source/gflags/generated/windows/aarch64/private/.defines.md @@ -0,0 +1,41 @@ + +Generated from `defines.h.in` by CMake at build time, this internal header captures platform and feature detection results for the `gflags` library on a Windows build target. + +## Key Components + +| Macro | Status | Purpose | +|---|---|---| +| `OS_WINDOWS` | ✅ Defined | Targets Microsoft Windows platform | +| `HAVE_STDINT_H` | ✅ Defined | `` is available | +| `HAVE_SYS_TYPES_H` | ✅ Defined | `` is available | +| `HAVE_INTTYPES_H` | ✅ Defined | `` is available | +| `HAVE_SYS_STAT_H` | ✅ Defined | `` is available | +| `HAVE_SHLWAPI_H` | ✅ Defined | Windows `` is available | +| `HAVE_UNISTD_H` | ❌ Undefined | POSIX header absent (Windows) | +| `HAVE_FNMATCH_H` | ❌ Undefined | POSIX fnmatch absent (Windows) | +| `HAVE_STRTOLL` | ❌ Undefined | `strtoll` function unavailable | +| `HAVE_STRTOQ` | ❌ Undefined | `strtoq` function unavailable | +| `HAVE_PTHREAD` | ❌ Undefined | pthreads not in use | +| `HAVE_RWLOCK` | ❌ Undefined | `pthread_rwlock_t` unavailable | + +## Usage Example + +```c +// Conditional compilation based on detected platform features +#include "defines.h" + +#ifdef OS_WINDOWS + #include // Safe — HAVE_SHLWAPI_H is defined +#endif + +#ifdef HAVE_STDINT_H + #include +#endif + +#ifndef HAVE_STRTOLL + // Provide a fallback implementation for strtoll on this platform + long long strtoll_fallback(const char *str, char **end, int base); +#endif +``` + +> **Note:** This file is **not part of the public API**. When building with Bazel, these macros are passed via `-D` compiler flags instead, and this file is excluded from `$(GENDIR)` to avoid leaking private build artifacts. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags.md b/libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags.md new file mode 100644 index 00000000000..2cf838d96d7 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags.md @@ -0,0 +1,69 @@ + +Google's command-line flag processing library header that provides macros and APIs for defining, declaring, parsing, and managing typed command-line flags in C++ programs. + +## Key Components + +### Flag Definition Macros +- `DEFINE_bool`, `DEFINE_int32`, `DEFINE_uint32`, `DEFINE_int64`, `DEFINE_uint64`, `DEFINE_double`, `DEFINE_string` — define a named flag with a default value and help text +- `DECLARE_*` — declare a flag defined in another translation unit (via `gflags_declare.h`) + +### Validator Registration +- `RegisterFlagValidator(flag*, validate_fn)` — registers a validation callback for a flag; overloaded for all supported types +- `DEFINE_validator(name, validator)` — convenience macro wrapping `RegisterFlagValidator` + +### Flag Introspection +- `CommandLineFlagInfo` — struct holding name, type, description, current/default value, filename, and validator state +- `GetAllFlags(vector*)` — returns info for all registered flags +- `GetCommandLineOption(name, output*)` — retrieves a flag's value by name as a string +- `GetCommandLineFlagInfo(name, output*)` — retrieves full `CommandLineFlagInfo` by name +- `GetCommandLineFlagInfoOrDie(name)` — same, but exits if flag not found + +### Program Metadata +- `SetArgv` / `GetArgv` / `GetArgv0` / `GetArgvs()` — store and retrieve `argv` +- `ProgramInvocationName()` / `ProgramInvocationShortName()` — program name utilities +- `ProgramUsage()` / `VersionString()` — usage and version string accessors + +### Flag Setting Modes (`FlagSettingMode`) +- `SET_FLAGS_VALUE` — unconditionally update flag value +- `SET_FLAG_IF_DEFAULT` — update only if not yet explicitly set + +## Usage Example + +```c +#include "gflags/gflags.h" + +// Define flags with defaults and help text +DEFINE_int32(port, 8080, "Port to listen on"); +DEFINE_string(host, "localhost", "Host address"); +DEFINE_bool(verbose, false, "Enable verbose output"); + +// Register a validator +static bool ValidatePort(const char* name, int32 value) { + return value > 0 && value < 65536; +} +DEFINE_validator(port, &ValidatePort); + +// Declare a flag defined elsewhere +DECLARE_bool(debug_mode); + +int main(int argc, char** argv) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_verbose) { + printf("Listening on %s:%d\n", FLAGS_host.c_str(), FLAGS_port); + } + + // Programmatic flag access + std::string val; + if (gflags::GetCommandLineOption("port", &val)) { + printf("port flag = %s\n", val.c_str()); + } + + return 0; +} +``` + +```bash +./myapp --port=9090 --host=example.com --verbose +./myapp --noverbose --port=443 +``` \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags_declare.md b/libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags_declare.md new file mode 100644 index 00000000000..160ac55e484 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/windows/aarch64/public/gflags/.gflags_declare.md @@ -0,0 +1,52 @@ + +Provides forward declarations for gflags command-line flag variables, allowing other translation units to reference flags defined elsewhere without including the full gflags library. + +## Key Components + +### Macros — DLL Import/Export +| Macro | Purpose | +|---|---| +| `GFLAGS_IS_A_DLL` | Set to `1` when linking against a shared gflags DLL on Windows | +| `GFLAGS_DLL_DECL` | Applies `dllimport` (MSVC) or `visibility("default")` (GCC 4+) | +| `GFLAGS_DLL_DECLARE_FLAG` | Same visibility rules applied to user-declared flag variables | + +### Type Definitions (`GFLAGS_NAMESPACE`) +Portable fixed-width integer typedefs resolved at compile time per platform: + +| Typedef | Platform | +|---|---| +| `int32`, `uint32`, `int64`, `uint64` | Windows (`__int32`, `__int64` variants) | + +### Declaration Macros +| Macro | Declares | +|---|---| +| `DECLARE_bool(name)` | `bool FLAGS_name` | +| `DECLARE_int32(name)` | `int32 FLAGS_name` | +| `DECLARE_uint32(name)` | `uint32 FLAGS_name` | +| `DECLARE_int64(name)` | `int64 FLAGS_name` | +| `DECLARE_uint64(name)` | `uint64 FLAGS_name` | +| `DECLARE_double(name)` | `double FLAGS_name` | +| `DECLARE_string(name)` | `std::string& FLAGS_name` | + +All macros use namespace isolation (`fLB`, `fLI`, `fLS`, etc.) then pull the symbol into the calling scope via `using`. + +## Usage Example + +```cpp +// In a .cpp or .h file that USES a flag defined elsewhere: +#include + +DECLARE_bool(enable_feature); // references FLAGS_enable_feature +DECLARE_int32(max_retries); // references FLAGS_max_retries +DECLARE_string(server_host); // references FLAGS_server_host + +void Connect() { + if (FLAGS_enable_feature) { + for (int i = 0; i < FLAGS_max_retries; ++i) { + // use FLAGS_server_host ... + } + } +} +``` + +> **Note:** Use `DECLARE_*` only in files that *consume* a flag. The flag itself must be defined with `DEFINE_*` (from `gflags.h`) in exactly one translation unit. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/windows/x86_64/private/.defines.md b/libraries/cmake/source/gflags/generated/windows/x86_64/private/.defines.md new file mode 100644 index 00000000000..65716224ae7 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/windows/x86_64/private/.defines.md @@ -0,0 +1,38 @@ + +Generated build configuration header for the `gflags` library on Windows, produced by CMake from `defines.h.in`. It is an **internal-only** file not exposed as part of the public API. + +## Key Components + +| Macro | Status | Purpose | +|---|---|---| +| `OS_WINDOWS` | ✅ Defined | Marks Windows build target | +| `HAVE_STDINT_H` | ✅ Defined | `` available | +| `HAVE_SYS_TYPES_H` | ✅ Defined | `` available | +| `HAVE_INTTYPES_H` | ✅ Defined | `` available | +| `HAVE_SYS_STAT_H` | ✅ Defined | `` available | +| `HAVE_SHLWAPI_H` | ✅ Defined | Windows `` available | +| `HAVE_UNISTD_H` | ❌ Undefined | POSIX header absent on Windows | +| `HAVE_FNMATCH_H` | ❌ Undefined | POSIX pattern matching absent | +| `HAVE_STRTOLL` | ❌ Undefined | `strtoll` function unavailable | +| `HAVE_STRTOQ` | ❌ Undefined | `strtoq` function unavailable | +| `HAVE_PTHREAD` | ❌ Undefined | POSIX threads not detected | +| `HAVE_RWLOCK` | ❌ Undefined | `pthread_rwlock_t` unavailable | + +## Usage Example + +```c +// Internal gflags source usage (not for public consumers) +#include "defines.h" + +#ifdef HAVE_STDINT_H + #include +#endif + +#ifdef OS_WINDOWS + #ifdef HAVE_SHLWAPI_H + #include + #endif +#endif +``` + +> **Note:** When building with Bazel, these macros are injected via `-D` compiler flags instead. This file is CMake-specific and excluded from Bazel's `$(GENDIR)` to avoid leaking internal build artifacts. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags.md b/libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags.md new file mode 100644 index 00000000000..311b09eb386 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags.md @@ -0,0 +1,61 @@ + +Google's gflags command-line flag parsing library header. Provides macros and APIs for declaring, defining, parsing, and validating command-line flags in C++ programs. + +## Key Components + +### Flag Definition Macros +- `DEFINE_bool`, `DEFINE_int32`, `DEFINE_string`, etc. — define flags with name, default value, and help text +- `DECLARE_bool`, `DECLARE_int32`, etc. — forward-declare flags defined in other files +- `DEFINE_validator(name, fn)` — convenience macro to register a validator for a flag + +### Validator Registration +- `RegisterFlagValidator(flag_ptr, validate_fn)` — overloaded for all supported types (`bool`, `int32`, `uint32`, `int64`, `uint64`, `double`, `string`); validator is called on parse and `SetCommandLineOption` + +### `CommandLineFlagInfo` Struct +Describes a single flag's metadata: `name`, `type`, `description`, `current_value`, `default_value`, `filename`, `has_validator_fn`, `is_default`, `flag_ptr` + +### Flag Introspection +- `GetAllFlags(vector*)` — retrieves info on all registered flags +- `GetCommandLineOption(name, output*)` — reads a flag value by name +- `GetCommandLineFlagInfo(name, output*)` — reads full flag metadata by name +- `GetCommandLineFlagInfoOrDie(name)` — same but exits if not found + +### Program Info Utilities +- `SetArgv` / `GetArgv` / `GetArgvs` / `GetArgv0` +- `ProgramInvocationName` / `ProgramInvocationShortName` +- `ProgramUsage` / `VersionString` + +### `FlagSettingMode` Enum +Controls behavior of `SetCommandLineOption`: `SET_FLAGS_VALUE`, `SET_FLAG_IF_DEFAULT`, or set-as-default mode. + +## Usage Example + +```c +#include "gflags/gflags.h" + +// Define flags +DEFINE_int32(port, 8080, "Port to listen on"); +DEFINE_string(host, "localhost", "Host address"); +DEFINE_bool(verbose, false, "Enable verbose output"); + +// Register a validator +static bool ValidatePort(const char* name, int32 value) { + return value > 0 && value < 65536; +} +DEFINE_validator(port, &ValidatePort); + +// Declare a flag defined elsewhere +DECLARE_bool(debug_mode); + +int main(int argc, char** argv) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_verbose) { + printf("Connecting to %s:%d\n", FLAGS_host.c_str(), FLAGS_port); + } + return 0; +} +// CLI usage: ./app --host=example.com --port=9090 --verbose +``` + +> **Thread safety note:** `RegisterFlagValidator`, `SetArgv`, and flag-definition macros are **thread-hostile** and must be called before threads are spawned. `GetCommandLineOption` and flag reads are **thread-safe** after initialization. \ No newline at end of file diff --git a/libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags_declare.md b/libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags_declare.md new file mode 100644 index 00000000000..3a1c3d95369 --- /dev/null +++ b/libraries/cmake/source/gflags/generated/windows/x86_64/public/gflags/.gflags_declare.md @@ -0,0 +1,38 @@ + +Header file for declaring (not defining) gflags command-line flags in other translation units, enabling cross-file access to flags without redefinition. + +## Key Components + +| Component | Description | +|---|---| +| `GFLAGS_NAMESPACE` | Macro defining the library namespace (`gflags`) | +| `GFLAGS_IS_A_DLL` | Controls DLL vs. static library linkage (default: `0`) | +| `GFLAGS_DLL_DECL` | Platform-specific export/import decorator for library symbols | +| `GFLAGS_DLL_DECLARE_FLAG` | Platform-specific decorator for user-declared flag variables | +| `DECLARE_VARIABLE` | Base macro that emits an `extern` declaration in the appropriate `fL*` namespace | +| `DECLARE_bool` / `DECLARE_int32` / `DECLARE_uint32` / `DECLARE_int64` / `DECLARE_uint64` / `DECLARE_double` | Type-specific declaration macros wrapping `DECLARE_VARIABLE` | +| `DECLARE_string` | Specialized macro for string flags, using `fLS::clstring` (`std::string` alias) | +| `int32`, `uint32`, `int64`, `uint64` | Fixed-width integer typedefs inside `GFLAGS_NAMESPACE`, resolved per platform (Windows `__int32`/`__int64` used here) | + +## Usage Example + +Include this header in any file that needs to **read** a flag defined elsewhere: + +```cpp +#include + +// Declare flags defined in another translation unit +DECLARE_bool(verbose); +DECLARE_int32(port); +DECLARE_string(hostname); + +void connect() { + if (FLAGS_verbose) { + printf("Connecting to %s:%d\n", + FLAGS_hostname.c_str(), + FLAGS_port); + } +} +``` + +> **Note:** `DECLARE_*` macros only forward-declare a flag. Use `DEFINE_*` macros (from `gflags.h`) in exactly one `.cc` file to define and register the flag with its default value and help text. Mixing up `DECLARE` and `DEFINE` in the same file is a common pitfall. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/aarch64/private/.config.md b/libraries/cmake/source/glog/generated/linux/aarch64/private/.config.md new file mode 100644 index 00000000000..29c5435ef6c --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/aarch64/private/.config.md @@ -0,0 +1,54 @@ + +Build-time configuration header for the **glog** (Google Logging Library) that defines feature detection macros, platform capabilities, and namespace utilities based on the target environment. + +## Key Components + +### Namespace Macros +| Macro | Value | +|---|---| +| `GOOGLE_NAMESPACE` | `google` | +| `_START_GOOGLE_NAMESPACE_` | `namespace google {` | +| `_END_GOOGLE_NAMESPACE_` | `}` | + +### Platform & Compiler Feature Flags +- **POSIX functions**: `HAVE_FCNTL`, `HAVE_PREAD`, `HAVE_PWRITE`, `HAVE_SIGACTION`, `HAVE_LOCALTIME_R` +- **Threading**: `HAVE_PTHREAD`, `HAVE_RWLOCK` (libpthread and `pthread_rwlock_*`) +- **Compiler builtins**: `HAVE___ATTRIBUTE__`, `HAVE___BUILTIN_EXPECT`, `HAVE___SYNC_VAL_COMPARE_AND_SWAP` + +### C++11 Support +- `HAVE_ALIGNED_STORAGE` — `std::aligned_storage` available +- `HAVE_CXX11_ATOMIC` — `std::atomic` available +- `HAVE_CXX11_NULLPTR_T` — `nullptr_t` available + +### System Headers Available +``, ``, ``, ``, ``, ``, ``, ``, ``, and standard `sys/` headers. + +### Pointer Size +```c +#define SIZEOF_VOID_P 8 // 64-bit platform +``` + +## Usage Example + +```c +#include "config.h" + +// Wrap code in the Google namespace using config macros +_START_GOOGLE_NAMESPACE_ + +void MyLogger() { +#ifdef HAVE_PTHREAD + // Use thread-safe logging path +#endif + +#ifdef HAVE___BUILTIN_EXPECT + if (__builtin_expect(error_condition, 0)) { + // Unlikely branch — compiler hint applied + } +#endif +} + +_END_GOOGLE_NAMESPACE_ +``` + +> **Note:** This file is auto-generated by the build system (CMake/Bazel/autoconf). Do not edit manually. The `GLOG_BAZEL_BUILD` guard handles a known Bazel namespace resolution workaround ([bazelbuild#3979](https://github.com/bazelbuild/bazel/issues/3979)). \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.export.md b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.export.md new file mode 100644 index 00000000000..885c4d99c7c --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.export.md @@ -0,0 +1,40 @@ + +Controls symbol visibility and export/import declarations for the Google glog logging library, supporting both static and dynamic (DLL/shared library) linkage configurations. + +## Key Components + +| Macro | Purpose | +|---|---| +| `GOOGLE_GLOG_DLL_DECL` | Marks symbols for export (when building) or import (when consuming) the library | +| `GLOG_NO_EXPORT` | Marks symbols with hidden visibility, excluded from the public API | +| `GLOG_DEPRECATED` | Applies `__attribute__((__deprecated__))` to flag obsolete symbols | +| `GLOG_DEPRECATED_EXPORT` | Combines `GOOGLE_GLOG_DLL_DECL` + `GLOG_DEPRECATED` for exported-but-deprecated symbols | +| `GLOG_DEPRECATED_NO_EXPORT` | Combines `GLOG_NO_EXPORT` + `GLOG_DEPRECATED` for hidden deprecated symbols | +| `GLOG_NO_DEPRECATED` | Optional guard (disabled by default) to suppress deprecated symbol definitions entirely | + +## Usage Example + +```c +/* Consuming glog as a shared library (default behavior) */ +#include "export.h" + +/* Exported public API symbol */ +GOOGLE_GLOG_DLL_DECL void SomePublicFunction(); + +/* Internal symbol, hidden from shared library consumers */ +GLOG_NO_EXPORT void InternalHelper(); + +/* Exported symbol marked for removal in a future release */ +GLOG_DEPRECATED_EXPORT void OldPublicFunction(); + +/* Building glog as a static library */ +#define GLOG_STATIC_DEFINE +#include "export.h" +/* GOOGLE_GLOG_DLL_DECL and GLOG_NO_EXPORT both resolve to empty */ +``` + +## Notes + +- On Linux/macOS, `GOOGLE_GLOG_DLL_DECL` resolves to empty (GCC/Clang handle visibility via compiler flags); on Windows it would typically expand to `__declspec(dllexport/dllimport)`. +- Define `GLOG_STATIC_DEFINE` before including this header when linking glog statically to neutralize all visibility macros. +- This file is typically auto-generated by CMake's `GenerateExportHeader` module. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.logging.md b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.logging.md new file mode 100644 index 00000000000..00c34e584c7 --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.logging.md @@ -0,0 +1,71 @@ + +Comprehensive logging header from the Google glog library, providing macros and infrastructure for severity-based, conditional, and verbose logging across C++ applications. + +## Key Components + +### Severity Levels +| Level | Description | +|---|---| +| `INFO` | General informational messages | +| `WARNING` | Non-critical issues | +| `ERROR` | Recoverable errors | +| `FATAL` | Terminates the program after logging | +| `DFATAL` | `FATAL` in debug, `ERROR` in release | + +### Core Logging Macros +- **`LOG(severity)`** — Basic severity-based logging +- **`LOG_IF(severity, condition)`** — Conditional logging +- **`LOG_EVERY_N(severity, n)`** — Logs every nth occurrence +- **`LOG_FIRST_N(severity, n)`** — Logs only the first n occurrences +- **`LOG_IF_EVERY_N(severity, cond, n)`** — Combined conditional + rate-limited +- **`LOG_STRING(severity, &vec)`** — Captures log output into a string vector +- **`VLOG(level)`** / **`VLOG_IF`** / **`VLOG_EVERY_N`** — Verbose level logging +- **`DLOG*`** — Debug-only variants (compiled out in release builds) +- **`SYSLOG*`** — Variants that also write to syslog + +### Supporting Structures (custom prefix support) +- **`LogMessageTime`** — Exposes timestamp, microseconds, and broken-down time fields +- **`LogMessageInfo`** — Bundles severity, filename, line number, thread ID, and time +- **`CustomPrefixCallback`** — Function pointer type for custom log line prefixes + +### Utility Macros +- **`GOOGLE_STRIP_LOG`** — Compile-time log stripping below a severity threshold +- **`GOOGLE_PREDICT_BRANCH_NOT_TAKEN`** / **`GOOGLE_PREDICT_FALSE`** / **`GOOGLE_PREDICT_TRUE`** — GCC branch prediction hints +- **`google::COUNTER`** — Identifies the current repetition count in rate-limited macros + +## Usage Example + +```c +#include "logging.h" + +int main(int argc, char* argv[]) { + // Basic severity logging + LOG(INFO) << "Server started on port " << port; + LOG(WARNING) << "Config file missing, using defaults"; + LOG(ERROR) << "Failed to open file: " << filename; + + // Conditional logging + LOG_IF(INFO, num_requests > 1000) << "High request volume detected"; + + // Rate-limited logging (every 10th occurrence) + LOG_EVERY_N(INFO, 10) << "Processed request #" << google::COUNTER; + + // Verbose logging (active with --v=2 or higher) + VLOG(2) << "Detailed trace: packet_size=" << size; + + // Check verbose level before expensive computation + if (VLOG_IS_ON(3)) { + std::string dump = BuildExpensiveDump(); + VLOG(3) << dump; + } + + // Debug-only (compiled out in release) + DLOG(INFO) << "Debug-only context info"; + + // Capture errors into a vector + std::vector errors; + LOG_STRING(ERROR, &errors) << "Parse error at line " << line; + + return 0; +} +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.raw_logging.md b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.raw_logging.md new file mode 100644 index 00000000000..06b3c8b01a5 --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.raw_logging.md @@ -0,0 +1,49 @@ + +Thread-safe, allocation-free logging header for low-level C++ modules that cannot use standard `LOG()` due to memory allocation or lock constraints. Logs directly to `stderr` without buffering. + +## Key Components + +| Macro / Function | Description | +|---|---| +| `RAW_LOG(severity, ...)` | Primary logging macro; dispatches to severity-specific macros (`INFO`, `WARNING`, `ERROR`, `FATAL`) | +| `RAW_VLOG(level, ...)` | Verbose logging macro; only emits if `VLOG_IS_ON(level)` is true | +| `RAW_LOG_INFO/WARNING/ERROR/FATAL(...)` | Severity-specific macros; conditionally compiled out via `STRIP_LOG` | +| `RAW_CHECK(condition, message)` | Assertion macro that calls `RAW_LOG(FATAL, ...)` on failure | +| `RAW_DLOG` / `RAW_DCHECK` | Debug-only variants; compiled out when `NDEBUG` is defined | +| `RawLog__(severity, file, line, format, ...)` | Core implementation function; no memory allocation, no locking | +| `RawLogStub__(int, ...)` | No-op stub used to suppress unused variable warnings when `STRIP_LOG` eliminates logging | + +### `STRIP_LOG` Levels + +| Value | Strips | +|---|---| +| `> 0` | `INFO` | +| `> 1` | `WARNING` | +| `> 2` | `ERROR` | +| `> 3` | `FATAL` (exits instead) | + +## Usage Example + +```c +#include "base/raw_logging.h" + +// Basic severity logging — writes directly to stderr, no heap allocation +RAW_LOG(ERROR, "Failed foo with %i: %s", status, error_msg); +RAW_LOG(INFO, "Initialized allocator at %p", ptr); + +// Verbose logging (only emits if verbosity level >= 3) +RAW_VLOG(3, "Status is %i", status); + +// Assertion — logs FATAL and aborts if condition is false +RAW_CHECK(ptr != nullptr, "Allocator returned null pointer"); + +// Debug-only variants (no-ops in release builds with NDEBUG) +RAW_DLOG(WARNING, "Debug-only warning: value=%d", val); +RAW_DCHECK(size > 0, "Size must be positive"); +``` + +Output format written to `stderr`: + +```text +E20200821 211317 file.cc:123] RAW: Failed foo with 22: bad_file +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.stl_logging.md b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.stl_logging.md new file mode 100644 index 00000000000..b7145fa2f8d --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.stl_logging.md @@ -0,0 +1,50 @@ + +Provides `std::ostream` stream output operators for STL containers, enabling direct use of standard containers in glog `LOG()` and `CHECK_EQ()` macros for logging purposes. + +## Key Components + +### `google::PrintSequence` +Iterates over a container range and writes elements to the output stream, capped at **100 elements** to keep log output manageable. Appends `" ..."` if truncated. + +### `operator<<` overloads +Templated stream operators generated via macros for: + +| Containers | Macro Used | +|---|---| +| `vector`, `deque`, `list` | `OUTPUT_TWO_ARG_CONTAINER` | +| `set`, `multiset` | `OUTPUT_THREE_ARG_CONTAINER` | +| `map`, `multimap`, `unordered_set/multiset` | `OUTPUT_FOUR_ARG_CONTAINER` | +| `unordered_map/multimap`, hash variants | `OUTPUT_FIVE_ARG_CONTAINER` | +| `std::pair` | Inline definition — outputs `(first, second)` | + +### Optional Extension Macros + +Define before including this header to enable additional container support: + +```text +GLOG_STL_LOGGING_FOR_UNORDERED → std::unordered_map / unordered_set +GLOG_STL_LOGGING_FOR_TR1_UNORDERED → tr1::unordered_(map|set) +GLOG_STL_LOGGING_FOR_EXT_HASH → __gnu_cxx::hash_(map|set) +GLOG_STL_LOGGING_FOR_EXT_SLIST → __gnu_cxx::slist +``` + +## Usage Example + +```cpp +#define GLOG_STL_LOGGING_FOR_UNORDERED +#include +#include + +std::vector v1 = {1, 2, 3}; +std::map m = {{"a", 1}, {"b", 2}}; +std::pair p = {42, "hello"}; + +LOG(INFO) << "vector: " << v1; // vector: 1 2 3 +LOG(INFO) << "map: " << m; // map: (a, 1) (b, 2) +LOG(INFO) << "pair: " << p; // pair: (42, hello) + +std::vector v2 = {1, 2, 3}; +CHECK_EQ(v1, v2); // works with container equality +``` + +> **Note:** This header injects operators into `namespace std` via `using ::operator<<` to support ADL-based lookup when `logging.h` is included first — technically undefined behavior but required for `CHECK_EQ` to work correctly with container types. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.vlog_is_on.md b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.vlog_is_on.md new file mode 100644 index 00000000000..5da90f6709d --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/aarch64/public/glog/.vlog_is_on.md @@ -0,0 +1,41 @@ + +Defines the `VLOG_IS_ON(level)` macro and supporting infrastructure for variable-verbosity conditional logging in the glog library, used by `VLOG`, `VLOG_IF`, and `RAW_VLOG` macros. + +## Key Components + +| Component | Description | +|---|---| +| `VLOG_IS_ON(verboselevel)` | Macro that evaluates to `true` if the current verbosity level is at or above `verboselevel`; uses per-site static pointers on GCC for `--vmodule` support | +| `SetVLOGLevel()` | Dynamically sets the verbosity level for a given module pattern at runtime | +| `InitVLOG3__()` | Internal helper that resolves which verbosity variable controls a given log site on first evaluation | +| `FLAGS_v` | Global flag controlling the default maximum active V-logging level (default: `0`) | + +## Behavior by Compiler + +- **GCC**: Uses a per-site static `int32*` pointer initialized lazily via `InitVLOG3__`, enabling per-module control via `--vmodule` +- **Non-GCC**: Falls back to `FLAGS_v >= verboselevel` — `--vmodule` is **not** supported + +## Usage Example + +```c +// Basic conditional verbose logging +if (VLOG_IS_ON(2)) { + // Expensive logging preparation only runs if verbosity >= 2 + std::string debug_info = ComputeExpensiveDebugInfo(); + LOG(INFO) << "Debug: " << debug_info; +} + +// Dynamically override verbosity for a module pattern +int previous_level = SetVLOGLevel("my_module", 3); +// All VLOG sites in "my_module.*" now log at level <= 3 + +// Per-module CLI flags (GCC only) +// --v=0 --vmodule="my_module=2,foo*=3" +``` + +## Runtime Control Flags + +| Flag | Description | +|---|---| +| `--v=` | Default max verbosity level (default: `0`) | +| `--vmodule=` | Per-module overrides, e.g. `"my_module=2,foo*=3"` (GCC only) | \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/x86_64/private/.config.md b/libraries/cmake/source/glog/generated/linux/x86_64/private/.config.md new file mode 100644 index 00000000000..eceff8c88b3 --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/x86_64/private/.config.md @@ -0,0 +1,45 @@ + +Compile-time configuration header for the **glog** (Google Logging Library), defining platform capabilities, available headers, compiler features, and namespace macros detected during the build process. + +## Key Components + +| Category | Defines | +|---|---| +| **Namespace** | `GOOGLE_NAMESPACE`, `_START_GOOGLE_NAMESPACE_`, `_END_GOOGLE_NAMESPACE_` | +| **Platform Headers** | `HAVE_UNISTD_H`, `HAVE_STDINT_H`, `HAVE_SYS_STAT_H`, `HAVE_PTHREAD`, etc. | +| **POSIX Functions** | `HAVE_FCNTL`, `HAVE_PREAD`, `HAVE_PWRITE`, `HAVE_SIGACTION`, `HAVE_LOCALTIME_R` | +| **Compiler Features** | `HAVE___ATTRIBUTE__`, `HAVE___BUILTIN_EXPECT`, `HAVE___SYNC_VAL_COMPARE_AND_SWAP` | +| **C++11 Features** | `HAVE_ALIGNED_STORAGE`, `HAVE_CXX11_ATOMIC`, `HAVE_CXX11_NULLPTR_T` | +| **Threading** | `HAVE_RWLOCK`, `HAVE_PTHREAD`, `NO_THREADS` (disabled) | +| **Pointer Size** | `SIZEOF_VOID_P 8` (64-bit platform) | +| **Optional Deps** | `HAVE_LIB_GFLAGS` (enabled), libunwind/gmock/gtest (disabled) | + +## Usage Example + +```c +#include "config.h" + +/* Conditionally use namespace macros */ +_START_GOOGLE_NAMESPACE_ + +void log_something() { +#ifdef HAVE_PTHREAD + /* Thread-safe logging path */ +#endif + +#ifdef HAVE___BUILTIN_EXPECT + if (__builtin_expect(error_condition, 0)) { + /* unlikely branch */ + } +#endif +} + +_END_GOOGLE_NAMESPACE_ + +/* Check pointer size for 64-bit-specific logic */ +#if SIZEOF_VOID_P == 8 + /* 64-bit platform handling */ +#endif +``` + +> **Note:** This file is auto-generated by the build system (CMake/Autoconf/Bazel). Do not edit manually — changes will be overwritten. For Bazel builds, `GLOG_BAZEL_BUILD` triggers an alternative namespace macro definition to work around [bazelbuild/bazel#3979](https://github.com/bazelbuild/bazel/issues/3979). \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.export.md b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.export.md new file mode 100644 index 00000000000..bcd45691793 --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.export.md @@ -0,0 +1,44 @@ + +Controls symbol visibility and export/import declarations for the Google glog logging library, handling both static and dynamic (DLL/shared library) build configurations. + +## Key Components + +| Macro | Purpose | +|---|---| +| `GOOGLE_GLOG_DLL_DECL` | Marks symbols for export (when building) or import (when consuming) the glog shared library | +| `GLOG_NO_EXPORT` | Marks symbols as private/hidden — not exported from the shared library | +| `GLOG_DEPRECATED` | Applies compiler deprecation attribute (`__attribute__((__deprecated__))`) to a symbol | +| `GLOG_DEPRECATED_EXPORT` | Combines export visibility with deprecation marking | +| `GLOG_DEPRECATED_NO_EXPORT` | Combines hidden visibility with deprecation marking | +| `GLOG_STATIC_DEFINE` | Define this to collapse all visibility macros to empty (for static builds) | + +## Build Modes + +```text +Static build: define GLOG_STATIC_DEFINE → all macros become no-ops +Dynamic build: GOOGLE_GLOG_IS_A_DLL defined when building the library + (undefined when consuming it) +``` + +## Usage Example + +```c +/* Consuming glog as a shared library (default) */ +#include "export.h" + +/* Public API function — visible to library consumers */ +GOOGLE_GLOG_DLL_DECL void glog_public_function(void); + +/* Internal helper — hidden from the public ABI */ +GLOG_NO_EXPORT void glog_internal_helper(void); + +/* Deprecated public function */ +GLOG_DEPRECATED_EXPORT void glog_old_api(void); + +/* Static build — define before including any glog headers */ +#define GLOG_STATIC_DEFINE +#include "export.h" +/* All visibility macros now expand to nothing */ +``` + +> **Note:** On Linux/macOS, `GOOGLE_GLOG_DLL_DECL` and `GLOG_NO_EXPORT` expand to empty strings since GCC/Clang use `-fvisibility` flags instead of per-symbol attributes in this configuration. On Windows builds, these would typically expand to `__declspec(dllexport/dllimport)`. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.logging.md b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.logging.md new file mode 100644 index 00000000000..8983b4a46f4 --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.logging.md @@ -0,0 +1,58 @@ + +Comprehensive logging header from the Google glog library, providing macros and utilities for structured, severity-based logging across C++ applications. + +## Key Components + +### Severity Levels +- `INFO` — General informational messages +- `WARNING` — Non-critical issues +- `ERROR` — Recoverable errors +- `FATAL` — Unrecoverable errors (terminates program) +- `DFATAL` — FATAL in debug mode, ERROR in release + +### Core Logging Macros +| Macro | Description | +|---|---| +| `LOG(severity)` | Basic severity-level logging | +| `LOG_IF(severity, cond)` | Conditional logging | +| `LOG_EVERY_N(severity, n)` | Log every Nth occurrence | +| `LOG_FIRST_N(severity, n)` | Log only the first N times | +| `LOG_STRING(severity, &vec)` | Capture log output into a vector | +| `VLOG(level)` | Verbose level logging (uses `--v` flag) | +| `DLOG(severity)` | Debug-only logging (compiled out in release) | +| `SYSLOG(severity)` | Log to syslog and normal logs | + +### Custom Prefix Support (`GLOG_CUSTOM_PREFIX_SUPPORT`) +- `LogMessageTime` — Exposes timestamp fields (sec, min, hour, day, usec, etc.) +- `LogMessageInfo` — Bundles severity, filename, line number, thread ID, and time +- `CustomPrefixCallback` — Function pointer type for custom log line prefixes + +### Compile-Time Controls +- `GOOGLE_STRIP_LOG` — Strip messages below a given severity at compile time +- `GOOGLE_PREDICT_BRANCH_NOT_TAKEN` / `GOOGLE_PREDICT_FALSE` / `GOOGLE_PREDICT_TRUE` — GCC branch prediction hints + +## Usage Example + +```c +#include "logging.h" + +// Basic logging +LOG(INFO) << "Server started on port " << port; +LOG(WARNING) << "Config missing, using defaults"; +LOG(ERROR) << "Failed to open file: " << filename; + +// Conditional logging +LOG_IF(INFO, num_requests > 1000) << "High request volume detected"; + +// Rate-limited logging +LOG_EVERY_N(INFO, 100) << "Processed " << google::COUNTER << " requests"; + +// First-N logging +LOG_FIRST_N(WARNING, 5) << "Deprecated API called"; + +// Verbose logging (enabled with --v=2) +VLOG(2) << "Detailed debug state: " << debug_info; + +// Debug-only logging (compiled out in release) +DLOG(INFO) << "Debug trace: entering function"; +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.raw_logging.md b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.raw_logging.md new file mode 100644 index 00000000000..8de43627115 --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.raw_logging.md @@ -0,0 +1,49 @@ + +Thread-safe, allocation-free logging header for low-level C++ modules that cannot use standard `LOG()` due to memory allocation or lock constraints. Logs directly to `stderr` without buffering. + +## Key Components + +### Macros + +| Macro | Description | +|---|---| +| `RAW_LOG(severity, ...)` | Primary logging macro; dispatches to severity-specific handlers (`INFO`, `WARNING`, `ERROR`, `FATAL`) | +| `RAW_VLOG(level, ...)` | Verbose logging; only emits if `VLOG_IS_ON(level)` is true | +| `RAW_LOG_INFO/WARNING/ERROR/FATAL(...)` | Direct severity-level macros, each gated by `STRIP_LOG` threshold | +| `RAW_CHECK(condition, message)` | Assertion macro; logs `FATAL` and aborts if condition is false | +| `RAW_DLOG(severity, ...)` | Debug-only variant of `RAW_LOG`; compiled out when `NDEBUG` is defined | +| `RAW_DCHECK(condition, message)` | Debug-only variant of `RAW_CHECK`; compiled out when `NDEBUG` is defined | + +### Functions + +- **`RawLog__(severity, file, line, format, ...)`** — Core implementation; printf-style, no memory allocation, no locks. Exported via `GOOGLE_GLOG_DLL_DECL` for Windows DLL compatibility. +- **`RawLogStub__(int, ...)`** — No-op inline stub used when logging is stripped via `STRIP_LOG` to suppress unused variable warnings. + +### Strip Log Levels + +`STRIP_LOG` controls compile-time log removal: +- `0` — All levels active +- `1` — Strips `INFO` +- `2` — Strips `INFO` + `WARNING` +- `3` — Strips `INFO` + `WARNING` + `ERROR` + +## Usage Example + +```c +// Basic severity logging +RAW_LOG(ERROR, "Failed foo with %i: %s", status, error_msg); + +// Verbose logging (only if verbosity level >= 3) +RAW_VLOG(3, "Status is %i", status); + +// Assertion check in low-level code +RAW_CHECK(ptr != nullptr, "Pointer must not be null"); + +// Debug-only logging (no-op in release builds) +RAW_DLOG(WARNING, "Debug value: %d", val); +``` + +Output format written to `stderr`: +```text +E20200821 211317 file.cc:123] RAW: Failed foo with 22: bad_file +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.stl_logging.md b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.stl_logging.md new file mode 100644 index 00000000000..9fd590be8c2 --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.stl_logging.md @@ -0,0 +1,45 @@ + +Provides `std::ostream` stream output operators for standard C++ STL containers, enabling direct logging of container contents via glog's `LOG()` and `CHECK_EQ()` macros. + +## Key Components + +| Component | Description | +|---|---| +| `google::PrintSequence` | Iterates and prints up to 100 elements from any container range, appending `...` if truncated | +| `operator<<` for `std::pair` | Formats pair as `(first, second)` | +| `OUTPUT_TWO_ARG_CONTAINER` | Macro expanding `operator<<` for `vector`, `deque`, `list` | +| `OUTPUT_THREE_ARG_CONTAINER` | Macro expanding `operator<<` for `set`, `multiset` | +| `OUTPUT_FOUR_ARG_CONTAINER` | Macro expanding `operator<<` for `map`, `multimap`, and optional unordered/hash sets | +| `OUTPUT_FIVE_ARG_CONTAINER` | Macro expanding `operator<<` for `unordered_map`, `hash_map`, and variants | + +### Optional Container Support (define before including) + +| Macro | Enables | +|---|---| +| `GLOG_STL_LOGGING_FOR_UNORDERED` | ``, `` | +| `GLOG_STL_LOGGING_FOR_TR1_UNORDERED` | ``, `` | +| `GLOG_STL_LOGGING_FOR_EXT_HASH` | ``, `` | +| `GLOG_STL_LOGGING_FOR_EXT_SLIST` | `` | + +## Usage Example + +```cpp +#define GLOG_STL_LOGGING_FOR_UNORDERED +#include +#include + +std::vector v1 = {1, 2, 3}; +std::map scores = {{"alice", 10}, {"bob", 20}}; +std::pair p = {42, "answer"}; + +// Direct container logging +LOG(INFO) << "vector: " << v1; +LOG(INFO) << "map: " << scores; +LOG(INFO) << "pair: " << p; + +// Works with CHECK macros +std::vector v2 = {1, 2, 3}; +CHECK_EQ(v1, v2); // Prints container contents on failure +``` + +> **Note:** This header injects operators into `namespace std` via `using ::operator<<` to ensure ADL resolution works correctly when `` is included first. Include this header after defining any needed `GLOG_STL_LOGGING_FOR_*` macros. Output is capped at **100 elements** per container, suitable for log verbosity. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.vlog_is_on.md b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.vlog_is_on.md new file mode 100644 index 00000000000..64e9da02dae --- /dev/null +++ b/libraries/cmake/source/glog/generated/linux/x86_64/public/glog/.vlog_is_on.md @@ -0,0 +1,33 @@ + +Defines the `VLOG_IS_ON(level)` macro used by glog's variable-verbosity conditional logging system, enabling fine-grained control over which verbose log statements are active at runtime. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `VLOG_IS_ON(verboselevel)` | Macro | Evaluates to `true` if verbose logging at the given level is enabled. Uses per-site static pointer optimization on GCC; falls back to `FLAGS_v` comparison on non-GCC compilers | +| `SetVLOGLevel()` | Function | Dynamically overrides the verbosity level for a given module pattern at runtime; returns the previously applied level | +| `InitVLOG3__()` | Function | Internal helper that resolves and caches the controlling verbosity pointer for a specific `VLOG_IS_ON` call site | +| `FLAGS_v` | External flag | Global default verbosity level (default `0`); set via `--v=` | +| `--vmodule` | CLI flag | Per-module verbosity overrides (e.g. `"my_module=2,foo*=3"`); **GCC only** | + +## Usage Example + +```c +// Basic conditional verbose logging +if (VLOG_IS_ON(2)) { + // Perform expensive prep work only when level 2 verbosity is active + std::string debug_info = BuildExpensiveDebugString(); + LOG(INFO) << debug_info; +} + +// Dynamically adjust verbosity for a specific module at runtime +int prev_level = SetVLOGLevel("my_module", 3); +// ... do work with elevated logging ... +SetVLOGLevel("my_module", prev_level); // restore + +// Typical use via VLOG macro (uses VLOG_IS_ON internally) +VLOG(1) << "Standard verbose message"; +``` + +> **Note:** Per-module `--vmodule` pattern matching and the site-local pointer optimization are **GCC-only**. On MSVC and other non-GCC compilers, all `VLOG_IS_ON(n)` calls reduce to a simple `FLAGS_v >= n` comparison, meaning `SetVLOGLevel()` per-module overrides have no effect on already-executed sites. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/aarch64/private/.config.md b/libraries/cmake/source/glog/generated/macos/aarch64/private/.config.md new file mode 100644 index 00000000000..a9b7dedb451 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/aarch64/private/.config.md @@ -0,0 +1,52 @@ + +Build-time configuration header for the **glog** (Google Logging Library) that detects platform capabilities, compiler features, and available system headers/functions to enable conditional compilation. + +## Key Components + +### Namespace Macros +- `GOOGLE_NAMESPACE` — defines the namespace (`google`) +- `_START_GOOGLE_NAMESPACE_` / `_END_GOOGLE_NAMESPACE_` — namespace open/close macros with Bazel build workaround support + +### Platform Feature Detection +| Category | Key Defines | +|---|---| +| System headers | `HAVE_DLFCN_H`, `HAVE_EXECINFO_H`, `HAVE_SYSLOG_H`, `HAVE_UNISTD_H`, `HAVE_PWD_H` | +| POSIX functions | `HAVE_DLADDR`, `HAVE_FCNTL`, `HAVE_PREAD`, `HAVE_PWRITE`, `HAVE_SIGACTION` | +| Threading | `HAVE_PTHREAD`, `HAVE_RWLOCK` (pthread_rwlock_*) | +| Time utilities | `HAVE_LOCALTIME_R`, `HAVE_SYS_TIME_H` | + +### Compiler Feature Detection +- `HAVE___ATTRIBUTE__` — GCC/Clang `__attribute__` support +- `HAVE___BUILTIN_EXPECT` — branch prediction hint support +- `HAVE___SYNC_VAL_COMPARE_AND_SWAP` — atomic CAS instruction +- `HAVE_CXX11_ATOMIC` / `HAVE_CXX11_NULLPTR_T` — C++11 feature availability +- `HAVE_USING_OPERATOR` — `using` expression for operators + +### Runtime Configuration +- `SIZEOF_VOID_P 8` — pointer size (64-bit platform) +- `STL_NAMESPACE std` — STL container namespace +- `HAVE_SYMBOLIZE` — stack trace symbolization support +- `HAVE_LIB_GFLAGS` — Google gflags integration enabled + +## Usage Example + +```c +// Typical conditional compilation pattern using this config +#include "config.h" + +_START_GOOGLE_NAMESPACE_ + +#ifdef HAVE_PTHREAD + #include + // Use POSIX thread primitives +#endif + +#ifdef HAVE_EXECINFO_H + #include + // Enable stack trace capture +#endif + +_END_GOOGLE_NAMESPACE_ +``` + +> **Note:** Commented-out defines (e.g., `/* #undef HAVE_LIBUNWIND_H */`) indicate features not available or not detected on this build target. This file is typically auto-generated by the build system (CMake/Autoconf/Bazel) — do not edit manually. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.export.md b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.export.md new file mode 100644 index 00000000000..2a0d1c26623 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.export.md @@ -0,0 +1,36 @@ + +Controls symbol visibility and export/import declarations for the Google glog logging library, enabling correct DLL linkage on Windows and shared library visibility on Linux/macOS. + +## Key Components + +| Macro | Purpose | +|---|---| +| `GOOGLE_GLOG_DLL_DECL` | Marks symbols for export (when building) or import (when consuming) | +| `GLOG_NO_EXPORT` | Hides symbols from the shared library's public interface | +| `GLOG_DEPRECATED` | Applies `__attribute__((__deprecated__))` to flag obsolete symbols | +| `GLOG_DEPRECATED_EXPORT` | Combines export visibility with deprecation warning | +| `GLOG_DEPRECATED_NO_EXPORT` | Combines hidden visibility with deprecation warning | +| `GLOG_STATIC_DEFINE` | Override flag — disables all visibility attributes for static linking | + +## Usage Example + +```c +/* Static linking — suppress all DLL decorators */ +#define GLOG_STATIC_DEFINE +#include "export.h" + +/* Mark a function as part of the public API */ +GOOGLE_GLOG_DLL_DECL void log_message(const char* msg); + +/* Mark an internal helper — excluded from the public ABI */ +GLOG_NO_EXPORT void internal_flush_buffer(void); + +/* Mark a deprecated public function */ +GLOG_DEPRECATED_EXPORT void old_log_api(const char* msg); +``` + +## Notes + +- When `GLOG_STATIC_DEFINE` is defined, both `GOOGLE_GLOG_DLL_DECL` and `GLOG_NO_EXPORT` expand to nothing, making the header safe for static builds. +- The `GOOGLE_GLOG_IS_A_DLL` flag distinguishes the **build** side from the **consumer** side — typically set by the build system (CMake/Bazel) automatically. +- This file is generally **auto-generated** by CMake's `GenerateExportHeader` module; avoid editing it manually. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.logging.md b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.logging.md new file mode 100644 index 00000000000..b8e9325c2a2 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.logging.md @@ -0,0 +1,60 @@ + +Comprehensive logging header from the Google glog library, providing macros, severity levels, and infrastructure for structured application logging in C++. + +## Key Components + +### Severity Levels +| Level | Description | +|---|---| +| `INFO` | General informational messages | +| `WARNING` | Non-critical issues | +| `ERROR` | Recoverable errors | +| `FATAL` | Unrecoverable errors (terminates program) | +| `DFATAL` | `FATAL` in debug, `ERROR` in release | + +### Core Logging Macros +- **`LOG(severity)`** — Basic logging via stream operator +- **`LOG_IF(severity, condition)`** — Conditional logging +- **`LOG_EVERY_N(severity, n)`** — Rate-limited logging (every nth occurrence) +- **`LOG_FIRST_N(severity, n)`** — Log only first N occurrences +- **`LOG_IF_EVERY_N(severity, cond, n)`** — Combined conditional + rate-limited +- **`DLOG*`** — Debug-only variants (compiled out in release) +- **`VLOG(level)`** — Verbose level logging, controlled by `--v` flag +- **`LOG_STRING(severity, &vec)`** — Capture log output into a string vector +- **`SYSLOG*`** — Also write to syslog (use sparingly — high overhead) + +### Custom Prefix Support (`GLOG_CUSTOM_PREFIX_SUPPORT`) +- **`LogMessageTime`** — Exposes timestamp, sec, min, hour, day, usec, etc. +- **`LogMessageInfo`** — Bundles severity, filename, line number, thread ID, and time +- **`CustomPrefixCallback`** — Function pointer type for custom log line prefixes + +### Compile-Time Controls +- **`GOOGLE_STRIP_LOG`** — Strip log messages below a severity at compile time +- **`GOOGLE_PREDICT_*`** macros — GCC branch prediction hints for hot paths + +## Usage Example + +```c +#include "logging.h" + +// Basic logging +LOG(INFO) << "Server started on port " << port; +LOG(WARNING) << "Config file missing, using defaults"; +LOG(FATAL) << "Database connection failed"; // Terminates program + +// Conditional logging +LOG_IF(ERROR, response_code != 200) << "Bad response: " << response_code; + +// Rate-limited logging +LOG_EVERY_N(INFO, 100) << "Processed request #" << google::COUNTER; + +// Verbose logging (enabled with --v=2) +VLOG(2) << "Detailed trace: packet=" << packet_id; + +// Debug-only logging (compiled out in release) +DLOG(INFO) << "Debug checkpoint reached"; + +// Capture to string vector +std::vector errors; +LOG_STRING(ERROR, &errors) << "Parse failed at token " << token; +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.raw_logging.md b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.raw_logging.md new file mode 100644 index 00000000000..6f8742cdcf5 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.raw_logging.md @@ -0,0 +1,48 @@ + +Thread-safe, allocation-free logging header for low-level C++ modules that cannot safely use the standard `LOG()` infrastructure (e.g., memory allocators, lock implementations). All output goes directly to `stderr` with no buffering or heap allocation. + +## Key Components + +### Macros + +| Macro | Description | +|---|---| +| `RAW_LOG(severity, ...)` | Primary logging macro; dispatches to severity-specific macros (`INFO`, `WARNING`, `ERROR`, `FATAL`) | +| `RAW_VLOG(level, ...)` | Verbose logging; only emits if `VLOG_IS_ON(level)` is true | +| `RAW_LOG_INFO/WARNING/ERROR/FATAL(...)` | Direct severity-level macros, conditionally compiled via `STRIP_LOG` | +| `RAW_CHECK(condition, message)` | Assertion macro; logs `FATAL` and aborts if condition is false | +| `RAW_DLOG / RAW_DCHECK` | Debug-only variants; compiled out when `NDEBUG` is defined | + +### Functions + +- **`RawLog__(severity, file, line, format, ...)`** — Core implementation; `printf`-style, writes to `stderr`, no memory allocation, no locking. Long messages are silently truncated. +- **`RawLogStub__(int, ...)`** — No-op stub used to suppress unused-variable warnings when `STRIP_LOG` eliminates logging calls. + +### Compile-time Controls + +- **`STRIP_LOG`** — Integer level (0–3) that strips log calls at or below the specified severity at compile time. +- **`NDEBUG`** — Disables `RAW_DLOG` and `RAW_DCHECK` in release builds. + +## Usage Example + +```c +// Basic severity logging +RAW_LOG(ERROR, "Failed foo with %i: %s", status, error_msg); +RAW_LOG(INFO, "Initialized subsystem %s", name); + +// Verbose logging (only emits if verbosity level >= 3) +RAW_VLOG(3, "Status is %i", status); + +// Assertion — aborts with FATAL log if condition is false +RAW_CHECK(ptr != nullptr, "Expected non-null pointer after alloc"); + +// Debug-only logging (no-op in release builds) +RAW_DLOG(WARNING, "Debug: unexpected state %d", state); +RAW_DCHECK(x > 0, "x must be positive"); +``` + +Output format written to `stderr`: + +```text +E20200821 211317 file.cc:123] RAW: Failed foo with 22: bad_file +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.stl_logging.md b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.stl_logging.md new file mode 100644 index 00000000000..e71f9902afb --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.stl_logging.md @@ -0,0 +1,43 @@ + +Provides `std::ostream` stream output operators for common STL containers, enabling direct logging of container contents via glog's `LOG()` and `CHECK_*` macros. + +## Key Components + +| Component | Description | +|---|---| +| `google::PrintSequence` | Core iterator-based printer; outputs up to 100 elements, appending `...` if truncated | +| `operator<<` for `std::pair` | Formats pairs as `(first, second)` | +| `OUTPUT_TWO_ARG_CONTAINER` | Macro wiring `vector`, `deque`, `list` (and optionally `slist`) | +| `OUTPUT_THREE_ARG_CONTAINER` | Macro wiring `set`, `multiset` | +| `OUTPUT_FOUR_ARG_CONTAINER` | Macro wiring `map`, `multimap`, and optionally unordered/hash sets | +| `OUTPUT_FIVE_ARG_CONTAINER` | Macro wiring optionally `unordered_map`, `hash_map`, and their multi variants | + +### Optional Macro Guards + +| Define before include | Enables | +|---|---| +| `GLOG_STL_LOGGING_FOR_UNORDERED` | `` / `` | +| `GLOG_STL_LOGGING_FOR_TR1_UNORDERED` | `` | +| `GLOG_STL_LOGGING_FOR_EXT_HASH` | `` | +| `GLOG_STL_LOGGING_FOR_EXT_SLIST` | `` | + +## Usage Example + +```cpp +#define GLOG_STL_LOGGING_FOR_UNORDERED +#include +#include + +std::vector v1 = {1, 2, 3}; +std::map scores = {{"alice", 10}, {"bob", 20}}; + +// Direct container logging +LOG(INFO) << "vector: " << v1; +LOG(INFO) << "scores: " << scores; + +// Container equality checks in assertions +std::vector v2 = {1, 2, 3}; +CHECK_EQ(v1, v2); // Logs both vectors on failure +``` + +> **Note:** The header injects operators into `namespace std` via `using ::operator<<` to support ADL resolution when `glog/logging.h` is included before this file. Output is capped at **100 elements** per container — intentional for log safety. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.vlog_is_on.md b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.vlog_is_on.md new file mode 100644 index 00000000000..16be5d144b8 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/aarch64/public/glog/.vlog_is_on.md @@ -0,0 +1,42 @@ + +Defines the `VLOG_IS_ON(level)` macro for variable-verbosity conditional logging in the glog library, enabling per-module and global verbosity level checks at runtime. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `VLOG_IS_ON(verboselevel)` | Macro | Returns `true` if logging is enabled at the given verbosity level | +| `SetVLOGLevel()` | Function | Dynamically overrides per-module verbosity level at runtime | +| `InitVLOG3__()` | Function | Internal helper that resolves the controlling verbosity variable for a VLOG site | +| `FLAGS_v` | External flag | Global default max verbosity level (default: `0`) | + +## Behavior Notes + +- **GCC builds**: Uses a per-site static pointer (`vlocal__`) initialized lazily via `InitVLOG3__`, enabling per-module control via `--vmodule`. +- **Non-GCC builds**: Falls back to simple global `FLAGS_v >= verboselevel` comparison; `--vmodule` is **not** supported. + +## Usage Example + +```c +// Basic verbosity check +if (VLOG_IS_ON(2)) { + // Expensive logging preparation only performed if level 2 is active + std::string debug_info = BuildDebugReport(); + LOG(INFO) << debug_info; +} + +// Override verbosity for a specific module pattern at runtime +// Returns the previously applied level +int old_level = SetVLOGLevel("my_module", 3); + +// Equivalent flag-based control (set at startup): +// --v=1 (global default) +// --vmodule=my_module=3,foo*=2 (per-module overrides) +``` + +## Verbosity Flag Reference + +| Flag | Description | +|---|---| +| `--v=` | Global default max verbosity level (default: `0`) | +| `--vmodule==` | Per-module override, e.g. `my_module=2,foo*=3` | \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/x86_64/private/.config.md b/libraries/cmake/source/glog/generated/macos/x86_64/private/.config.md new file mode 100644 index 00000000000..92ea8a3cbcf --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/x86_64/private/.config.md @@ -0,0 +1,56 @@ + +Build-time configuration header for the **glog** (Google Logging Library) that defines platform capabilities, compiler features, and namespace macros detected during the CMake/autoconf configuration step. + +## Key Components + +### Namespace Macros +| Macro | Value | +|---|---| +| `GOOGLE_NAMESPACE` | `google` | +| `_START_GOOGLE_NAMESPACE_` | `namespace google {` | +| `_END_GOOGLE_NAMESPACE_` | `}` | + +> `GLOG_BAZEL_BUILD` triggers an alternative namespace expansion path to work around [bazelbuild/bazel#3979](https://github.com/bazelbuild/bazel/issues/3979). + +### Platform / POSIX Feature Flags +Indicates availability of system headers and functions detected at configure time: + +- **Headers:** `dlfcn.h`, `execinfo.h`, `glob.h`, `inttypes.h`, `pwd.h`, `syslog.h`, `sys/syscall.h`, `unistd.h` +- **Functions:** `dladdr`, `snprintf`, `fcntl`, `pread`, `pwrite`, `sigaction`, `localtime_r` +- **Threading:** `HAVE_PTHREAD`, `HAVE_RWLOCK` (pthread read-write locks enabled; `NO_THREADS` disabled) + +### Compiler Feature Flags +- `HAVE___ATTRIBUTE__` — GCC/Clang `__attribute__` support +- `HAVE___BUILTIN_EXPECT` — branch prediction hint support +- `HAVE___SYNC_VAL_COMPARE_AND_SWAP` — GCC atomic CAS intrinsic +- `HAVE_CXX11_ATOMIC` / `HAVE_CXX11_NULLPTR_T` — C++11 standard library features +- `HAVE_USING_OPERATOR` — compiler `using` expression for operators +- `HAVE_SYMBOLIZE` — stack trace symbolization available + +### Sizing & STL +- `SIZEOF_VOID_P 8` — 64-bit pointer size +- `STL_NAMESPACE std` — STL lives in `std::` + +## Usage Example + +```c +// Typical usage inside glog source files +#include "config.h" + +_START_GOOGLE_NAMESPACE_ + +#ifdef HAVE_PTHREAD + // Use POSIX thread-safe paths + pthread_rwlock_t lock; +#endif + +#ifdef HAVE___BUILTIN_EXPECT + #define PREDICT_TRUE(x) __builtin_expect(!!(x), 1) +#else + #define PREDICT_TRUE(x) (x) +#endif + +_END_GOOGLE_NAMESPACE_ +``` + +> **Note:** This file is auto-generated — do not edit manually. Modify the corresponding `config.h.in` template and re-run `cmake` or `./configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.export.md b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.export.md new file mode 100644 index 00000000000..36174d2ab5d --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.export.md @@ -0,0 +1,36 @@ + +Visibility and linkage control header for the Google glog logging library, defining macros that manage symbol export/import behavior across shared libraries, static builds, and deprecated API annotations. + +## Key Components + +| Macro | Purpose | +|---|---| +| `GOOGLE_GLOG_DLL_DECL` | Marks symbols for DLL export (when building) or import (when consuming) | +| `GLOG_NO_EXPORT` | Hides symbols from the public library interface | +| `GLOG_DEPRECATED` | Applies compiler deprecation warnings via `__attribute__((__deprecated__))` | +| `GLOG_DEPRECATED_EXPORT` | Combines public visibility with a deprecation warning | +| `GLOG_DEPRECATED_NO_EXPORT` | Combines hidden visibility with a deprecation warning | +| `GLOG_STATIC_DEFINE` | Define this to collapse all visibility macros to empty (static linking) | + +## Usage Example + +```c +/* Static linking — suppress all DLL import/export decoration */ +#define GLOG_STATIC_DEFINE +#include "export.h" + +/* Mark a function as part of the public API */ +GOOGLE_GLOG_DLL_DECL void my_public_function(void); + +/* Mark an internal helper — not visible outside the shared library */ +GLOG_NO_EXPORT void my_internal_helper(void); + +/* Expose a legacy function with a deprecation warning */ +GLOG_DEPRECATED_EXPORT void old_api(void); +``` + +## Notes + +- On Linux/macOS the export macros resolve to empty strings since ELF/Mach-O visibility is controlled separately (e.g., `-fvisibility=hidden`). +- On Windows with MSVC the macros would expand to `__declspec(dllexport)` / `__declspec(dllimport)` when this file is generated by CMake's `GenerateExportHeader`. +- The `DEFINE_NO_DEPRECATED` block (currently disabled with `#if 0`) can be enabled to define `GLOG_NO_DEPRECATED`, allowing consumers to compile out all deprecated API surfaces at once. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.logging.md b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.logging.md new file mode 100644 index 00000000000..c8a6f1bf741 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.logging.md @@ -0,0 +1,68 @@ + +Provides the core logging macros, types, and infrastructure for the Google glog C++ logging library, enabling structured severity-based logging throughout an application. + +## Key Components + +### Severity Levels +| Level | Description | +|---|---| +| `INFO` | General informational messages | +| `WARNING` | Non-critical issues | +| `ERROR` | Recoverable errors | +| `FATAL` | Terminates the program after logging | +| `DFATAL` | `FATAL` in debug, `ERROR` in release | + +### Core Macros + +| Macro | Purpose | +|---|---| +| `LOG(severity)` | Basic severity-level logging | +| `LOG_IF(severity, cond)` | Conditional logging | +| `LOG_EVERY_N(severity, n)` | Log every nth occurrence | +| `LOG_FIRST_N(severity, n)` | Log only the first n occurrences | +| `LOG_IF_EVERY_N(sev, cond, n)` | Conditional + rate-limited | +| `VLOG(level)` | Verbose/numeric-level logging | +| `DLOG(severity)` | Debug-only logging (compiled out in release) | +| `SYSLOG(severity)` | Log to syslog and normal logs | +| `LOG_STRING(severity, &vec)` | Capture log messages into a string vector | + +### Custom Prefix Support (`GLOG_CUSTOM_PREFIX_SUPPORT`) + +- **`LogMessageTime`** — Wraps `tm` struct with accessors for timestamp components (sec, min, hour, day, etc.) +- **`LogMessageInfo`** — Bundles severity, filename, line number, thread ID, and time for prefix callbacks +- **`CustomPrefixCallback`** — `typedef void(*)(std::ostream&, const LogMessageInfo&, void*)` for user-defined log prefixes + +### Platform & Type Utilities + +- Cross-platform fixed-width integer typedefs (`int32`, `uint32`, `int64`, `uint64`) in `namespace google` +- `GOOGLE_STRIP_LOG` — Compile-time log stripping below a set severity level +- `GOOGLE_PREDICT_BRANCH_NOT_TAKEN`, `GOOGLE_PREDICT_FALSE`, `GOOGLE_PREDICT_TRUE` — GCC branch prediction hints + +## Usage Example + +```c +#include "logging.h" + +// Basic logging +LOG(INFO) << "Server started on port " << port; +LOG(WARNING) << "Config missing, using defaults"; +LOG(ERROR) << "Failed to connect: " << error_msg; + +// Conditional logging +LOG_IF(INFO, num_requests > 1000) << "High traffic detected"; + +// Rate-limited logging +LOG_EVERY_N(INFO, 100) << "Processed " << google::COUNTER << " requests"; + +// Verbose logging (enabled with --v=2) +VLOG(2) << "Detailed debug info: " << internal_state; + +// Capture to vector +std::vector errors; +LOG_STRING(ERROR, &errors) << "Parse failed at line " << line; + +// Strip all INFO/WARNING at compile time +// #define GOOGLE_STRIP_LOG 2 (before including logging.h) +``` + +> **Note:** Logging at `FATAL` severity will terminate the program. Use `DFATAL` when you want fatal behavior only in debug builds. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.raw_logging.md b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.raw_logging.md new file mode 100644 index 00000000000..2ea094392c7 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.raw_logging.md @@ -0,0 +1,43 @@ + +Thread-safe, allocation-free logging header for low-level C++ modules that cannot use standard `LOG()` due to memory allocation or lock constraints. Logs directly to `stderr` without buffering. + +## Key Components + +| Macro / Function | Description | +|---|---| +| `RAW_LOG(severity, ...)` | Core logging macro; dispatches to severity-specific macros (`INFO`, `WARNING`, `ERROR`, `FATAL`) | +| `RAW_VLOG(level, ...)` | Verbose logging macro; respects `VLOG_IS_ON()` check | +| `RAW_LOG_INFO/WARNING/ERROR/FATAL(...)` | Severity-specific macros; stripped at compile time based on `STRIP_LOG` level | +| `RAW_CHECK(condition, message)` | Assertion macro using `RAW_LOG(FATAL, ...)` on failure; avoids heap allocation | +| `RAW_DLOG / RAW_DCHECK` | Debug-only variants; compiled out when `NDEBUG` is defined | +| `RawLog__()` | Underlying implementation function; `printf`-formatted, no memory allocation, no locks | +| `RawLogStub__()` | No-op stub used to suppress unused variable warnings when `STRIP_LOG` eliminates logging | + +## Usage Example + +```c +// Basic severity logging +RAW_LOG(ERROR, "Failed foo with %i: %s", status, error_msg); + +// Verbose logging (respects VLOG verbosity level) +RAW_VLOG(3, "Status is %i", status); + +// Assertion check (FATAL on failure) +RAW_CHECK(ptr != nullptr, "Pointer must not be null"); + +// Debug-only logging (no-op in release builds) +RAW_DLOG(WARNING, "Debug value: %d", debug_val); +``` + +Output format written to `stderr`: + +```text +E20200821 211317 file.cc:123] RAW: Failed foo with 22: bad_file +``` + +## Notes + +- **No heap allocation, no locks** — safe inside memory allocators and synchronization primitives +- `STRIP_LOG` compile flag controls which severity levels are compiled in (0 = all, 3 = FATAL only) +- Always writes to `stderr` only — no log file routing +- Long messages are silently truncated \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.stl_logging.md b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.stl_logging.md new file mode 100644 index 00000000000..7b74feea0f3 --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.stl_logging.md @@ -0,0 +1,45 @@ + +Provides `std::ostream` stream output operators for standard C++ STL containers, enabling them to be used directly with glog's `LOG()` and `CHECK_EQ()` macros for diagnostic logging. + +## Key Components + +| Component | Description | +|---|---| +| `google::PrintSequence` | Core iterator-based printer; outputs up to 100 elements followed by `...` if truncated | +| `operator<<` for `std::pair` | Formats pair as `(first, second)` | +| `OUTPUT_TWO_ARG_CONTAINER` | Macro expanding stream operators for `vector`, `deque`, `list` | +| `OUTPUT_THREE_ARG_CONTAINER` | Macro for `set`, `multiset` | +| `OUTPUT_FOUR_ARG_CONTAINER` | Macro for `map`, `multimap`, and optionally unordered/hash sets | +| `OUTPUT_FIVE_ARG_CONTAINER` | Macro for `unordered_map`, `unordered_multimap`, and hash map variants | + +### Optional Container Support (define before including) + +| Macro | Enables | +|---|---| +| `GLOG_STL_LOGGING_FOR_UNORDERED` | ``, `` | +| `GLOG_STL_LOGGING_FOR_TR1_UNORDERED` | ``, `` | +| `GLOG_STL_LOGGING_FOR_EXT_HASH` | ``, `` | +| `GLOG_STL_LOGGING_FOR_EXT_SLIST` | `` | + +## Usage Example + +```cpp +#define GLOG_STL_LOGGING_FOR_UNORDERED +#include +#include + +// Log STL containers directly +std::vector nums = {1, 2, 3}; +LOG(INFO) << "nums: " << nums; +// Output: nums: 1 2 3 + +std::map scores = {{"alice", 10}, {"bob", 20}}; +LOG(INFO) << "scores: " << scores; +// Output: scores: (alice, 10) (bob, 20) + +// Use with CHECK macros +std::vector v1 = {1, 2}, v2 = {1, 2}; +CHECK_EQ(v1, v2); // Streams container contents on failure +``` + +> **Note:** This header injects operators into `namespace std` via `using ::operator<<` to support ADL resolution when `glog/logging.h` is included first. This is acknowledged as technically undefined behavior but is intentional for correct ADL lookup. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.vlog_is_on.md b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.vlog_is_on.md new file mode 100644 index 00000000000..d5ab36b26db --- /dev/null +++ b/libraries/cmake/source/glog/generated/macos/x86_64/public/glog/.vlog_is_on.md @@ -0,0 +1,36 @@ + +Defines the `VLOG_IS_ON(level)` macro for variable-verbosity conditional logging in the Google glog library, enabling fine-grained control over V-logging levels per module or globally. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `VLOG_IS_ON(verboselevel)` | Macro | Evaluates to `true` if the current verbosity level meets or exceeds `verboselevel`; uses per-site static caching on GCC | +| `SetVLOGLevel` | Function | Dynamically overrides the V-log level for a given module pattern at runtime | +| `InitVLOG3__` | Function | Internal helper that resolves which verbosity variable controls a specific `VLOG_IS_ON` call site | +| `FLAGS_v` | External flag | Global default max V-logging level (default `0`) | +| `GOOGLE_GLOG_DLL_DECL` | Macro | DLL import/export declaration for Windows compatibility | + +## Behavior Notes + +- **GCC builds**: Uses a per-site `static google::int32*` pointer that lazily binds to either `FLAGS_v` or a module-specific level derived from `--vmodule`, enabling efficient repeated checks. +- **Non-GCC builds**: Falls back to a simple `FLAGS_v >= verboselevel` comparison; `--vmodule` is not supported. + +## Usage Example + +```c +// Direct use when logging prep logic is needed +if (VLOG_IS_ON(2)) { + // perform expensive computation only if verbosity >= 2 + std::string debug_info = BuildDebugReport(); + LOG(INFO) << debug_info; +} + +// Dynamically raise verbosity for a specific module at runtime +int previous_level = SetVLOGLevel("my_module", 3); + +// Typical indirect use via VLOG macro (defined in logging.h) +VLOG(1) << "Standard verbose log message"; +``` + +> **Note:** To affect `VLOG_IS_ON` sites already initialized after `InitGoogleLogging`, `SetVLOGLevel` requires the exact `--vmodule` pattern that was originally matched. Sites not matched by any `--vmodule` pattern continue to follow `FLAGS_v`. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/aarch64/private/.config.md b/libraries/cmake/source/glog/generated/windows/aarch64/private/.config.md new file mode 100644 index 00000000000..17f9802b43b --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/aarch64/private/.config.md @@ -0,0 +1,45 @@ + +Platform-specific build configuration header for the **glog** (Google Logging) library, defining compile-time feature flags, platform capability detection macros, and namespace utilities. + +## Key Components + +| Macro | Value | Purpose | +|---|---|---| +| `GOOGLE_NAMESPACE` | `google` | Defines the enclosing C++ namespace | +| `_START_GOOGLE_NAMESPACE_` | `namespace google {` | Opens the Google namespace block | +| `_END_GOOGLE_NAMESPACE_` | `}` | Closes the Google namespace block | +| `SIZEOF_VOID_P` | `8` | Pointer size (64-bit platform) | +| `STL_NAMESPACE` | `std` | STL container namespace | +| `HAVE_LIB_GFLAGS` | defined | gflags library is available | +| `HAVE_SYMBOLIZE` | defined | Stack trace symbolization is supported | +| `HAVE_CXX11_ATOMIC` | `1` | C++11 `` is available | +| `HAVE_ALIGNED_STORAGE` | `1` | `std::aligned_storage` and `alignof` are present | +| `HAVE_CXX11_NULLPTR_T` | `1` | C++11 `nullptr_t` is available | + +## Usage Example + +```c +#include "config.h" + +// Use namespace macros to wrap glog internals +_START_GOOGLE_NAMESPACE_ + +void MyLoggingHelper() { + // Code inside namespace google {} +} + +_END_GOOGLE_NAMESPACE_ + +// Conditional compilation based on detected features +#ifdef HAVE_SYMBOLIZE + // Enable human-readable stack traces + EnableSymbolize(); +#endif + +#ifdef HAVE_LIB_GFLAGS + // Parse command-line flags via gflags + google::ParseCommandLineFlags(&argc, &argv, true); +#endif +``` + +> **Note:** This file is auto-generated by the build system (CMake/Bazel/Autoconf). Do not edit manually. The `GLOG_BAZEL_BUILD` guard handles a [known Bazel namespace resolution issue](https://github.com/bazelbuild/bazel/issues/3979). \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.export.md b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.export.md new file mode 100644 index 00000000000..21bb0caa398 --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.export.md @@ -0,0 +1,41 @@ + +Visibility and linkage control macros for the Google glog logging library, defining symbol export/import decorators for shared and static library builds. + +## Key Components + +| Macro | Purpose | +|---|---| +| `GOOGLE_GLOG_DLL_DECL` | Primary export/import decorator applied to public API symbols | +| `GLOG_NO_EXPORT` | Marks symbols explicitly hidden from the public ABI | +| `GLOG_DEPRECATED` | Marks symbols as deprecated via `__declspec(deprecated)` | +| `GLOG_DEPRECATED_EXPORT` | Combines public visibility with deprecation warning | +| `GLOG_DEPRECATED_NO_EXPORT` | Combines hidden visibility with deprecation warning | +| `GLOG_STATIC_DEFINE` | Define this to collapse all decorators to empty (static linking) | + +## Build Mode Behavior + +```text +GLOG_STATIC_DEFINE defined → GOOGLE_GLOG_DLL_DECL = (empty) +GOOGLE_GLOG_IS_A_DLL defined → building the DLL (GOOGLE_GLOG_DLL_DECL = dllexport equivalent) +Neither defined → consuming the DLL (GOOGLE_GLOG_DLL_DECL = dllimport equivalent) +``` + +## Usage Example + +```c +/* Consuming glog as a shared library (default) */ +#include "export.h" + +/* Public API function decorated for correct linkage */ +GOOGLE_GLOG_DLL_DECL void SomeGlogFunction(void); + +/* Mark a symbol as deprecated but still exported */ +GLOG_DEPRECATED_EXPORT void OldGlogFunction(void); + +/* Static linking: define before including any glog header */ +#define GLOG_STATIC_DEFINE +#include "export.h" +/* All decorators now expand to nothing */ +``` + +> **Note:** On this build, both `GOOGLE_GLOG_DLL_DECL` and `GLOG_NO_EXPORT` expand to empty strings, indicating the platform does not require explicit visibility annotations (common on Linux/macOS with default symbol visibility or when decorators are injected elsewhere in the build system). \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.logging.md b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.logging.md new file mode 100644 index 00000000000..127802a9419 --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.logging.md @@ -0,0 +1,63 @@ + +Comprehensive logging interface header from Google's glog library, providing macros and utilities for multi-level, conditional, and verbose logging across C++ applications. + +## Key Components + +### Severity Levels +- `INFO` — General informational messages +- `WARNING` — Non-critical warnings +- `ERROR` — Recoverable errors +- `FATAL` — Unrecoverable errors (terminates program) + +### Core Logging Macros +| Macro | Description | +|---|---| +| `LOG(severity)` | Basic severity-level logging | +| `LOG_IF(severity, cond)` | Conditional logging | +| `LOG_EVERY_N(severity, n)` | Log every nth occurrence | +| `LOG_FIRST_N(severity, n)` | Log only first N occurrences | +| `LOG_IF_EVERY_N(severity, cond, n)` | Conditional + rate-limited logging | +| `VLOG(level)` | Verbose level logging (always at INFO) | +| `DLOG(severity)` | Debug-only logging (compiled out in release) | +| `SYSLOG(severity)` | Log to syslog + normal logs | + +### Custom Prefix Support (`GLOG_CUSTOM_PREFIX_SUPPORT`) +- `LogMessageTime` — Wraps `tm` struct with accessors for timestamp components +- `LogMessageInfo` — Bundles severity, filename, line number, thread ID, and time +- `CustomPrefixCallback` — Function pointer type `void(ostream&, LogMessageInfo&, void*)` + +### Configuration Macros +- `GOOGLE_STRIP_LOG` — Compile-time log level stripping (default `0`) +- `GOOGLE_PREDICT_BRANCH_NOT_TAKEN` / `GOOGLE_PREDICT_FALSE` / `GOOGLE_PREDICT_TRUE` — Branch prediction hints +- `GOOGLE_GLOG_DLL_DECL` — Windows DLL import/export decoration + +### Type Definitions (`google` namespace) +Cross-platform fixed-width integer types: `int32`, `uint32`, `int64`, `uint64` + +## Usage Example + +```c +#include "logging.h" + +// Basic logging +LOG(INFO) << "Server started on port " << port; +LOG(WARNING) << "Config file missing, using defaults"; +LOG(ERROR) << "Failed to connect: " << strerror(errno); + +// Conditional logging +LOG_IF(INFO, num_requests > 1000) << "High load detected"; + +// Rate-limited logging +LOG_EVERY_N(INFO, 100) << "Processed " << google::COUNTER << " requests"; + +// Verbose logging (enabled with --v=2) +VLOG(2) << "Detailed debug info: " << payload; + +// Debug-only (compiled out in release builds) +DLOG(INFO) << "Debug checkpoint reached"; + +// Custom prefix (when GLOG_CUSTOM_PREFIX_SUPPORT is defined) +void MyPrefix(std::ostream& s, const LogMessageInfo& l, void*) { + s << l.severity << " [" << l.filename << ":" << l.line_number << "]"; +} +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.raw_logging.md b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.raw_logging.md new file mode 100644 index 00000000000..44d56f6c24f --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.raw_logging.md @@ -0,0 +1,39 @@ + +Thread-safe, allocation-free logging header for low-level C++ modules that cannot use standard `LOG()` due to memory allocation or lock constraints. Writes directly to `stderr` without buffering. + +## Key Components + +| Macro / Function | Description | +|---|---| +| `RAW_LOG(severity, ...)` | Primary logging macro; maps severity to `INFO`, `WARNING`, `ERROR`, or `FATAL` | +| `RAW_VLOG(level, ...)` | Verbose logging; respects `VLOG_IS_ON()` check | +| `RAW_LOG_INFO/WARNING/ERROR/FATAL(...)` | Severity-specific macros; conditionally compiled via `STRIP_LOG` | +| `RAW_CHECK(condition, message)` | Assertion macro; logs `FATAL` and aborts if condition is false | +| `RAW_DLOG` / `RAW_DCHECK` | Debug-only variants; compiled out when `NDEBUG` is defined | +| `RawLog__(severity, file, line, format, ...)` | Underlying implementation; no memory allocation, no locks | +| `RawLogStub__(int, ...)` | No-op stub used to suppress unused variable warnings when `STRIP_LOG` eliminates logging | + +## Behavior Notes + +- Output goes **only to `stderr`**, never buffered +- Long messages are **silently truncated** +- Log lines follow near-standard glog format: `E20200821 211317 file.cc:123] RAW: ...` +- `STRIP_LOG` level controls compile-time removal: `0`=all, `1`=strip INFO, `2`=strip WARNING+, `3`=strip ERROR+ + +## Usage Example + +```c +// Basic severity logging +RAW_LOG(ERROR, "Failed foo with %i: %s", status, error_msg); +RAW_LOG(INFO, "Server started on port %d", port); + +// Verbose logging (respects -v flag level) +RAW_VLOG(3, "Status is %i", status); + +// Assertion (fatally aborts if condition is false) +RAW_CHECK(ptr != nullptr, "pointer must not be null"); + +// Debug-only variants (no-ops in release builds) +RAW_DLOG(WARNING, "Debug warning: value is %d", val); +RAW_DCHECK(count > 0, "count must be positive"); +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.stl_logging.md b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.stl_logging.md new file mode 100644 index 00000000000..fed3a2d7210 --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.stl_logging.md @@ -0,0 +1,49 @@ + +Stream output operators for C++ STL containers, designed exclusively for use with the glog logging framework (`LOG()`, `CHECK_EQ()`, etc.). + +## Key Components + +### Core Function + +- **`google::PrintSequence(out, begin, end)`** — Iterates a container range and writes elements to an `ostream`, capped at **100 elements** to keep logs readable. Appends `" ..."` if truncated. + +### Operator Overloads + +| Macro | Containers Covered | +|---|---| +| `OUTPUT_TWO_ARG_CONTAINER` | `std::vector`, `std::deque`, `std::list` | +| `OUTPUT_THREE_ARG_CONTAINER` | `std::set`, `std::multiset` | +| `OUTPUT_FOUR_ARG_CONTAINER` | `std::map`, `std::multimap`, unordered sets (optional) | +| `OUTPUT_FIVE_ARG_CONTAINER` | `std::unordered_map`, hash maps (optional) | +| `operator<<` (inline) | `std::pair` → outputs `(first, second)` | + +### Optional Extension Macros + +Define **before** including this header to enable additional container support: + +```c +#define GLOG_STL_LOGGING_FOR_UNORDERED // std::unordered_map/set +#define GLOG_STL_LOGGING_FOR_TR1_UNORDERED // tr1::unordered_map/set +#define GLOG_STL_LOGGING_FOR_EXT_HASH // __gnu_cxx::hash_map/set +#define GLOG_STL_LOGGING_FOR_EXT_SLIST // __gnu_cxx::slist +``` + +## Usage Example + +```cpp +#define GLOG_STL_LOGGING_FOR_UNORDERED +#include + +std::vector v1 = {1, 2, 3}; +std::map scores = {{"alice", 10}, {"bob", 7}}; + +// Direct stream logging +LOG(INFO) << "Vector: " << v1; +LOG(INFO) << "Scores: " << scores; + +// Works with CHECK macros via ADL +std::vector v2 = {1, 2, 3}; +CHECK_EQ(v1, v2); // Streams both vectors on failure +``` + +> **⚠️ Logging Only:** This header intentionally injects operators into `namespace std` via ADL to support `CHECK_EQ` and similar macros in glog. It is not intended for general-purpose stream output. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.vlog_is_on.md b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.vlog_is_on.md new file mode 100644 index 00000000000..e8ecc949fba --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/aarch64/public/glog/.vlog_is_on.md @@ -0,0 +1,40 @@ + +Defines the `VLOG_IS_ON(level)` macro for conditional verbose logging in Google glog, used by `VLOG`, `VLOG_IF`, and `RAW_VLOG` to evaluate whether a given verbosity level is active. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `VLOG_IS_ON(verboselevel)` | Macro | Evaluates to `true` if the current verbosity level meets or exceeds `verboselevel`. Uses per-site static pointer on GCC; falls back to `FLAGS_v` on non-GCC compilers | +| `SetVLOGLevel()` | Function | Dynamically overrides the per-module verbosity level set by `--vmodule`; returns the previously applied level | +| `InitVLOG3__()` | Function | Internal helper that resolves and caches the controlling verbosity variable for a specific VLOG call site | + +## Verbosity Control Flags + +- `--v=` — Global default verbosity level (default: `0`) +- `--vmodule=` — Per-module overrides, e.g. `"my_module=2,foo*=3"` (GCC only) + +## Usage Example + +```c +// Basic conditional verbose logging +if (VLOG_IS_ON(2)) { + // Perform expensive log preparation only when level 2 is active + std::string debug_info = BuildExpensiveDebugString(); + LOG(INFO) << debug_info; +} + +// Dynamically adjust verbosity for a specific module at runtime +int prev_level = SetVLOGLevel("my_module", 3); +// ... do work with elevated logging ... +SetVLOGLevel("my_module", prev_level); // restore + +// Typical usage via VLOG macro (uses VLOG_IS_ON internally) +VLOG(1) << "Standard verbose log message"; +``` + +## Notes + +- On GCC, each `VLOG_IS_ON(n)` site caches a static pointer after first evaluation for efficiency +- `--vmodule` pattern matching and `SetVLOGLevel` are **GCC-only**; non-GCC builds always use `FLAGS_v` +- On Windows, `GOOGLE_GLOG_DLL_DECL` resolves to `__declspec(dllimport)` for proper DLL linkage \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/x86_64/private/.config.md b/libraries/cmake/source/glog/generated/windows/x86_64/private/.config.md new file mode 100644 index 00000000000..67c86e6fbb8 --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/x86_64/private/.config.md @@ -0,0 +1,53 @@ + +Build-time configuration header for the Google glog logging library, defining platform capabilities, available headers, compiler features, and namespace macros detected during the build process. + +## Key Components + +| Macro | Value | Purpose | +|---|---|---| +| `GOOGLE_NAMESPACE` | `google` | Namespace for all Google classes | +| `STL_NAMESPACE` | `std` | Namespace where STL types are defined | +| `SIZEOF_VOID_P` | `8` | Pointer size (64-bit platform) | +| `_START_GOOGLE_NAMESPACE_` | `namespace google {` | Opens the Google namespace block | +| `_END_GOOGLE_NAMESPACE_` | `}` | Closes the Google namespace block | +| `HAVE_LIB_GFLAGS` | defined | Google gflags library is available | +| `HAVE_SYMBOLIZE` | defined | Stack trace symbolization is supported | +| `HAVE_CXX11_ATOMIC` | `1` | C++11 `std::atomic` is available | +| `HAVE_CXX11_NULLPTR_T` | `1` | C++11 `nullptr_t` is available | +| `HAVE_ALIGNED_STORAGE` | `1` | `std::aligned_storage` and `alignof` present | + +## Feature Flags Summary + +- **Threading**: POSIX threads (`HAVE_PTHREAD`, `HAVE_RWLOCK`) are **disabled** — this is a non-POSIX (likely Windows) build +- **Headers available**: ``, ``, ``, ``, `` +- **Headers unavailable**: ``, ``, ``, ``, signal handling headers +- **Compiler features**: Namespaces (`HAVE_NAMESPACES`), `using` operator (`HAVE_USING_OPERATOR`), `snprintf` (`HAVE_SNPRINTF`) + +## Usage Example + +```c +#include "config.h" + +// Use namespace macros to wrap glog code +_START_GOOGLE_NAMESPACE_ + +class Logger { + // Placed inside namespace google {} +}; + +_END_GOOGLE_NAMESPACE_ + +// Conditionally compile pthread-dependent code +#ifdef HAVE_PTHREAD + pthread_mutex_lock(&mutex); +#else + // fallback single-threaded path +#endif + +// Guard against missing C++11 features +#ifdef HAVE_CXX11_ATOMIC + std::atomic counter{0}; +#endif +``` + +> **Note:** This file is auto-generated by the build system (CMake/Bazel). Do not edit manually — changes will be overwritten on the next configure step. The Bazel build path (`GLOG_BAZEL_BUILD`) uses a workaround for [bazelbuild/bazel#3979](https://github.com/bazelbuild/bazel/issues/3979). \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.export.md b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.export.md new file mode 100644 index 00000000000..8a747130604 --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.export.md @@ -0,0 +1,42 @@ + +Visibility and export control macros for the Google glog logging library, managing symbol visibility across static and shared (DLL) build configurations on Windows. + +## Key Components + +| Macro | Purpose | +|---|---| +| `GOOGLE_GLOG_DLL_DECL` | Marks symbols for DLL export/import; empty on this platform (no `__declspec` needed) | +| `GLOG_NO_EXPORT` | Marks symbols that should not be exported from the library | +| `GLOG_DEPRECATED` | Tags symbols as deprecated using `__declspec(deprecated)` | +| `GLOG_DEPRECATED_EXPORT` | Combines export visibility with deprecation marking | +| `GLOG_DEPRECATED_NO_EXPORT` | Combines hidden visibility with deprecation marking | +| `GLOG_STATIC_DEFINE` | Define this to suppress all DLL decorators for static linking | + +## Usage Example + +```c +/* Static linking — suppress DLL decorators */ +#define GLOG_STATIC_DEFINE +#include "export.h" + +/* Annotate a public API function */ +GOOGLE_GLOG_DLL_DECL void my_log_function(const char* msg); + +/* Mark a function as deprecated but still exported */ +GLOG_DEPRECATED_EXPORT void old_log_function(const char* msg); + +/* Internal symbol — not exported */ +GLOG_NO_EXPORT void internal_helper(void); +``` + +## Build Modes + +```mermaid +graph TD + A{GLOG_STATIC_DEFINE?} -->|Yes| B[All macros empty] + A -->|No| C{GOOGLE_GLOG_IS_A_DLL?} + C -->|Building DLL| D[GOOGLE_GLOG_DLL_DECL = export] + C -->|Consuming DLL| E[GOOGLE_GLOG_DLL_DECL = import] +``` + +> **Note:** On this particular build, both the export and import definitions of `GOOGLE_GLOG_DLL_DECL` resolve to empty strings, indicating the platform does not require explicit `__declspec` annotations (e.g., Linux/macOS GCC/Clang builds). \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.logging.md b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.logging.md new file mode 100644 index 00000000000..98070e227da --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.logging.md @@ -0,0 +1,82 @@ + +Brief description of the purpose of the logging.h file in 1-2 sentences. + +## Key Components + +### Severity Levels + +| Level | Description | +|-------|-------------| +| `INFO` | General informational messages | +| `WARNING` | Non-critical warnings | +| `ERROR` | Error conditions | +| `FATAL` | Critical failures (terminates program) | + +### Core Logging Macros + +| Macro | Purpose | +|-------|---------| +| `LOG(severity)` | Basic severity-level logging | +| `LOG_IF(severity, cond)` | Conditional logging | +| `LOG_EVERY_N(severity, n)` | Log every nth occurrence | +| `LOG_IF_EVERY_N(severity, cond, n)` | Conditional + rate-limited logging | +| `LOG_FIRST_N(severity, n)` | Log only the first N occurrences | +| `LOG_STRING(severity, &vec)` | Capture log messages into a string vector | +| `VLOG(level)` | Verbose/debug level logging | +| `DLOG(severity)` | Debug-mode only logging (compiled out in release) | +| `LOG_ASSERT(assertion)` | Log FATAL if assertion fails | + +### Custom Prefix Support (`GLOG_CUSTOM_PREFIX_SUPPORT`) + +- **`LogMessageTime`** — Wraps a `struct tm` with microsecond precision accessors (`timestamp()`, `sec()`, `usec()`, `hour()`, etc.) +- **`LogMessageInfo`** — Aggregates severity, filename, line number, thread ID, and `LogMessageTime` for prefix formatting +- **`CustomPrefixCallback`** — Function pointer typedef `void(std::ostream&, const LogMessageInfo&, void*)` for user-defined log prefixes + +### Type Aliases (`google` namespace) + +```c +typedef int32_t int32; +typedef uint32_t uint32; +typedef int64_t int64; +typedef uint64_t uint64; +``` + +### Compile-Time Controls + +| Macro | Purpose | +|-------|---------| +| `GOOGLE_STRIP_LOG` | Strip log messages below this severity at compile time (default: `0`) | +| `GOOGLE_PREDICT_BRANCH_NOT_TAKEN(x)` | Branch prediction hint for unlikely paths | +| `GOOGLE_PREDICT_FALSE(x)` / `GOOGLE_PREDICT_TRUE(x)` | Likelihood hints for optimizer | +| `GOOGLE_GLOG_DLL_DECL` | DLL import/export decoration for Windows | + +## Usage Example + +```c +#include "logging.h" + +// Basic logging +LOG(INFO) << "Server started on port " << port; +LOG(WARNING) << "Cache miss rate high: " << miss_rate << "%"; +LOG(ERROR) << "Failed to open file: " << filename; + +// Conditional logging +LOG_IF(INFO, num_requests > 1000) << "High request volume detected"; + +// Rate-limited logging (every 10th occurrence) +LOG_EVERY_N(INFO, 10) << "Processed request #" << google::COUNTER; + +// Verbose logging (enabled via --v=2 flag) +VLOG(2) << "Detailed debug info: " << debug_data; + +// Capture logs into a vector +std::vector errors; +LOG_STRING(ERROR, &errors) << "Parse error at token: " << token; + +// Debug-only logging (compiled away in release builds) +DLOG(INFO) << "Debug checkpoint reached"; + +// Strip INFO and WARNING at compile time +#define GOOGLE_STRIP_LOG 2 +#include "logging.h" +``` \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.raw_logging.md b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.raw_logging.md new file mode 100644 index 00000000000..8d7a45bce05 --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.raw_logging.md @@ -0,0 +1,47 @@ + +Thread-safe, allocation-free logging header for low-level C++ modules that cannot use glog's standard `LOG()` infrastructure (e.g., memory allocators, lock implementations). + +## Key Components + +### Macros + +| Macro | Description | +|---|---| +| `RAW_LOG(severity, ...)` | Primary logging macro; dispatches to severity-specific variants (`INFO`, `WARNING`, `ERROR`, `FATAL`) | +| `RAW_VLOG(level, ...)` | Verbose logging; only emits when `VLOG_IS_ON(level)` is true | +| `RAW_LOG_INFO/WARNING/ERROR/FATAL(...)` | Direct severity-level macros; stripped at compile time based on `STRIP_LOG` | +| `RAW_CHECK(condition, message)` | Assertion macro; logs `FATAL` and aborts if condition is false | +| `RAW_DLOG / RAW_DCHECK` | Debug-only variants; compiled out when `NDEBUG` is defined | + +### Functions + +- **`RawLog__(severity, file, line, format, ...)`** — Core implementation; writes formatted output directly to `stderr` without memory allocation or locking. Silently truncates very long messages. +- **`RawLogStub__(int, ...)`** — No-op stub used to suppress unused-variable warnings when logging is stripped via `STRIP_LOG`. + +## Usage Example + +```c +#include "base/raw_logging.h" + +// Basic severity logging +RAW_LOG(ERROR, "Failed foo with %i: %s", status, error_msg); +RAW_LOG(INFO, "Server started on port %d", port); + +// Verbose logging (only emits if verbosity level >= 3) +RAW_VLOG(3, "Current status is %i", status); + +// Assertion — fatally aborts if fd < 0 +RAW_CHECK(fd >= 0, "Failed to open file descriptor"); + +// Debug-only logging (no-op in release builds) +RAW_DLOG(WARNING, "Debug: buffer size is %zu", buf_size); +RAW_DCHECK(ptr != nullptr, "Null pointer encountered"); +``` + +Output is written exclusively to `stderr` in near-standard glog format: + +```text +E20200821 211317 file.cc:123] RAW: Failed foo with 22: bad_file +``` + +> **Note:** Use these macros **only** in low-level code where normal `LOG()` is unsafe. All other code should prefer standard glog macros. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.stl_logging.md b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.stl_logging.md new file mode 100644 index 00000000000..551d1d6b8dc --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.stl_logging.md @@ -0,0 +1,46 @@ + +Provides `std::ostream` stream output operators for common STL containers, enabling them to be used directly with glog's `LOG()` and `CHECK_*()` macros for diagnostic logging. + +## Key Components + +| Component | Description | +|---|---| +| `google::PrintSequence` | Core iterator-based printer; outputs up to 100 elements, appending `...` if truncated | +| `operator<<` for `std::pair` | Formats as `(first, second)` | +| `OUTPUT_TWO_ARG_CONTAINER` | Macro expanding `operator<<` for `vector`, `deque`, `list` | +| `OUTPUT_THREE_ARG_CONTAINER` | Macro expanding `operator<<` for `set`, `multiset` | +| `OUTPUT_FOUR_ARG_CONTAINER` | Macro expanding `operator<<` for `map`, `multimap`, and optionally unordered/hash sets | +| `OUTPUT_FIVE_ARG_CONTAINER` | Macro expanding `operator<<` for `unordered_map`, `hash_map`, and variants | + +### Optional Extension Macros + +| Macro | Enables | +|---|---| +| `GLOG_STL_LOGGING_FOR_UNORDERED` | `` / `` | +| `GLOG_STL_LOGGING_FOR_TR1_UNORDERED` | `` / `` | +| `GLOG_STL_LOGGING_FOR_EXT_HASH` | `` / `` | +| `GLOG_STL_LOGGING_FOR_EXT_SLIST` | `` | + +## Usage Example + +```cpp +#define GLOG_STL_LOGGING_FOR_UNORDERED +#include +#include + +// Log a vector directly +std::vector v = {1, 2, 3}; +LOG(INFO) << "values: " << v; +// Output: values: 1 2 3 + +// Use CHECK_EQ with containers +std::vector a = {1, 2}, b = {1, 2}; +CHECK_EQ(a, b); + +// Pairs are formatted as (first, second) +std::map m = {{"x", 1}}; +LOG(INFO) << "map: " << m; +// Output: map: (x, 1) +``` + +> **Note:** Elements beyond the first 100 are silently truncated with `...` — this header is intended for logging/debugging use only, not serialization. \ No newline at end of file diff --git a/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.vlog_is_on.md b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.vlog_is_on.md new file mode 100644 index 00000000000..dd8db9befb9 --- /dev/null +++ b/libraries/cmake/source/glog/generated/windows/x86_64/public/glog/.vlog_is_on.md @@ -0,0 +1,32 @@ + +Defines the `VLOG_IS_ON(level)` macro used by glog's variable-verbosity conditional logging system, enabling per-module and global log level control at runtime. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `VLOG_IS_ON(verboselevel)` | Macro | Returns `true` if verbose logging is enabled at the given level. GCC version uses per-site static pointers for efficient `--vmodule` support; non-GCC falls back to global `FLAGS_v` comparison | +| `SetVLOGLevel()` | Function | Dynamically overrides the verbosity level for a given module pattern at runtime; returns the previously applied level | +| `InitVLOG3__()` | Function | Internal helper that resolves which verbosity variable controls a specific `VLOG_IS_ON` call site on first execution | +| `GOOGLE_GLOG_DLL_DECL` | Macro | Windows DLL import/export decoration; expands to `__declspec(dllimport)` on Win32, empty elsewhere | + +## Usage Example + +```c +// Basic conditional verbose logging block +if (VLOG_IS_ON(2)) { + // Expensive preparation only performed when level 2 logging is active + std::string debug_info = BuildExpensiveDebugString(); + LOG(INFO) << debug_info; +} + +// Dynamically set verbosity for a specific module at runtime +// Overrides --vmodule flag behavior +int previous_level = SetVLOGLevel("my_module", 3); + +// Control via flags at startup: +// --v=1 sets global default verbosity to 1 +// --vmodule=foo*=2 sets verbosity to 2 for all files matching foo* +``` + +> **Note:** Per-module `--vmodule` support (via the GCC path with static site pointers) is only available when compiling with GCC. On other compilers, `VLOG_IS_ON(n)` always evaluates to `FLAGS_v >= n`. \ No newline at end of file diff --git a/libraries/cmake/source/krabsetw/config/.krabs.md b/libraries/cmake/source/krabsetw/config/.krabs.md new file mode 100644 index 00000000000..d1381367283 --- /dev/null +++ b/libraries/cmake/source/krabsetw/config/.krabs.md @@ -0,0 +1,23 @@ + +Instantiates the `krabs` namespace with a default-constructed `schema_locator` object, serving as a minimal translation unit to satisfy the linker for the krabs ETW (Event Tracing for Windows) library. + +## Key Components + +- **`krabs::dummy`** — A global `schema_locator` instance used as a placeholder to ensure the `krabs` namespace and its associated symbols are included in the compiled output. + +## Usage Example + +```cpp +#include "krabs.hpp" + +// The dummy schema_locator is defined globally in the krabs namespace. +// No direct usage required — this file exists to satisfy linker requirements. +// Consumers interact with the krabs ETW library through krabs.hpp: + +krabs::user_trace trace; +krabs::provider<> provider(L"Microsoft-Windows-Kernel-Process"); +trace.enable(provider); +trace.start(); +``` + +> **Note:** This file contains no logic beyond the global instantiation. Its primary role is as a compilation unit anchor for the krabs ETW library, ensuring the `schema_locator` type is linked into the final binary. \ No newline at end of file diff --git a/libraries/cmake/source/libarchive/config/linux/aarch64/.config.md b/libraries/cmake/source/libarchive/config/linux/aarch64/.config.md new file mode 100644 index 00000000000..87aedca9556 --- /dev/null +++ b/libraries/cmake/source/libarchive/config/linux/aarch64/.config.md @@ -0,0 +1,42 @@ + +CMake-generated configuration header for the libarchive library that establishes platform-specific integer type definitions, feature detection flags, and compile-time capability guards. + +## Key Components + +### Integer Type Definitions +- **Fixed-width signed types**: `int16_t`, `int32_t`, `int64_t`, `intmax_t` — resolved via cascading fallback logic using `SIZEOF_*` constants +- **Fixed-width unsigned types**: `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`, `uintmax_t` — same fallback pattern +- **Size constants**: `SIZEOF_SHORT`, `SIZEOF_INT`, `SIZEOF_LONG`, `SIZEOF_LONG_LONG` and unsigned equivalents + +### Feature Detection Macros +- **ACL support flags**: `ARCHIVE_ACL_DARWIN`, `ARCHIVE_ACL_FREEBSD`, `ARCHIVE_ACL_LIBACL`, `ARCHIVE_ACL_SUNOS`, etc. (all disabled in this build) +- **Crypto backend flags**: `ARCHIVE_CRYPTO_MD5_*`, `ARCHIVE_CRYPTO_SHA*_*` across providers (OpenSSL, Nettle, libc, WinAPI — all disabled) +- **Extended attribute support**: `ARCHIVE_XATTR_LINUX`, `ARCHIVE_XATTR_DARWIN`, `ARCHIVE_XATTR_FREEBSD`, etc. (all disabled) +- **POSIX function availability**: `HAVE_CHOWN`, `HAVE_CHROOT`, `HAVE_CTIME_R`, `HAVE_CTYPE_H`, etc. + +### Version Strings +- `BSDCPIO_VERSION_STRING`, `BSDTAR_VERSION_STRING`, `BSDCAT_VERSION_STRING` — all set to `"3.7.9"` + +## Usage Example + +```c +#include "config.h" + +/* Safe 64-bit integer usage guaranteed by config.h fallback logic */ +int64_t offset = 0; +uint32_t checksum = 0xDEADBEEF; + +/* Conditional compilation based on detected features */ +#ifdef ARCHIVE_ACL_LIBACL + /* Linux POSIX ACL path */ + apply_posix_acl(entry); +#elif defined(ARCHIVE_ACL_DARWIN) + /* macOS ACL path */ + apply_darwin_acl(entry); +#endif + +/* Version reporting */ +printf("bsdtar version: %s\n", BSDTAR_VERSION_STRING); +``` + +> **Note:** This file is auto-generated by CMake from `build/cmake/config.h.in`. Do not edit manually — regenerate via `cmake configure` when changing build options or targeting a new platform. \ No newline at end of file diff --git a/libraries/cmake/source/libarchive/config/linux/x86_64/.config.md b/libraries/cmake/source/libarchive/config/linux/x86_64/.config.md new file mode 100644 index 00000000000..b413e5fc798 --- /dev/null +++ b/libraries/cmake/source/libarchive/config/linux/x86_64/.config.md @@ -0,0 +1,64 @@ + +CMake-generated configuration header for the libarchive library that establishes platform-specific type definitions, integer sizes, and feature availability flags used throughout the codebase. + +## Key Components + +### Integer Type Definitions +Portable C99-style fixed-width integer types with fallback typedef chains: + +| Type | Signed | Unsigned | +|------|--------|----------| +| 8-bit | — | `uint8_t` | +| 16-bit | `int16_t` | `uint16_t` | +| 32-bit | `int32_t` | `uint32_t` | +| 64-bit | `int64_t` | `uint64_t` | +| Max-width | `intmax_t` | `uintmax_t` | + +### Platform Size Constants +```c +#define SIZEOF_SHORT 2 +#define SIZEOF_INT 4 +#define SIZEOF_LONG 8 +#define SIZEOF_LONG_LONG 8 +#define SIZEOF_UNSIGNED_LONG 8 +#define SIZEOF_UNSIGNED_LONG_LONG 8 +``` + +### Version Strings +```c +#define BSDCPIO_VERSION_STRING "3.7.9" +#define BSDTAR_VERSION_STRING "3.7.9" +#define BSDCAT_VERSION_STRING "3.7.9" +``` + +### Feature Detection Flags (disabled in this build) +- **ACL support**: Darwin, FreeBSD, Linux (`libacl`/`librichacl`), Solaris +- **Crypto backends**: MD5, RMD160, SHA1/256/384/512 via OpenSSL, Nettle, libc, Windows +- **Extended attributes**: AIX, Darwin, FreeBSD, Linux + +### Enabled POSIX Functions +```c +#define HAVE_CHOWN 1 +#define HAVE_CHROOT 1 +#define HAVE_CTIME_R 1 +#define HAVE_CTYPE_H 1 +``` + +## Usage Example + +```c +/* This header is auto-included by libarchive internals. + The fallback typedef chain ensures int64_t is always available: */ + +#include "config.h" + +/* Safe cross-platform 64-bit usage */ +int64_t offset = 0; +uint32_t checksum = 0xDEADBEEF; + +/* Guard against missing types at compile time — config.h emits + #error if no suitable native type is found, e.g.: + #error No 64-bit integer type was found. */ +``` + +> **Note:** This file is auto-generated by CMake from `build/cmake/config.h.in`. Do not edit manually — regenerate via `cmake configure` to reflect the target platform's capabilities. \ No newline at end of file diff --git a/libraries/cmake/source/libarchive/config/macos/aarch64/.config.md b/libraries/cmake/source/libarchive/config/macos/aarch64/.config.md new file mode 100644 index 00000000000..d6e5f160bb1 --- /dev/null +++ b/libraries/cmake/source/libarchive/config/macos/aarch64/.config.md @@ -0,0 +1,56 @@ + +Generated by cmake from `config.h.in`, this file provides compile-time platform detection and capability configuration for the libarchive library, ensuring portable integer types, cryptographic backend selection, and system feature availability across different operating systems. + +## Key Components + +### Integer Type Detection & Sizing +- **C99 fixed-width types** (`int16_t`, `int32_t`, `int64_t`, `uint8_t`–`uint64_t`, `intmax_t`, `uintmax_t`) — defined or typedef'd based on platform type sizes +- **`SIZEOF_*` macros** — records byte sizes of `short`, `int`, `long`, `long long` and their unsigned variants (e.g., `SIZEOF_LONG 8`) +- Compile-time `#error` guards halt the build if a required integer width cannot be satisfied + +### Cryptographic Backend Selection (`ARCHIVE_CRYPTO_*`) +Selects the hashing library for each algorithm. In this configuration, all active algorithms route through **libSystem** (macOS): + +| Algorithm | Active Backend | +|-----------|---------------| +| MD5 | `ARCHIVE_CRYPTO_MD5_LIBSYSTEM` | +| SHA1 | `ARCHIVE_CRYPTO_SHA1_LIBSYSTEM` | +| SHA256 | `ARCHIVE_CRYPTO_SHA256_LIBSYSTEM` | +| SHA384 | `ARCHIVE_CRYPTO_SHA384_LIBSYSTEM` | +| SHA512 | `ARCHIVE_CRYPTO_SHA512_LIBSYSTEM` | + +### Platform Feature Flags (`HAVE_*`) +Indicates presence of system headers, functions, and types — e.g.: +- `HAVE_CHFLAGS`, `HAVE_CHOWN`, `HAVE_CHROOT` +- `HAVE_COPYFILE_H` (macOS-specific) +- `HAVE_ARC4RANDOM_BUF` +- ACL and xattr support (all disabled in this build) + +### Version Strings +```c +#define BSDCPIO_VERSION_STRING "3.7.9" +#define BSDTAR_VERSION_STRING "3.7.9" +#define BSDCAT_VERSION_STRING "3.7.9" +``` + +## Usage Example + +```c +#include "config.h" + +/* Safe portable 64-bit counter — works on any supported platform */ +uint64_t byte_count = 0; + +/* Conditionally use a detected function */ +#ifdef HAVE_ARC4RANDOM_BUF + arc4random_buf(buffer, sizeof(buffer)); +#else + /* fallback PRNG */ +#endif + +/* Guard against missing integer support at compile time — + the header already emits #error if int64_t cannot be resolved */ +int64_t offset = INT64_MAX; +``` + +> **Note:** This file is auto-generated by CMake and should not be edited manually. Regenerate it by re-running `cmake configure` after modifying `build/cmake/config.h.in` or platform toolchain settings. \ No newline at end of file diff --git a/libraries/cmake/source/libarchive/config/macos/x86_64/.config.md b/libraries/cmake/source/libarchive/config/macos/x86_64/.config.md new file mode 100644 index 00000000000..4eaf4fe0ecc --- /dev/null +++ b/libraries/cmake/source/libarchive/config/macos/x86_64/.config.md @@ -0,0 +1,59 @@ + +Generated from `build/cmake/config.h.in` by CMake, this header provides platform-specific compile-time configuration for the libarchive library, defining integer type availability, sizes, cryptographic backend selections, and feature detection macros. + +## Key Components + +### Integer Type Detection & Fallback Typedefs +Ensures C99-compatible fixed-width integer types (`int16_t`, `int32_t`, `int64_t`, `uint8_t`–`uint64_t`, `intmax_t`, `uintmax_t`) are available, with cascading fallbacks based on detected `SIZEOF_*` macros if the system does not natively provide them. Emits a `#error` at compile time if no suitable type is found. + +### Platform Size Macros +```c +#define SIZEOF_SHORT 2 +#define SIZEOF_INT 4 +#define SIZEOF_LONG 8 +#define SIZEOF_LONG_LONG 8 +#define SIZEOF_UNSIGNED_LONG 8 +#define SIZEOF_UNSIGNED_LONG_LONG 8 +``` + +### Cryptographic Backend Flags +Selects the active hash implementation per algorithm. In this build, macOS `LibSystem` is used for all digests: + +| Algorithm | Active Backend | +|---|---| +| MD5 | `ARCHIVE_CRYPTO_MD5_LIBSYSTEM` | +| SHA1 | `ARCHIVE_CRYPTO_SHA1_LIBSYSTEM` | +| SHA256 | `ARCHIVE_CRYPTO_SHA256_LIBSYSTEM` | +| SHA384 | `ARCHIVE_CRYPTO_SHA384_LIBSYSTEM` | +| SHA512 | `ARCHIVE_CRYPTO_SHA512_LIBSYSTEM` | + +### Feature Detection Macros +Flags available POSIX/platform functions and headers (e.g., `HAVE_CHFLAGS`, `HAVE_CHOWN`, `HAVE_COPYFILE_H`, `HAVE_ARC4RANDOM_BUF`). ACL and xattr support macros are all disabled in this build. + +### Version Strings +```c +#define BSDCPIO_VERSION_STRING "3.7.9" +#define BSDTAR_VERSION_STRING "3.7.9" +#define BSDCAT_VERSION_STRING "3.7.9" +``` + +## Usage Example + +```c +#include "config.h" + +/* Safe 64-bit integer usage guaranteed by config.h fallbacks */ +int64_t offset = 0; +uint32_t checksum = 0; + +/* Conditional compilation using feature flags */ +#ifdef HAVE_COPYFILE_H +# include +#endif + +#ifdef ARCHIVE_CRYPTO_SHA256_LIBSYSTEM +/* Use macOS CommonCrypto for SHA256 */ +#endif +``` + +> **Note:** This file is auto-generated by CMake and should not be edited manually. Regenerate it by re-running CMake configuration. ACL and platform-specific xattr support (Darwin, FreeBSD, Linux, Solaris, AIX) are all disabled in this configuration, indicating a macOS build targeting only `LibSystem` crypto backends. \ No newline at end of file diff --git a/libraries/cmake/source/libarchive/config/windows/aarch64/.config.md b/libraries/cmake/source/libarchive/config/windows/aarch64/.config.md new file mode 100644 index 00000000000..76f66b66b26 --- /dev/null +++ b/libraries/cmake/source/libarchive/config/windows/aarch64/.config.md @@ -0,0 +1,59 @@ + +CMake-generated build configuration header for the libarchive library, providing platform-specific integer type definitions, feature detection flags, and cryptographic backend selections for Windows. + +## Key Components + +### Integer Type Definitions +Resolves C99-compatible fixed-width integer types (`int16_t`, `int32_t`, `int64_t`, `uint8_t`–`uint64_t`, `intmax_t`, `uintmax_t`) by probing available platform types in priority order: +- `__int64` / `unsigned __int64` (MSVC) +- `int`, `long`, `long long` (sized by `SIZEOF_*` constants) +- Falls back to `#error` if no suitable type is found + +### Integer Size Constants +```c +#define SIZEOF_SHORT 2 +#define SIZEOF_INT 4 +#define SIZEOF_LONG 4 +#define SIZEOF_LONG_LONG 8 +``` + +### Cryptographic Backends (Active) +Windows CNG (`_WIN`) backends are enabled for all hash algorithms: +```c +#define ARCHIVE_CRYPTO_MD5_WIN 1 +#define ARCHIVE_CRYPTO_SHA1_WIN 1 +#define ARCHIVE_CRYPTO_SHA256_WIN 1 +#define ARCHIVE_CRYPTO_SHA384_WIN 1 +#define ARCHIVE_CRYPTO_SHA512_WIN 1 +``` +All POSIX alternatives (OpenSSL, Nettle, libc, LibSystem) are disabled. + +### Feature Flags (Disabled in This Build) +- **ACL support**: Darwin, FreeBSD (NFSv4), Linux (`libacl`/`librichacl`), Solaris — all off +- **Extended attribute (xattr) support**: AIX, Darwin, FreeBSD, Linux — all off +- **POSIX functions**: `chown`, `chflags`, `chroot`, `ctime_r`, `arc4random_buf` — all off + +### Version Strings +```c +#define BSDCPIO_VERSION_STRING "3.7.9" +#define BSDTAR_VERSION_STRING "3.7.9" +#define BSDCAT_VERSION_STRING "3.7.9" +``` + +## Usage Example + +```c +/* Including config.h ensures portable integer types are available */ +#include "config.h" + +int64_t offset = INT64_MAX; /* resolved via __int64 on MSVC */ +uint32_t checksum = 0xDEADBEEF; + +/* Conditional crypto backend selection */ +#ifdef ARCHIVE_CRYPTO_SHA256_WIN + /* Use Windows CNG for SHA-256 */ + BCryptHashData(...); +#endif +``` + +> **Note:** This file is auto-generated by CMake from `build/cmake/config.h.in`. Do not edit manually — regenerate via `cmake configure` when changing build options or targeting a new platform. \ No newline at end of file diff --git a/libraries/cmake/source/libarchive/config/windows/x86_64/.config.md b/libraries/cmake/source/libarchive/config/windows/x86_64/.config.md new file mode 100644 index 00000000000..ad2bfcb5f88 --- /dev/null +++ b/libraries/cmake/source/libarchive/config/windows/x86_64/.config.md @@ -0,0 +1,59 @@ + +CMake-generated configuration header for libarchive that establishes platform-specific type definitions, feature availability flags, and build-time capability detection for the library. + +## Key Components + +### Integer Type Definitions +Ensures C99-compatible fixed-width integer types (`int16_t`, `int32_t`, `int64_t`, `uint8_t`–`uint64_t`, `intmax_t`, `uintmax_t`) are available, falling back to platform-specific alternatives (`__int64`, `unsigned __int64`) when standard types are absent. + +### Integer Size Constants +```c +#define SIZEOF_SHORT 2 +#define SIZEOF_INT 4 +#define SIZEOF_LONG 4 +#define SIZEOF_LONG_LONG 8 +``` +Used to select appropriate type aliases at compile time. + +### Crypto Backend Flags +Indicates which cryptographic hash implementations are available. This build targets **Windows CNG** (`_WIN` variants): +```c +#define ARCHIVE_CRYPTO_MD5_WIN 1 +#define ARCHIVE_CRYPTO_SHA1_WIN 1 +#define ARCHIVE_CRYPTO_SHA256_WIN 1 +#define ARCHIVE_CRYPTO_SHA384_WIN 1 +#define ARCHIVE_CRYPTO_SHA512_WIN 1 +``` +OpenSSL, Nettle, and platform libc backends are disabled (`#undef`). + +### ACL & xattr Support +Platform-specific ACL (Darwin, FreeBSD, Linux, Solaris) and extended attribute backends — all disabled in this configuration, indicating a minimal Windows build. + +### Version Strings +```c +#define BSDCPIO_VERSION_STRING "3.7.9" +#define BSDTAR_VERSION_STRING "3.7.9" +#define BSDCAT_VERSION_STRING "3.7.9" +``` + +### Feature Detection Macros (`HAVE_*`) +Presence flags for system headers, functions, and types (e.g., `HAVE_CTYPE_H`, `HAVE_DECL_INT64_MAX`) consumed by libarchive's internal `#ifdef` guards. + +## Usage Example + +This header is auto-included by libarchive internals — not typically included directly. However, its definitions drive conditional compilation: + +```c +/* Inside libarchive source files */ +#include "config.h" + +#if defined(ARCHIVE_CRYPTO_SHA256_WIN) + /* Use Windows CNG for SHA-256 */ + BCryptHashData(hHash, buffer, len, 0); +#elif defined(ARCHIVE_CRYPTO_SHA256_OPENSSL) + /* Use OpenSSL fallback */ + SHA256_Update(&ctx, buffer, len); +#endif +``` + +> **Note:** This file is generated by CMake from `build/cmake/config.h.in`. Do not edit it manually — regenerate via `cmake configure` when changing build options. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/config/aarch64/.config.md b/libraries/cmake/source/libaudit/config/aarch64/.config.md new file mode 100644 index 00000000000..91293f368af --- /dev/null +++ b/libraries/cmake/source/libaudit/config/aarch64/.config.md @@ -0,0 +1,66 @@ + +Auto-generated build configuration header for the **audit 2.4.3** package, produced by `autoconf`/`autoheader` to capture platform capabilities, available system calls, header files, and optional feature flags detected at configure time. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"audit"` | +| `PACKAGE_VERSION` | `"2.4.3"` | +| `PACKAGE_STRING` | `"audit 2.4.3"` | + +### System Call & Function Availability +Flags indicating presence of key Linux APIs: + +- `HAVE_EPOLL_CTL` — epoll-based I/O multiplexing +- `HAVE_INOTIFY_INIT` — filesystem event monitoring +- `HAVE_SIGNALFD` — signal handling via file descriptors +- `HAVE_EVENTFD` — event notification between processes +- `HAVE_CLOCK_GETTIME` — high-resolution POSIX clock +- `HAVE_NANOSLEEP`, `HAVE_POLL`, `HAVE_SELECT` — timing and I/O polling + +### Optional Feature Flags +Disabled (commented out) optional features: + +- `HAVE_KQUEUE` — BSD kqueue support *(disabled)* +- `HAVE_LIBCAP_NG` — capability management *(disabled)* +- `HAVE_LIBWRAP` — TCP wrappers *(disabled)* +- `USE_GSSAPI` — Kerberos/GSSAPI auth *(disabled)* +- `WITH_APPARMOR` — AppArmor event support *(disabled)* + +### Enabled Architecture Support +```c +#define WITH_AARCH64 1 // ARM 64-bit +#define WITH_ARM 1 // ARM EABI 32-bit +``` + +### Type Sizes +```c +#define SIZEOF_UNSIGNED_INT 4 // 32-bit +#define SIZEOF_UNSIGNED_LONG 8 // 64-bit (LP64 platform) +``` + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_EPOLL_CTL + // Use epoll for event loop + int epfd = epoll_create1(0); +#elif defined(HAVE_POLL) + // Fallback to poll() + struct pollfd fds[MAX_FDS]; +#else + // Last resort: select() + fd_set readfds; +#endif + +#ifdef USE_LISTENER + // Compile in auditd network listener + init_network_listener(); +#endif +``` + +> **Note:** This file is auto-generated — edit `config.h.in` or `configure.ac` instead of modifying it directly. Re-run `./configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/config/x86_64/.config.md b/libraries/cmake/source/libaudit/config/x86_64/.config.md new file mode 100644 index 00000000000..e213145d2b9 --- /dev/null +++ b/libraries/cmake/source/libaudit/config/x86_64/.config.md @@ -0,0 +1,64 @@ + +Auto-generated by `autoconf` from `configure.ac`, this header captures the results of system capability detection for the **audit 2.4.3** package build, defining feature flags consumed throughout the codebase. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE` / `PACKAGE_NAME` | `"audit"` | +| `PACKAGE_VERSION` / `VERSION` | `"2.4.3"` | +| `PACKAGE_STRING` | `"audit 2.4.3"` | + +### I/O & Event Mechanism Support +Flags indicating which kernel event interfaces are available on the build host: + +- **Enabled:** `HAVE_EPOLL_CTL`, `HAVE_EVENTFD`, `HAVE_INOTIFY_INIT`, `HAVE_SIGNALFD`, `HAVE_POLL`, `HAVE_SELECT` +- **Disabled:** `HAVE_KQUEUE` (BSD), `HAVE_PORT_CREATE` (Solaris) — confirms Linux-only build + +### Timing & Sleep +- `HAVE_CLOCK_GETTIME`, `HAVE_NANOSLEEP`, `TIME_WITH_SYS_TIME` — POSIX high-resolution timing available; syscall fallback (`HAVE_CLOCK_SYSCALL`) not needed + +### Audit-Specific Features +- `HAVE_DECL_AUDIT_FEATURE_VERSION 1` — kernel audit feature versioning supported +- `HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME 0` — backlog wait time API **not** available on this kernel +- `USE_LISTENER 1` — network listener (`auditd`) compiled in + +### Disabled Optional Features +```c +/* #undef HAVE_LIBCAP_NG */ // No capability dropping +/* #undef HAVE_LIBWRAP */ // No TCP wrappers +/* #undef USE_GSSAPI */ // No Kerberos/GSSAPI +/* #undef WITH_APPARMOR */ // No AppArmor event support +/* #undef WITH_AARCH64 */ // x86-64 only (SIZEOF_UNSIGNED_LONG = 8) +``` + +### Type Sizes +```c +#define SIZEOF_UNSIGNED_INT 4 // 32-bit int +#define SIZEOF_UNSIGNED_LONG 8 // 64-bit platform confirmed +``` + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile epoll-based event loop */ +#if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H) +# include + // use epoll_ctl(), epoll_wait() +#elif defined(HAVE_POLL) && defined(HAVE_POLL_H) +# include + // fallback to poll() +#else +# error "No supported I/O multiplexing mechanism found" +#endif + +/* Guard network listener code */ +#ifdef USE_LISTENER +void start_network_listener(void); +#endif +``` + +> **Note:** Do not edit `config.h` directly. Regenerate it by running `./configure` after modifying `configure.ac`. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.aarch64_tables.md b/libraries/cmake/source/libaudit/generated/.aarch64_tables.md new file mode 100644 index 00000000000..9a0a716a506 --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.aarch64_tables.md @@ -0,0 +1,36 @@ + +Generated file providing bidirectional mapping between AArch64 (ARM64) Linux syscall names and their corresponding syscall numbers. + +## Key Components + +| Component | Type | Description | +|---|---|---| +| `aarch64_syscall_strings` | `const char[]` | Null-delimited string table of all syscall names (e.g., `accept`, `read`, `write`) | +| `aarch64_syscall_s2i_s` | `const unsigned[]` | Byte offsets into the string table for name→number lookup | +| `aarch64_syscall_s2i_i` | `const int[]` | Corresponding syscall numbers for each string offset entry | +| `aarch64_syscall_i2s_direct` | `const unsigned[]` | Direct-indexed offset table for number→name lookup (indexed by syscall number, `-1u` for gaps) | +| `aarch64_syscall_s2i()` | `static int` | Looks up a syscall number by name (case-insensitive); returns non-zero on success, writes result to `*value` | +| `aarch64_syscall_i2s()` | `static const char*` | Looks up a syscall name by number; returns `NULL` for unknown numbers | + +## Usage Example + +```c +#include "aarch64_tables.h" + +/* Name → number */ +int nr; +if (aarch64_syscall_s2i("read", &nr)) + printf("read = %d\n", nr); /* read = 63 */ + +/* Number → name */ +const char *name = aarch64_syscall_i2s(63); +if (name) + printf("syscall 63 = %s\n", name); /* syscall 63 = read */ +``` + +## Notes + +- **Auto-generated** — do not edit directly; see `Makefile.am` for the generator inputs +- Covers **266 syscalls** spanning syscall numbers `0–281` (with sparse gaps marked `-1u` in the direct table) +- Lookup helpers delegate to `s2i__()` and `i2s_direct__()` from a shared utility header +- Used by audit/seccomp tooling (e.g., `libaudit`) to translate syscall identifiers for the AArch64 architecture \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.actiontabs.md b/libraries/cmake/source/libaudit/generated/.actiontabs.md new file mode 100644 index 00000000000..afe5e1f5eeb --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.actiontabs.md @@ -0,0 +1,44 @@ + +Auto-generated lookup table header providing bidirectional string-to-integer and integer-to-string conversion for the `action` enumeration type, supporting the values `always`, `never`, and `possible`. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `action_strings` | `const char[]` | Packed, null-delimited string table containing all enum value names | +| `action_s2i_s` | `const unsigned[]` | Byte offsets into `action_strings` for each name | +| `action_s2i_i` | `const int[]` | Corresponding integer values for each name | +| `action_s2i()` | `static int` | Case-insensitive string → integer lookup; returns non-zero on success | +| `action_i2s_direct[]` | `const unsigned[]` | Direct-indexed offsets mapping integer values back to strings | +| `action_i2s()` | `static const char*` | Integer → string lookup; returns `NULL` on out-of-range input | + +**Enum mapping:** + +| String | Integer | +|--------|---------| +| `never` | `0` | +| `possible` | `1` | +| `always` | `2` | + +## Usage Example + +```c +#include "actiontabs.h" + +/* String to integer */ +int val; +if (action_s2i("Always", &val)) { /* case-insensitive */ + printf("always = %d\n", val); /* always = 2 */ +} + +/* Integer to string */ +const char *name = action_i2s(1); +if (name) { + printf("1 = %s\n", name); /* 1 = possible */ +} + +/* Out-of-range returns NULL */ +const char *bad = action_i2s(99); /* bad == NULL */ +``` + +> **Note:** This file is auto-generated — do not edit manually. Modify the generator inputs referenced in `Makefile.am` instead. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.arm_tables.md b/libraries/cmake/source/libaudit/generated/.arm_tables.md new file mode 100644 index 00000000000..9e1f9c4b9c7 --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.arm_tables.md @@ -0,0 +1,38 @@ + +Auto-generated lookup table for ARM architecture Linux system call name-to-integer (`s2i`) and integer-to-name (`i2s`) bidirectional mapping, covering syscall numbers 0–387. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `arm_syscall_strings` | `const char[]` | Packed null-delimited string pool of all ARM syscall names | +| `arm_syscall_s2i_s` | `const unsigned[]` | Sorted byte-offset index into the string pool for binary search | +| `arm_syscall_s2i_i` | `const int[]` | Parallel syscall number array corresponding to `s2i_s` entries | +| `arm_syscall_i2s_direct` | `const unsigned[]` | Direct-mapped array: syscall number → string pool offset (`-1u` = unmapped) | +| `arm_syscall_s2i()` | `static int` | Case-insensitive name → syscall number lookup; returns 0 on miss | +| `arm_syscall_i2s()` | `static const char*` | Syscall number → name lookup via direct index; returns `NULL` on miss | + +## Usage Example + +```c +#include "arm_tables.h" + +/* Name to syscall number */ +int num; +if (arm_syscall_s2i("read", &num)) + printf("read = %d\n", num); /* read = 3 */ + +/* Syscall number to name */ +const char *name = arm_syscall_i2s(3); +if (name) + printf("syscall 3 = %s\n", name); /* syscall 3 = read */ + +/* Case-insensitive lookup */ +arm_syscall_s2i("READ", &num); /* also resolves to 3 */ +``` + +## Notes + +- Generated file — **do not edit manually**; see `Makefile.am` for source inputs +- Gaps in `arm_syscall_i2s_direct` (marked `-1u`) represent unassigned or reserved syscall numbers in the ARM ABI +- Lookup delegates to shared helpers `s2i__()` and `i2s_direct__()` from the parent audit library \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.errtabs.md b/libraries/cmake/source/libaudit/generated/.errtabs.md new file mode 100644 index 00000000000..a006f68992e --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.errtabs.md @@ -0,0 +1,40 @@ + +Auto-generated lookup table for converting between POSIX/Linux error code names (e.g., `EACCES`, `EINVAL`) and their corresponding integer values. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `err_strings` | `static const char[]` | Packed null-delimited string table of all error name literals | +| `err_s2i_s` | `static const unsigned[]` | Byte offsets into `err_strings` for string-to-int lookup | +| `err_s2i_i` | `static const int[]` | Mapped integer errno values for each offset entry | +| `err_s2i_direct` | `static const unsigned[]` | Direct index array mapping errno integer values to string offsets | +| `err_s2i()` | `static int` | Converts an error name string to its errno integer value | +| `err_i2s()` | `static const char *` | Converts an errno integer value back to its error name string | + +## Usage Example + +```c +/* String → integer (e.g., "EACCES" → 13) */ +int value; +if (err_s2i("EACCES", &value) == 0) { + printf("EACCES = %d\n", value); /* Output: EACCES = 13 */ +} + +/* Also case-insensitive — input is uppercased internally */ +err_s2i("eacces", &value); + +/* Integer → string (e.g., 13 → "EACCES") */ +const char *name = err_i2s(13); +if (name) { + printf("errno 13 = %s\n", name); /* Output: errno 13 = EACCES */ +} +``` + +## Notes + +- This file is **auto-generated** — do not edit manually; modify the generator inputs via `Makefile.am` instead. +- Lookups delegate to `s2i__()` and `i2s_direct__()` helper functions expected to be defined elsewhere in the codebase. +- `err_s2i()` normalizes input to uppercase before matching, making lookups case-insensitive. +- Unmapped errno values in `err_i2s_direct` are marked with `-1u` sentinel values, causing `err_i2s()` to return `NULL`. +- Covers 131 error codes spanning standard POSIX errors through Linux-specific extensions. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.fieldtabs.md b/libraries/cmake/source/libaudit/generated/.fieldtabs.md new file mode 100644 index 00000000000..3e6faa7b7af --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.fieldtabs.md @@ -0,0 +1,38 @@ + +Provides bidirectional mapping between audit rule field name strings and their corresponding integer constants, generated from build system inputs. + +## Key Components + +- **`field_strings`** — Packed null-separated string table of all supported audit field names (e.g., `uid`, `pid`, `path`, `auid`, `inode`, etc.) +- **`field_s2i_s[]`** — Byte offsets into `field_strings` for the string-to-integer lookup table (42 entries) +- **`field_s2i_i[]`** — Corresponding integer values for each field name in the s2i table +- **`field_s2i()`** — Case-insensitive string-to-integer lookup; converts a field name string to its integer constant, writing the result via an output pointer +- **`field_i2s_i[]`** — Sorted integer keys for the integer-to-string reverse lookup table (41 entries) +- **`field_i2s_s[]`** — Byte offsets into `field_strings` for each integer key's corresponding name +- **`field_i2s()`** — Integer-to-string reverse lookup using binary search; returns the field name string for a given integer constant + +## Usage Example + +```c +#include "fieldtabs.h" + +/* String → integer */ +int val; +if (field_s2i("uid", &val)) { + /* val == 1 */ +} + +if (field_s2i("AUID", &val)) { + /* case-insensitive: val == 9 */ +} + +/* Integer → string */ +const char *name = field_i2s(105); /* returns "path" */ +const char *unknown = field_i2s(999); /* returns NULL */ +``` + +## Notes + +- This file is **auto-generated** — do not edit directly; modify the corresponding `Makefile.am` inputs instead +- `field_s2i()` performs case folding (uppercase → lowercase) before lookup, making it case-insensitive +- Both lookups delegate to shared utility functions (`s2i__`, `i2s_bsearch__`) from the parent library \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.flagtabs.md b/libraries/cmake/source/libaudit/generated/.flagtabs.md new file mode 100644 index 00000000000..aa74faed36e --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.flagtabs.md @@ -0,0 +1,57 @@ + +Brief description of the file's purpose (1-2 sentences): + +Auto-generated lookup table header providing bidirectional string-to-integer and integer-to-string conversion for audit flag types used in the Linux Audit system. It maps human-readable flag names (`entry`, `exclude`, `exit`, `task`, `user`) to their corresponding integer values and back. + +--- + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `flag_strings` | `const char[]` | Packed, null-delimited string pool containing all flag names | +| `flag_s2i_s` | `const unsigned[]` | Byte offsets into `flag_strings` for each flag name | +| `flag_s2i_i` | `const int[]` | Integer values parallel to `flag_s2i_s` entries | +| `flag_s2i()` | `static int` | Case-insensitive string → integer lookup; returns non-zero on match | +| `flag_i2s_direct` | `const unsigned[]` | Direct-indexed offsets for integer → string reverse lookup | +| `flag_i2s()` | `static const char *` | Integer → string lookup; returns `NULL` for unknown values | + +**Flag value mapping:** + +| Name | Integer Value | +|---|---| +| `user` | `0` | +| `task` | `1` | +| `entry` | `2` | +| `exit` | `4` | +| `exclude` | `5` | + +--- + +## Usage Example + +```c +#include "flagtabs.h" + +/* String → Integer */ +int val; +if (flag_s2i("exit", &val)) { + printf("exit = %d\n", val); /* exit = 4 */ +} + +/* Case-insensitive match */ +if (flag_s2i("EXIT", &val)) { + printf("EXIT = %d\n", val); /* EXIT = 4 */ +} + +/* Integer → String */ +const char *name = flag_i2s(2); +if (name) { + printf("2 = %s\n", name); /* 2 = entry */ +} + +/* Unknown value returns NULL */ +const char *unknown = flag_i2s(99); /* NULL */ +``` + +> **Note:** This file is auto-generated — do not edit manually. See `Makefile.am` for the generation inputs and rules. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.ftypetabs.md b/libraries/cmake/source/libaudit/generated/.ftypetabs.md new file mode 100644 index 00000000000..39270871a1f --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.ftypetabs.md @@ -0,0 +1,33 @@ + +Provides bidirectional mapping between file type string names and their corresponding integer constants for use in audit rule parsing. + +## Key Components + +- **`ftype_strings`** — Packed string table containing all file type names: `block`, `character`, `dir`, `fifo`, `file`, `link`, `socket` +- **`ftype_s2i_i[]`** — Integer constants mapped to each file type (e.g., `32768` = regular file, `16384` = directory) +- **`ftype_s2i()`** — Converts a file type string to its integer value; performs case-insensitive matching via lowercase normalization +- **`ftype_i2s()`** — Converts an integer constant back to its file type string using binary search + +## Usage Example + +```c +// String to integer lookup +int value; +if (ftype_s2i("file", &value) == 0) { + // value == 32768 (S_IFREG) +} + +if (ftype_s2i("DIR", &value) == 0) { + // value == 16384 (S_IFDIR) — case-insensitive match +} + +// Integer to string lookup +const char *name = ftype_i2s(49152); // returns "socket" +const char *unknown = ftype_i2s(999); // returns NULL if not found +``` + +## Notes + +- This is a **generated file** — do not edit directly; see `Makefile.am` for source inputs +- Integer values correspond to standard POSIX `st_mode` file type bits (e.g., `S_IFBLK`, `S_IFCHR`, `S_IFDIR`) +- Relies on shared helpers `s2i__()` and `i2s_bsearch__()` defined elsewhere in the audit library \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.gen_tables.md b/libraries/cmake/source/libaudit/generated/.gen_tables.md new file mode 100644 index 00000000000..654fd819d11 --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.gen_tables.md @@ -0,0 +1,41 @@ + +Declares inline utility functions and data structures for bidirectional lookup between integer constants and their string representations, used to support generated translation tables in the audit library. + +## Key Components + +### Macros +- **`GT_ISUPPER(X)`** / **`GT_ISLOWER(X)`** — ASCII case-check macros (avoids locale-dependent `ctype.h` functions) + +### Inline Functions +- **`s2i__()`** — Binary search converting a string to its corresponding integer value using sorted string/index tables +- **`i2s_direct__()`** — O(1) integer-to-string lookup via direct index offset into a precomputed table, bounded by `min`/`max` +- **`i2s_bsearch__()`** — Binary search converting an integer to its corresponding string when a direct table isn't available + +### Struct +- **`struct transtab`** — Pairs an integer `value` with a string pool `offset`; used as the base type for generated translation tables + +## Usage Example + +```c +/* Typical pattern in generated table code */ +static const char strings[] = "read\0write\0exec\0"; + +static const unsigned s_table[] = { 9, 0, 5 }; /* sorted: exec, read, write */ +static const int i_table[] = { 3, 1, 2 }; + +/* String → int */ +int val; +if (s2i__(strings, s_table, i_table, 3, "write", &val)) + printf("write = %d\n", val); /* write = 2 */ + +/* Int → string (direct) */ +static const unsigned direct[] = { 0, 5, 9 }; /* offsets for values 1..3 */ +const char *name = i2s_direct__(strings, direct, 1, 3, 2); +/* name → "write" */ + +/* Int → string (bsearch fallback) */ +const char *name2 = i2s_bsearch__(strings, i_table, s_table, 3, 3); +/* name2 → "exec" */ +``` + +> All three lookup functions are `inline static`, so there is zero call overhead in the generated table translation units that include this header. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.i386_tables.md b/libraries/cmake/source/libaudit/generated/.i386_tables.md new file mode 100644 index 00000000000..879004acfba --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.i386_tables.md @@ -0,0 +1,39 @@ + +Provides string-to-integer and integer-to-string lookup tables for i386 (32-bit x86) Linux system call names and numbers, along with bidirectional conversion functions. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `i386_syscall_strings` | `const char[]` | Packed string table of all i386 syscall names (null-separated) | +| `i386_syscall_s2i_s` | `const unsigned[]` | Byte offsets into `i386_syscall_strings` for sorted name lookup | +| `i386_syscall_s2i_i` | `const int[]` | Syscall numbers corresponding to each sorted name entry | +| `i386_syscall_s2i()` | `static int` | Converts a syscall name string to its syscall number | +| `i386_syscall_i2s_direct` | `const unsigned[]` | Direct-indexed offsets into `i386_syscall_strings`, indexed by syscall number | +| `i386_syscall_i2s()` | `static const char *` | Converts a syscall number to its name string | + +## Usage Example + +```c +#include + +/* Name → number */ +int num; +if (i386_syscall_s2i("read", &num)) { + printf("read = %d\n", num); /* read = 3 */ +} + +/* Number → name */ +const char *name = i386_syscall_i2s(3); +if (name) { + printf("syscall 3 = %s\n", name); /* syscall 3 = read */ +} +``` + +## Notes + +- **Generated file** — do not edit manually; regenerate via `Makefile.am` with updated inputs +- Covers **356 syscalls**, number range **0–358** (two entries are `-1u` sentinels for unmapped numbers) +- `i386_syscall_s2i()` performs **case-insensitive** matching via lowercase normalization before lookup +- Lookup helpers `s2i__` and `i2s_direct__` are external utilities (binary search and direct array index, respectively) +- Syscall numbers follow the **i386 Linux ABI** (`arch/x86/entry/syscalls/syscall_32.tbl`) \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.ia64_tables.md b/libraries/cmake/source/libaudit/generated/.ia64_tables.md new file mode 100644 index 00000000000..3ff8263ec63 --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.ia64_tables.md @@ -0,0 +1,32 @@ + +Generated lookup tables and conversion functions for mapping IA-64 (Itanium) Linux system call names to their numeric identifiers and back. + +## Key Components + +- **`ia64_syscall_strings`** — Packed string table containing all 313 IA-64 Linux syscall names, null-delimited and concatenated into a single `char[]` for memory efficiency. +- **`ia64_syscall_s2i_s[]`** / **`ia64_syscall_s2i_i[]`** — Parallel arrays used for binary search: string byte offsets into `ia64_syscall_strings` paired with their corresponding syscall numbers (range `1024`–`1342`). +- **`ia64_syscall_i2s_direct[]`** — Direct-mapped lookup array indexed by syscall number offset from base `1024`; each entry is a byte offset into `ia64_syscall_strings`, or `-1u` for gaps in the syscall table. +- **`ia64_syscall_s2i()`** — Case-insensitive name-to-number lookup; normalizes input to lowercase before delegating to `s2i__()`. +- **`ia64_syscall_i2s()`** — Number-to-name lookup via direct array indexing, delegating to `i2s_direct__()`. + +## Usage Example + +```c +#include "ia64_tables.h" + +/* Syscall name → number */ +int nr; +if (ia64_syscall_s2i("read", &nr)) + printf("read = %d\n", nr); /* read = 1198 */ + +/* Syscall number → name */ +const char *name = ia64_syscall_i2s(1198); +if (name) + printf("%s\n", name); /* read */ +``` + +## Notes + +- This file is **auto-generated** — edit the upstream `Makefile.am` inputs, not this file directly. +- The syscall number range covered is `1024`–`1342` (319 slots, 313 valid entries, 6 gaps marked `-1u`). +- Depends on helper macros/functions `s2i__()`, `i2s_direct__()`, and `GT_ISUPPER()` defined elsewhere in the parent library (typically `libaudit`). \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.machinetabs.md b/libraries/cmake/source/libaudit/generated/.machinetabs.md new file mode 100644 index 00000000000..7b9b78e25be --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.machinetabs.md @@ -0,0 +1,44 @@ + +A generated lookup table header providing bidirectional string-to-integer and integer-to-string mappings for CPU machine/architecture identifiers. + +## Key Components + +### String Table +- **`machine_strings`** — Null-delimited packed string of all supported architecture names: `aarch64`, `arm`, `armeb`, `armv5tejl`, `armv5tel`, `armv6l`, `armv7l`, `i386`, `i486`, `i586`, `i686`, `ia64`, `ppc`, `ppc64`, `ppc64le`, `s390`, `s390x`, `x86_64` + +### Lookup Tables +- **`machine_s2i_s`** — Byte offsets into `machine_strings` for each architecture entry +- **`machine_s2i_i`** — Integer enum values corresponding to each architecture string +- **`machine_i2s_direct`** — Direct-mapped offsets for reverse integer-to-string lookup + +### Functions +- **`machine_s2i(const char *s, int *value)`** — Case-insensitive string-to-integer conversion; returns nonzero on match and writes the enum value to `*value` +- **`machine_i2s(int v)`** — Integer-to-string conversion; returns the architecture name string or `NULL` for unknown values + +## Usage Example + +```c +#include "machinetabs.h" + +/* String → integer */ +int arch_id; +if (machine_s2i("x86_64", &arch_id)) { + printf("x86_64 enum value: %d\n", arch_id); /* prints 1 */ +} + +/* Case-insensitive match */ +if (machine_s2i("ARM", &arch_id)) { + printf("ARM enum value: %d\n", arch_id); /* prints 8 */ +} + +/* Integer → string */ +const char *name = machine_i2s(9); +printf("Arch name: %s\n", name); /* prints "aarch64" */ + +/* Unknown value returns NULL */ +if (machine_i2s(99) == NULL) { + printf("Unknown architecture\n"); +} +``` + +> **Note:** This file is auto-generated — do not edit directly. Refer to the corresponding `Makefile.am` for the source inputs that produce this table. \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.msg_typetabs.md b/libraries/cmake/source/libaudit/generated/.msg_typetabs.md new file mode 100644 index 00000000000..28d0c83b27e --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.msg_typetabs.md @@ -0,0 +1,44 @@ + +Generated lookup tables for bidirectional conversion between Linux audit message type names (strings) and their numeric constants, supporting both string-to-integer and integer-to-string lookups. + +## Key Components + +- **`msg_type_strings`** — Packed null-delimited string table containing all 155 audit message type names (e.g., `SYSCALL`, `USER_AUTH`, `ANOM_ABEND`, `AVC`, `CRYPTO_SESSION`) +- **`msg_type_s2i_s[]`** — Byte offsets into `msg_type_strings` for the string-to-integer lookup table +- **`msg_type_s2i_i[]`** — Corresponding numeric audit type constants (e.g., `1300`, `2405`) for each string entry +- **`msg_type_i2s_i[]`** — Sorted integer keys for the integer-to-string reverse lookup (binary search) +- **`msg_type_i2s_s[]`** — Corresponding offsets into `msg_type_strings` for the reverse lookup +- **`msg_type_s2i()`** — Case-insensitive string-to-integer converter; normalizes input to uppercase before delegating to `s2i__()` +- **`msg_type_i2s()`** — Integer-to-string converter using binary search via `i2s_bsearch__()` + +## Usage Example + +```c +#include + +/* String → integer */ +int msg_type; +if (msg_type_s2i("syscall", &msg_type)) { + printf("SYSCALL numeric type: %d\n", msg_type); + /* Output: SYSCALL numeric type: 1300 */ +} + +/* Also works case-insensitively */ +if (msg_type_s2i("USER_AUTH", &msg_type)) { + printf("USER_AUTH numeric type: %d\n", msg_type); + /* Output: USER_AUTH numeric type: 1100 */ +} + +/* Integer → string */ +const char *name = msg_type_i2s(1400); +if (name) { + printf("Type 1400: %s\n", name); + /* Output: Type 1400: AVC */ +} +``` + +## Notes + +- This file is **auto-generated** — do not edit manually; see `Makefile.am` for the generation inputs +- Covers audit message type ranges: `1000–1135` (core), `1200–1210` (daemon), `1300–1330` (config/label), `1400–1417` (MAC), `1700–1805` (anomaly/RBAC), `2000–2212` (integrity/response), `2300–2312` (crypto), `2400–2409` (netfilter), `2500–2502` (virt) +- Lookup depends on the shared utilities `s2i__()` and `i2s_bsearch__()` being available in the translation unit \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.optabs.md b/libraries/cmake/source/libaudit/generated/.optabs.md new file mode 100644 index 00000000000..c1f7ace88f6 --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.optabs.md @@ -0,0 +1,30 @@ + +Brief description of a generated lookup table header for mapping integer operator codes to their string representations. + +## Key Components + +- **`op_strings`** — Null-delimited string pool containing all operator symbols: `!=`, `&`, `&=`, `<`, `<=`, `=`, `>`, `>=` +- **`op_i2s_i[]`** — Sorted integer array of encoded operator values used as binary search keys +- **`op_i2s_s[]`** — Corresponding byte offsets into `op_strings` for each operator +- **`op_i2s(int v)`** — Public lookup function; performs a binary search via `i2s_bsearch__` and returns the string for a given operator code, or `NULL` if not found + +## Usage Example + +```c +#include "optabs.h" + +void print_operator(int op_code) { + const char *op = op_i2s(op_code); + if (op) { + printf("Operator: %s\n", op); + } else { + printf("Unknown operator code: %d\n", op_code); + } +} +``` + +## Notes + +- This file is **auto-generated** — do not edit manually; modify the corresponding `Makefile.am` inputs instead +- The lookup relies on `i2s_bsearch__` being defined elsewhere (typically a shared utility in the audit rules library) +- Operator codes are stored as large pre-shifted integer constants, suggesting a bitmask/enum encoding scheme used by the Linux Audit subsystem \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.ppc_tables.md b/libraries/cmake/source/libaudit/generated/.ppc_tables.md new file mode 100644 index 00000000000..a7cece0e8ab --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.ppc_tables.md @@ -0,0 +1,43 @@ + +Lookup tables for PowerPC (PPC) system call name-to-integer and integer-to-name bidirectional mapping, auto-generated from syscall definitions. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `ppc_syscall_strings` | `const char[]` | Packed, null-delimited string pool of all PPC syscall names | +| `ppc_syscall_s2i_s` | `const unsigned[]` | Byte offsets into the string pool for binary search (string→int direction) | +| `ppc_syscall_s2i_i` | `const int[]` | Syscall numbers corresponding to each string pool entry | +| `ppc_syscall_i2s_direct` | `const unsigned[]` | Direct-indexed array mapping syscall number → string pool offset | +| `ppc_syscall_s2i()` | `static int` | Looks up a syscall number by name (case-insensitive); returns non-zero on match | +| `ppc_syscall_i2s()` | `static const char *` | Looks up a syscall name by number; returns `NULL` for unknown numbers | + +## Usage Example + +```c +#include + +/* Look up syscall number from name */ +int num; +if (ppc_syscall_s2i("read", &num)) { + printf("read = %d\n", num); /* read = 3 */ +} + +/* Look up syscall name from number */ +const char *name = ppc_syscall_i2s(3); +if (name) { + printf("syscall 3 = %s\n", name); /* syscall 3 = read */ +} + +/* Case-insensitive lookup */ +if (ppc_syscall_s2i("WRITE", &num)) { + printf("write = %d\n", num); +} +``` + +## Notes + +- Covers syscall numbers **1–363** for PowerPC Linux +- Entries mapped to `-1u` (`0xFFFFFFFF`) in `ppc_syscall_i2s_direct` indicate **unassigned/gap** syscall numbers +- Lookup delegates to shared helpers `s2i__()` and `i2s_direct__()` from the parent audit library +- **Do not edit manually** — regenerate via the project `Makefile.am` \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.s390_tables.md b/libraries/cmake/source/libaudit/generated/.s390_tables.md new file mode 100644 index 00000000000..713bab8e175 --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.s390_tables.md @@ -0,0 +1,29 @@ + +Provides syscall name ↔ integer lookup tables and conversion functions for the s390 (IBM mainframe) architecture, mapping Linux system call names to their corresponding numeric identifiers. + +## Key Components + +- **`s390_syscall_strings`** — Null-delimited string pool containing all 325 s390 Linux syscall names (e.g., `read`, `write`, `clone`, `execve`, plus s390-specific calls like `s390_pci_mmio_read`) +- **`s390_syscall_s2i_s[]`** — Byte offset array into `s390_syscall_strings`, used as the string index for name-to-integer lookups +- **`s390_syscall_s2i_i[]`** — Parallel integer array mapping each offset entry to its syscall number +- **`s390_syscall_i2s_direct[]`** — Direct-indexed array mapping syscall numbers (1–354) back to string offsets; `-1u` marks undefined/unused numbers +- **`s390_syscall_s2i()`** — Converts a syscall name string to its integer number (case-insensitive); returns non-zero on success, writes result via `value` pointer +- **`s390_syscall_i2s()`** — Converts a syscall integer number back to its name string; returns `NULL` for undefined numbers + +> **Note:** This file is auto-generated. Do not edit manually — see `Makefile.am` for the generation inputs. + +## Usage Example + +```c +#include "s390_tables.h" + +// Name → number +int num; +if (s390_syscall_s2i("read", &num)) + printf("read = %d\n", num); // read = 3 + +// Number → name +const char *name = s390_syscall_i2s(3); +if (name) + printf("syscall 3 = %s\n", name); // syscall 3 = read +``` \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.s390x_tables.md b/libraries/cmake/source/libaudit/generated/.s390x_tables.md new file mode 100644 index 00000000000..9d0b6ca3f9b --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.s390x_tables.md @@ -0,0 +1,31 @@ + +Generated lookup tables for bidirectional mapping between s390x Linux syscall names and their numeric identifiers. + +## Key Components + +- **`s390x_syscall_strings`** — Packed null-terminated string table containing all 291 syscall names for the s390x architecture (e.g., `read`, `write`, `clone`, `bpf`, etc.) +- **`s390x_syscall_s2i_s[]`** — Sorted byte-offset index into the string table, used for binary search during name-to-number lookups +- **`s390x_syscall_s2i_i[]`** — Parallel array mapping sorted string offsets to their corresponding syscall numbers +- **`s390x_syscall_i2s_direct[]`** — Direct-indexed array mapping syscall numbers (1–354) to string table offsets; `-1u` marks gaps where no syscall exists at that number +- **`s390x_syscall_s2i()`** — Looks up a syscall number by name (case-insensitive); returns nonzero on success, writes result into `*value` +- **`s390x_syscall_i2s()`** — Looks up a syscall name by number; returns `NULL` if the number is out of range or unmapped + +## Usage Example + +```c +/* Name → number */ +int nr; +if (s390x_syscall_s2i("read", &nr)) + printf("read = %d\n", nr); /* read = 3 */ + +/* Number → name */ +const char *name = s390x_syscall_i2s(3); +if (name) + printf("syscall 3 = %s\n", name); /* syscall 3 = read */ +``` + +## Notes + +- This file is **auto-generated** — do not edit manually; see `Makefile.am` for the generation inputs +- Covers syscall numbers in the range **1–354** for the s390x (IBM Z) 64-bit Linux ABI +- Depends on helper macros/functions `s2i__`, `i2s_direct__`, and `GT_ISUPPER` defined elsewhere in the audit library \ No newline at end of file diff --git a/libraries/cmake/source/libaudit/generated/.x86_64_tables.md b/libraries/cmake/source/libaudit/generated/.x86_64_tables.md new file mode 100644 index 00000000000..1170b477ba2 --- /dev/null +++ b/libraries/cmake/source/libaudit/generated/.x86_64_tables.md @@ -0,0 +1,40 @@ + +Brief description of this file's purpose in 1-2 sentences. + +This is an auto-generated lookup table header for x86_64 Linux system call name-to-number and number-to-name mappings. It provides bidirectional conversion between syscall string names and their corresponding integer identifiers for the x86_64 architecture. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `x86_64_syscall_strings` | `static const char[]` | Packed null-delimited string table of all syscall names | +| `x86_64_syscall_s2i_s` | `static const unsigned[]` | Byte offsets into the string table for sorted syscall name lookup | +| `x86_64_syscall_s2i_i` | `static const int[]` | Syscall numbers corresponding to each sorted name entry | +| `x86_64_syscall_s2i()` | `static int` | Case-insensitive syscall name → number lookup function | +| `x86_64_syscall_i2s_direct` | `static const unsigned[]` | Direct-indexed offsets for syscall number → name lookup | +| `x86_64_syscall_i2s()` | `static const char *` | Syscall number → name lookup function (returns `NULL` for unknown) | + +## Usage Example + +```c +#include "x86_64_tables.h" + +/* Syscall name to number */ +int syscall_num; +if (x86_64_syscall_s2i("read", &syscall_num)) { + printf("read = %d\n", syscall_num); /* read = 0 */ +} + +/* Syscall number to name */ +const char *name = x86_64_syscall_i2s(1); +if (name) { + printf("syscall 1 = %s\n", name); /* syscall 1 = write */ +} +``` + +## Notes + +- The file is **auto-generated** — do not edit manually; modify the upstream `Makefile.am` inputs instead. +- `x86_64_syscall_s2i()` performs **case-insensitive** matching by normalizing input to lowercase before the binary search via `s2i__()`. +- The lookup tables cover syscall numbers **0–322**, mapping all standard x86_64 Linux syscalls including `read`, `write`, `open`, `clone`, `execve`, `bpf`, and more. +- Depends on helper functions `s2i__()` and `i2s_direct__()` defined elsewhere in the audit library. \ No newline at end of file diff --git a/libraries/cmake/source/libcap/generated/.cap_names.list.md b/libraries/cmake/source/libcap/generated/.cap_names.list.md new file mode 100644 index 00000000000..95595d3fecf --- /dev/null +++ b/libraries/cmake/source/libcap/generated/.cap_names.list.md @@ -0,0 +1,37 @@ + +A static lookup table mapping Linux capability names (as lowercase strings) to their corresponding numeric capability constants, used for capability name-to-index resolution. + +## Key Components + +- **Capability entries** — Each entry is a `{string, int}` pair mapping a POSIX/Linux capability name to its kernel-defined capability number (0–40) +- **Coverage** — Includes all standard Linux capabilities from `CAP_CHOWN` (0) through `CAP_CHECKPOINT_RESTORE` (40) + +## Usage Example + +```c +/* Typical usage: iterate to resolve a capability name to its numeric value */ +struct { const char *name; int value; } cap_names[] = { + #include "cap_names.list.h" + {NULL, -1} /* sentinel */ +}; + +int cap_name_to_value(const char *name) { + for (int i = 0; cap_names[i].name != NULL; i++) { + if (strcmp(cap_names[i].name, name) == 0) + return cap_names[i].value; + } + return -1; /* not found */ +} + +/* Example calls */ +cap_name_to_value("cap_net_admin"); /* returns 12 */ +cap_name_to_value("cap_sys_admin"); /* returns 21 */ +cap_name_to_value("cap_bpf"); /* returns 39 */ +``` + +## Notes + +- Entries are ordered sequentially by capability number, making index-based access predictable +- All names are lowercase; callers performing lookups should normalize input accordingly +- This file is intended to be `#include`d directly into a struct/array initializer, not compiled standalone +- Reflects Linux kernel capability definitions up to `CAP_CHECKPOINT_RESTORE` (kernel 5.9+) \ No newline at end of file diff --git a/libraries/cmake/source/libcap/generated/.cap_names.md b/libraries/cmake/source/libcap/generated/.cap_names.md new file mode 100644 index 00000000000..1cd9bd50857 --- /dev/null +++ b/libraries/cmake/source/libcap/generated/.cap_names.md @@ -0,0 +1,39 @@ + +Auto-generated header mapping Linux capability numbers (0–40) to their string names, derived from ``. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `__CAP_BITS` | `41` | Total number of defined Linux capabilities | +| `__CAP_NAME_SIZE` | `23` | Maximum string length of any capability name | +| `LIBCAP_CAP_NAMES` | Array macro | String array of all capability names, indexed by capability number | + +The `LIBCAP_CAP_NAMES` array is only included when `LIBCAP_PLEASE_INCLUDE_ARRAY` is defined, preventing accidental multiple-definition errors across translation units. + +## Usage Example + +```c +#define LIBCAP_PLEASE_INCLUDE_ARRAY +#include "cap_names.h" + +/* Declare the names array using the macro */ +static const char *cap_names[] = LIBCAP_CAP_NAMES; + +/* Look up a capability name by its number */ +const char *get_cap_name(int cap_num) { + if (cap_num < 0 || cap_num >= __CAP_BITS) { + return NULL; + } + return cap_names[cap_num]; +} + +/* Example: prints "cap_sys_admin" */ +printf("%s\n", get_cap_name(21)); + +/* Safely size buffers using __CAP_NAME_SIZE */ +char buf[__CAP_NAME_SIZE + 1]; +snprintf(buf, sizeof(buf), "%s", get_cap_name(21)); +``` + +> **Do not edit this file manually.** It is auto-generated from the Linux kernel UAPI headers. Changes should be made upstream in the generation tooling. \ No newline at end of file diff --git a/libraries/cmake/source/libcryptsetup/config/.config.md b/libraries/cmake/source/libcryptsetup/config/.config.md new file mode 100644 index 00000000000..18b0e636b3f --- /dev/null +++ b/libraries/cmake/source/libcryptsetup/config/.config.md @@ -0,0 +1,118 @@ + +Auto-generated build configuration header for **cryptsetup 1.7.5**, produced by `autoconf`/`autoheader` from `configure.ac`. It defines compile-time defaults, feature flags, and platform capability detection macros used throughout the cryptsetup codebase. + +## Key Components + +### Cryptographic Defaults + +| Macro | Value | Purpose | +|---|---|---| +| `DEFAULT_LUKS1_CIPHER` | `"aes"` | Block cipher for LUKS1 volumes | +| `DEFAULT_LUKS1_MODE` | `"xts-plain64"` | Cipher mode for LUKS1 | +| `DEFAULT_LUKS1_KEYBITS` | `256` | Key size in bits for LUKS1 | +| `DEFAULT_LUKS1_HASH` | `"sha256"` | Header hash function for LUKS1 | +| `DEFAULT_LUKS1_ITER_TIME` | `2000` | PBKDF2 iteration time (ms) | +| `DEFAULT_PLAIN_CIPHER` | `"aes"` | Cipher for plain dm-crypt mode | +| `DEFAULT_PLAIN_MODE` | `"cbc-essiv:sha256"` | Cipher mode for plain | +| `DEFAULT_PLAIN_HASH` | `"ripemd160"` | Password hash for plain mode | +| `DEFAULT_LOOPAES_CIPHER` | `"aes"` | Cipher for loop-AES mode | +| `DEFAULT_RNG` | `"/dev/urandom"` | Random number generator source | + +### Verity Defaults + +| Macro | Value | Purpose | +|---|---|---| +| `DEFAULT_VERITY_DATA_BLOCK` | `4096` | Data block size (bytes) | +| `DEFAULT_VERITY_HASH_BLOCK` | `4096` | Hash block size (bytes) | +| `DEFAULT_VERITY_HASH` | `"sha256"` | Hash algorithm | +| `DEFAULT_VERITY_SALT_SIZE` | `32` | Salt length (bytes) | + +### Size Limits + +- `DEFAULT_KEYFILE_SIZE_MAXKB` — `8192` KiB maximum keyfile size +- `DEFAULT_PASSPHRASE_SIZE_MAX` — `512` characters maximum passphrase length + +### Feature Flags + +| Macro | Status | Meaning | +|---|---|---| +| `ENABLE_NLS` | `1` (on) | Native language support enabled | +| `ENABLE_AF_ALG` | undefined | Kernel AF_ALG crypto socket disabled | +| `ENABLE_FIPS` | undefined | FIPS mode restrictions disabled | +| `ENABLE_PASSWDQC` | undefined | passwdqc password quality disabled | +| `ENABLE_PWQUALITY` | undefined | pwquality library disabled | +| `USE_INTERNAL_PBKDF2` | `0` | Use external PBKDF2 implementation | +| `USE_UDEV` | undefined | udev synchronisation disabled | + +### Library & Header Detection (`HAVE_*`) + +Autoconf-generated presence flags for required headers and libraries: + +```c +HAVE_LIBPOPT // 1 — popt argument parsing available +HAVE_LIBUUID // 1 — uuid generation available +HAVE_UUID_UUID_H // 1 — present +HAVE_BYTESWAP_H // 1 — byte-swap macros available +HAVE_ENDIAN_H // 1 — endianness detection available +HAVE_POSIX_MEMALIGN // 1 — aligned memory allocation available +HAVE_CLOCK_GETTIME // 1 — POSIX clock available +// Notable absences: +HAVE_LIBGCRYPT // undefined — gcrypt not detected at configure time +HAVE_LIBDEVMAPPER // undefined — devmapper not detected at configure time +HAVE_LIBSELINUX // undefined — SELinux support absent +``` + +### Package Identity + +```c +PACKAGE "cryptsetup" +PACKAGE_VERSION "1.7.5" +PACKAGE_STRING "cryptsetup 1.7.5" +GCRYPT_REQ_VERSION "1.1.42" // minimum required libgcrypt version +``` + +### Platform Extensions + +Conditionally enables GNU, POSIX, and vendor-specific extensions: + +```c +_GNU_SOURCE // GNU libc extensions +_ALL_SOURCE // AIX/Interix extensions +_POSIX_PTHREAD_SEMANTICS // Solaris threading semantics +_DARWIN_USE_64_BIT_INODE // macOS 10.5+ large inode support +``` + +## Usage Example + +```c +#include "config.h" +#include + +/* Use compile-time defaults to initialize a new LUKS1 volume */ +void print_luks1_defaults(void) { + printf("Cipher: %s\n", DEFAULT_LUKS1_CIPHER); // "aes" + printf("Mode: %s\n", DEFAULT_LUKS1_MODE); // "xts-plain64" + printf("Key bits: %d\n", DEFAULT_LUKS1_KEYBITS); // 256 + printf("Hash: %s\n", DEFAULT_LUKS1_HASH); // "sha256" + printf("Iter time: %d ms\n", DEFAULT_LUKS1_ITER_TIME); // 2000 + printf("RNG: %s\n", DEFAULT_RNG); // "/dev/urandom" +} + +/* Conditionally compile NLS support */ +#ifdef ENABLE_NLS + #include + #define _(str) gettext(str) +#else + #define _(str) (str) +#endif + +/* Guard against oversized keyfiles */ +void validate_keyfile(size_t size_kb) { + if (size_kb > DEFAULT_KEYFILE_SIZE_MAXKB) { + fprintf(stderr, _("Keyfile exceeds maximum size of %d KiB\n"), + DEFAULT_KEYFILE_SIZE_MAXKB); + } +} +``` + +> **Note:** This file is auto-generated by `./configure` and should not be edited manually. Regenerate it by re-running the autoconf build system. The `HAVE_LIBGCRYPT` and `HAVE_LIBDEVMAPPER` flags being undefined indicates this header snapshot was captured before those libraries were linked — production builds require both. \ No newline at end of file diff --git a/libraries/cmake/source/libdevmapper/config/.config.md b/libraries/cmake/source/libdevmapper/config/.config.md new file mode 100644 index 00000000000..d61df22ea82 --- /dev/null +++ b/libraries/cmake/source/libdevmapper/config/.config.md @@ -0,0 +1,53 @@ + +Header file defining the LVM2 configuration subsystem — types, structures, flags, and function declarations for managing hierarchical configuration trees, profiles, and validation across multiple configuration sources. + +## Key Components + +### Types & Enumerations + +- **`config_source_t`** — Identifies the origin of a config (file, string, command/metadata profile, merged, special) +- **`cfg_def_type_t`** — Bitmask enum for config item types: `SECTION`, `ARRAY`, `BOOL`, `INT`, `FLOAT`, `STRING` +- **`cfg_def_tree_t`** — Enum for tree views: `CURRENT`, `MISSING`, `FULL`, `DEFAULT`, `NEW`, `DIFF`, `PROFILABLE`, etc. + +### Key Structures + +- **`struct profile`** — Represents a loaded config profile (name, source type, parsed config tree) +- **`struct profile_params`** — Aggregates all active profiles: global command/metadata, shell, and pending load lists +- **`cfg_def_item_t`** — Core definition record for a single config item: ID, parent, name, type, default value, flags, version history, deprecation info, and comment +- **`struct config_def_tree_spec`** — Spec for generating/querying a config definition tree with filtering options (advanced, unsupported, deprecated, local) +- **`struct cft_check_handle`** — Handle for a config validation run, tracking per-item status flags + +### Macros & Flags + +- **`vsn(major, minor, patchlevel)`** — Encodes LVM2 version into a 16-bit integer +- **`CFG_*` flags** — Control item behavior: `CFG_PROFILABLE`, `CFG_DEFAULT_RUN_TIME`, `CFG_DISABLED`, `CFG_ADVANCED`, `CFG_DISALLOW_INTERACTIVE`, etc. + +### Key Functions + +- **`add_profile` / `load_profile` / `load_pending_profiles`** — Profile lifecycle management +- **`config_def_check` / `config_force_check`** — Validate a config tree against definitions +- **`override_config_tree_from_string` / `override_config_tree_from_profile`** — Apply runtime config overrides +- **`get_config_tree_by_source` / `remove_config_tree_by_source`** — Source-scoped tree access +- **`config_def_get_path`** — Resolve a config item's full path by ID + +## Usage Example + +```c +/* Load and validate a command profile */ +struct profile *prof = add_profile(cmd, "myprofile", CONFIG_PROFILE_COMMAND); +if (!load_profile(cmd, prof)) { + /* handle error */ +} + +/* Override active config tree from a CLI --config string */ +override_config_tree_from_string(cmd, "global { use_lvmetad = 0 }"); + +/* Force a validation check on the current config */ +struct dm_config_tree *cft = get_config_tree_by_source(cmd, CONFIG_FILE); +config_force_check(cmd, CONFIG_FILE, cft); + +/* Check version-gated feature availability */ +if (cfg_def_item.since_version <= vsn(2, 3, 0)) { + /* item available in LVM 2.3.0+ */ +} +``` \ No newline at end of file diff --git a/libraries/cmake/source/libdevmapper/config/.configure.md b/libraries/cmake/source/libdevmapper/config/.configure.md new file mode 100644 index 00000000000..61f49770b9a --- /dev/null +++ b/libraries/cmake/source/libdevmapper/config/.configure.md @@ -0,0 +1,90 @@ + +Auto-generated build configuration header for LVM2 (Logical Volume Manager), produced by `autoconf` from `configure.h.in`. It defines compile-time feature flags, system capability detection results, and default path/behavior settings used across the LVM2 codebase. + +## Key Components + +### Feature Flags +Controls which LVM2 subsystems are compiled in: + +| Macro | Value | Purpose | +|---|---|---| +| `CACHE_INTERNAL` | `1` | Built-in dm-cache support | +| `CLUSTER_LOCKING_INTERNAL` | `1` | Clustered LVM locking | +| `DEVMAPPER_SUPPORT` | `1` | device-mapper kernel interaction | +| `DM_IOCTLS` | `1` | ioctl-based kernel communication | +| `HAVE_REALTIME` | `1` | Realtime clock support | + +### Default Paths & Directories + +```c +#define DEFAULT_SYS_DIR "/etc/lvm" +#define DEFAULT_RUN_DIR "/run/lvm" +#define DEFAULT_LOCK_DIR "/run/lock/lvm" +#define DEFAULT_ETC_DIR "/etc" +#define DEFAULT_DM_RUN_DIR "/run" +#define DEFAULT_PID_DIR "/run" +``` + +### External Tool Paths + +```c +#define CACHE_CHECK_CMD "/usr/sbin/cache_check" +#define CACHE_REPAIR_CMD "/usr/sbin/cache_repair" +#define CACHE_RESTORE_CMD "/usr/sbin/cache_restore" +#define CACHE_DUMP_CMD "/usr/sbin/cache_dump" +#define CLVMD_PATH "/usr/sbin/clvmd" +#define FSADM_PATH "/sbin/fsadm" +``` + +### System Capability Detection (`HAVE_*`) +Autoconf-generated presence checks for headers and functions: + +```c +#define HAVE_ALARM 1 +#define HAVE_ALLOCA_H 1 +#define HAVE_FORK 1 +#define HAVE_MMAP 1 +#define HAVE_GETLINE 1 +#define HAVE_LOCALTIME_R 1 +/* Disabled examples: */ +/* #undef HAVE_PTHREAD_H */ +/* #undef HAVE_SELINUX */ +``` + +### Default Behavioral Settings + +```c +#define DEFAULT_MIRROR_SEGTYPE "raid1" +#define DEFAULT_SPARSE_SEGTYPE "thin" +#define DEFAULT_RAID10_SEGTYPE "raid10" +#define DEFAULT_USE_LVMETAD 0 +#define DEFAULT_USE_LVMLOCKD 0 +#define DEFAULT_USE_BLKID_WIPING 0 +#define DEFAULT_DATA_ALIGNMENT 1 +#define DM_LIB_VERSION "1.02.142-git (2017-07-20)" +``` + +## Usage Example + +```c +#include "configure.h" + +/* Conditionally compile device-mapper support */ +#ifdef DEVMAPPER_SUPPORT + dm_task_run(task); +#endif + +/* Use compiled-in default paths */ +const char *lvm_dir = DEFAULT_SYS_DIR; /* "/etc/lvm" */ +const char *run_dir = DEFAULT_RUN_DIR; /* "/run/lvm" */ + +/* Check for optional feature availability */ +#ifdef HAVE_SELINUX + setup_selinux_context(); +#endif + +/* Use external tool path constants */ +execl(CACHE_CHECK_CMD, "cache_check", device, NULL); +``` + +> **Note:** This file is auto-generated — do not edit manually. Modify `configure.h.in` and re-run `./configure` to regenerate with different options. \ No newline at end of file diff --git a/libraries/cmake/source/libdevmapper/include/.lvm-version.md b/libraries/cmake/source/libdevmapper/include/.lvm-version.md new file mode 100644 index 00000000000..f1341960696 --- /dev/null +++ b/libraries/cmake/source/libdevmapper/include/.lvm-version.md @@ -0,0 +1,42 @@ + +Defines the LVM2 version constants used throughout the LVM2 codebase to identify the software release, library API version, and build date. + +## Key Components + +| Macro | Value | Description | +|---|---|---| +| `LVM_VERSION` | `"2.02.173(2)-git (2017-07-20)"` | Full human-readable version string | +| `LVM_MAJOR` | `2` | Major version number | +| `LVM_MINOR` | `02` | Minor version number | +| `LVM_PATCHLEVEL` | `173` | Patch level | +| `LVM_LIBAPI` | `2` | Library API compatibility version | +| `LVM_RELEASE` | `"git"` | Release tag (e.g. `git`, `rc1`, stable label) | +| `LVM_RELEASE_DATE` | `"2017-07-20"` | ISO 8601 build/release date | + +Version format follows the convention: + +```text +LVM_MAJOR.LVM_MINOR.LVM_PATCHLEVEL(LVM_LIBAPI)[-LVM_RELEASE] + 2 .02 .173 (2 )-git +``` + +## Usage Example + +Include this header to perform compile-time version checks or embed version info in output: + +```c +#include "lvm-version.h" +#include + +void print_version(void) { + printf("LVM2 version: %s\n", LVM_VERSION); + printf("Release date: %s\n", LVM_RELEASE_DATE); +} + +/* Compile-time API compatibility guard */ +#if LVM_LIBAPI < 2 +#error "LVM library API version 2 or higher required" +#endif +``` + +> **Note:** The header uses a standard include guard (`_LVM_VERSION_H`) to prevent multiple inclusion. To update the version, modify the macros here — they propagate across the entire LVM2 build automatically. \ No newline at end of file diff --git a/libraries/cmake/source/libdpkg/config/aarch64/.config.md b/libraries/cmake/source/libdpkg/config/aarch64/.config.md new file mode 100644 index 00000000000..a91ac8b2d6d --- /dev/null +++ b/libraries/cmake/source/libdpkg/config/aarch64/.config.md @@ -0,0 +1,87 @@ + +Auto-generated build configuration header for the `dpkg` 1.21.7 package, produced by `autoconf`/`configure` for an `arm64` Linux target. It exposes compile-time capability flags, platform settings, and library/tool paths used throughout the dpkg source tree. + +## Key Components + +### Architecture & Package Identity + +| Macro | Value | +|---|---| +| `ARCHITECTURE` | `"arm64"` | +| `ARCHITECTURE_CPU` | `"arm64"` | +| `ARCHITECTURE_OS` | `"linux"` | +| `PACKAGE_VERSION` | `"1.21.7"` | +| `PACKAGE_RELEASE` | `PACKAGE_VERSION " (" ARCHITECTURE ")"` | + +### Build Feature Flags + +| Macro | Value | Meaning | +|---|---|---| +| `BUILD_DSELECT` | `0` | dselect UI not compiled | +| `BUILD_START_STOP_DAEMON` | `0` | start-stop-daemon not compiled | +| `BUILD_UPDATE_ALTERNATIVES` | `1` | update-alternatives compiled | +| `ENABLE_NLS` | `1` | Native language support enabled | +| `LIBDPKG_VOLATILE_API` | `1` | Acknowledges unstable libdpkg API | + +### Compression & Storage + +| Macro | Value | +|---|---| +| `DPKG_DEB_DEFAULT_COMPRESSOR` | `COMPRESSOR_TYPE_XZ` | +| `USE_LIBZ_IMPL` | `USE_LIBZ_IMPL_ZLIB` (standard zlib) | +| `USE_DISK_PREALLOCATE` | undefined (disabled) | +| `USE_MMAP` | undefined (disabled) | +| `HAVE_LZMA_MT_ENCODER` | undefined (no XZ multithreaded) | + +### Capability Flags (`HAVE_*`) + +Covers available POSIX/GNU functions and headers, including: + +- **String utilities:** `asprintf`, `strndup`, `strnlen`, `strchrnul`, `strsignal` +- **File I/O:** `fallocate`, `posix_fadvise`, `posix_fallocate`, `fsync` on dirs, `lutimes` +- **System:** `scandir`, `alphasort`, `setsid`, `lchown`, `unsetenv`, `uselocale` +- **Headers:** ``, ``, ``, ``, ``, `` +- **C standard:** `HAVE_C99`, `HAVE_CXX11`, `HAVE_VA_COPY`, `HAVE_C99_SNPRINTF` +- **i18n:** `HAVE_GETTEXT`, `HAVE_DCGETTEXT`, `HAVE_LIBINTL_H` + +### Platform Extension Guards + +Conditionally defines POSIX/GNU extension macros for cross-platform compatibility: + +```c +#define _GNU_SOURCE 1 /* GNU extensions (Linux) */ +#define _ALL_SOURCE 1 /* AIX/Interix extensions */ +#define _DARWIN_C_SOURCE 1 /* macOS general extensions */ +#define _NETBSD_SOURCE 1 /* NetBSD extensions */ +``` + +### External Tool Paths + +```c +#define PATCH "patch" /* GNU patch binary name */ +#define TAR "tar" /* GNU tar binary name */ +``` + +## Usage Example + +```c +#include "config.h" + +/* Guard platform-specific code with HAVE_* macros */ +#ifdef HAVE_FALLOCATE + fallocate(fd, 0, 0, size); +#elif defined(HAVE_POSIX_FALLOCATE) + posix_fallocate(fd, 0, size); +#endif + +/* Use package identity macros in version output */ +fprintf(stdout, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION); +/* Output: dpkg version 1.21.7 */ + +/* Select compression backend */ +#if USE_LIBZ_IMPL == USE_LIBZ_IMPL_ZLIB + /* Use standard zlib */ +#endif +``` + +> **Note:** This file is auto-generated by `./configure` — do not edit manually. Regenerate by running `autoconf` and `./configure` with the appropriate target flags for your platform. \ No newline at end of file diff --git a/libraries/cmake/source/libdpkg/config/x86_64/.config.md b/libraries/cmake/source/libdpkg/config/x86_64/.config.md new file mode 100644 index 00000000000..8135fdc094e --- /dev/null +++ b/libraries/cmake/source/libdpkg/config/x86_64/.config.md @@ -0,0 +1,68 @@ + +Auto-generated build configuration header for the `dpkg` 1.21.7 package, produced by `autoconf`/`configure` to capture platform capabilities, feature availability, and build-time settings for an `amd64` Linux target. + +## Key Components + +### Package Identity +- `PACKAGE_VERSION` — `"1.21.7"` +- `PACKAGE_STRING` — `"dpkg 1.21.7"` +- `PACKAGE_RELEASE` — version + architecture string +- `ARCHITECTURE` / `ARCHITECTURE_CPU` / `ARCHITECTURE_OS` — target platform (`amd64`/`linux`) + +### Build Feature Flags +| Macro | Value | Meaning | +|---|---|---| +| `BUILD_DSELECT` | `0` | dselect UI not compiled | +| `BUILD_START_STOP_DAEMON` | `0` | start-stop-daemon not compiled | +| `BUILD_UPDATE_ALTERNATIVES` | `1` | update-alternatives compiled | +| `ENABLE_NLS` | `1` | Native language support enabled | +| `LIBDPKG_VOLATILE_API` | `1` | Unstable API acknowledged | + +### Compiler & Standard Library Support +- `HAVE_C99`, `HAVE_CXX11` — C99 and C++11 compiler support confirmed +- `HAVE_C99_SNPRINTF` — C99-conformant `snprintf` family available +- `STDC_HEADERS` — full C90 standard headers present + +### Compression & I/O +- `DPKG_DEB_DEFAULT_COMPRESSOR` — defaults to `COMPRESSOR_TYPE_XZ` +- `USE_LIBZ_IMPL` — set to `USE_LIBZ_IMPL_ZLIB` (standard zlib) +- `HAVE_FALLOCATE`, `HAVE_POSIX_FALLOCATE`, `HAVE_POSIX_FADVISE` — advanced file I/O available +- `HAVE_FSYNC_DIR` — `fsync` works on directories + +### Platform Extensions +Conditionally defines POSIX/GNU/BSD extension macros: +- `_GNU_SOURCE`, `_ALL_SOURCE`, `_DARWIN_C_SOURCE`, `_NETBSD_SOURCE`, `_OPENBSD_SOURCE` + +### External Tool Paths +- `PATCH` — `"patch"` (GNU patch) +- `TAR` — `"gtar"` (GNU tar) + +## Usage Example + +```c +#include "config.h" + +#if HAVE_C99 + /* Use C99-specific features safely */ + #include +#endif + +#ifdef ENABLE_NLS + #include + #define _(str) gettext(str) +#else + #define _(str) (str) +#endif + +/* Check architecture at compile time */ +#if defined(ARCHITECTURE) + const char *arch = ARCHITECTURE; /* "amd64" */ +#endif + +/* Guard against volatile libdpkg API */ +#ifndef LIBDPKG_VOLATILE_API + #error "Must acknowledge volatile API" +#endif +#define LIBDPKG_VOLATILE_API 1 +#include +``` \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/config/aarch64/.config.md b/libraries/cmake/source/libgcrypt/config/aarch64/.config.md new file mode 100644 index 00000000000..6cb9d484d3b --- /dev/null +++ b/libraries/cmake/source/libgcrypt/config/aarch64/.config.md @@ -0,0 +1,88 @@ + +Auto-generated build configuration header for **libgcrypt 1.8.1**, produced by `autoconf`/`configure` to capture platform capabilities, available hardware acceleration, and compiled-in algorithm support for the target system. + +## Key Components + +### Build Identity +| Macro | Value | +|---|---| +| `PACKAGE_STRING` | `"libgcrypt 1.8.1"` | +| `BUILD_REVISION` | `"80fd8615"` (Git commit) | +| `BUILD_TIMESTAMP` | `""` | + +### Hardware Acceleration Flags +Controls which CPU-specific instruction sets are enabled at compile time: + +| Macro | Status | +|---|---| +| `ENABLE_ARM_CRYPTO_SUPPORT` | ✅ Enabled | +| `ENABLE_NEON_SUPPORT` | ✅ Enabled | +| `HAVE_GCC_INLINE_ASM_AARCH64_CRYPTO` | ✅ Enabled | +| `HAVE_GCC_INLINE_ASM_AARCH64_NEON` | ✅ Enabled | +| `ENABLE_AESNI_SUPPORT` | ❌ Disabled (non-x86 build) | +| `ENABLE_AVX2_SUPPORT` | ❌ Disabled | + +> This configuration targets **ARM AArch64** (`HAVE_CPU_ARCH_ARM`), not x86. + +### Algorithm Registries + +```c +// Symmetric ciphers +#define LIBGCRYPT_CIPHERS \ + "arcfour:blowfish:cast5:des:aes:twofish:serpent:rfc2268:seed:camellia:idea:salsa20:gost28147:chacha20" + +// Hash digests +#define LIBGCRYPT_DIGESTS \ + "crc:gostr3411-94::md4:md5:rmd160:sha1:sha256:sha512:sha3:tiger:whirlpool:stribog:blake2" + +// Public key algorithms +#define LIBGCRYPT_PUBKEY_CIPHERS "dsa:elgamal:rsa:ecc" + +// Key derivation functions +#define LIBGCRYPT_KDFS "s2k:pkdf2:scrypt" +``` + +### Platform & POSIX Feature Detection +Confirms availability of standard system interfaces used internally: + +```c +#define HAVE_PTHREAD 1 // Threading support +#define HAVE_MLOCK 1 // Secure memory locking +#define HAVE_MMAP 1 // Memory-mapped I/O +#define HAVE_DEV_RANDOM 1 // Entropy source +#define NAME_OF_DEV_RANDOM "/dev/random" +#define NAME_OF_DEV_URANDOM "/dev/urandom" +``` + +### Compiler Intrinsics & Attributes +```c +#define HAVE_BUILTIN_BSWAP32 1 // Byte-swap optimization +#define HAVE_BUILTIN_BSWAP64 1 +#define HAVE_BUILTIN_CTZ 1 // Count trailing zeros +#define HAVE_GCC_ATTRIBUTE_ALIGNED 1 +#define HAVE_GCC_ATTRIBUTE_PACKED 1 +#define HAVE_GCC_ASM_VOLATILE_MEMORY 1 // Memory barrier support +#define GCRY_USE_VISIBILITY 1 // GCC symbol visibility +``` + +## Usage Example + +This header is consumed internally by libgcrypt source files via the include guard pattern: + +```c +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef ENABLE_ARM_CRYPTO_SUPPORT + // Use ARMv8 hardware AES acceleration path +#else + // Fall back to portable C implementation +#endif + +#ifdef HAVE_PTHREAD + // Use pthread-based locking for secure memory +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Regenerate by running `./configure` targeting the appropriate platform. The current configuration is an **AArch64 Linux** build with ARM Crypto Extensions enabled and all x86-specific acceleration disabled. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/config/x86_64/.config.md b/libraries/cmake/source/libgcrypt/config/x86_64/.config.md new file mode 100644 index 00000000000..cdc24c36996 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/config/x86_64/.config.md @@ -0,0 +1,49 @@ + +Auto-generated build configuration header for **libgcrypt 1.8.1**, produced by `autoconf`/`configure` to capture platform capabilities, available hardware acceleration, and enabled algorithm sets for the target build environment. + +## Key Components + +### Build Metadata +| Macro | Value | +|---|---| +| `BUILD_REVISION` | `"80fd861"` (Git commit) | +| `PACKAGE_STRING` | `"libgcrypt 1.8.1"` | +| `BUILD_TIMESTAMP` | `""` | + +### Hardware Acceleration Flags +- `ENABLE_AESNI_SUPPORT` — Intel AES-NI instructions enabled +- `ENABLE_PADLOCK_SUPPORT` — VIA PadLock engine enabled +- `HAVE_CPU_ARCH_X86` — x86 platform detected +- `HAVE_COMPATIBLE_GCC_AMD64_PLATFORM_AS` — AMD64 inline assembly supported +- `HAVE_GCC_INLINE_ASM_PCLMUL`, `HAVE_GCC_INLINE_ASM_SSE41`, `HAVE_GCC_INLINE_ASM_SSSE3`, `HAVE_GCC_INLINE_ASM_BMI2` — SSE/BMI2 inline asm available + +### Algorithm Registries +- `LIBGCRYPT_CIPHERS` — arcfour, blowfish, cast5, des, aes, twofish, serpent, camellia, chacha20, and more +- `LIBGCRYPT_DIGESTS` — md4, md5, sha1, sha256, sha512, sha3, blake2, stribog, and more +- `LIBGCRYPT_PUBKEY_CIPHERS` — dsa, elgamal, rsa, ecc +- `LIBGCRYPT_KDFS` — s2k, pkdf2, scrypt + +### System Capability Flags +- `HAVE_PTHREAD`, `HAVE_MLOCK`, `HAVE_MMAP` — threading and memory locking available +- `HAVE_DEV_RANDOM` — `/dev/random` and `/dev/urandom` present +- `GCRY_USE_VISIBILITY` — GCC symbol visibility attribute enabled + +## Usage Example + +```c +#include "config.h" + +#ifdef ENABLE_AESNI_SUPPORT + /* Use hardware-accelerated AES path */ + aesni_encrypt(ctx, out, in); +#else + /* Fall back to portable implementation */ + aes_encrypt(ctx, out, in); +#endif + +#ifdef HAVE_PTHREAD + pthread_mutex_lock(&lock); +#endif +``` + +> **Note:** This file is auto-generated by `./configure` and should never be edited manually. Re-run `configure` to regenerate for a different target platform. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/aarch64/cipher/.gost-sb.md b/libraries/cmake/source/libgcrypt/generated/aarch64/cipher/.gost-sb.md new file mode 100644 index 00000000000..33f6c9a7272 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/aarch64/cipher/.gost-sb.md @@ -0,0 +1,44 @@ + +Precomputed S-box lookup table for the GOST R 34.11 hash algorithm (Soviet/Russian standard), providing 4 interleaved 256-entry tables of pre-shifted 32-bit values used to accelerate the substitution step in the GOST block cipher. + +## Key Components + +- **`sbox_test_3411`** — A static `const u32` array of 4×256 entries (1024 total `u32` values) organized into 4 logical sub-tables (sections 0–3), each containing 256 pre-computed, bit-shifted substitution values + +## Structure + +```text +sbox_test_3411[4 * 256] +├── Section 0 [ 0–255] — S-box values pre-shifted for bits 11–15 +├── Section 1 [256–511] — S-box values pre-shifted for bits 19–23 +├── Section 2 [512–767] — S-box values pre-shifted for bits 27–31 + low bits +└── Section 3 [768–1023]— S-box values pre-shifted for bits 3–7 +``` + +## Usage Example + +```c +#include "gost-sb.h" + +/* Perform GOST S-box substitution on a 32-bit word + * using the precomputed lookup table. + * 'x' is an 8-bit input index into one of the 4 sub-tables. */ +static inline u32 gost_sbox_lookup(int table, u8 index) { + /* table: 0–3 selects which 256-entry sub-table to use */ + return sbox_test_3411[table * 256 + index]; +} + +/* Example: combine all 4 sub-table lookups for a full round */ +u32 gost_subst(u32 x) { + return sbox_test_3411[0 *256 + ((x >> 0) & 0xFF)] + ^ sbox_test_3411[1 *256 + ((x >> 8) & 0xFF)] + ^ sbox_test_3411[2 *256 + ((x >> 16) & 0xFF)] + ^ sbox_test_3411[3 *256 + ((x >> 24) & 0xFF)]; +} +``` + +## Notes + +- Values are pre-rotated/shifted to eliminate runtime bit manipulation during cipher rounds, following the standard optimization technique for GOST 28147-89 / R 34.11 +- The `_test_3411` suffix indicates this uses the test S-box defined in the GOST R 34.11-94 specification (as opposed to CryptoPro or other S-box parameter sets) +- This is a header-only data table with no function exports \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.asm-syntax.md b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.asm-syntax.md new file mode 100644 index 00000000000..15d8c77dfb6 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.asm-syntax.md @@ -0,0 +1,30 @@ + +A minimal auto-generated configuration header that establishes assembly syntax settings for the `aarch64` target architecture on a `aarch64-unknown-linux-gnu` host. This file is machine-generated by `config.links` and should not be manually edited. + +## Key Components + +No active declarations or macros are present in this file. It serves as a placeholder/stub generated during the build configuration process for the AArch64 (ARM 64-bit) architecture target. + +| Field | Value | +|---|---| +| **Host** | `aarch64-unknown-linux-gnu` | +| **Target Architecture** | `aarch64` | +| **Generated By** | `config.links` | + +## Usage Example + +This header is typically included indirectly through the assembler or compiler toolchain configuration pipeline. If referenced directly: + +```c +#include "asm-syntax.h" + +/* Assembler macros and syntax definitions for aarch64 + will be provided by this header once populated + during a full toolchain build */ +``` + +## Notes + +- **Do not edit** this file manually — it is regenerated during each build configuration cycle via `config.links`. +- In a complete toolchain build (e.g., binutils/GAS), this file would typically define assembly syntax macros such as register prefix conventions, comment characters, or pseudo-op formats specific to the target ISA. +- If assembly syntax macros are missing or incorrect, regenerate by re-running the configure step targeting `aarch64-unknown-linux-gnu`. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mod-source-info.md b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mod-source-info.md new file mode 100644 index 00000000000..5cf57f5c8b1 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mod-source-info.md @@ -0,0 +1,32 @@ + +Auto-generated header that records the source file manifest for the `mpi` (multi-precision integer) module, targeting the `aarch64-unknown-linux-gnu` host architecture. + +## Key Components + +- **`mod_source_info[]`** — A static char array listing the relative paths of all source files compiled into the MPI module. Entries are colon-prefixed path strings that concatenate into a single string literal at compile time. + +## Source Files Referenced + +| Path | Description | +|---|---| +| `aarch64/mpih-add1.S` | ARM64 assembly: MPI limb addition | +| `aarch64/mpih-sub1.S` | ARM64 assembly: MPI limb subtraction | +| `aarch64/mpih-mul1.S` | ARM64 assembly: MPI single-word multiply | +| `aarch64/mpih-mul2.S` | ARM64 assembly: MPI double-word multiply | +| `aarch64/mpih-mul3.S` | ARM64 assembly: MPI triple-word multiply | +| `generic/mpih-lshift.c` | Generic C: limb left-shift | +| `generic/mpih-rshift.c` | Generic C: limb right-shift | + +## Usage Example + +```c +/* Inspecting embedded source info at runtime (e.g. for diagnostics) */ +#include "mod-source-info.h" + +void print_mpi_sources(void) { + /* mod_source_info is a colon-delimited string of source paths */ + printf("MPI module sources: %s\n", mod_source_info); +} +``` + +> **Do not edit manually.** This file is regenerated by `config.links` during the build configuration step. Any changes will be overwritten. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpi-asm-defs.md b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpi-asm-defs.md new file mode 100644 index 00000000000..70410cc4a8e --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpi-asm-defs.md @@ -0,0 +1,21 @@ + +Defines the fundamental memory layout constant for the MPI (Multi-Precision Integer) subsystem by tying limb size to the platform's native `unsigned long long` width. + +## Key Components + +| Macro | Description | +|---|---| +| `BYTES_PER_MPI_LIMB` | Number of bytes in a single MPI limb, derived from `SIZEOF_UNSIGNED_LONG_LONG` | + +## Usage Example + +```c +/* Calculating the number of limbs needed to hold a given number of bytes */ +size_t limbs_needed = (byte_count + BYTES_PER_MPI_LIMB - 1) / BYTES_PER_MPI_LIMB; +``` + +## Notes + +- `SIZEOF_UNSIGNED_LONG_LONG` must be defined before including this header (typically via a platform detection step such as `autoconf`). +- On most 64-bit platforms this resolves to `8`, giving 64-bit limbs. +- On 32-bit targets where `unsigned long long` is still 64-bit, limbs remain 8 bytes wide; the distinction from a `SIZEOF_UNSIGNED_LONG`-based definition matters for mixed-width builds. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-lshift.md b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-lshift.md new file mode 100644 index 00000000000..00805f946e9 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-lshift.md @@ -0,0 +1,39 @@ + +Multi-precision integer (MPI) left-shift helper for the Libgcrypt library, implementing a bitwise left shift across an array of MPI limbs (digits). + +## Key Components + +### `_gcry_mpih_lshift` + +```c +mpi_limb_t _gcry_mpih_lshift( + mpi_ptr_t wp, // destination limb array + mpi_ptr_t up, // source limb array + mpi_size_t usize, // number of limbs in source + unsigned int cnt // bit shift count (must satisfy: 0 < cnt < BITS_PER_MPI_LIMB) +); +``` + +Shifts the multi-limb integer stored in `up` left by `cnt` bits, writes the `usize` least significant result limbs into `wp`, and returns the bits shifted out from the most significant limb. + +**Constraints:** +- `cnt` must be in the range `(0, BITS_PER_MPI_LIMB)` +- For in-place operation, `wp` must be `>= up` + +## Usage Example + +```c +#include "mpi-internal.h" + +/* Left-shift a 2-limb MPI by 3 bits */ +mpi_limb_t src[2] = { 0xDEADBEEF, 0x12345678 }; +mpi_limb_t dst[2]; + +mpi_limb_t overflow = _gcry_mpih_lshift(dst, src, 2, 3); +/* dst now contains the shifted result; + overflow holds the bits shifted out of the MSL */ +``` + +## Algorithm Notes + +The function iterates from most significant to least significant limb, combining adjacent limb pairs using two complementary shifts (`sh_1 = cnt`, `sh_2 = BITS_PER_MPI_LIMB - cnt`) to correctly propagate bits across limb boundaries without data loss. This is a standard technique for arbitrary-precision shift operations derived from the GNU MP Library. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-rshift.md b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-rshift.md new file mode 100644 index 00000000000..260fa898bb1 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/aarch64/mpi/.mpih-rshift.md @@ -0,0 +1,34 @@ + +Implements a right-shift operation on a multi-precision integer (MPI) limb array, part of the Libgcrypt MPI helper layer derived from GNU MP. + +## Key Components + +### `_gcry_mpih_rshift` +```c +mpi_limb_t _gcry_mpih_rshift(mpi_ptr_t wp, mpi_ptr_t up, mpi_size_t usize, unsigned cnt) +``` +Shifts a multi-limb integer right by `cnt` bits (where `0 < cnt < BITS_PER_MPI_LIMB`). Processes limbs sequentially, combining adjacent limb pairs via complementary shift amounts (`sh_1 = cnt`, `sh_2 = BITS_PER_MPI_LIMB - cnt`) to preserve bits crossing limb boundaries. Returns the bits shifted out from the least significant end. + +**Constraints:** +- `cnt` must satisfy `0 < cnt < BITS_PER_MPI_LIMB` +- In-place operation is safe only if `wp <= up` + +## Usage Example + +```c +mpi_limb_t limbs[3] = { 0x00000001, 0x00000002, 0x00000004 }; +mpi_limb_t result[3]; +mpi_limb_t shifted_out; + +/* Shift the 3-limb integer right by 1 bit */ +shifted_out = _gcry_mpih_rshift(result, limbs, 3, 1); + +/* result now contains the right-shifted value; + shifted_out holds the bits lost off the right end */ +``` + +## Notes + +- Uses a two-register sliding window (`high_limb`/`low_limb`) to avoid redundant memory reads +- The return value (`retval`) captures the outgoing bits, useful for chained multi-word shifts or rounding +- In-place shifts (`wp == up`) are supported due to the left-to-right traversal order \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/aarch64/src/.gcrypt.md b/libraries/cmake/source/libgcrypt/generated/aarch64/src/.gcrypt.md new file mode 100644 index 00000000000..51675803874 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/aarch64/src/.gcrypt.md @@ -0,0 +1,48 @@ + +The main public API header for **Libgcrypt 1.8.1**, the GNU cryptographic library. It exposes all interfaces for cryptographic primitives, multi-precision integers, S-expressions, error handling, and library initialization/control. + +## Key Components + +| Category | Types / Functions | +|---|---| +| **Error Handling** | `gcry_error_t`, `gcry_err_code_t`, `gcry_err_source_t`, `gcry_strerror()`, `gcry_strsource()`, `gcry_err_make()`, `gcry_error_from_errno()` | +| **Library Control** | `gcry_check_version()`, `gcry_control()`, `enum gcry_ctl_cmds` | +| **Multi-Precision Integers** | `gcry_mpi_t`, `gcry_mpi_point_t` | +| **S-Expressions** | `gcry_sexp_t`, `gcry_sexp_new()`, `gcry_sexp_create()`, `gcry_sexp_sscan()`, `enum gcry_sexp_format` | +| **Scatter-Gather Buffers** | `gcry_buffer_t` (size, offset, length, data) | +| **Threading** | `gcry_thread_cbs` (legacy, kept for source compatibility since 1.6) | +| **Context Objects** | `gcry_ctx_t` (generic context for select functions) | + +## Usage Example + +```c +#include + +int main(void) { + /* Verify header and library versions match */ + if (!gcry_check_version(GCRYPT_VERSION)) { + fprintf(stderr, "libgcrypt version mismatch\n"); + return 1; + } + + /* Disable secure memory warnings, initialize secure memory pool */ + gcry_control(GCRYCTL_DISABLE_SECMEM_WARN); + gcry_control(GCRYCTL_INIT_SECMEM, 16384, 0); + + /* Signal that initialization is complete */ + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + + /* Parse an S-expression for public key operations */ + gcry_sexp_t key; + gcry_error_t err = gcry_sexp_new(&key, "(public-key ...)", 0, 1); + if (err) { + fprintf(stderr, "Error: %s / %s\n", + gcry_strsource(err), gcry_strerror(err)); + return 1; + } + + return 0; +} +``` + +> **Note:** Always call `gcry_check_version()` before any other Libgcrypt function, then complete initialization with `GCRYCTL_INITIALIZATION_FINISHED` before using cryptographic operations. FIPS mode can be enabled via `GCRYCTL_FORCE_FIPS_MODE` prior to initialization. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/cipher/.gost-sb.md b/libraries/cmake/source/libgcrypt/generated/x86_64/cipher/.gost-sb.md new file mode 100644 index 00000000000..d2825a42498 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/cipher/.gost-sb.md @@ -0,0 +1,27 @@ + +Precomputed S-box lookup table for the GOST R 34.10-94 / GOST 28147-89 block cipher, encoding substitution and rotation operations into four 256-entry 32-bit arrays for efficient software implementation. + +## Key Components + +- **`sbox_test_3411[4*256]`** — A static constant array of 1024 `u32` values organized as four 256-entry sub-tables (indexed 0–3), each corresponding to one of the four byte-lane substitution steps in the GOST cipher's key schedule and encryption round function. + +## Usage Example + +```c +#include "gost-sb.h" + +/* Perform a GOST S-box lookup for a given 32-bit word 'x' */ +static inline u32 gost_sbox_lookup(u32 x) { + return sbox_test_3411[ (x >> 0) & 0xFF] /* lane 0 */ + ^ sbox_test_3411[0x100 + ((x >> 8) & 0xFF)] /* lane 1 */ + ^ sbox_test_3411[0x200 + ((x >> 16) & 0xFF)] /* lane 2 */ + ^ sbox_test_3411[0x300 + ((x >> 24) & 0xFF)]; /* lane 3 */ +} +``` + +## Notes + +- The four sub-tables (marked `/* 0 */` through `/* 3 */`) encode both the S-box substitution and the 11-bit left rotation (`ROL11`) required by GOST 28147-89, merged into a single table-lookup step. +- Sub-tables 0 and 1 hold values with significant bits in the lower 23 bits; sub-tables 2 and 3 span the full 32-bit range, encoding the rotation output. +- This is a **test/reference S-box** (CryptoPro S-box variant used in GOST R 34.11 hashing), not intended for production keying material — substitute with your deployment-specific S-box parameters for production use. +- No external dependencies; include directly in GOST cipher or hash implementation translation units. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mod-source-info.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mod-source-info.md new file mode 100644 index 00000000000..7e5e2f67dff --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mod-source-info.md @@ -0,0 +1,31 @@ + +Auto-generated header that embeds source file path metadata for a multi-precision integer (MPI) arithmetic module, identifying which generic implementation files were linked during the build configuration for an `x86_64-pc-linux-gnu` host. + +## Key Components + +- **`mod_source_info[]`** — A static character array containing a concatenated list of relative paths to the generic MPI helper source files included in the build. + +## Included Source References + +| File | Purpose | +|---|---| +| `mpih-add1.c` | Multi-precision addition | +| `mpih-sub1.c` | Multi-precision subtraction | +| `mpih-mul1.c` | Multi-precision multiplication (variant 1) | +| `mpih-mul2.c` | Multi-precision multiplication (variant 2) | +| `mpih-mul3.c` | Multi-precision multiplication (variant 3) | +| `mpih-lshift.c` | Left bit-shift operation | +| `mpih-rshift.c` | Right bit-shift operation | + +## Usage Example + +```c +#include "mod-source-info.h" + +/* Access build-time source path metadata */ +void print_sources(void) { + printf("Module sources: %s\n", mod_source_info); +} +``` + +> **Note:** This file is auto-generated by `config.links` during the build process. Do not edit manually — changes will be overwritten. To modify the included source files, update the build configuration that drives `config.links`. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpi-asm-defs.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpi-asm-defs.md new file mode 100644 index 00000000000..3b4d1837f85 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpi-asm-defs.md @@ -0,0 +1,24 @@ + +Defines the `BYTES_PER_MPI_LIMB` constant for the MPI (Multi-Precision Integer) library, handling a special case for AMD64 with the x32 ABI where the standard `unsigned long` size would be incorrect. + +## Key Components + +- **`BYTES_PER_MPI_LIMB`** — Macro defining the byte size of a single MPI limb (the fundamental unit of a multi-precision integer): + - Set to `8` on GCC 3+ targets using x86-64 with the ILP32 ABI (`__x86_64__` + `__ILP32__`) + - Defaults to `SIZEOF_UNSIGNED_LONG` on all other platforms + +## Usage Example + +```c +#include "mpi-asm-defs.h" + +/* Allocate storage for an N-limb MPI */ +size_t limb_count = 4; +size_t buffer_size = limb_count * BYTES_PER_MPI_LIMB; +void *limb_storage = malloc(buffer_size); +``` + +## Notes + +- The x32 ABI runs 64-bit x86 code with 32-bit pointers, meaning `sizeof(unsigned long)` returns `4`, but MPI limbs must still be 64-bit (`8` bytes) to match the native register width. +- `SIZEOF_UNSIGNED_LONG` is expected to be provided by the build system (e.g., via `config.h` from autoconf). \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-add1.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-add1.md new file mode 100644 index 00000000000..f18313f199a --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-add1.md @@ -0,0 +1,41 @@ + +Implements multi-precision integer (MPI) limb-level addition for the Libgcrypt library, adding two equal-length limb arrays with carry propagation. + +## Key Components + +### `_gcry_mpih_add_n` + +```c +mpi_limb_t _gcry_mpih_add_n( + mpi_ptr_t res_ptr, // output limb array + mpi_ptr_t s1_ptr, // first operand limb array + mpi_ptr_t s2_ptr, // second operand limb array + mpi_size_t size // number of limbs to add +); +``` + +Adds two MPI limb arrays of identical length (`size`), storing the result in `res_ptr`. Returns the final carry out (`0` or `1`) for the caller to handle overflow into higher limbs. + +**Loop optimization:** Uses a negative-index counting trick — the loop counter `j` starts at `-size` and increments to `-1`, allowing the loop condition to be a simple zero-check (`while (++j)`), which is faster on most architectures. + +**Carry handling:** Two-step carry detection per limb: +1. Add previous carry to `s2` limb, detect overflow +2. Add `s1` limb to the result, accumulate carry + +## Usage Example + +```c +/* Add two 2-limb MPI values */ +mpi_limb_t a[2] = { 0xFFFFFFFF, 0x00000001 }; +mpi_limb_t b[2] = { 0x00000001, 0x00000000 }; +mpi_limb_t result[2]; + +mpi_limb_t carry = _gcry_mpih_add_n(result, a, b, 2); +/* result[0] = 0x00000000, result[1] = 0x00000002, carry = 0 */ + +if (carry) { + /* Handle overflow into the next higher limb */ +} +``` + +> **Note:** This function operates on fixed-size limb arrays. For adding arrays of different lengths or a single limb, see companion helpers such as `_gcry_mpih_add` and `_gcry_mpih_add_1` in the same MPI subsystem. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-lshift.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-lshift.md new file mode 100644 index 00000000000..3b634b9c4d2 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-lshift.md @@ -0,0 +1,41 @@ + +Multi-precision integer (MPI) helper that performs a left bit-shift on a limb array, returning the overflow bits shifted out from the most significant digit. Part of the Libgcrypt MPI implementation, derived from the GNU MP Library. + +## Key Components + +### `_gcry_mpih_lshift` + +```c +mpi_limb_t _gcry_mpih_lshift( + mpi_ptr_t wp, // destination limb array (must satisfy wp >= up for in-place ops) + mpi_ptr_t up, // source limb array + mpi_size_t usize, // number of limbs in the source array + unsigned int cnt // bit shift count (must satisfy: 0 < cnt < BITS_PER_MPI_LIMB) +); +``` + +Shifts the `usize`-limb integer at `up` left by `cnt` bits, writing the `usize` least significant limbs of the result to `wp`. Returns the bits that overflow from the most significant limb. + +**Algorithm:** Iterates from most significant to least significant limb, combining adjacent limbs via a left shift (`sh_1 = cnt`) and a complementary right shift (`sh_2 = BITS_PER_MPI_LIMB - cnt`) using bitwise OR to reconstruct each output limb without losing cross-limb bits. + +## Constraints + +| Constraint | Reason | +|---|---| +| `0 < cnt < BITS_PER_MPI_LIMB` | Ensures both `sh_1` and `sh_2` are valid shift amounts | +| `wp >= up` (for in-place writes) | Prevents overwriting source data before it is read | + +## Usage Example + +```c +#include "mpi-internal.h" + +/* Shift a 2-limb integer left by 3 bits */ +mpi_limb_t limbs[2] = { 0xDEADBEEF, 0x00000001 }; +mpi_limb_t result[2]; +mpi_limb_t overflow; + +overflow = _gcry_mpih_lshift(result, limbs, 2, 3); +/* result[] now holds the shifted value; + overflow holds the 3 bits shifted out of the MSL */ +``` \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul1.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul1.md new file mode 100644 index 00000000000..d826306e344 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul1.md @@ -0,0 +1,36 @@ + +Implements a single-limb multiplication operation for the Libgcrypt MPI (Multi-Precision Integer) library, multiplying an array of limbs by a single limb value. + +## Key Components + +### `_gcry_mpih_mul_1` +Multiplies a multi-precision integer (`s1_ptr`, of length `s1_size` limbs) by a single limb (`s2_limb`), storing the result in `res_ptr`. + +**Parameters:** +| Parameter | Type | Description | +|---|---|---| +| `res_ptr` | `mpi_ptr_t` | Output buffer for the result | +| `s1_ptr` | `mpi_ptr_t` | Input MPI limb array | +| `s1_size` | `mpi_size_t` | Number of limbs in `s1_ptr` | +| `s2_limb` | `mpi_limb_t` | Single-limb multiplier | + +**Returns:** `mpi_limb_t` — the carry limb propagated beyond the most significant position. + +## Usage Example + +```c +mpi_limb_t result_buf[4]; +mpi_limb_t operand[4] = { 0x1, 0x2, 0x3, 0x4 }; +mpi_limb_t scalar = 0xDEADBEEF; +mpi_limb_t carry; + +carry = _gcry_mpih_mul_1(result_buf, operand, 4, scalar); +/* result_buf now holds operand[] * scalar, carry holds overflow */ +``` + +## Implementation Notes + +- Uses a **negative-index loop** (`j` from `-s1_size` to `-1`) for a minor branch-prediction/performance optimization. +- Relies on the `umul_ppmm` macro (from `longlong.h`) to perform a full double-width limb multiply, producing a high and low product word. +- Carry propagation is handled manually: `cy_limb = (prod_low < cy_limb ? 1 : 0) + prod_high`. +- Derived from the GNU MP Library with minor adaptations to support Libgcrypt's optional secure memory allocation model. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul2.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul2.md new file mode 100644 index 00000000000..62c4d858b92 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul2.md @@ -0,0 +1,33 @@ + +Multiply-accumulate helper for multi-precision integers (MPI) in Libgcrypt. Computes `res += s1 * s2_limb` across a limb array, returning the final carry. + +## Key Components + +### `_gcry_mpih_addmul_1` +```c +mpi_limb_t _gcry_mpih_addmul_1( + mpi_ptr_t res_ptr, // result/accumulator array (modified in-place) + mpi_ptr_t s1_ptr, // multiplicand limb array + mpi_size_t s1_size, // number of limbs in s1 + mpi_limb_t s2_limb // single-limb multiplier +); +``` +Iterates from index `-s1_size` to `-1` (a negative-index loop for performance), multiplying each limb of `s1` by `s2_limb` using `umul_ppmm` (double-width multiply macro), accumulating carry, then adding into `res_ptr`. Returns the final carry limb. + +## Usage Example + +```c +/* Multiply a 3-limb integer by a single limb and add into result */ +mpi_limb_t result[4] = {0, 0, 0, 0}; +mpi_limb_t s1[3] = {0xDEAD, 0xBEEF, 0xCAFE}; +mpi_limb_t s2 = 0x1234; + +mpi_limb_t carry = _gcry_mpih_addmul_1(result, s1, 3, s2); +/* result[] now holds s1*s2 (low limbs), carry holds overflow */ +``` + +## Notes + +- Relies on `umul_ppmm` from `longlong.h` for portable double-width multiplication. +- The negative-index loop pattern (`j` from `-size` to `-1`) avoids a separate bounds comparison, improving inner-loop efficiency. +- Part of Libgcrypt's internal MPI engine; derived from GNU MP with modifications for optional secure memory allocation to prevent sensitive data leaks via paging. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul3.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul3.md new file mode 100644 index 00000000000..67784168cf7 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-mul3.md @@ -0,0 +1,38 @@ + +Performs a combined multiply-and-subtract operation on multi-precision integer (MPI) limb arrays, computing `res = res - (s1 * s2_limb)` with carry propagation. + +## Key Components + +### `_gcry_mpih_submul_1` + +```c +mpi_limb_t _gcry_mpih_submul_1( + mpi_ptr_t res_ptr, // result array (modified in-place) + mpi_ptr_t s1_ptr, // multiplicand limb array + mpi_size_t s1_size, // number of limbs in s1 + mpi_limb_t s2_limb // single-limb multiplier +); +``` + +Iterates over each limb of `s1`, multiplying by `s2_limb` using the `umul_ppmm` macro (which produces a double-width product via `prod_high:prod_low`), then subtracts the result from the corresponding limb in `res_ptr`, tracking borrow/carry across iterations. + +**Returns:** the final carry/borrow limb after all subtractions. + +## Usage Example + +```c +/* Subtract 7 * s1 from res across a 3-limb array */ +mpi_limb_t carry; +mpi_limb_t res[3] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; +mpi_limb_t s1[3] = { 0x00000003, 0x00000002, 0x00000001 }; + +carry = _gcry_mpih_submul_1(res, s1, 3, (mpi_limb_t)7); +/* res now holds (res - 7*s1), carry holds any overflow */ +``` + +## Notes + +- Part of **Libgcrypt**'s internal MPI (Multi-Precision Integer) engine, derived from GNU MP. +- The loop uses a **negative index** trick (`j` from `-s1_size` to `-1`) to eliminate a separate bounds comparison, improving branch prediction performance. +- Operates at the raw limb level — callers are responsible for ensuring `res_ptr` has sufficient allocated limbs. +- Supports optional **secure memory allocation** to prevent sensitive data leakage via paging. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-rshift.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-rshift.md new file mode 100644 index 00000000000..6004dfe80a6 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-rshift.md @@ -0,0 +1,41 @@ + +Implements a right-shift operation on a multi-precision integer (MPI) limb array, part of the Libgcrypt MPI helper library derived from GNU MP. + +## Key Components + +### `_gcry_mpih_rshift` + +```c +mpi_limb_t _gcry_mpih_rshift(mpi_ptr_t wp, mpi_ptr_t up, mpi_size_t usize, unsigned cnt); +``` + +Shifts a multi-limb integer right by `cnt` bits (where `0 < cnt < BITS_PER_MPI_LIMB`), writing `usize` result limbs to `wp`. Returns the bits shifted out from the least significant end. + +**Parameters:** + +| Parameter | Description | +|-----------|-------------| +| `wp` | Destination limb array (must satisfy `wp <= up` for in-place use) | +| `up` | Source limb array | +| `usize` | Number of limbs in the source array | +| `cnt` | Bit shift count (`0 < cnt < BITS_PER_MPI_LIMB`) | + +**Returns:** The bits shifted out to the right (as a `mpi_limb_t`). + +## Usage Example + +```c +mpi_limb_t carry; +mpi_limb_t src[2] = { 0x00000001UL, 0x80000000UL }; +mpi_limb_t dst[2]; + +/* Shift right by 1 bit across a 2-limb integer */ +carry = _gcry_mpih_rshift(dst, src, 2, 1); +/* dst[0] = 0xC0000000, dst[1] = 0x40000000, carry = 0x80000000 */ +``` + +## Notes + +- The algorithm processes limb pairs in a single pass, combining adjacent limbs via complementary shifts (`sh_1 = cnt`, `sh_2 = BITS_PER_MPI_LIMB - cnt`) to reconstruct each output limb. +- In-place operation is safe only when `wp <= up`. +- Part of Libgcrypt's secure MPI layer, supporting optional secure memory allocation to prevent sensitive data exposure via paging. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-sub1.md b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-sub1.md new file mode 100644 index 00000000000..82dc14df687 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/mpi/.mpih-sub1.md @@ -0,0 +1,44 @@ + +Multi-precision integer subtraction helper implementing limb-by-limb subtraction with borrow propagation for the Libgcrypt MPI library. + +## Key Components + +### `_gcry_mpih_sub_n` +Subtracts two multi-precision integers of equal size, storing the result and returning the final borrow (carry-out). + +| Parameter | Type | Description | +|-----------|------|-------------| +| `res_ptr` | `mpi_ptr_t` | Output buffer for the result | +| `s1_ptr` | `mpi_ptr_t` | Minuend (value being subtracted from) | +| `s2_ptr` | `mpi_ptr_t` | Subtrahend (value being subtracted) | +| `size` | `mpi_size_t` | Number of limbs to process | + +**Returns:** `mpi_limb_t` — final borrow bit (`1` if underflow occurred, `0` otherwise) + +## Algorithm Notes + +- Uses a **negative index loop** (`j` from `-size` to `-1`) for performance optimization — avoids a separate size comparison each iteration +- Borrow propagation is handled in two stages per limb: + 1. Add previous carry into the subtrahend + 2. Subtract, then accumulate carry from both operations +- Derived from the GNU MP Library with minor modifications to support optional secure memory allocation (guarding sensitive data from paging) + +## Usage Example + +```c +#include "mpi-internal.h" + +mpi_limb_t s1[2] = { 0x00000005, 0x00000003 }; /* s1 = large number */ +mpi_limb_t s2[2] = { 0x00000002, 0x00000001 }; /* s2 = smaller number */ +mpi_limb_t res[2]; + +mpi_limb_t borrow = _gcry_mpih_sub_n(res, s1, s2, 2); + +/* res now holds s1 - s2 across both limbs */ +/* borrow == 1 indicates underflow (result was negative) */ +if (borrow) { + /* handle underflow */ +} +``` + +> **Note:** Both operands must have the same `size`. For subtraction of different-sized operands, higher-level MPI routines handle pre-processing before calling this function. \ No newline at end of file diff --git a/libraries/cmake/source/libgcrypt/generated/x86_64/src/.gcrypt.md b/libraries/cmake/source/libgcrypt/generated/x86_64/src/.gcrypt.md new file mode 100644 index 00000000000..3583a234307 --- /dev/null +++ b/libraries/cmake/source/libgcrypt/generated/x86_64/src/.gcrypt.md @@ -0,0 +1,61 @@ + +Public header for the GNU Cryptographic Library (Libgcrypt), defining the full API surface including types, enumerations, error handling, and function declarations for cryptographic operations. + +## Key Components + +### Types & Handles +- `gcry_mpi_t` / `gcry_mpi_point_t` — Multi-precision integer and elliptic curve point handles +- `gcry_sexp_t` — S-expression object handle (used with public-key operations) +- `gcry_ctx_t` — Generic context handle +- `gcry_buffer_t` — Scatter-gather buffer structure for hashing +- `gcry_error_t` / `gcry_err_code_t` / `gcry_err_source_t` — Error type aliases wrapping `libgpg-error` + +### Error Handling +Inline wrappers around `libgpg-error`: +- `gcry_err_make()` — Compose error from source + code +- `gcry_error()` — Create error from code using default source +- `gcry_err_code()` / `gcry_err_source()` — Decompose error values +- `gcry_strerror()` / `gcry_strsource()` — Human-readable error descriptions +- `gcry_err_code_from_errno()` / `gcry_error_from_errno()` — Map system `errno` values + +### Library Control (`gcry_control`) +Accepts `enum gcry_ctl_cmds` values to configure runtime behavior: +- Secure memory: `GCRYCTL_INIT_SECMEM`, `GCRYCTL_DISABLE_SECMEM` +- FIPS mode: `GCRYCTL_FORCE_FIPS_MODE`, `GCRYCTL_FIPS_MODE_P` +- RNG: `GCRYCTL_SET_PREFERRED_RNG_TYPE`, `GCRYCTL_DRBG_REINIT` +- Initialization: `GCRYCTL_INITIALIZATION_FINISHED` + +### S-Expression API +- `gcry_sexp_new()` — Parse buffer into S-expression object +- `gcry_sexp_create()` — Same, with ownership transfer via `freefnc` +- `gcry_sexp_sscan()` — Printf-style S-expression parser + +## Usage Example + +```c +#include + +int main(void) { + /* Verify header/library version match */ + if (!gcry_check_version(GCRYPT_VERSION)) { + fprintf(stderr, "libgcrypt version mismatch\n"); + return 1; + } + + /* Initialize secure memory pool (16KB) */ + gcry_control(GCRYCTL_INIT_SECMEM, 16384, 0); + + /* Signal initialization complete */ + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + + /* Error handling example */ + gcry_error_t err = gcry_error(GPG_ERR_BAD_KEY); + fprintf(stderr, "Error: %s / %s\n", + gcry_strsource(err), + gcry_strerror(err)); + + return 0; +} +``` + +> **Note:** Always call `gcry_check_version()` before any other Libgcrypt function. Secure memory and FIPS mode must be configured before `GCRYCTL_INITIALIZATION_FINISHED`. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/config/aarch64/.config.md b/libraries/cmake/source/libgpg-error/config/aarch64/.config.md new file mode 100644 index 00000000000..09b1dda27aa --- /dev/null +++ b/libraries/cmake/source/libgpg-error/config/aarch64/.config.md @@ -0,0 +1,57 @@ + +Auto-generated build configuration header for **libgpg-error 1.27**, produced by `autoconf`/`autoheader` for an `aarch64-unknown-linux-gnu` target system. + +## Key Components + +### Build Identity +| Macro | Value | +|---|---| +| `BUILD_REVISION` | `"c1668f6"` | +| `PACKAGE_VERSION` / `VERSION` | `"1.27"` | +| `HOST_TRIPLET_STRING` | `"aarch64-unknown-linux-gnu"` | + +### Feature Detection Macros +- **NLS/i18n**: `ENABLE_NLS`, `HAVE_GETTEXT`, `HAVE_DCGETTEXT`, `HAVE_LANGINFO_THOUSANDS_SEP` +- **Threading**: `USE_POSIX_THREADS`, `USE_POSIX_THREADS_WEAK`, `HAVE_PTHREAD_MUTEX_RECURSIVE`, `HAVE_PTHREAD_RWLOCK` +- **Compiler/Visibility**: `GPGRT_USE_VISIBILITY`, `HAVE_GCC_ATTRIBUTE_ALIGNED` +- **Standard Headers**: `HAVE_STDINT_H`, `HAVE_INTTYPES_H`, `HAVE_UNISTD_H`, `HAVE_DLFCN_H`, etc. + +### Type Sizes (aarch64 64-bit) +| Type | Size (bytes) | +|---|---| +| `int` | 4 | +| `long` / `long long` | 8 | +| `void *` | 8 | +| `pthread_mutex_t` | 48 | +| `time_t` | 8 | + +### Framework Integration Macros +- `_ESTREAM_PRINTF_REALLOC` → `_gpgrt_realloc` +- `_ESTREAM_PRINTF_EXTRA_INCLUDE` → `"gpgrt-int.h"` +- `GPG_ERR_ENABLE_GETTEXT_MACROS`, `GPG_ERR_ENABLE_ERRNO_MACROS`, `GPGRT_ENABLE_ES_MACROS` + +### Platform Extensions +Conditionally defines `_GNU_SOURCE`, `_ALL_SOURCE`, `_POSIX_PTHREAD_SEMANTICS`, `_DARWIN_USE_64_BIT_INODE`, and `__EXTENSIONS__` for cross-platform compatibility. + +## Usage Example + +```c +#include "config.h" + +/* Guard platform-specific code using detected features */ +#ifdef HAVE_DLFCN_H +#include +#endif + +#ifdef USE_POSIX_THREADS +#include +/* pthread_mutex_t is guaranteed 48 bytes on this build target */ +#endif + +/* Check NLS availability at compile time */ +#ifdef ENABLE_NLS + /* Internationalization enabled — gettext macros are active */ +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Regenerate by running `./configure` in the source tree. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/config/x86_64/.config.md b/libraries/cmake/source/libgpg-error/config/x86_64/.config.md new file mode 100644 index 00000000000..274f819e0e8 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/config/x86_64/.config.md @@ -0,0 +1,52 @@ + +Auto-generated build configuration header for `libgpg-error 1.27`, produced by `autoconf`/`autoheader` for an `x86_64-pc-linux-gnu` Linux target. It exposes platform capability flags, type sizes, and feature toggles consumed across the library's source files. + +## Key Components + +**Build Identity** +- `BUILD_REVISION` — Git commit hash (`c1668f6`) +- `PACKAGE_STRING` / `VERSION` — Package name and version (`libgpg-error 1.27`) +- `HOST_TRIPLET_STRING` — Target platform (`x86_64-pc-linux-gnu`) + +**Feature Availability Flags** (`HAVE_*`) +- Standard headers: `HAVE_STDINT_H`, `HAVE_INTTYPES_H`, `HAVE_UNISTD_H`, etc. +- Functions: `HAVE_VASPRINTF`, `HAVE_MEMRCHR`, `HAVE_STRERROR_R`, `HAVE_FLOCKFILE` +- POSIX threading: `HAVE_PTHREAD_MUTEX_RECURSIVE`, `HAVE_PTHREAD_RWLOCK` +- NLS/i18n: `HAVE_GETTEXT`, `HAVE_DCGETTEXT`, `ENABLE_NLS` + +**Type Sizes** (`SIZEOF_*`) +- `SIZEOF_VOID_P 8` — 64-bit pointer +- `SIZEOF_LONG 8`, `SIZEOF_LONG_LONG 8`, `SIZEOF_TIME_T 8` +- `SIZEOF_PTHREAD_MUTEX_T 40` + +**Threading Model** +- `USE_POSIX_THREADS 1` / `USE_POSIX_THREADS_WEAK 1` — POSIX threads with weak references +- Windows (`USE_WINDOWS_THREADS`) and Solaris (`USE_SOLARIS_THREADS`) threading disabled + +**Library-Specific Macros** +- `GPG_ERR_ENABLE_GETTEXT_MACROS`, `GPG_ERR_ENABLE_ERRNO_MACROS`, `GPGRT_ENABLE_ES_MACROS` — activate macro sets within `libgpg-error` +- `_ESTREAM_PRINTF_REALLOC` / `_ESTREAM_PRINTF_EXTRA_INCLUDE` — connect `estream-printf` to the internal allocator + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile POSIX thread support */ +#if USE_POSIX_THREADS + #include + /* pthread_mutex_t occupies SIZEOF_PTHREAD_MUTEX_T (40) bytes */ +#endif + +/* Guard NLS initialization */ +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); +#endif + +/* Use GCC visibility attribute if supported */ +#if defined(GPGRT_USE_VISIBILITY) && defined(__GNUC__) + #define GPGRT_API __attribute__((visibility("default"))) +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Regenerate by running `./configure` in the source tree. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.code-from-errno.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.code-from-errno.md new file mode 100644 index 00000000000..2345b8a48c8 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.code-from-errno.md @@ -0,0 +1,37 @@ + +Auto-generated lookup table (via `mkerrcodes2.awk`) that maps system `errno` values to their corresponding `GPG_ERR_E*` error codes within the libgpg-error library. + +## Key Components + +### `err_code_from_index[]` + +A static integer array mapping sequential index positions to `GPG_ERR_E*` constants. Covers ~125 standard POSIX/Linux errno values including: + +- Basic I/O errors (`GPG_ERR_EIO`, `GPG_ERR_ENOENT`) +- Network errors (`GPG_ERR_ECONNREFUSED`, `GPG_ERR_ETIMEDOUT`) +- Filesystem errors (`GPG_ERR_ENOSPC`, `GPG_ERR_EROFS`) +- Socket errors (`GPG_ERR_ENOTSOCK`, `GPG_ERR_EADDRINUSE`) + +### `errno_to_idx(code)` Macro + +Converts a numeric `errno` value to the corresponding index in `err_code_from_index[]`. Uses range-based arithmetic offsets to handle non-contiguous errno number ranges efficiently. Returns `-1` for unmapped codes. + +## Usage Example + +```c +#include "code-from-errno.h" + +/* Convert a system errno to its GPG error code */ +gpg_err_code_t gpg_err_code_from_errno(int err) { + int idx = errno_to_idx(err); + if (idx < 0) { + return GPG_ERR_UNKNOWN_ERRNO; + } + return err_code_from_index[idx]; +} + +/* Example: map ENOENT (errno=2) to GPG_ERR_ENOENT */ +gpg_err_code_t code = gpg_err_code_from_errno(ENOENT); +``` + +> **Note:** This file is auto-generated by `mkerrcodes2.awk` and should not be edited manually. Regenerate it by running the AWK script against the target platform's errno definitions. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.code-to-errno.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.code-to-errno.md new file mode 100644 index 00000000000..e93b390c276 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.code-to-errno.md @@ -0,0 +1,30 @@ + +Auto-generated lookup table (via `mkerrnos.awk`) that maps libgpg-error internal error codes to their corresponding platform-native `errno` values, with cross-platform support for both POSIX and Windows (Winsock2) error constants. + +## Key Components + +- **`err_code_to_errno[]`** — A static `const int` array where each index corresponds to a libgpg-error error code, and each value is the matching system `errno` constant. Falls back to `0` if neither the POSIX nor Windows (`WSAE*`) variant is defined. + +- **Cross-platform resolution** — For each error code, a three-tier preprocessor chain is used: + 1. Native POSIX constant (e.g., `ECONNREFUSED`) + 2. Winsock2 equivalent (e.g., `WSAECONNREFUSED`) on `_WIN32` + 3. `0` if neither is available + +- **Covered error categories** include: file I/O (`ENOENT`, `EACCES`), networking (`ECONNREFUSED`, `ENETDOWN`, `EHOSTUNREACH`), sockets (`ENOTSOCK`, `EISCONN`), memory (`ENOMEM`), process control (`ECHILD`, `EPERM`), and many platform-specific codes. + +## Usage Example + +```c +#include "code-to-errno.h" + +/* Convert a libgpg-error code index to a system errno value */ +int get_system_errno(int gpg_err_code) { + size_t table_size = sizeof(err_code_to_errno) / sizeof(err_code_to_errno[0]); + if (gpg_err_code >= 0 && (size_t)gpg_err_code < table_size) { + return err_code_to_errno[gpg_err_code]; + } + return 0; /* Unknown or unmapped error */ +} +``` + +> **Note:** This file is auto-generated by `mkerrnos.awk` from `errnos.in`. Do not edit manually — regenerate via the build system instead. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes-sym.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes-sym.md new file mode 100644 index 00000000000..e329a97a134 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes-sym.md @@ -0,0 +1,37 @@ + +Auto-generated symbol name lookup table for `libgpg-error`, mapping numeric error codes to their corresponding `GPG_ERR_*` symbolic constant name strings. This file is machine-generated by `mkstrtable.awk` and should not be edited manually. + +## Key Components + +- **`msgstr[]`** — A static packed string table containing null-terminated `GPG_ERR_*` symbol name strings concatenated sequentially. This layout minimizes binary relocations by storing all strings in a single `const char[]` rather than an array of pointers. + +The symbol names cover all major error categories in `libgpg-error`, including: + +| Category | Example Symbols | +|---|---| +| General/core | `GPG_ERR_NO_ERROR`, `GPG_ERR_GENERAL`, `GPG_ERR_BUG` | +| Cryptography | `GPG_ERR_CIPHER_ALGO`, `GPG_ERR_DIGEST_ALGO`, `GPG_ERR_BAD_SIGNATURE` | +| Certificates & PKI | `GPG_ERR_CERT_REVOKED`, `GPG_ERR_CERT_EXPIRED`, `GPG_ERR_NO_CRL_KNOWN` | +| S-expressions | `GPG_ERR_SEXP_INV_LEN_SPEC`, `GPG_ERR_SEXP_UNMATCHED_PAREN` | +| Assuan IPC | `GPG_ERR_ASS_CONNECT_FAILED`, `GPG_ERR_ASS_SYNTAX` | +| LDAP | `GPG_ERR_LDAP_CONNECT`, `GPG_ERR_LDAP_TIMEOUT`, `GPG_ERR_LDAP_NO_MEMORY` | +| TLS/Handshake | `GPG_ERR_BAD_HS_CERT`, `GPG_ERR_BAD_HS_FINISHED` | +| DNS | `GPG_ERR_DNS_TIMEOUT`, `GPG_ERR_DNS_NO_ANSWER` | +| Smart card | `GPG_ERR_CARD_RESET`, `GPG_ERR_PIN_BLOCKED` | + +## Usage Example + +This table is used alongside a companion offset array (typically `msgidx[]`) to resolve a numeric error code to its symbol name string: + +```c +/* Typical lookup pattern using this file's msgstr + an offset table */ +const char *gpg_strsym(gpg_error_t err) { + unsigned int code = gpg_err_code(err); + if (code >= GPG_ERR_CODE_DIM) + return NULL; + /* msgidx[code] holds the byte offset into msgstr[] */ + return msgstr + msgidx[code]; +} +``` + +> **Note:** This file is auto-generated by `mkstrtable.awk` from `err-codes.h`. Regenerate it by running the build system rather than editing directly. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes.md new file mode 100644 index 00000000000..b6d8f3d50fb --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-codes.md @@ -0,0 +1,34 @@ + +Auto-generated header file (via `mkstrtable.awk`) that defines a packed string table of human-readable error code descriptions for the `libgpg-error` library, enabling i18n-ready error message lookup with minimal memory relocations. + +## Key Components + +- **`msgstr[]`** — A single `static const char` array containing all error message strings concatenated with null terminators (`\0`). This "packed string table" pattern avoids per-string pointer relocations at load time. +- **`gettext_noop()`** — Macro wrapper that marks strings for extraction by `xgettext` without performing runtime translation, enabling deferred i18n support. + +## Usage Example + +```c +/* Typical usage: index into msgstr[] using a pre-computed offset table + (generated alongside this file) to retrieve an error description. */ + +#include "err-codes.h" + +/* msgidx[] maps error code -> byte offset into msgstr[] */ +const char *get_error_string(int err_code) { + return msgstr + msgidx[err_code]; +} + +/* Example output for common codes: + 0 -> "Success" + 1 -> "General error" + 7 -> "Bad secret key" + 46 -> "Invalid argument" +*/ +``` + +## Notes + +- **Do not edit manually** — this file is generated by `mkstrtable.awk`; modify the source AWK script or input data instead. +- Covers ~200+ error codes spanning cryptographic operations (GPG/GnuPG), S-expressions, ASN.1/BER/DER parsing, TLS handshake failures, smart card errors, and Assuan IPC faults. +- Designed for use with a companion offset/index table (typically `err-codes.h` paired with a `msgidx[]` array) to achieve O(1) message lookup without any heap allocation. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources-sym.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources-sym.md new file mode 100644 index 00000000000..fb72982daaf --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources-sym.md @@ -0,0 +1,34 @@ + +Auto-generated lookup table mapping `gpg_err_source_t` integer codes to their symbolic name strings (e.g. `GPG_ERR_SOURCE_GPG`) for use in libgpg-error diagnostics and debugging. + +## Key Components + +- **`msgstr`** — A single concatenated, null-delimited string containing all 23 error source symbol names, optimized to minimize relocations at link time. +- **`msgidx[]`** — Parallel integer array of byte offsets into `msgstr`, allowing O(1) lookup of any symbol name by index. +- **`msgidxof(int code)`** — Inline function that maps a raw `gpg_err_source_t` code to its corresponding `msgidx` slot, handling sparse/non-contiguous code ranges and returning a sentinel index (`36 - 14 = 22`) for unknown codes. + +## Usage Example + +```c +#include "err-sources-sym.h" + +/* Retrieve the symbolic name for a given error source code */ +const char *get_source_symbol(int source_code) { + int idx = msgidxof(source_code); + return msgstr + msgidx[idx]; +} + +int main(void) { + /* GPG_ERR_SOURCE_GPG has code 2 */ + printf("%s\n", get_source_symbol(2)); + /* Output: GPG_ERR_SOURCE_GPG */ + + /* Unknown source code falls back to GPG_ERR_SOURCE_DIM */ + printf("%s\n", get_source_symbol(99)); + /* Output: GPG_ERR_SOURCE_DIM */ + + return 0; +} +``` + +> **Note:** This file is auto-generated by `mkstrtable.awk` and should **not be edited manually**. It is part of [libgpg-error](https://gnupg.org/software/libgpg-error/) and covers sources including `GCRYPT`, `GPG`, `GPGSM`, `GPGAGENT`, `PINENTRY`, `DIRMNGR`, `ASSUAN`, and four user-defined slots (`USER_1`–`USER_4`). \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources.md new file mode 100644 index 00000000000..a237c8b25dc --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.err-sources.md @@ -0,0 +1,39 @@ + +Auto-generated header (via `mkstrtable.awk`) providing a compact string table mapping libgpg-error source component codes to human-readable descriptions, with gettext internationalization support. + +## Key Components + +| Component | Type | Description | +|---|---|---| +| `msgstr[]` | `static const char[]` | Concatenated null-terminated string table of all error source names | +| `msgidx[]` | `static const int[]` | Byte offset index into `msgstr` for each source entry | +| `msgidxof()` | `static inline int` | Maps a numeric source code to its corresponding `msgidx` slot | + +## Recognized Sources + +The table covers 23 named sources including: `gcrypt`, `GnuPG`, `GpgSM`, `GPG Agent`, `Pinentry`, `SCD`, `GPGME`, `KSBA`, `Dirmngr`, `Assuan`, `TLS`, user-defined sources 1–4, and a fallback `Unknown source`. + +## Usage Example + +```c +/* Retrieve the human-readable name for a given error source code */ +#include "err-sources.h" + +const char *get_source_name(int source_code) { + /* msgidxof() converts the source code to a table index, + accounting for sparse/non-contiguous code ranges */ + int idx = msgidxof(source_code); + + /* Use the index to look up the offset in msgstr */ + return msgstr + msgidx[idx]; +} + +/* Example: source code 4 → "GPG Agent" */ +printf("%s\n", get_source_name(4)); +``` + +## Notes + +- **Do not edit directly** — regenerate via `mkstrtable.awk` +- The sparse `msgidxof()` range checks (`0–15`, `17`, `31–35`) handle non-contiguous source code assignments; out-of-range codes return the `Unknown source` fallback +- The concatenated string design minimizes relocations compared to a pointer array approach \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.errnos-sym.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.errnos-sym.md new file mode 100644 index 00000000000..e1e66f2b0b5 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.errnos-sym.md @@ -0,0 +1,34 @@ + +Auto-generated lookup table (via `mkstrtable.awk`) that maps `GPG_ERR_E*` errno symbol names to their string representations within the `libgpg-error` library. + +## Key Components + +| Component | Type | Description | +|---|---|---| +| `errnos_msgstr` | `static const char[]` | Packed, null-delimited string table containing all `GPG_ERR_E*` symbol name strings | +| `errnos_msgidx` | `static const int[]` | Byte-offset index array mapping each error code position to its string start in `errnos_msgstr` | +| `errnos_msgidxof()` | `static inline int` | Bounds-checked function translating a numeric error code (0–140) to its index into `errnos_msgidx` | + +## Usage Example + +```c +/* Look up the symbolic name string for a given errno code */ +int code = 5; /* e.g. GPG_ERR_EAFNOSUPPORT */ + +int idx = errnos_msgidxof(code); +if (idx >= 0) { + /* Retrieve the symbol name string from the packed table */ + const char *sym_name = &errnos_msgstr[errnos_msgidx[idx]]; + printf("Symbol: %s\n", sym_name); + /* Output: Symbol: GPG_ERR_EAFNOSUPPORT */ +} else { + printf("Unknown error code: %d\n", code); +} +``` + +## Notes + +- **Do not edit manually** — this file is auto-generated by `mkstrtable.awk` from `errnos.in` +- The packed string table design minimizes relocations at link time compared to a pointer array approach +- Valid code range is `0` to `140` inclusive; `errnos_msgidxof()` returns `-1` for out-of-range codes +- Part of [libgpg-error](https://www.gnupg.org/software/libgpg-error/) (GNU LGPL 2.1+) \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.gpg-error.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.gpg-error.md new file mode 100644 index 00000000000..294cc20353e --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.gpg-error.md @@ -0,0 +1,48 @@ + +Public C header defining the unified error handling interface for the GnuPG project (`libgpg-error` / `libgpgrt`), providing error codes, error sources, and utility types shared across all GnuPG components (GPG, GPGSM, GPGME, Gcrypt, etc.). + +## Key Components + +### Version Constants +- `GPG_ERROR_VERSION` / `GPGRT_VERSION` — Library version string (`"1.27"`) +- `GPG_ERROR_VERSION_NUMBER` / `GPGRT_VERSION_NUMBER` — Numeric version (`0x011b00`) + +### Types & Enumerations + +| Type | Description | +|------|-------------| +| `gpg_err_source_t` | Identifies which GnuPG component originated the error (0–127) | +| `gpg_err_code_t` | Numeric error code (200+ defined codes, 0 = no error) | + +### Error Sources (`gpg_err_source_t`) +Notable sources include `GPG_ERR_SOURCE_GCRYPT`, `GPG_ERR_SOURCE_GPG`, `GPG_ERR_SOURCE_GPGME`, `GPG_ERR_SOURCE_ASSUAN`, plus four user-definable slots (`USER_1`–`USER_4`). + +### Error Codes (`gpg_err_code_t`) +Covers cryptographic errors (`GPG_ERR_BAD_SIGNATURE`, `GPG_ERR_WEAK_KEY`), certificate errors (`GPG_ERR_CERT_EXPIRED`, `GPG_ERR_CERT_REVOKED`), S-expression errors (`GPG_ERR_SEXP_*`), TLS handshake errors (`GPG_ERR_BAD_HS_*`), and general system errors. + +### Compile-time Tuning Macros +- `GPG_ERR_SOURCE_DEFAULT` — Override default error source +- `GPG_ERR_ENABLE_GETTEXT_MACROS` — Map internal gettext to standard names (Windows) +- `GPGRT_ENABLE_ES_MACROS` — Enable `es_` prefixed estream function aliases + +## Usage Example + +```c +#include + +gpg_error_t perform_operation(void) { + /* Combine an error source and code into a single error value */ + gpg_error_t err = gpg_err_make(GPG_ERR_SOURCE_GPGME, + GPG_ERR_BAD_SIGNATURE); + + /* Extract components */ + gpg_err_source_t src = gpg_err_source(err); + gpg_err_code_t code = gpg_err_code(err); + + if (code != GPG_ERR_NO_ERROR) { + fprintf(stderr, "Error from source %d: %d\n", src, code); + return err; + } + return 0; +} +``` \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.gpgrt.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.gpgrt.md new file mode 100644 index 00000000000..85ac348cd2a --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.gpgrt.md @@ -0,0 +1,57 @@ + +The public C header interface for `libgpg-error` (version 1.27), defining error codes, error sources, and utility types used across all GnuPG components to communicate structured errors between processes and libraries. + +## Key Components + +### Version Macros +- `GPG_ERROR_VERSION` / `GPGRT_VERSION` — String version (`"1.27"`) +- `GPG_ERROR_VERSION_NUMBER` / `GPGRT_VERSION_NUMBER` — Numeric version (`0x011b00`) + +### Types + +| Type | Description | +|------|-------------| +| `gpg_err_source_t` | Enum identifying the originating GnuPG component (e.g., `GPG_ERR_SOURCE_GCRYPT`, `GPG_ERR_SOURCE_GPG`) | +| `gpg_err_code_t` | Enum of ~200+ error codes covering crypto, I/O, certificates, S-expressions, TLS handshake failures, and more | + +### Error Sources (`gpg_err_source_t`) +Identifies which subsystem generated the error — values `0–35` are reserved for known components (`GCRYPT`, `GPG`, `GPGSM`, `GPGAGENT`, `PINENTRY`, `GPGME`, etc.), `32–35` are user-defined slots. + +### Error Codes (`gpg_err_code_t`) +Covers categories including: +- **Crypto**: `GPG_ERR_BAD_SIGNATURE`, `GPG_ERR_WEAK_KEY`, `GPG_ERR_DECRYPT_FAILED` +- **Certificates**: `GPG_ERR_CERT_EXPIRED`, `GPG_ERR_CERT_REVOKED`, `GPG_ERR_BAD_CA_CERT` +- **S-expressions**: `GPG_ERR_SEXP_UNMATCHED_PAREN`, `GPG_ERR_SEXP_BAD_HEX_CHAR` +- **TLS/Handshake**: `GPG_ERR_BAD_HS_CLIENT_HELLO`, `GPG_ERR_FATAL_ALERT` +- **General**: `GPG_ERR_TIMEOUT`, `GPG_ERR_NOT_IMPLEMENTED`, `GPG_ERR_CANCELED` + +### Compiler Portability +- `GPG_ERR_INLINE` — Cross-compiler inline macro (GCC, MSVC, C99) + +## Usage Example + +```c +#include + +gpg_error_t err; +gpg_err_code_t code; +gpg_err_source_t source; + +/* Construct an error value from source + code */ +err = gpg_err_make(GPG_ERR_SOURCE_GPGME, GPG_ERR_BAD_SIGNATURE); + +/* Decompose an error value */ +code = gpg_err_code(err); +source = gpg_err_source(err); + +if (code == GPG_ERR_BAD_SIGNATURE) { + fprintf(stderr, "Signature verification failed (source: %d)\n", source); +} + +/* Check for no error */ +if (gpg_err_code(err) == GPG_ERR_NO_ERROR) { + /* success */ +} +``` + +> **Note:** Error values encode both source and code in a single `gpg_error_t` integer, preserving cross-component traceability as errors propagate through the GnuPG stack. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/aarch64/.mkerrcodes.md b/libraries/cmake/source/libgpg-error/generated/aarch64/.mkerrcodes.md new file mode 100644 index 00000000000..d0405454070 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/aarch64/.mkerrcodes.md @@ -0,0 +1,41 @@ + +Auto-generated lookup table mapping Linux/POSIX `errno` numeric codes to their corresponding `GPG_ERR_E*` symbol strings, used by the `libgpg-error` library for cross-platform error code translation. + +## Key Components + +### `err_table[]` + +A static array of anonymous structs, each containing: + +| Field | Type | Description | +|---|---|---| +| `err` | `int` | Numeric POSIX/Linux `errno` value | +| `err_sym` | `const char *` | Corresponding GPG error symbol string | + +The table covers **~130 error codes**, including: + +- Standard POSIX errors (`GPG_ERR_ENOENT`, `GPG_ERR_EINVAL`, `GPG_ERR_ENOMEM`) +- Network/socket errors (`GPG_ERR_ECONNREFUSED`, `GPG_ERR_ETIMEDOUT`) +- Stream/STREAMS errors (`GPG_ERR_ENOSTR`, `GPG_ERR_ENODATA`) +- Linux-specific errors (`GPG_ERR_EREMOTEIO`, `GPG_ERR_EMEDIUMTYPE`) + +> **Note:** Some numeric codes intentionally appear twice — for example, `35` maps to both `GPG_ERR_EDEADLK` and `GPG_ERR_EDEADLOCK`, and `11` maps to both `GPG_ERR_EAGAIN` and `GPG_ERR_EWOULDBLOCK`, reflecting platform aliases. + +## Usage Example + +```c +/* Typical reverse-lookup pattern consumers of this table would use */ +const char *find_gpg_err_sym(int errno_val) { + size_t n = sizeof(err_table) / sizeof(err_table[0]); + for (size_t i = 0; i < n; i++) { + if (err_table[i].err == errno_val) + return err_table[i].err_sym; + } + return NULL; +} + +/* Example */ +const char *sym = find_gpg_err_sym(ENOENT); /* Returns "GPG_ERR_ENOENT" */ +``` + +> ⚠️ **Do not edit this file manually.** It is auto-generated by `mkerrcodes.awk`. Modify the AWK script and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.code-from-errno.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.code-from-errno.md new file mode 100644 index 00000000000..0506d721960 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.code-from-errno.md @@ -0,0 +1,41 @@ + +Auto-generated lookup table (via `mkerrcodes2.awk`) that maps system `errno` values to their corresponding `GPG_ERR_E*` error codes used by libgpg-error. + +## Key Components + +### `err_code_from_index[]` +A static integer array of `GPG_ERR_E*` constants ordered by their sequential index position. Used as the target lookup table when converting a raw `errno` integer to its libgpg-error equivalent. + +### `errno_to_idx(code)` +A preprocessor macro that maps a raw `errno` integer to its position in `err_code_from_index[]`. Handles non-contiguous errno ranges by applying range-specific offsets across six defined bands: + +| Range | Offset | +|---|---| +| `1–11` | `-1` | +| `11–35` | `0` | +| `35–40` | `+1` | +| `42–57` | `0` | +| `59–95` | `-1` | +| `95–125` | `0` | + +Returns `-1` for any `errno` value outside the supported ranges. + +## Usage Example + +```c +#include "code-from-errno.h" + +/* Convert a POSIX errno to a GPG error code */ +gpg_err_code_t gpg_err_code_from_errno(int err) { + int idx = errno_to_idx(err); + if (idx < 0) { + return GPG_ERR_UNKNOWN_ERRNO; + } + return err_code_from_index[idx]; +} + +/* Example: map EACCES (errno=13) to GPG_ERR_EACCES */ +gpg_err_code_t code = gpg_err_code_from_errno(EACCES); +``` + +> **Note:** This file is auto-generated by `mkerrcodes2.awk` and should not be edited manually. Modifications to errno mappings must be made in the generating script. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.code-to-errno.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.code-to-errno.md new file mode 100644 index 00000000000..d9f5f4177ce --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.code-to-errno.md @@ -0,0 +1,30 @@ + +Auto-generated lookup table (via `mkerrnos.awk`) that maps libgpg-error internal error codes to their corresponding platform-native `errno` values, with cross-platform support for both POSIX and Windows (Winsock2) error constants. + +## Key Components + +- **`err_code_to_errno[]`** — A static `const int` array where each index corresponds to a libgpg-error error code, and the value is the matching system `errno` constant. Entries fall back to `0` if neither the POSIX nor Windows variant is defined. + +- **Platform detection** — Each entry uses a three-tier `#ifdef` chain: + 1. Standard POSIX constant (e.g., `EACCES`) + 2. Winsock2 prefixed fallback (e.g., `WSAEACCES`) on `_WIN32` + 3. `0` if neither is available + +- **Covered error categories** — File I/O, networking (`ECONNREFUSED`, `ENETDOWN`, `EHOSTUNREACH`), process management, IPC, socket operations, and POSIX-extended errors. + +## Usage Example + +```c +#include "code-to-errno.h" + +/* Convert a libgpg-error code index to the system errno value */ +int get_system_errno(int gpg_err_code) { + size_t table_size = sizeof(err_code_to_errno) / sizeof(err_code_to_errno[0]); + if (gpg_err_code >= 0 && (size_t)gpg_err_code < table_size) { + return err_code_to_errno[gpg_err_code]; + } + return 0; /* unknown / unmapped */ +} +``` + +> **Do not edit this file manually.** It is auto-generated by `mkerrnos.awk` from `errnos.in`. Modify the source template and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes-sym.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes-sym.md new file mode 100644 index 00000000000..db90177499c --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes-sym.md @@ -0,0 +1,34 @@ + +Auto-generated symbol name lookup table for `libgpg-error`, mapping numeric error codes to their corresponding `GPG_ERR_*` string identifiers. This file is machine-generated by `mkstrtable.awk` and should not be edited manually. + +## Key Components + +- **`msgstr[]`** — A static concatenated string table containing null-terminated `GPG_ERR_*` symbol names (e.g., `"GPG_ERR_NO_ERROR"`, `"GPG_ERR_GENERAL"`, etc.), optimized for minimal memory relocations at runtime. +- **`msgidx[]`** (implied companion array) — Typically paired with an index array mapping error code integers to byte offsets within `msgstr[]`, enabling O(1) symbol name lookup. + +The table covers ~300+ error categories across cryptographic operations, certificate handling, card/smartcard errors, LDAP errors, S-expression parsing, TLS handshake failures, Assuan IPC protocol errors, and DNS errors. + +## Usage Example + +```c +/* Typical usage pattern in libgpg-error internals */ +#include "err-codes-sym.h" + +const char *gpg_strsym(gpg_err_code_t code) { + /* msgidx[] maps code -> byte offset into msgstr[] */ + if (code >= GPG_ERR_CODE_DIM) + return NULL; + return msgstr + msgidx[code]; +} + +/* Caller example */ +gpg_err_code_t code = GPG_ERR_BAD_PASSPHRASE; +printf("Error symbol: %s\n", gpg_strsym(code)); +/* Output: Error symbol: GPG_ERR_BAD_PASSPHRASE */ +``` + +## Notes + +- **Do not edit directly** — regenerate via `mkstrtable.awk` from `err-codes.h` +- The concatenated-string + offset-index pattern avoids a pointer-per-entry array, reducing the relocation table size in shared libraries +- Licensed under **GNU LGPL v2.1+** as part of `libgpg-error` (© 2003–2004 g10 Code GmbH) \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes.md new file mode 100644 index 00000000000..b7c942da77a --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-codes.md @@ -0,0 +1,41 @@ + +Auto-generated header from `mkstrtable.awk` that defines the complete string table of human-readable error messages for the `libgpg-error` library. + +## Key Components + +### `msgstr[]` +A static concatenated string table containing all error message strings for `libgpg-error`. Each entry is wrapped in `gettext_noop()` to mark strings for translation without immediately invoking gettext, enabling i18n support while minimizing runtime relocations. + +**Coverage includes errors for:** +- Cryptographic operations (keys, signatures, ciphers, digests) +- Certificate and CRL handling +- S-expression parsing +- Assuan/IPC protocol +- SmartCard/PKCS15 operations +- TLS handshake messages +- ASN.1/BER/DER encoding +- Network and keyserver operations +- General system errors (timeout, EOF, resource exhaustion) + +## Usage Example + +```c +/* Typical usage via libgpg-error's public API — do not use msgstr[] directly */ +#include + +gpg_error_t err = gpg_error(GPG_ERR_BAD_PASSPHRASE); + +/* Retrieve the human-readable string backed by msgstr[] */ +const char *msg = gpg_strerror(err); +fprintf(stderr, "Error: %s\n", msg); +/* Output: Error: Bad passphrase */ + +/* With gettext enabled, strings in msgstr[] are translatable */ +const char *translated = gettext(gpg_strerror(err)); +``` + +## Notes + +- **Do not edit manually** — this file is auto-generated by `mkstrtable.awk` from a canonical error code source. Edits will be overwritten. +- The concatenated string table design avoids pointer arrays, reducing the number of relocations in the final binary. +- `gettext_noop()` is a no-op macro at compile time; actual translation happens at runtime via `gettext()` when the application calls `gpg_strerror()`. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources-sym.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources-sym.md new file mode 100644 index 00000000000..ff3b2820042 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources-sym.md @@ -0,0 +1,36 @@ + +Auto-generated lookup table mapping `gpg_err_source_t` numeric codes to their symbolic name strings (e.g., `GPG_ERR_SOURCE_GCRYPT`) for use in error reporting within libgpg-error. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `msgstr` | `static const char[]` | Concatenated null-terminated string table of all error source symbol names | +| `msgidx` | `static const int[]` | Byte offsets into `msgstr` for each symbol, enabling O(1) string lookup by index | +| `msgidxof()` | `static inline int` | Maps raw `gpg_err_source_t` code to a contiguous `msgidx` index, handling sparse code ranges | + +## Error Sources Covered + +The table covers 23 entries spanning three non-contiguous code ranges: + +- **0–15**: `GPG_ERR_SOURCE_UNKNOWN` through `GPG_ERR_SOURCE_ASSUAN` +- **17**: `GPG_ERR_SOURCE_TLS` +- **31–35**: `GPG_ERR_SOURCE_ANY` through `GPG_ERR_SOURCE_USER_4` + +Out-of-range codes return index `22` (`GPG_ERR_SOURCE_DIM`), acting as a sentinel/fallback. + +## Usage Example + +```c +/* Retrieve the symbolic name for an error source code */ +const char *source_name = msgstr + msgidx[msgidxof(source_code)]; +printf("Error source: %s\n", source_name); + +/* Example: code 1 → "GPG_ERR_SOURCE_GCRYPT" */ +printf("%s\n", msgstr + msgidx[msgidxof(1)]); +``` + +## Notes + +- **Auto-generated** by `mkstrtable.awk` — do not edit manually; regenerate from the source error list instead. +- The concatenated string + offset index pattern avoids per-string pointer relocations, producing more efficient position-independent code compared to a plain `char *[]` array. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources.md new file mode 100644 index 00000000000..e9a8cca0a13 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.err-sources.md @@ -0,0 +1,37 @@ + +Auto-generated header file (via `mkstrtable.awk`) that maps libgpg-error source codes to human-readable description strings using an optimized string table with minimal relocations. + +## Key Components + +- **`msgstr[]`** — Concatenated, null-delimited string table of all error source names (e.g., `"gcrypt"`, `"GnuPG"`, `"GPG Agent"`), marked for i18n translation via `gettext_noop`. +- **`msgidx[]`** — Parallel integer array of byte offsets into `msgstr[]`, mapping each source index to its string's starting position. +- **`msgidxof(int code)`** — Inline function that translates a raw error source code (with possible gaps in the numeric range) into a contiguous `msgidx[]` array index. Returns a fallback index for unknown codes. + +## Usage Example + +```c +/* Retrieve the description string for a given error source code */ +#include "err-sources.h" + +const char *get_source_name(int source_code) { + /* Map sparse source code to dense index */ + int idx = msgidxof(source_code); + + /* Use offset to index into the flat string table */ + return gettext(msgstr + msgidx[idx]); +} + +/* Example: source code 4 → "GPG Agent" */ +printf("%s\n", get_source_name(4)); +``` + +## Source Codes Covered + +| Range | Sources | +|---|---| +| `0–15` | Unspecified, gcrypt, GnuPG, GpgSM, GPG Agent, Pinentry, SCD, GPGME, Keybox, KSBA, Dirmngr, GSTI, GPA, Kleopatra, G13, Assuan | +| `17` | TLS | +| `31–35` | Any source, User defined sources 1–4 | +| fallback | Unknown source | + +> **Note:** This file is auto-generated — do not edit manually. Modify the source `.awk` template and regenerate instead. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.errnos-sym.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.errnos-sym.md new file mode 100644 index 00000000000..da21a577758 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.errnos-sym.md @@ -0,0 +1,29 @@ + +Auto-generated lookup table (via `mkstrtable.awk`) providing symbol name strings for `libgpg-error` errno-mapped error codes, used to resolve `GPG_ERR_E*` codes to their string representations at runtime. + +## Key Components + +- **`errnos_msgstr`** — A single concatenated, null-delimited static string containing all 140+ `GPG_ERR_E*` symbol names (e.g., `GPG_ERR_EACCES`, `GPG_ERR_EINVAL`). +- **`errnos_msgidx[]`** — Parallel integer array of byte offsets into `errnos_msgstr`, enabling O(1) lookup of any symbol name by index without pointer relocations. +- **`errnos_msgidxof(int code)`** — Inline range-check function mapping a raw error code (0–140) to its index in `errnos_msgidx[]`, returning `-1` for out-of-range codes. + +## Usage Example + +```c +/* Resolve a GPG errno code to its symbol name string */ +const char *get_errno_symbol(int code) { + int idx = errnos_msgidxof(code); + if (idx < 0) + return NULL; + return &errnos_msgstr[errnos_msgidx[idx]]; +} + +/* Example: prints "GPG_ERR_EINVAL" */ +printf("%s\n", get_errno_symbol(48)); +``` + +## Notes + +- **Do not edit manually** — this file is auto-generated from `errnos.in` using `mkstrtable.awk`. Modify the source `.in` file and regenerate. +- The single-string + offset-array design avoids per-string pointer relocations in shared libraries, producing smaller and more efficient position-independent code. +- Valid code range: **0–140** (mapped directly as `code - 0`). \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.gpg-error.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.gpg-error.md new file mode 100644 index 00000000000..7e5b256d020 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.gpg-error.md @@ -0,0 +1,43 @@ + +Public header for `libgpg-error` (also aliased as `libgpgrt`) — defines the unified error value system used across all GnuPG components, encoding both an **error source** and an **error code** into a single portable integer type. + +## Key Components + +### Types +- **`gpg_err_source_t`** — Enum identifying which GnuPG component produced the error (e.g., `GPG_ERR_SOURCE_GCRYPT`, `GPG_ERR_SOURCE_GPGME`, `GPG_ERR_SOURCE_GPGAGENT`). Supports up to 128 sources including 4 user-defined slots. +- **`gpg_err_code_t`** — Enum of ~200+ error codes covering cryptographic failures, certificate errors, S-expression parsing, TLS handshake faults, card/PIN errors, and POSIX-mapped system errors. +- **`gpg_error_t`** — Combined error value (`source << 24 | code`) passed between components. + +### Constants +- `GPG_ERROR_VERSION` / `GPGRT_VERSION` — `"1.27"` (header version string) +- `GPG_ERROR_VERSION_NUMBER` / `GPGRT_VERSION_NUMBER` — `0x011b00` + +### Macros +- `GPG_ERR_INLINE` — Cross-compiler inline hint (`__inline__`, `__inline`, `inline`) +- `GPG_ERR_SOURCE_DEFAULT` — Override default error source (defaults to `GPG_ERR_SOURCE_UNKNOWN`) + +## Usage Example + +```c +#include + +gpg_error_t check_key(void) { + /* Combine source + code into one error value */ + gpg_err_source_t src = GPG_ERR_SOURCE_GPGME; + gpg_err_code_t code = GPG_ERR_NO_PUBKEY; + gpg_error_t err = gpg_err_make(src, code); + + /* Inspect components */ + if (gpg_err_code(err) == GPG_ERR_NO_PUBKEY) { + fprintf(stderr, "Source: %d, Code: %d\n", + gpg_err_source(err), /* → 7 (GPGME) */ + gpg_err_code(err)); /* → 9 (NO_PUBKEY) */ + } + return err; +} + +/* Quick error from default source */ +gpg_error_t simple_err = gpg_error(GPG_ERR_BAD_SIGNATURE); +``` + +> **Note:** Error codes are stable — values must **never be reordered or reused**. New codes are appended to preserve ABI compatibility across all GnuPG components. \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.gpgrt.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.gpgrt.md new file mode 100644 index 00000000000..8c1bd0c44c0 --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.gpgrt.md @@ -0,0 +1,51 @@ + +Public interface header for `libgpg-error` (also known as `libgpgrt`), defining error codes, error sources, and utility types used across all GnuPG components for interoperable error handling. + +## Key Components + +### Version Macros +- `GPG_ERROR_VERSION` / `GPGRT_VERSION` — Library version string (`"1.27"`) +- `GPG_ERROR_VERSION_NUMBER` / `GPGRT_VERSION_NUMBER` — Numeric version (`0x011b00`) + +### Types + +| Type | Description | +|------|-------------| +| `gpg_err_source_t` | Enum identifying the GnuPG component that originated an error (e.g., `GPG_ERR_SOURCE_GCRYPT`, `GPG_ERR_SOURCE_GPGME`) | +| `gpg_err_code_t` | Enum of 200+ standardized error codes shared across all GnuPG components | + +### Error Sources (`gpg_err_source_t`) +Identifies originating subsystem — values 0–35 are reserved for known components (`GCRYPT`, `GPG`, `GPGSM`, `GPGAGENT`, `ASSUAN`, etc.); values 32–35 are user-defined slots. + +### Error Codes (`gpg_err_code_t`) +200+ codes covering cryptographic errors, certificate/CRL handling, S-expression parsing, TLS handshake failures, card/smartcard operations, and general I/O conditions. + +### Compiler Compatibility +- `GPG_ERR_INLINE` — Portable inline keyword macro supporting GCC, MSVC, and C99 + +### Tuning Macros (define before inclusion) +- `GPG_ERR_SOURCE_DEFAULT` — Override default error source +- `GPG_ERR_ENABLE_GETTEXT_MACROS` — Map internal gettext API on Windows +- `GPGRT_ENABLE_ES_MACROS` — Enable `es_` prefixed estream function aliases + +## Usage Example + +```c +#include + +/* Combine error source and code into a single error value */ +gpg_error_t err = gpg_error(GPG_ERR_BAD_SIGNATURE); + +/* Extract source and code */ +gpg_err_source_t src = gpg_err_source(err); +gpg_err_code_t code = gpg_err_code(err); + +if (code == GPG_ERR_BAD_SIGNATURE) { + fprintf(stderr, "Source %d: bad signature detected\n", src); +} + +/* Check for success */ +if (gpg_err_code(err) == GPG_ERR_NO_ERROR) { + /* operation succeeded */ +} +``` \ No newline at end of file diff --git a/libraries/cmake/source/libgpg-error/generated/x86_64/.mkerrcodes.md b/libraries/cmake/source/libgpg-error/generated/x86_64/.mkerrcodes.md new file mode 100644 index 00000000000..86a706c27aa --- /dev/null +++ b/libraries/cmake/source/libgpg-error/generated/x86_64/.mkerrcodes.md @@ -0,0 +1,45 @@ + +Auto-generated lookup table mapping numeric Linux `errno` values to their corresponding `GPG_ERR_E*` symbol name strings, produced by the `mkerrcodes.awk` script and consumed by the libgpg-error library for error code translation. + +## Key Components + +### `err_table[]` +A static array of anonymous structs, each containing: + +| Field | Type | Description | +|-------|------|-------------| +| `err` | `int` | Numeric Linux errno value (e.g. `2` for `ENOENT`) | +| `err_sym` | `const char *` | Corresponding GPG error symbol string (e.g. `"GPG_ERR_ENOENT"`) | + +**Notable entries and aliased codes:** + +```c +{ 35, "GPG_ERR_EDEADLK" }, // Two symbols... +{ 35, "GPG_ERR_EDEADLOCK" }, // ...share the same code + +{ 11, "GPG_ERR_EAGAIN" }, +{ 11, "GPG_ERR_EWOULDBLOCK" }, // Also aliases errno 11 + +{ 95, "GPG_ERR_ENOTSUP" }, +{ 95, "GPG_ERR_EOPNOTSUPP" }, // Also aliases errno 95 +``` + +## Usage Example + +```c +/* Typical reverse-lookup usage pattern */ +const char *find_gpg_err_sym(int errno_val) { + size_t n = sizeof(err_table) / sizeof(err_table[0]); + for (size_t i = 0; i < n; i++) { + if (err_table[i].err == errno_val) + return err_table[i].err_sym; + } + return NULL; +} + +/* Example */ +const char *sym = find_gpg_err_sym(2); +/* sym == "GPG_ERR_ENOENT" */ +``` + +> ⚠️ **Do not edit this file manually.** It is auto-generated by `mkerrcodes.awk`. Multiple entries may share the same numeric code due to platform aliasing (e.g. `EAGAIN`/`EWOULDBLOCK`). \ No newline at end of file diff --git a/libraries/cmake/source/libiptables/config/.config.md b/libraries/cmake/source/libiptables/config/.config.md new file mode 100644 index 00000000000..a209a057375 --- /dev/null +++ b/libraries/cmake/source/libiptables/config/.config.md @@ -0,0 +1,53 @@ + +Auto-generated build configuration header for the **iptables 1.8.3** package, produced by `autoconf`/`autoheader` from `configure.ac`. It captures system capability detection results and package metadata for use during compilation. + +## Key Components + +### Package Metadata +| Macro | Value | +|---|---| +| `PACKAGE` / `PACKAGE_NAME` | `"iptables"` | +| `PACKAGE_VERSION` / `VERSION` | `"1.8.3"` | +| `PACKAGE_STRING` | `"iptables 1.8.3"` | +| `PACKAGE_TARNAME` | `"iptables"` | + +### System Header Availability Flags +Defines `HAVE_*` macros to `1` when the corresponding header was detected at configure time: + +- **Standard headers:** ``, ``, ``, ``, ``, ``, ``, `` +- **POSIX/system headers:** ``, `` +- **Linux kernel headers:** ``, ``, ``, `` +- **Not found / disabled:** `HAVE_LIBPCAP`, `HAVE_LINUX_PROC_FS_H`, `YYTEXT_POINTER` + +### Runtime Constants +- `XT_LOCK_NAME` — path to the xtables lock file (`/run/xtables.lock`) +- `SIZEOF_STRUCT_IP6_HDR` — precomputed size of `struct ip6_hdr` (`40` bytes) +- `LT_OBJDIR` — libtool uninstalled library directory (`.libs/`) +- `STDC_HEADERS` — confirms ANSI C standard headers are available + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile pcap support */ +#ifdef HAVE_LIBPCAP +# include +#endif + +/* Guard Linux-specific kernel headers */ +#ifdef HAVE_LINUX_BPF_H +# include +#endif + +/* Use package version in output */ +void print_version(void) { + printf("%s version %s\n", PACKAGE_NAME, VERSION); + /* Output: iptables version 1.8.3 */ +} + +/* Use precomputed struct size instead of sizeof() at runtime */ +char buf[SIZEOF_STRUCT_IP6_HDR]; +``` + +> **Note:** This file is auto-generated — do not edit manually. Re-run `./configure` to regenerate it based on the current build environment. \ No newline at end of file diff --git a/libraries/cmake/source/libmagic/config/linux/aarch64/.config.md b/libraries/cmake/source/libmagic/config/linux/aarch64/.config.md new file mode 100644 index 00000000000..48c7b733130 --- /dev/null +++ b/libraries/cmake/source/libmagic/config/linux/aarch64/.config.md @@ -0,0 +1,46 @@ + +Auto-generated build configuration header for the `file` utility (version 5.45), produced by `autoconf`/`configure` to capture platform-specific feature availability at compile time. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"file"` | +| `PACKAGE_VERSION` | `"5.45"` | +| `PACKAGE_BUGREPORT` | `"christos@astron.com"` | + +### Core Feature Flags +- **`BUILTIN_ELF`** — Built-in ELF binary format support enabled +- **`ELFCORE`** — ELF core file support enabled +- **`HAVE_LIBZ`** — zlib compression available; bzlib, lzma, zstd, lzlib are **disabled** +- **`MAJOR_IN_SYSMACROS`** — `major()`/`minor()`/`makedev()` from `` + +### POSIX / System Headers Detected +Confirms presence of standard headers including ``, ``, ``, ``, ``, ``, and ``. + +### Functions Detected +Key POSIX/GNU functions confirmed available: `mmap`, `fork`/`vfork`, `pread`, `pipe2`, `posix_spawnp`, `getline`, `memmem`, `newlocale`/`uselocale`, `asprintf`/`vasprintf`, `getopt_long`. + +### Platform Extension Macros +Conditionally defines portability guards for GNU, macOS, Solaris, NetBSD, OpenBSD, HP-UX, and IEC 60559 standard extensions. + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_MMAP + /* use mmap-based file reading */ + void *buf = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); +#else + /* fallback to read() */ +#endif + +#ifdef HAVE_LIBZ + /* zlib decompression available */ + gzFile gz = gzopen(path, "rb"); +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Re-run `./configure` to regenerate it for your target platform. \ No newline at end of file diff --git a/libraries/cmake/source/libmagic/config/linux/x86_64/.config.md b/libraries/cmake/source/libmagic/config/linux/x86_64/.config.md new file mode 100644 index 00000000000..be5919f21f4 --- /dev/null +++ b/libraries/cmake/source/libmagic/config/linux/x86_64/.config.md @@ -0,0 +1,55 @@ + +Auto-generated build configuration header for the `file` utility (version 5.45), produced by `autoconf`/`configure` to capture platform feature availability and build settings. + +## Key Components + +### Package Metadata +- `PACKAGE` / `PACKAGE_NAME` — `"file"` +- `PACKAGE_VERSION` / `VERSION` — `"5.45"` +- `PACKAGE_BUGREPORT` — maintainer contact address +- `PACKAGE_STRING` — full name + version string + +### ELF & Core Support +- `BUILTIN_ELF 1` — built-in ELF parser is active (no external libelf required) +- `ELFCORE 1` — ELF core file inspection enabled + +### Compression Support +| Macro | Status | +|---|---| +| `ZLIBSUPPORT` | ✅ Enabled | +| `XZLIBSUPPORT` | ✅ Enabled | +| `BZLIBSUPPORT` | ❌ Disabled | +| `LZLIBSUPPORT` | ❌ Disabled | +| `ZSTDLIBSUPPORT` | ❌ Disabled | + +### Available Libraries +- `HAVE_LIBZ 1` — zlib present +- `HAVE_LZMA_H 1` — LZMA headers available +- `HAVE_ZLIB_H 1` — zlib headers available + +### POSIX / System Feature Flags +Detected functions include: `fork`, `vfork`, `mmap`, `pread`, `pipe2`, `getline`, `memmem`, `posix_spawnp`, `newlocale`, `uselocale`, locale-safe time functions (`asctime_r`, `gmtime_r`, `localtime_r`), and wide-character support (`mbrtowc`, `wcwidth`). + +### Platform Extensions +Conditionally enables `_GNU_SOURCE`, `_ALL_SOURCE` (AIX), `_POSIX_PTHREAD_SEMANTICS` (Solaris), `_DARWIN_USE_64_BIT_INODE` (macOS), and `__EXTENSIONS__`. + +### Endianness +- `WORDS_BIGENDIAN` — defined only on big-endian targets; unset here (little-endian build) + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_MMAP + /* Use mmap-based I/O path */ +#endif + +#ifdef ZLIBSUPPORT + /* Decompress zlib streams */ +#endif + +#if defined(BUILTIN_ELF) && defined(ELFCORE) + /* Parse ELF core files with built-in parser */ +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/libmagic/config/macos/aarch64/.config.md b/libraries/cmake/source/libmagic/config/macos/aarch64/.config.md new file mode 100644 index 00000000000..f044963e8f7 --- /dev/null +++ b/libraries/cmake/source/libmagic/config/macos/aarch64/.config.md @@ -0,0 +1,49 @@ + +Auto-generated build configuration header for the `file` utility (version 5.45), produced by GNU Autoconf's `configure` script to record platform capability detection results. + +## Key Components + +### Package Metadata +- `PACKAGE_NAME` / `PACKAGE_VERSION` — identifies the `file` package at version `5.45` +- `PACKAGE_BUGREPORT` — maintainer contact (`christos@astron.com`) + +### ELF Support +- `BUILTIN_ELF` — enables built-in ELF binary parsing +- `ELFCORE` — enables ELF core file support + +### Compression Libraries +| Macro | Status | Library | +|---|---|---| +| `HAVE_LIBZ` / `HAVE_ZLIB_H` | ✅ Enabled | zlib | +| `HAVE_LIBLZMA` / `HAVE_LZMA_H` | ✅ Enabled | liblzma | +| `BZLIBSUPPORT` / `HAVE_LIBBZ2` | ❌ Disabled | bzip2 | +| `HAVE_LIBZSTD` | ❌ Disabled | zstd | +| `LZLIBSUPPORT` | ❌ Disabled | lzlib | + +### POSIX & System Function Availability +Feature-test macros for functions such as `mmap`, `fork`, `pread`, `getline`, `posix_spawnp`, locale functions (`newlocale`, `uselocale`, `freelocale`), and string utilities (`strlcpy`, `strlcat`, `memmem`). + +### Platform Extension Guards +Conditional defines enabling OS-specific extensions, including `_GNU_SOURCE`, `_DARWIN_C_SOURCE`, `_NETBSD_SOURCE`, `_ALL_SOURCE` (AIX), and several IEC 60559 floating-point extension macros. + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_MMAP + /* Use mmap-based file reading */ + #include +#endif + +#ifdef HAVE_LIBLZMA + /* Enable LZMA decompression path */ + #include +#endif + +#if defined(BUILTIN_ELF) && defined(ELFCORE) + /* Process ELF core dumps natively */ +#endif +``` + +> **Note:** This file is auto-generated by `configure` and should not be edited manually. Regenerate it by running `./configure` after modifying `configure.ac`. \ No newline at end of file diff --git a/libraries/cmake/source/libmagic/config/macos/x86_64/.config.md b/libraries/cmake/source/libmagic/config/macos/x86_64/.config.md new file mode 100644 index 00000000000..f1000134206 --- /dev/null +++ b/libraries/cmake/source/libmagic/config/macos/x86_64/.config.md @@ -0,0 +1,50 @@ + +Auto-generated build configuration header for the `file` utility (version 5.45), produced by autoconf's `configure` script. It defines feature-detection macros that control conditional compilation based on the target platform's available headers, functions, libraries, and system capabilities. + +## Key Components + +### Package Identity +- `PACKAGE_NAME` / `PACKAGE_VERSION` — identifies the package as `"file 5.45"` +- `PACKAGE_BUGREPORT` — maintainer contact: `christos@astron.com` + +### Core Feature Flags +| Macro | Purpose | +|---|---| +| `BUILTIN_ELF` | Use built-in ELF parser (not system libelf) | +| `ELFCORE` | Enable ELF core file support | +| `HAVE_LIBLZMA` / `HAVE_LIBZ` | Compression library availability | +| `HAVE_VISIBILITY` | Compiler supports symbol visibility declarations | +| `HAVE_MMAP` | Working `mmap` syscall available | +| `HAVE_POSIX_SPAWNP` | POSIX spawn support enabled | + +### Disabled Optional Features +- `BZLIBSUPPORT` — bzip2 compression (disabled) +- `LZLIBSUPPORT` — lzlib compression (disabled) +- `HAVE_LIBZSTD` — zstd compression (disabled) +- `HAVE_LIBSECCOMP` — seccomp sandboxing (disabled) + +### Platform Extension Macros +Conditionally enables OS-specific POSIX/GNU extensions: +- `_GNU_SOURCE`, `_DARWIN_C_SOURCE`, `_NETBSD_SOURCE`, `_ALL_SOURCE`, `__EXTENSIONS__` (Solaris) + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_MMAP + /* Use memory-mapped I/O for file reading */ + ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); +#else + /* Fall back to read() */ + ptr = malloc(size); + read(fd, ptr, size); +#endif + +#ifdef BUILTIN_ELF + /* Use internal ELF parser */ + parse_elf_internal(buf, len); +#endif +``` + +> This file is **auto-generated** — edit `config.h.in` or `configure.ac` instead of modifying it directly. Re-run `./configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/libmagic/include/.magic.md b/libraries/cmake/source/libmagic/include/.magic.md new file mode 100644 index 00000000000..6bef5873e48 --- /dev/null +++ b/libraries/cmake/source/libmagic/include/.magic.md @@ -0,0 +1,84 @@ + +Public C header for `libmagic` — the file-type identification library underlying the Unix `file(1)` command. Defines the opaque `magic_t` handle, all behavior flags, tunable parameters, and the full API surface for identifying file types by content, path, descriptor, or in-memory buffer. + +## Key Components + +### Type & Lifecycle +| Symbol | Description | +|---|---| +| `magic_t` | Opaque handle (`struct magic_set *`) for a magic database instance | +| `magic_open(int)` | Create a new handle with the given flags | +| `magic_close(magic_t)` | Release all resources associated with a handle | +| `magic_load(magic_t, const char *)` | Load a magic database file | +| `magic_load_buffers(...)` | Load magic databases from in-memory buffers | + +### Identification Functions +| Symbol | Description | +|---|---| +| `magic_file()` | Identify a file by path | +| `magic_descriptor()` | Identify a file by open file descriptor | +| `magic_buffer()` | Identify data in a memory buffer | +| `magic_getpath()` | Retrieve the default magic database path | + +### Configuration & Diagnostics +| Symbol | Description | +|---|---| +| `magic_setflags()` / `magic_getflags()` | Set/get behavior flags | +| `magic_setparam()` / `magic_getparam()` | Tune numeric limits (indirection depth, ELF sections, regex, etc.) | +| `magic_error()` / `magic_errno()` | Retrieve last error message or errno | +| `magic_version()` | Returns current library version (currently `545`) | +| `magic_compile()` / `magic_check()` / `magic_list()` | Compile, validate, and list magic database entries | + +### Key Flags (combinable bitmask) +```c +MAGIC_NONE // Default behavior +MAGIC_MIME_TYPE // Return MIME type string (e.g. "text/plain") +MAGIC_MIME_ENCODING // Return charset encoding +MAGIC_MIME // MAGIC_MIME_TYPE | MAGIC_MIME_ENCODING +MAGIC_SYMLINK // Follow symbolic links +MAGIC_COMPRESS // Inspect inside compressed files +MAGIC_ERROR // Treat ENOENT etc. as hard errors +MAGIC_NO_CHECK_ELF // Skip ELF binary analysis +MAGIC_NO_CHECK_BUILTIN// Disable all built-in checks; magic file only +``` + +## Usage Example + +```c +#include +#include + +int main(void) { + /* Open a magic handle requesting MIME type output */ + magic_t cookie = magic_open(MAGIC_MIME_TYPE | MAGIC_ERROR); + if (cookie == NULL) { + fprintf(stderr, "magic_open failed\n"); + return 1; + } + + /* Load the default system magic database */ + if (magic_load(cookie, NULL) != 0) { + fprintf(stderr, "magic_load: %s\n", magic_error(cookie)); + magic_close(cookie); + return 1; + } + + /* Identify a file by path */ + const char *mime = magic_file(cookie, "/etc/passwd"); + printf("MIME type: %s\n", mime); /* e.g. "text/plain" */ + + /* Identify from a buffer */ + const char buf[] = "\x89PNG\r\n\x1a\n"; + const char *btype = magic_buffer(cookie, buf, sizeof(buf) - 1); + printf("Buffer type: %s\n", btype); /* e.g. "image/png" */ + + /* Tune max indirection depth */ + size_t max_indir = 10; + magic_setparam(cookie, MAGIC_PARAM_INDIR_MAX, &max_indir); + + magic_close(cookie); + return 0; +} +``` + +> **Compile with:** `gcc example.c -lmagic -o example` \ No newline at end of file diff --git a/libraries/cmake/source/librdkafka/config/linux/aarch64/.config.md b/libraries/cmake/source/librdkafka/config/linux/aarch64/.config.md new file mode 100644 index 00000000000..e6afe9c46b3 --- /dev/null +++ b/libraries/cmake/source/librdkafka/config/linux/aarch64/.config.md @@ -0,0 +1,42 @@ + +Compile-time build configuration header for the librdkafka C library, defining feature flags, atomic operation backends, and optional dependency availability detected during the CMake build process. + +## Key Components + +### Build Flags +| Macro | Value | Purpose | +|---|---|---| +| `WITHOUT_OPTIMIZATION` | `0` | Compiler optimizations enabled | +| `ENABLE_DEVEL` | `0` | Development mode disabled | +| `ENABLE_REFCNT_DEBUG` | `0` | Reference count debugging disabled | + +### Atomic Operations +- **`ATOMIC_OP32(OP1,OP2,PTR,VAL)`** — 32-bit atomic operation macro; routes to either `__sync_*` (legacy GCC) or `__atomic_*` (C11/GCC intrinsics) based on `HAVE_ATOMICS_32_SYNC` +- **`ATOMIC_OP64(OP1,OP2,PTR,VAL)`** — 64-bit equivalent; same routing logic via `HAVE_ATOMICS_64_SYNC` + +### Enabled Optional Features (`1`) +`HDRHISTOGRAM`, `ZLIB`, `ZSTD`, `LIBDL`, `PLUGINS`, `SNAPPY`, `SOCKEM`, `SSL`, `SASL`, `SASL_SCRAM`, `SASL_OAUTHBEARER` + +### Disabled Features (`0`) +`WITH_PKGCONFIG`, `WITH_SASL_CYRUS`, `WITH_LZ4_EXT`, `WITH_CRC32C_HW`, `WITH_C11THREADS`, `HAVE_PTHREAD_SETNAME_FREEBSD` + +### Platform/Runtime Info +- `SOLIB_EXT` — Shared library extension (`.so`) +- `BUILT_WITH` — Human-readable string summarizing the active build components + +## Usage Example + +```c +#include "config.h" + +/* Use the atomic macro to perform a 32-bit fetch-and-add */ +int32_t counter = 0; +ATOMIC_OP32(fetch, add, &counter, 1); + +/* Conditionally compile SSL-dependent code */ +#if WITH_SSL + rd_kafka_conf_set_ssl_cert(...); +#endif +``` + +> **Note:** This file is auto-generated by CMake — do not edit manually. Rebuild the project after changing feature flags in `CMakeLists.txt`. \ No newline at end of file diff --git a/libraries/cmake/source/librdkafka/config/linux/x86_64/.config.md b/libraries/cmake/source/librdkafka/config/linux/x86_64/.config.md new file mode 100644 index 00000000000..e13c9ce8bcb --- /dev/null +++ b/libraries/cmake/source/librdkafka/config/linux/x86_64/.config.md @@ -0,0 +1,41 @@ + +Compile-time build configuration header for the librdkafka library, defining feature flags, atomic operation backends, and optional dependency support based on the detected platform and CMake build settings. + +## Key Components + +### Build & Debug Flags +| Macro | Value | Purpose | +|---|---|---| +| `WITHOUT_OPTIMIZATION` | `0` | Disables compiler optimizations when set | +| `ENABLE_DEVEL` | `0` | Enables development/debug mode | +| `ENABLE_REFCNT_DEBUG` | `0` | Enables reference count debugging | + +### Atomic Operations +- **`ATOMIC_OP32(OP1,OP2,PTR,VAL)`** — 32-bit atomic operation macro; routes to either `__sync_*` (GCC legacy) or `__atomic_*` (C11/GCC 4.7+) builtins based on `HAVE_ATOMICS_32_SYNC` +- **`ATOMIC_OP64(OP1,OP2,PTR,VAL)`** — Same pattern for 64-bit atomics + +### Optional Feature Flags +Key enabled features (`1`) include SSL, SASL/SCRAM, OAuthBearer, zlib, zstd, Snappy compression, HdrHistogram, plugins, CRC32C hardware acceleration, and regex support. Notable disabled features (`0`) include Cyrus SASL and external LZ4. + +### Platform Capabilities +- `HAVE_STRNDUP`, `HAVE_RAND_R` — POSIX string/random function availability +- `HAVE_PTHREAD_SETNAME_GNU` / `_DARWIN` — Thread naming support per OS +- `WITH_C11THREADS` — C11 native threads (disabled; pthreads used instead) +- `SOLIB_EXT` — Shared library extension (`.so`) + +## Usage Example + +```c +#include "config.h" + +// Use atomic op macros at call sites +int32_t counter = 0; +ATOMIC_OP32(fetch, add, &counter, 1); + +// Conditionally compile SSL support +#if WITH_SSL + rd_kafka_conf_set(conf, "security.protocol", "ssl", NULL, 0); +#endif +``` + +> **Note:** This file is auto-generated by CMake during the build process. Do not edit manually — modify `CMakeLists.txt` or the platform detection scripts instead. The `BUILT_WITH` string captures the full feature summary for runtime diagnostics. \ No newline at end of file diff --git a/libraries/cmake/source/librdkafka/config/macos/aarch64/.config.md b/libraries/cmake/source/librdkafka/config/macos/aarch64/.config.md new file mode 100644 index 00000000000..b120f7323a8 --- /dev/null +++ b/libraries/cmake/source/librdkafka/config/macos/aarch64/.config.md @@ -0,0 +1,39 @@ + +Compile-time build configuration header for the librdkafka library, defining platform capabilities, optional feature flags, and atomic operation backends. + +## Key Components + +### Feature Toggle Flags +- `WITH_*` macros — Enable/disable optional library features at build time (SSL, SASL, ZLIB, ZSTD, Snappy, plugins, etc.) +- `HAVE_*` macros — Platform capability detection results (regex, `strndup`, `rand_r`, pthread naming variants) +- `WITHOUT_OPTIMIZATION` / `ENABLE_DEVEL` / `ENABLE_REFCNT_DEBUG` — Developer/debug build switches (all disabled here) + +### Atomic Operations +- `ATOMIC_OP32(OP1,OP2,PTR,VAL)` — Macro dispatching 32-bit atomic operations to either `__sync_*` (legacy GCC) or `__atomic_*` (C11 `__ATOMIC_SEQ_CST`) builtins +- `ATOMIC_OP64(OP1,OP2,PTR,VAL)` — Same dispatch logic for 64-bit atomics +- `HAVE_ATOMICS_32` / `HAVE_ATOMICS_64` — Enabled; `*_SYNC` variants disabled, so `__atomic_*` builtins are selected + +### Build Metadata +- `SOLIB_EXT` — Shared library extension (`.dylib`, indicating macOS/Darwin target) +- `BUILT_WITH` — Human-readable string listing all active components for runtime introspection + +## Usage Example + +```c +#include "config.h" + +/* Use atomic macro to fetch-and-add a 32-bit counter */ +uint32_t counter = 0; +ATOMIC_OP32(fetch, add, &counter, 1); + +/* Conditionally compile SSL-dependent code */ +#if WITH_SSL + rd_kafka_conf_set(conf, "security.protocol", "ssl", NULL, 0); +#endif + +/* Log build features at startup */ +printf("Built with: %s\n", BUILT_WITH); +/* Output: Built with: CMAKE AppleClang ... SNAPPY SOCKEM */ +``` + +> **Note:** This file is auto-generated by the build system (CMake in this case, as indicated by `BUILT_WITH`). Do not edit manually — regenerate via the build configuration step. \ No newline at end of file diff --git a/libraries/cmake/source/librdkafka/config/macos/x86_64/.config.md b/libraries/cmake/source/librdkafka/config/macos/x86_64/.config.md new file mode 100644 index 00000000000..0fec3f9bddc --- /dev/null +++ b/libraries/cmake/source/librdkafka/config/macos/x86_64/.config.md @@ -0,0 +1,41 @@ + +Compile-time build configuration header for the librdkafka C library, defining feature flags, atomic operation backends, and available system capabilities detected during the CMake build process. + +## Key Components + +### Build Flags +| Macro | Value | Description | +|---|---|---| +| `WITHOUT_OPTIMIZATION` | `0` | Compiler optimizations enabled | +| `ENABLE_DEVEL` | `0` | Development mode disabled | +| `ENABLE_REFCNT_DEBUG` | `0` | Reference count debugging disabled | + +### Atomic Operations +- **`ATOMIC_OP32(OP1,OP2,PTR,VAL)`** — 32-bit atomic operation macro, dispatches to either `__sync_*` or `__atomic_*` GCC builtins based on `HAVE_ATOMICS_32_SYNC` +- **`ATOMIC_OP64(OP1,OP2,PTR,VAL)`** — 64-bit equivalent; both default to `__atomic_*` with `__ATOMIC_SEQ_CST` ordering on this build + +### Feature Toggles (`WITH_*`) +Enabled: `PKGCONFIG`, `HDRHISTOGRAM`, `ZLIB`, `ZSTD`, `LIBDL`, `PLUGINS`, `SNAPPY`, `SOCKEM`, `SSL`, `SASL`, `SASL_SCRAM`, `SASL_OAUTHBEARER`, `CRC32C_HW` +Disabled: `WITH_SASL_CYRUS`, `WITH_LZ4_EXT`, `WITH_C11THREADS` + +### Platform Capabilities (`HAVE_*`) +- Standard C library features: `HAVE_STRNDUP`, `HAVE_RAND_R`, `HAVE_REGEX` +- Thread naming: `HAVE_PTHREAD_SETNAME_DARWIN` (macOS-specific; GNU/FreeBSD variants disabled) +- `SOLIB_EXT` — `.dylib` (macOS shared library extension) + +## Usage Example + +```c +#include "config.h" + +// Use atomic add on a 32-bit counter +int32_t counter = 0; +ATOMIC_OP32(fetch, add, &counter, 1); + +// Conditional compilation based on SSL support +#if WITH_SSL + rd_kafka_conf_set(conf, "security.protocol", "ssl", NULL, 0); +#endif +``` + +> **Note:** This file is auto-generated by CMake and targets **macOS/AppleClang**. Do not edit manually — regenerate via the build system when changing feature requirements. \ No newline at end of file diff --git a/libraries/cmake/source/librdkafka/config/windows/aarch64/.config.md b/libraries/cmake/source/librdkafka/config/windows/aarch64/.config.md new file mode 100644 index 00000000000..80d6c96a439 --- /dev/null +++ b/libraries/cmake/source/librdkafka/config/windows/aarch64/.config.md @@ -0,0 +1,55 @@ + +Compile-time configuration header for the librdkafka build system, defining feature flags, platform capabilities, and enabled/disabled components for a Windows (MSVC) target build. + +## Key Components + +### Feature Flags + +| Macro | Value | Description | +|---|---|---| +| `WITHOUT_OPTIMIZATION` | `0` | Compiler optimizations enabled | +| `ENABLE_DEVEL` | `0` | Development mode disabled | +| `ENABLE_REFCNT_DEBUG` | `0` | Reference count debugging disabled | + +### Atomic Operations + +- **`HAVE_ATOMICS_32` / `HAVE_ATOMICS_64`** — 32/64-bit atomics support flags (both disabled) +- **`ATOMIC_OP32(OP1,OP2,PTR,VAL)`** — Expands to either `__sync_*` or `__atomic_*` intrinsic depending on `HAVE_ATOMICS_32_SYNC` +- **`ATOMIC_OP64(OP1,OP2,PTR,VAL)`** — Same pattern for 64-bit operations + +### Enabled Components (`1`) + +| Component | Macro | +|---|---| +| Compression | `WITH_ZLIB`, `WITH_ZSTD`, `WITH_SNAPPY` | +| Security | `WITH_SSL`, `WITH_SASL`, `WITH_SASL_SCRAM`, `WITH_SASL_OAUTHBEARER` | +| Plugins | `WITH_PLUGINS` | +| Testing | `WITH_SOCKEM` | + +### Platform Metadata + +- **`SOLIB_EXT`** — Shared library extension (`.dll` — Windows target) +- **`BUILT_WITH`** — Human-readable build summary string + +## Usage Example + +```c +#include "config.h" + +// Conditionally compile SSL-secured code paths +#if WITH_SSL + rd_kafka_conf_set(conf, "security.protocol", "ssl", NULL, 0); +#endif + +// Use atomic operations when available +#if HAVE_ATOMICS_32 + ATOMIC_OP32(fetch, add, &counter, 1); +#else + // fallback to mutex-protected increment + pthread_mutex_lock(&lock); + counter++; + pthread_mutex_unlock(&lock); +#endif +``` + +> **Note:** This file is auto-generated by CMake during the build process. Do not edit manually — modify `CMakeLists.txt` or the corresponding `.cmake` configuration files instead. \ No newline at end of file diff --git a/libraries/cmake/source/librdkafka/config/windows/x86_64/.config.md b/libraries/cmake/source/librdkafka/config/windows/x86_64/.config.md new file mode 100644 index 00000000000..ea32cc79f98 --- /dev/null +++ b/libraries/cmake/source/librdkafka/config/windows/x86_64/.config.md @@ -0,0 +1,53 @@ + +Compile-time configuration header that defines feature flags, platform capabilities, and build options for the librdkafka library on Windows (MSVC/CMAKE). + +## Key Components + +| Macro | Description | +|---|---| +| `WITHOUT_OPTIMIZATION` / `ENABLE_DEVEL` | Debug/development build toggles | +| `HAVE_ATOMICS_32/64` | 32/64-bit atomic operation availability flags | +| `ATOMIC_OP32/64` | Conditional macros mapping atomic ops to either `__sync_*` or `__atomic_*` GCC builtins | +| `WITH_*` | Feature enablement flags (compression, auth, plugins, etc.) | +| `HAVE_*` | Platform capability flags (regex, POSIX functions, pthread naming) | +| `SOLIB_EXT` | Shared library file extension (`.dll` for Windows) | +| `BUILT_WITH` | Human-readable string of active build features | + +## Active Features (set to `1`) + +```text +Enabled: ZLIB, ZSTD, PLUGINS, SNAPPY, SOCKEM, SSL + SASL, SASL_SCRAM, SASL_OAUTHBEARER + +Disabled: SASL_CYRUS, LZ4_EXT, HDRHISTOGRAM, PKGCONFIG + ATOMICS (32/64), C11THREADS, CRC32C_HW + HAVE_REGEX, HAVE_STRNDUP, HAVE_RAND_R + PTHREAD_SETNAME (all variants) +``` + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile SSL support */ +#if WITH_SSL + rd_kafka_conf_set(conf, "security.protocol", "ssl", NULL, 0); +#endif + +/* Use atomic operations if available */ +#if HAVE_ATOMICS_32 + ATOMIC_OP32(fetch, add, &counter, 1); +#else + /* fallback to mutex-protected increment */ + pthread_mutex_lock(&lock); + counter++; + pthread_mutex_unlock(&lock); +#endif + +/* Log the build configuration */ +printf("Built with: %s\n", BUILT_WITH); +printf("Shared lib ext: %s\n", SOLIB_EXT); +``` + +> **Note:** This file is auto-generated by CMake during the build process. Do not edit manually — modify `CMakeLists.txt` or the corresponding `.cmake` template instead. \ No newline at end of file diff --git a/libraries/cmake/source/librpm/config/aarch64/.config.md b/libraries/cmake/source/librpm/config/aarch64/.config.md new file mode 100644 index 00000000000..81f8cfc1f01 --- /dev/null +++ b/libraries/cmake/source/librpm/config/aarch64/.config.md @@ -0,0 +1,56 @@ + +Auto-generated build configuration header for RPM 4.18.2, produced by `autoconf`/`configure`. It defines compile-time feature flags, available system functions/headers, library support, and package metadata used throughout the RPM source tree. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_STRING` | `"rpm 4.18.2"` | +| `PACKAGE_VERSION` | `"4.18.2"` | +| `PACKAGE_BUGREPORT` | `"rpm-maint@lists.rpm.org"` | +| `RPMCANONVENDOR` | `"unknown"` | +| `RUNDIR` | `"/run"` | + +### Enabled Features +- `ENABLE_INHIBIT_PLUGIN` — systemd inhibit plugin support +- `ENABLE_NDB` — new RPM database format +- `ENABLE_NLS` — native language (i18n) support +- `WITH_BDB_RO` — read-only Berkeley DB backend +- `WITH_SQLITE` — SQLite database backend +- `HAVE_ZSTD` / `HAVE_LZMA_H` / `HAVE_ZLIB_H` — compression libraries + +### Disabled Features (notable) +- `DBUS`, `ENABLE_PLUGINS`, `ENABLE_OPENMP` — commented out +- `WITH_SELINUX`, `WITH_CAP`, `WITH_ACL`, `WITH_AUDIT` — not built +- `HAVE_LIBELF`, `HAVE_LIBDW` — ELF/DWARF introspection absent + +### OpenSSL API Surface +Confirms modern OpenSSL API availability: +- `HAVE_EVP_MD_CTX_NEW`, `HAVE_BN2BINPAD` +- `HAVE_RSA_SET0_KEY`, `HAVE_DSA_SET0_KEY`, `HAVE_DSA_SIG_SET0` + +### Platform Extensions +Unconditionally enables `_GNU_SOURCE`, `_ALL_SOURCE`, `_POSIX_PTHREAD_SEMANTICS`, and `__EXTENSIONS__` for broad POSIX/GNU API access. + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_LZMA_H +# include +#endif + +#ifdef WITH_SQLITE + /* initialize SQLite RPM database backend */ + db = rpmdbOpenSQLite(dbpath); +#endif + +#if defined(ENABLE_NLS) + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); +#endif +``` + +> This file is **auto-generated** — do not edit manually. Re-run `./configure` with the appropriate flags to change feature detection results. \ No newline at end of file diff --git a/libraries/cmake/source/librpm/config/x86_64/.config.md b/libraries/cmake/source/librpm/config/x86_64/.config.md new file mode 100644 index 00000000000..839595bc8b5 --- /dev/null +++ b/libraries/cmake/source/librpm/config/x86_64/.config.md @@ -0,0 +1,61 @@ + +Auto-generated build configuration header for **RPM 4.18.2**, produced by `autoconf`/`configure` to capture compile-time feature detection results for the target system. + +## Key Components + +### Build Feature Flags +| Macro | Value | Purpose | +|---|---|---| +| `ENABLE_INHIBIT_PLUGIN` | `1` | systemd inhibit plugin enabled | +| `ENABLE_NDB` | `1` | New RPM database format enabled | +| `ENABLE_NLS` | `1` | Native language support enabled | +| `WITH_BDB_RO` | `1` | Read-only Berkeley DB backend | +| `WITH_SQLITE` | `1` | SQLite database backend | + +### Package Identity +```c +#define PACKAGE_STRING "rpm 4.18.2" +#define PACKAGE_VERSION "4.18.2" +#define VERSION "4.18.2" +#define PACKAGE_BUGREPORT "rpm-maint@lists.rpm.org" +#define RPMCANONVENDOR "unknown" +#define RUNDIR "/run" +``` + +### System Capability Detection +Detected POSIX/Linux functions and headers including `fstatat`, `openat`, `mkdirat`, `linkat`, `symlinkat`, `renameat`, `utimensat`, and `unlinkat` (full `*at()` family). OpenSSL APIs confirmed: `EVP_MD_CTX_new`, `RSA_set0_key`, `DSA_set0_key`, `BN_bn2binpad`. + +### Platform Extensions +```c +#define _GNU_SOURCE 1 /* GNU extensions */ +#define _ALL_SOURCE 1 /* AIX/Interix extensions */ +#define _POSIX_PTHREAD_SEMANTICS 1 /* Solaris threading */ +``` + +### Disabled Features +The following are explicitly **not** built: DBUS, SELinux, ACL, audit, capability (`cap`), fsverity, IMA/EVM, bzip2, OpenMP multithreading, and plugin support. + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_LZMA_H +# include +#endif + +#ifdef WITH_SQLITE + /* Initialize SQLite RPM database backend */ + rpmdb_sqlite_init(); +#elif defined(WITH_BDB_RO) + /* Fall back to read-only BerkeleyDB */ + rpmdb_bdb_ro_init(); +#endif + +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); +#endif +``` + +> **Note:** This file is machine-generated — do not edit manually. Re-run `./configure` to regenerate it for a different target system or feature set. \ No newline at end of file diff --git a/libraries/cmake/source/libudev/config/.config.md b/libraries/cmake/source/libudev/config/.config.md new file mode 100644 index 00000000000..af5d4537342 --- /dev/null +++ b/libraries/cmake/source/libudev/config/.config.md @@ -0,0 +1,45 @@ + +Auto-generated build configuration header for the `udev` package (version 174), produced by `autoconf`/`configure`. Defines compile-time feature flags, available system headers, library detection results, and portability extensions. + +## Key Components + +| Macro | Value | Purpose | +|---|---|---| +| `PACKAGE_VERSION` / `VERSION` | `"174"` | udev package version string | +| `ENABLE_LOGGING` | `1` | System logging enabled at build time | +| `ENABLE_DEBUG` | *(undef)* | Debug output disabled | +| `HAVE_LIBSELINUX` | *(undef)* | SELinux library not detected | +| `HAVE__USR_SHARE_MISC_PCI_IDS` | `1` | PCI ID database found at `/usr/share/misc/pci.ids` | +| `_GNU_SOURCE` | `1` | GNU extensions enabled | +| `LT_OBJDIR` | `".libs/"` | libtool uninstalled library path | + +**Disabled features** (commented `#undef`): +- `ENABLE_DEBUG` — debug messages +- `ENABLE_INTROSPECTION` — GObject introspection +- `WITH_SELINUX` — SELinux support +- `HAVE_LIBSELINUX` — SELinux library + +**Portability guards** enabled: `_ALL_SOURCE`, `_GNU_SOURCE`, `_POSIX_PTHREAD_SEMANTICS`, `_TANDEM_SOURCE`, `__EXTENSIONS__`, `_DARWIN_USE_64_BIT_INODE` + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile logging support */ +#ifdef ENABLE_LOGGING + syslog(LOG_INFO, "udev event processed"); +#endif + +/* Guard optional SELinux calls */ +#ifdef WITH_SELINUX + setfilecon(path, context); +#endif + +/* Use detected PCI IDs path */ +#ifdef HAVE__USR_SHARE_MISC_PCI_IDS + const char *pci_ids = "/usr/share/misc/pci.ids"; +#endif +``` + +> **Note:** This file is auto-generated by `./configure` — do not edit manually. Modify `config.h.in` or `configure.ac` and re-run `autoconf`/`./configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/lzma/config/linux/aarch64/.config.md b/libraries/cmake/source/lzma/config/linux/aarch64/.config.md new file mode 100644 index 00000000000..7bcc083a4b7 --- /dev/null +++ b/libraries/cmake/source/lzma/config/linux/aarch64/.config.md @@ -0,0 +1,54 @@ + +Auto-generated build configuration header for **XZ Utils 5.4.4**, produced by `autoconf`/`configure` to describe platform capabilities and enabled features for the current build target. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"XZ Utils"` | +| `PACKAGE_VERSION` | `"5.4.4"` | +| `PACKAGE_BUGREPORT` | `"xz@tukaani.org"` | +| `PACKAGE_URL` | `"https://tukaani.org/xz/"` | + +### Enabled Codecs + +**Encoders & Decoders:** ARM, ARM64, ARMThumb, Delta, LZMA1, LZMA2, x86 +**Disabled:** IA64, PowerPC, SPARC + +**Match Finders:** BT2, BT3, BT4, HC3, HC4 + +**Integrity Checks:** CRC32, CRC64, SHA256, lzip decoder + +### Threading Model +- `MYTHREAD_POSIX 1` — POSIX pthreads with `pthread_condattr_setclock` and `PTHREAD_PRIO_INHERIT` + +### Platform Capabilities +- `SIZEOF_SIZE_T 8` — 64-bit platform +- `ASSUME_RAM 128` — fallback RAM assumption (MiB) +- `HAVE_SYMBOL_VERSIONS_LINUX 2` — conditional GNU/Linux symbol versioning +- `HAVE_VISIBILITY 1` — GCC visibility attribute support +- `HAVE___BUILTIN_BSWAPXX 1` — GCC built-in byte-swap intrinsics +- `HAVE___BUILTIN_ASSUME_ALIGNED 1` — GCC alignment hint support +- `NDEBUG 1` — debug assertions disabled (release build) + +### Standard Headers Available +``, ``, ``, ``, ``, ``, ``, ``, `` + +## Usage Example + +This header is consumed automatically by the build system — never included manually. It guards feature usage like: + +```c +#include "config.h" + +#ifdef HAVE_CLOCK_GETTIME + clock_gettime(CLOCK_MONOTONIC, &ts); +#endif + +#if defined(HAVE_ENCODER_LZMA2) && defined(HAVE_ENCODERS) + // LZMA2 compression path +#endif +``` + +> **Note:** This file is regenerated by running `./configure` and should not be edited by hand. Commit `configure.ac` instead. \ No newline at end of file diff --git a/libraries/cmake/source/lzma/config/linux/x86_64/.config.md b/libraries/cmake/source/lzma/config/linux/x86_64/.config.md new file mode 100644 index 00000000000..6602b22b075 --- /dev/null +++ b/libraries/cmake/source/lzma/config/linux/x86_64/.config.md @@ -0,0 +1,55 @@ + +Auto-generated configuration header for XZ Utils 5.4.4, produced by `autoconf` from `configure.ac`. It captures compile-time feature detection results for the target build environment. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"XZ Utils"` | +| `PACKAGE_VERSION` | `"5.4.4"` | +| `PACKAGE_STRING` | `"XZ Utils 5.4.4"` | +| `PACKAGE_BUGREPORT` | `"xz@tukaani.org"` | + +### Codec Support +- **Encoders enabled:** ARM, ARM64, ARMThumb, Delta, LZMA1, LZMA2, x86 +- **Decoders enabled:** ARM, ARM64, ARMThumb, Delta, LZMA1, LZMA2, x86, lzip +- **Integrity checks:** CRC32, CRC64, SHA256 +- **Match finders:** BT2, BT3, BT4, HC3, HC4 + +### Threading Model +- `MYTHREAD_POSIX 1` — POSIX threads (`pthreads`) in use +- `HAVE_PTHREAD_CONDATTR_SETCLOCK 1` — monotonic clock support for condition variables + +### Platform / Hardware +- `SIZEOF_SIZE_T 8` — 64-bit platform +- `HAVE_USABLE_CLMUL 1` — hardware carry-less multiply (CLMUL) available for accelerated CRC +- `HAVE__MM_MOVEMASK_EPI8 1` — SSE2 intrinsics available +- `HAVE_CPUID_H 1` — x86 CPU feature detection header present +- `ASSUME_RAM 128` — fallback RAM assumption (MiB) when system query fails + +### Symbol Versioning +- `HAVE_SYMBOL_VERSIONS_LINUX 2` — GNU/Linux ELF symbol versioning enabled conditionally on PIC + +## Usage Example + +```c +#include "config.h" + +/* Guard threading code */ +#ifdef MYTHREAD_POSIX +#include +#endif + +/* Guard hardware-accelerated CRC */ +#ifdef HAVE_USABLE_CLMUL + /* use CLMUL-based CRC32/CRC64 path */ +#else + /* fall back to software CRC */ +#endif + +/* Check codec availability before calling encoder */ +#ifdef HAVE_ENCODER_LZMA2 + lzma_easy_encoder(&stream, preset, LZMA_CHECK_CRC64); +#endif +``` \ No newline at end of file diff --git a/libraries/cmake/source/lzma/config/macos/aarch64/.config.md b/libraries/cmake/source/lzma/config/macos/aarch64/.config.md new file mode 100644 index 00000000000..6ee12a3f4be --- /dev/null +++ b/libraries/cmake/source/lzma/config/macos/aarch64/.config.md @@ -0,0 +1,55 @@ + +Auto-generated build configuration header for **XZ Utils 5.4.4**, produced by `autoconf`/`configure` to capture platform capabilities and enabled features for the liblzma compression library. + +## Key Components + +### Package Identity +- `PACKAGE_NAME` — `"XZ Utils"` +- `PACKAGE_VERSION` — `"5.4.4"` +- `PACKAGE_STRING` — `"XZ Utils 5.4.4"` +- `PACKAGE_BUGREPORT` — `"xz@tukaani.org"` + +### Enabled Codecs +| Direction | Algorithms | +|---|---| +| Encoders (`HAVE_ENCODER_*`) | ARM, ARM64, ARMTHUMB, DELTA, LZMA1, LZMA2, X86 | +| Decoders (`HAVE_DECODER_*`) | ARM, ARM64, ARMTHUMB, DELTA, LZMA1, LZMA2, X86 | + +### Integrity Checks +- `HAVE_CHECK_CRC32`, `HAVE_CHECK_CRC64`, `HAVE_CHECK_SHA256` — all enabled + +### Match Finders +- `HAVE_MF_BT2`, `HAVE_MF_BT3`, `HAVE_MF_BT4`, `HAVE_MF_HC3`, `HAVE_MF_HC4` — all enabled + +### Threading Model +- `MYTHREAD_POSIX 1` — POSIX pthreads with `HAVE_PTHREAD_PRIO_INHERIT` + +### Platform Capabilities +- `SIZEOF_SIZE_T 8` — 64-bit platform +- `ASSUME_RAM 128` — fallback RAM assumption (MiB) +- `HAVE_VISIBILITY 1` — GCC symbol visibility supported +- `NDEBUG 1` — debug assertions disabled (release build) +- macOS CoreFoundation locale functions enabled (`HAVE_CFLOCALECOPYPREFERREDLANGUAGES`, `HAVE_CFPREFERENCESCOPYAPPVALUE`) + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile SHA-256 support */ +#ifdef HAVE_CHECK_SHA256 + compute_sha256(buf, len, digest); +#endif + +/* Use POSIX threads if available */ +#ifdef MYTHREAD_POSIX + pthread_create(&thread, NULL, worker_fn, args); +#endif + +/* Guard 64-bit-only code paths */ +#if SIZEOF_SIZE_T == 8 + use_64bit_optimized_path(); +#endif +``` + +> **Note:** This file is auto-generated by `./configure` — do not edit manually. Regenerate by running `autoconf` then `./configure` with the desired feature flags. \ No newline at end of file diff --git a/libraries/cmake/source/lzma/config/macos/x86_64/.config.md b/libraries/cmake/source/lzma/config/macos/x86_64/.config.md new file mode 100644 index 00000000000..615b8cb59a9 --- /dev/null +++ b/libraries/cmake/source/lzma/config/macos/x86_64/.config.md @@ -0,0 +1,55 @@ + +Auto-generated build configuration header for XZ Utils 5.4.4, produced by `autoconf`/`configure` to capture platform capabilities and compile-time feature flags for the liblzma compression library. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"XZ Utils"` | +| `PACKAGE_VERSION` | `"5.4.4"` | +| `PACKAGE_STRING` | `"XZ Utils 5.4.4"` | +| `PACKAGE_BUGREPORT` | `"xz@tukaani.org"` | + +### Codec Support +Enabled encoders/decoders for: **ARM**, **ARM64**, **ARMThumb**, **Delta**, **LZMA1**, **LZMA2**, **x86**. Disabled: IA64, PowerPC, SPARC. + +Match finders enabled: `bt2`, `bt3`, `bt4`, `hc3`, `hc4`. + +Integrity checks: **CRC32**, **CRC64**, **SHA256**. + +### Threading Model +```c +#define MYTHREAD_POSIX 1 // POSIX pthreads +#define HAVE_PTHREAD_PRIO_INHERIT 1 +``` + +### Platform / Hardware Flags +```c +#define HAVE_USABLE_CLMUL 1 // CLMUL instruction (fast CRC) +#define HAVE__MM_MOVEMASK_EPI8 1 // SSE2 SIMD available +#define HAVE_CPUID_H 1 +#define HAVE_IMMINTRIN_H 1 +#define SIZEOF_SIZE_T 8 // 64-bit platform +#define ASSUME_RAM 128 // MiB fallback if RAM undetectable +``` + +### Standard Headers Present +`stdint.h`, `stdbool.h`, `stdio.h`, `stdlib.h`, `string.h`, `unistd.h`, `fcntl.h`, `wchar.h`, `sys/stat.h`, `sys/types.h`, `limits.h` + +## Usage Example + +```c +#include "config.h" + +#if defined(HAVE_ENCODER_LZMA2) && defined(MYTHREAD_POSIX) + // Multi-threaded LZMA2 encoding path + init_lzma2_encoder_threaded(); +#endif + +#if SIZEOF_SIZE_T == 8 + // Use 64-bit optimized buffers +#endif +``` + +> **Note:** This file is auto-generated by `./configure` and should not be edited manually. Regenerate by running `autoconf` + `./configure` with the desired feature flags. \ No newline at end of file diff --git a/libraries/cmake/source/lzma/config/windows/aarch64/.config.md b/libraries/cmake/source/lzma/config/windows/aarch64/.config.md new file mode 100644 index 00000000000..3eafbad9c3c --- /dev/null +++ b/libraries/cmake/source/lzma/config/windows/aarch64/.config.md @@ -0,0 +1,51 @@ + +Configuration header for compiling **liblzma** (XZ Utils) with MSVC 2019 on Windows. Defines feature flags, codec support, platform detection, and runtime assumptions used at compile time. + +## Key Components + +### Integrity Checks +| Macro | Status | +|---|---| +| `HAVE_CHECK_CRC32` | ✅ Enabled | +| `HAVE_CHECK_CRC64` | ✅ Enabled | +| `HAVE_CHECK_SHA256` | ✅ Enabled | + +### Codecs (Encoders & Decoders) +Enabled for: **ARM**, **ARM64**, **ARMThumb**, **Delta**, **LZMA1**, **LZMA2**, **x86**, **lzip** +Disabled (commented out): `IA64`, `PowerPC`, `SPARC` + +### Match Finders +All BT/HC variants enabled: `BT2`, `BT3`, `BT4`, `HC3`, `HC4` + +### Platform / Threading +```c +#ifdef _M_IX86 +#define MYTHREAD_WIN95 1 // 32-bit x86: WinXP-compatible threads +#else +#define MYTHREAD_VISTA 1 // 64-bit: Vista+ threads +#endif +``` + +### Size Detection +```c +#ifdef _WIN64 +#define SIZEOF_SIZE_T 8 // 64-bit build +#else +#define SIZEOF_SIZE_T 4 // 32-bit build +#endif +``` + +## Usage Example + +This file is consumed automatically by the MSVC build system — no manual inclusion required. To disable a codec, comment out the relevant define: + +```c +// Disable SPARC encoder/decoder (already disabled by default) +// #define HAVE_ENCODER_SPARC 1 +// #define HAVE_DECODER_SPARC 1 + +// Override assumed RAM (default: 128 MiB) +#define ASSUME_RAM 256 +``` + +> **Note:** `HAVE_VISIBILITY 0` disables GCC-style symbol visibility — expected behavior under MSVC where `__declspec(dllexport/dllimport)` is used instead. `TUKLIB_FAST_UNALIGNED_ACCESS 1` assumes x86/x64 hardware tolerates unaligned 16/32-bit reads. \ No newline at end of file diff --git a/libraries/cmake/source/lzma/config/windows/x86_64/.config.md b/libraries/cmake/source/lzma/config/windows/x86_64/.config.md new file mode 100644 index 00000000000..b3c559f63ec --- /dev/null +++ b/libraries/cmake/source/lzma/config/windows/x86_64/.config.md @@ -0,0 +1,69 @@ + +Configuration header for compiling **liblzma** (part of XZ Utils) specifically targeting **MSVC 2019** on Windows. It defines feature flags, codec support, platform detection, and build options used throughout the library's C source files. + +## Key Components + +### Integrity Checks +| Macro | Status | +|---|---| +| `HAVE_CHECK_CRC32` | ✅ Enabled | +| `HAVE_CHECK_CRC64` | ✅ Enabled | +| `HAVE_CHECK_SHA256` | ✅ Enabled | + +### Codec Support (Encoders & Decoders) + +| Codec | Encoder | Decoder | +|---|:---:|:---:| +| LZMA1 / LZMA2 | ✅ | ✅ | +| ARM / ARM64 / ARMTHUMB | ✅ | ✅ | +| X86 | ✅ | ✅ | +| DELTA | ✅ | ✅ | +| IA64 / PowerPC / SPARC | ❌ | ❌ | + +### Match Finders +`HAVE_MF_BT2`, `HAVE_MF_BT3`, `HAVE_MF_BT4`, `HAVE_MF_HC3`, `HAVE_MF_HC4` — all enabled. + +### Platform Branching + +```c +#ifdef _M_IX86 + #define MYTHREAD_WIN95 1 // 32-bit x86: XP-compatible threads +#else + #define MYTHREAD_VISTA 1 // 64-bit: Vista+ threads +#endif + +#ifdef _WIN64 + #define SIZEOF_SIZE_T 8 // 64-bit pointer size +#else + #define SIZEOF_SIZE_T 4 // 32-bit pointer size +#endif +``` + +### Other Notable Flags + +| Macro | Value | Purpose | +|---|---|---| +| `TUKLIB_SYMBOL_PREFIX` | `lzma_` | Namespace prefix for exported symbols | +| `ASSUME_RAM` | `128` (MiB) | Fallback RAM estimate | +| `HAVE_LZIP_DECODER` | `1` | `.lz` (lzip) decompression support | +| `TUKLIB_FAST_UNALIGNED_ACCESS` | `1` | Enables fast unaligned 16/32-bit reads | +| `HAVE_VISIBILITY` | `0` | GCC visibility attributes not available (MSVC) | +| `NDEBUG` | `1` | Debug assertions disabled in release build | + +## Usage Example + +This file is consumed automatically by the build system — include it as the implicit config header when compiling liblzma sources under MSVC: + +```c +// Typically injected via /FI flag in MSVC project settings, +// or included at the top of each translation unit: +#include "config.h" +#include + +// After inclusion, codec availability can be checked: +#if defined(HAVE_ENCODER_LZMA2) && defined(HAVE_CHECK_CRC64) + // Safe to use LZMA2 encoder with CRC64 integrity checking +#endif +``` + +> **Note:** Commented-out defines (IA64, PowerPC, SPARC) can be uncommented to enable those codecs if targeting those architectures. \ No newline at end of file diff --git a/libraries/cmake/source/popt/config/linux/aarch64/.config.md b/libraries/cmake/source/popt/config/linux/aarch64/.config.md new file mode 100644 index 00000000000..6eec837e9b6 --- /dev/null +++ b/libraries/cmake/source/popt/config/linux/aarch64/.config.md @@ -0,0 +1,51 @@ + +Auto-generated build configuration header for the **popt 1.19** command-line option parsing library, produced by `autoconf`/`autoheader` during the `./configure` phase. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE` | `"popt"` | +| `PACKAGE_VERSION` | `"1.19"` | +| `PACKAGE_STRING` | `"popt 1.19"` | +| `PACKAGE_BUGREPORT` | `"rpm-maint@lists.rpm.org"` | + +### Feature Detection Macros +Capability flags set by `configure` probing the build host: + +- **Internationalization** — `ENABLE_NLS`, `HAVE_GETTEXT`, `HAVE_DCGETTEXT`, `HAVE_ICONV` +- **Security** — `HAVE_SECURE_GETENV`, `HAVE_SETREUID`, `HAVE_SETUID`, `HAVE_GETEUID`, `HAVE_GETUID` +- **Standard Headers** — `HAVE_STDLIB_H`, `HAVE_STRING_H`, `HAVE_UNISTD_H`, `HAVE_STDINT_H`, `HAVE_INTTYPES_H` +- **POSIX/GNU Functions** — `HAVE_GLOB_PATTERN_P`, `HAVE_MBSRTOWCS`, `HAVE_VASPRINTF`, `HAVE_STPCPY` +- **Debug/Tracing** — `HAVE_MTRACE`, `HAVE_MCHECK_H` + +### Platform Extension Guards +Conditionally enables OS-specific extensions: + +```c +_GNU_SOURCE // GNU/Linux extensions +_ALL_SOURCE // AIX 3 / Interix +_POSIX_PTHREAD_SEMANTICS // Solaris threading +_TANDEM_SOURCE // HP NonStop +_DARWIN_USE_64_BIT_INODE // macOS 10.5+ large inodes +``` + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_SECURE_GETENV + val = secure_getenv("HOME"); +#else + val = getenv("HOME"); +#endif + +#ifdef ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Modify `config.h.in` or `configure.ac` and re-run `./configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/popt/config/linux/x86_64/.config.md b/libraries/cmake/source/popt/config/linux/x86_64/.config.md new file mode 100644 index 00000000000..8d947ce0b08 --- /dev/null +++ b/libraries/cmake/source/popt/config/linux/x86_64/.config.md @@ -0,0 +1,51 @@ + +Auto-generated build configuration header for the **popt 1.19** command-line option parsing library, produced by `autoconf`/`autoheader` from `configure.ac`. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE` / `PACKAGE_NAME` | `"popt"` | +| `PACKAGE_VERSION` / `VERSION` | `"1.19"` | +| `PACKAGE_STRING` | `"popt 1.19"` | +| `PACKAGE_BUGREPORT` | `"rpm-maint@lists.rpm.org"` | + +### Feature Detection Macros +Capability flags set by `configure` at build time: + +- **Internationalization** — `ENABLE_NLS`, `HAVE_DCGETTEXT`, `HAVE_GETTEXT`, `HAVE_ICONV`, `HAVE_LIBINTL_H` +- **POSIX/Security** — `HAVE_GETEUID`, `HAVE_GETUID`, `HAVE_SETREUID`, `HAVE_SETUID`, `HAVE___SECURE_GETENV` (`HAVE_SECURE_GETENV` is undefined) +- **Standard Headers** — `HAVE_STDLIB_H`, `HAVE_STRING_H`, `HAVE_UNISTD_H`, `HAVE_INTTYPES_H`, `HAVE_STDINT_H` +- **Glob/Pattern** — `HAVE_GLOB_H`, `HAVE_FNMATCH_H`, `HAVE_GLOB_PATTERN_P` +- **Memory/Debug** — `HAVE_MCHECK_H`, `HAVE_MTRACE`, `HAVE_VASPRINTF` + +### Platform Extension Guards +Conditionally enables OS-specific extensions if not already defined: + +- `_GNU_SOURCE` — GNU libc extensions +- `_ALL_SOURCE` — AIX/Interix extensions +- `_POSIX_PTHREAD_SEMANTICS` — Solaris threading +- `_DARWIN_USE_64_BIT_INODE` — macOS 10.5+ large inodes +- `__EXTENSIONS__` — General Solaris extensions + +## Usage Example + +```c +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_GETTEXT + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + +#ifdef HAVE_GLOB_H +# include +#endif + +printf("Version: %s\n", PACKAGE_STRING); /* "popt 1.19" */ +``` + +> **Note:** This file is auto-generated — do not edit manually. Modify `configure.ac` and regenerate via `autoconf`/`./configure`. \ No newline at end of file diff --git a/libraries/cmake/source/rocksdb/patches/.env_win.md b/libraries/cmake/source/rocksdb/patches/.env_win.md new file mode 100644 index 00000000000..63b6c0c2460 --- /dev/null +++ b/libraries/cmake/source/rocksdb/patches/.env_win.md @@ -0,0 +1,22 @@ + +Windows NT version targeting header that ensures consistent Windows Vista (`_WIN32_WINNT_VISTA`) API compatibility by undefining and redefining the `_WIN32_WINNT` macro. + +## Key Components + +- **`_WIN32_WINNT` redefinition** — Undefines any existing `_WIN32_WINNT` value and sets it to `_WIN32_WINNT_VISTA`, locking the minimum Windows API target to Vista (0x0600). + +## Usage Example + +```c +// Include before any Windows headers to enforce Vista-level API targeting +#include "env_win.h" +#include + +// Now Windows APIs are compiled against the Vista minimum target +``` + +## Notes + +- Uses `#pragma once` for include guard safety. +- The undef/redefine pattern prevents conflicts when `_WIN32_WINNT` is set earlier in the build system or by a third-party header. +- Targeting Vista ensures access to APIs introduced in Windows 6.0+ while maintaining broad compatibility across supported Windows versions. \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_config.md b/libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_config.md new file mode 100644 index 00000000000..3dd777ee34d --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_config.md @@ -0,0 +1,44 @@ + +Auto-generated configuration header for The Sleuth Kit (TSK) 4.11.0, produced by `autoconf`/`configure` to capture compile-time feature detection results for the target build environment. + +## Key Components + +| Category | Defines | +|---|---| +| **Package Identity** | `PACKAGE`, `PACKAGE_NAME`, `PACKAGE_STRING`, `PACKAGE_VERSION` → `"sleuthkit 4.11.0"` | +| **Available Libraries** | `HAVE_LIBDL`, `HAVE_LIBZ` (enabled); `HAVE_LIBAFFLIB`, `HAVE_LIBEWF`, `HAVE_LIBSQLITE3`, `HAVE_LIBVHDI`, `HAVE_LIBVMDK` (disabled) | +| **System Headers** | Standard POSIX headers confirmed: `stdint.h`, `stdlib.h`, `unistd.h`, `sys/stat.h`, `sys/types.h`, `inttypes.h`, `zlib.h` | +| **Runtime Features** | `HAVE_PTHREAD`, `HAVE_FSEEKO`, `HAVE_GETLINE`, `HAVE_GETRUSAGE`, `HAVE_VASPRINTF`, `HAVE_ALLOCA` | +| **Platform Flags** | `_DARWIN_USE_64_BIT_INODE`, `LSTAT_FOLLOWS_SLASHED_SYMLINK`, `SELECT_TYPE_ARG1/234/5` | +| **Language Standard** | `HAVE_CXX14` — C++14 compiler support confirmed | + +## Usage Example + +This header is typically included indirectly via the TSK main header, but can be checked directly for conditional compilation: + +```c +#include "tsk/tsk_config.h" + +/* Conditionally use large file support */ +#ifdef HAVE_FSEEKO + fseeko(fp, offset, SEEK_SET); +#else + fseek(fp, (long)offset, SEEK_SET); +#endif + +/* Conditionally compile EWF image support */ +#ifdef HAVE_LIBEWF + /* enable Expert Witness Format parsing */ +#endif + +/* Use zlib compression if available */ +#ifdef HAVE_LIBZ + #include +#endif +``` + +## Notes + +- **Optional forensic format libraries** (`afflib`, `ewf`, `vhdi`, `vmdk`, `sqlite3`) are all **disabled** in this build — forensic image support beyond raw/dd is not compiled in. +- **OpenSSL** (`HAVE_LIBOPENSSL`) is also disabled, meaning hash verification features may be limited. +- This file is **generated** — do not edit manually; re-run `./configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_incs.md b/libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_incs.md new file mode 100644 index 00000000000..4b58c7e6eb5 --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/linux/aarch64/tsk/.tsk_incs.md @@ -0,0 +1,39 @@ + +Centralized include header for The Sleuth Kit (TSK) library, providing essential system headers and compile-time configuration for consumers of `libtsk`. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `_TSK_INCS_H` | Include guard | Prevents duplicate inclusion | +| `__STDC_FORMAT_MACROS` | Macro | Enables C99 format specifiers (e.g., `PRId64`) from `` | +| `TSK_MULTITHREAD_LIB` | Macro | Enables multithreading support across the TSK library | + +**Included headers:** + +| Header | Purpose | +|---|---| +| `` | POSIX API (file I/O, process control) | +| `` | Fixed-width integer types and format macros | +| `` | System parameters and limits | + +## Usage Example + +Include this header when building against `libtsk` to ensure consistent type definitions and threading configuration: + +```c +#include "tsk_incs.h" + +// PRId64 and fixed-width types are now available +#include + +void print_offset(int64_t offset) { + printf("Offset: %" PRId64 "\n", offset); +} +``` + +## Notes + +- This file is **auto-generated** by the `./configure` script — do not edit manually. +- `TSK_MULTITHREAD_LIB` must be defined before including other TSK headers to activate thread-safe code paths. +- `__STDC_FORMAT_MACROS` is explicitly guarded and defined to ensure `` exposes `PRI*`/`SCN*` format specifiers in C++ translation units. \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_config.md b/libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_config.md new file mode 100644 index 00000000000..5009fd962f7 --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_config.md @@ -0,0 +1,47 @@ + +Auto-generated build configuration header for **The Sleuth Kit (TSK) v4.11.0**, produced by `autoconf`/`configure`. It defines preprocessor macros that reflect the capabilities and available libraries/headers detected on the target build system. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"sleuthkit"` | +| `PACKAGE_VERSION` | `"4.11.0"` | +| `PACKAGE_STRING` | `"sleuthkit 4.11.0"` | + +### Feature Availability Macros +Macros prefixed with `HAVE_` indicate detected system capabilities: + +- **Standard headers**: `HAVE_STDINT_H`, `HAVE_STDLIB_H`, `HAVE_STRING_H`, `HAVE_UNISTD_H`, `HAVE_SYS_STAT_H` +- **Runtime functions**: `HAVE_FSEEKO`, `HAVE_GETLINE`, `HAVE_GETRUSAGE`, `HAVE_VASPRINTF`, `HAVE_VPRINTF` +- **Threading**: `HAVE_PTHREAD` +- **Libraries**: `HAVE_LIBZ`, `HAVE_LIBDL` +- **Optional forensic libs** *(disabled)*: `HAVE_LIBAFFLIB`, `HAVE_LIBEWF`, `HAVE_LIBSQLITE3`, `HAVE_LIBVHDI`, `HAVE_LIBVMDK` + +### Platform / Compatibility Flags +- `HAVE_CXX14` — C++14 compiler support confirmed +- `_DARWIN_USE_64_BIT_INODE` — 64-bit inode support on macOS 10.5+ +- `LSTAT_FOLLOWS_SLASHED_SYMLINK` — `lstat` symlink trailing-slash behavior +- `SELECT_TYPE_ARG1/234/5` — Correct types for `select()` arguments + +## Usage Example + +```c +#include "tsk/tsk_config.h" + +#ifdef HAVE_PTHREAD + /* Enable multi-threaded code paths */ + pthread_t worker; +#endif + +#ifdef HAVE_LIBEWF + /* Use Expert Witness Format support */ + ewf_open(image_path); +#else + /* Fall back to raw image handling */ + raw_open(image_path); +#endif +``` + +> **Note:** This file is auto-generated by `./configure` and should not be edited manually. Re-run `configure` to regenerate it for a different target platform. \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_incs.md b/libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_incs.md new file mode 100644 index 00000000000..47a48ef2543 --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/linux/x86_64/tsk/.tsk_incs.md @@ -0,0 +1,31 @@ + +Convenience header that consolidates essential system includes and preprocessor definitions required by programs linking against `libtsk` (The Sleuth Kit library). + +## Key Components + +| Definition / Include | Purpose | +|---|---| +| `__STDC_FORMAT_MACROS` | Enables C99 `PRI*`/`SCN*` format macros (e.g., `PRId64`) from `` in C++ translation units | +| `` | POSIX standard symbols (`read`, `write`, `close`, etc.) | +| `` | Fixed-width integer types and their printf/scanf format specifiers | +| `` | System parameters and utility macros (e.g., `MIN`, `MAX`, `MAXPATHLEN`) | +| `TSK_MULTITHREAD_LIB` | Activates thread-safety code paths within `libtsk` | + +## Usage Example + +```c +// Include this header before any other libtsk headers +// to ensure the correct configuration context is set up. + +#include "tsk_incs.h" +#include "tsk/libtsk.h" + +int main(void) { + // PRId64 is available because __STDC_FORMAT_MACROS is defined + TSK_OFF_T offset = 512; + printf("Offset: %" PRId64 "\n", (int64_t)offset); + return 0; +} +``` + +> **Note:** This header is auto-generated by the `./configure` script and should not be edited manually. It is typically the first header included in any translation unit that uses `libtsk`, ensuring consistent platform and format-macro definitions across the build. \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_config.md b/libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_config.md new file mode 100644 index 00000000000..61cd483dbd9 --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_config.md @@ -0,0 +1,49 @@ + +Auto-generated build configuration header for The Sleuth Kit (TSK) v4.11.0, produced by `autoconf`/`configure` to capture platform capability detection results as preprocessor macros. + +## Key Components + +### Package Identity +| Macro | Value | +|---|---| +| `PACKAGE` / `PACKAGE_NAME` | `sleuthkit` | +| `PACKAGE_VERSION` / `VERSION` | `4.11.0` | +| `PACKAGE_STRING` | `sleuthkit 4.11.0` | + +### Feature Detection Categories + +- **Standard headers** — `HAVE_STDINT_H`, `HAVE_STDLIB_H`, `HAVE_UNISTD_H`, `HAVE_STRING_H`, `HAVE_STDBOOL_H`, etc. +- **System capabilities** — `HAVE_ALLOCA`, `HAVE_PTHREAD`, `HAVE_FSEEKO`, `HAVE_GETLINE`, `HAVE_GETRUSAGE` +- **String utilities** — `HAVE_STRLCAT`, `HAVE_STRLCPY`, `HAVE_VASPRINTF`, `HAVE_VPRINTF` +- **Error handling** — `HAVE_ERR`, `HAVE_ERR_H`, `HAVE_ERRX`, `HAVE_WARN`, `HAVE_WARNX` +- **Required libraries** — `HAVE_LIBSQLITE3`, `HAVE_LIBZ`, `HAVE_LIBDL`, `HAVE_LIBSTDC__` +- **Optional libraries** — AFFLIB, libewf, libvhdi, libvmdk (all disabled/undef in this build) +- **C++14 support** — `HAVE_CXX14` +- **Platform portability** — `SELECT_TYPE_ARG1/234/5`, `_DARWIN_USE_64_BIT_INODE` + +## Usage Example + +```c +#include "tsk/tsk_config.h" + +/* Conditionally compile large file support */ +#ifdef HAVE_FSEEKO + fseeko(fp, offset, SEEK_SET); +#else + fseek(fp, (long)offset, SEEK_SET); +#endif + +/* Use safe string copy if available */ +#ifdef HAVE_STRLCPY + strlcpy(dest, src, sizeof(dest)); +#else + strncpy(dest, src, sizeof(dest) - 1); +#endif + +/* Conditional POSIX thread support */ +#ifdef HAVE_PTHREAD + pthread_create(&thread, NULL, worker_fn, NULL); +#endif +``` + +> **Note:** This file is auto-generated — do not edit manually. Re-run `./configure` to regenerate for your target platform. Optional forensic image format libraries (AFFLIB, libewf, libvhdi, libvmdk) are not enabled in this build configuration. \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_incs.md b/libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_incs.md new file mode 100644 index 00000000000..750a21c6b99 --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/macos/aarch64/tsk/.tsk_incs.md @@ -0,0 +1,30 @@ + +Convenience umbrella header for TSK (The Sleuth Kit) library consumers, auto-generated by `./configure` to bundle essential compile-time dependencies and configuration flags. + +## Key Components + +| Element | Description | +|---|---| +| `__STDC_FORMAT_MACROS` | Ensures `PRI*`/`SCN*` format macros from `` are exposed in C++ builds | +| `TSK_MULTITHREAD_LIB` | Preprocessor flag that enables multithreading support within libtsk | +| `` | POSIX standard symbolic constants and types | +| `` | Fixed-width integer format specifiers | +| `` | System parameters and limits | + +## Usage Example + +```c +// Include as the first header in any libtsk consumer +#include "tsk_incs.h" + +// TSK_MULTITHREAD_LIB is now defined — thread-safe libtsk calls are enabled +// Fixed-width integer format macros are available +uint64_t offset = 0; +printf("Offset: %" PRIu64 "\n", offset); +``` + +## Notes + +- This header is **auto-generated** by the `./configure` script — do not edit manually. +- The include guard `_TSK_INCS_H` prevents duplicate inclusion. +- `__STDC_FORMAT_MACROS` is defined **before** `` to guarantee format macro availability in C++ translation units where the standard does not expose them by default. \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_config.md b/libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_config.md new file mode 100644 index 00000000000..5e0a43082bf --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_config.md @@ -0,0 +1,53 @@ + +Auto-generated build configuration header for **The Sleuth Kit (TSK) v4.11.0**, produced by `autoconf`/`configure` to record platform capability detections and package metadata for conditional compilation. + +## Key Components + +### Package Metadata +| Macro | Value | +|---|---| +| `PACKAGE_NAME` | `"sleuthkit"` | +| `PACKAGE_VERSION` | `"4.11.0"` | +| `PACKAGE_STRING` | `"sleuthkit 4.11.0"` | + +### Enabled Library Support +- `HAVE_LIBSQLITE3` — SQLite3 database support +- `HAVE_LIBZ` — zlib compression support +- `HAVE_LIBDL` — dynamic linking support +- `HAVE_PTHREAD` — POSIX thread support +- `HAVE_CXX14` — C++14 compiler support + +### Disabled Optional Libraries +- `HAVE_LIBAFFLIB` — AFF forensic image format (not found) +- `HAVE_LIBEWF` — Expert Witness Format support (not found) +- `HAVE_LIBVHDI` / `HAVE_LIBVMDK` — VHD/VMDK virtual disk formats (not found) +- `HAVE_LIBOPENSSL` — OpenSSL cryptography (not found) + +### Platform Capabilities +- `HAVE_FSEEKO`, `HAVE_GETLINE`, `HAVE_VASPRINTF` — POSIX function availability +- `HAVE_STRLCAT`, `HAVE_STRLCPY` — BSD safe string functions +- `_DARWIN_USE_64_BIT_INODE` — macOS 64-bit inode compatibility +- `SELECT_TYPE_ARG*` — platform-specific `select()` argument types + +## Usage Example + +```c +#include "tsk/tsk_config.h" + +/* Conditionally compile EWF image support */ +#ifdef HAVE_LIBEWF + #include + // Enable EWF disk image reading +#endif + +/* Use safe string functions when available */ +#ifdef HAVE_STRLCPY + strlcpy(dest, src, sizeof(dest)); +#else + strncpy(dest, src, sizeof(dest) - 1); +#endif + +/* Check package version at runtime */ +printf("Built with %s\n", PACKAGE_STRING); +// Output: Built with sleuthkit 4.11.0 +``` \ No newline at end of file diff --git a/libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_incs.md b/libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_incs.md new file mode 100644 index 00000000000..0989f2062da --- /dev/null +++ b/libraries/cmake/source/sleuthkit/config/macos/x86_64/tsk/.tsk_incs.md @@ -0,0 +1,35 @@ + +Central include header for the TSK (The Sleuth Kit) library, providing essential standard library dependencies and compile-time configuration for programs consuming `libtsk`. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `_TSK_INCS_H` | Guard macro | Prevents multiple inclusion | +| `__STDC_FORMAT_MACROS` | Feature macro | Enables `PRI*` format specifiers from `` | +| `TSK_MULTITHREAD_LIB` | Config macro | Activates multithreading support across the library | + +**Included headers:** + +- `` — POSIX API (file descriptors, process control) +- `` — Fixed-width integer types and `printf`/`scanf` format macros +- `` — System parameters and limits (e.g., `MAXPATHLEN`) + +## Usage Example + +```c +// In a program linking against libtsk: +#include "tsk_incs.h" + +void print_offset(uint64_t offset) { + // PRIu64 is available because __STDC_FORMAT_MACROS is defined + // before is pulled in + printf("Offset: %" PRIu64 "\n", offset); +} +``` + +## Notes + +- This file is **auto-generated** by `./configure` at build time — manual edits will be overwritten. +- `TSK_MULTITHREAD_LIB` must be defined before any TSK headers to ensure thread-safe code paths are compiled in. +- The explicit `__STDC_FORMAT_MACROS` guard ensures C++ translation units (which do not define it by default) correctly expose the `PRI*` macros from ``. \ No newline at end of file diff --git a/libraries/cmake/source/thrift/config/linux/aarch64/thrift/.config.md b/libraries/cmake/source/thrift/config/linux/aarch64/thrift/.config.md new file mode 100644 index 00000000000..4f9922ea189 --- /dev/null +++ b/libraries/cmake/source/thrift/config/linux/aarch64/thrift/.config.md @@ -0,0 +1,53 @@ + +CMake-generated build configuration header that defines package metadata, platform capability flags, and feature availability macros for the Apache Thrift library (v0.15.0). + +## Key Components + +### Package Version Macros +| Macro | Value | +|---|---| +| `PACKAGE_VERSION` | `"0.15.0"` | +| `PACKAGE_STRING` | `" 0.15.0"` | + +### Platform Behavior Defines +| Macro | Purpose | +|---|---| +| `ARITHMETIC_RIGHT_SHIFT` | Signals arithmetic (sign-preserving) right shift behavior | +| `SIGNED_RIGHT_SHIFT_IS` | Encodes signed integer right-shift behavior | +| `STRERROR_R_CHAR_P` | Indicates `strerror_r` returns `char*` (POSIX variant) | + +### Header Availability Flags (`HAVE_*_H`) +Confirms presence of POSIX/system headers including `pthread.h`, `sys/socket.h`, `arpa/inet.h`, `poll.h`, `signal.h`, and others required for cross-platform socket and threading support. + +### Function Availability Flags (`HAVE_*`) +| Macro | Function | +|---|---| +| `HAVE_GETHOSTBYNAME` | DNS hostname resolution | +| `HAVE_GETHOSTBYNAME_R` | Thread-safe DNS resolution | +| `HAVE_STRERROR_R` | Thread-safe error string lookup | +| `HAVE_SCHED_GET_PRIORITY_MAX/MIN` | Thread scheduling priority bounds | + +## Usage Example + +```c +#include "config.h" + +/* Conditional compilation based on detected headers */ +#ifdef HAVE_PTHREAD_H +#include +#endif + +#ifdef HAVE_STRERROR_R + /* Use thread-safe variant */ + char buf[256]; + strerror_r(errno, buf, sizeof(buf)); +#else + /* Fallback to non-reentrant version */ + char *msg = strerror(errno); +#endif + +/* Version check at runtime */ +printf("Thrift version: %s\n", PACKAGE_VERSION); +``` + +> **Note:** This file is auto-generated by CMake from `config.h.in` — do not edit manually. Modify `CMakeLists.txt` or `config.h.in` to change detected features. \ No newline at end of file diff --git a/libraries/cmake/source/thrift/config/linux/x86_64/thrift/.config.md b/libraries/cmake/source/thrift/config/linux/x86_64/thrift/.config.md new file mode 100644 index 00000000000..f05a5c66251 --- /dev/null +++ b/libraries/cmake/source/thrift/config/linux/x86_64/thrift/.config.md @@ -0,0 +1,50 @@ + +CMake-generated build configuration header that captures compile-time platform detection results, version metadata, and system capability flags for the Apache Thrift library (v0.17.0). + +## Key Components + +### Version Metadata +| Macro | Value | +|---|---| +| `PACKAGE_VERSION` | `"0.17.0"` | +| `PACKAGE_STRING` | `" 0.17.0"` | + +### Platform Behavior Defines +| Macro | Purpose | +|---|---| +| `ARITHMETIC_RIGHT_SHIFT` | Right-shift behavior for signed integers | +| `SIGNED_RIGHT_SHIFT_IS` | Signed integer right-shift mode (set to `1`) | +| `STRERROR_R_CHAR_P` | Indicates `strerror_r` returns `char *` | + +### Header Availability Flags +Presence of system headers detected at CMake configure time, including: +- Networking: `HAVE_ARPA_INET_H`, `HAVE_NETDB_H`, `HAVE_NETINET_IN_H`, `HAVE_SYS_SOCKET_H` +- I/O & polling: `HAVE_POLL_H`, `HAVE_SYS_SELECT_H`, `HAVE_FCNTL_H` +- Threading: `HAVE_PTHREAD_H`, `HAVE_SCHED_H` +- POSIX standard: `HAVE_UNISTD_H`, `HAVE_STDINT_H`, `HAVE_INTTYPES_H` + +### Function Availability Flags +| Macro | Function | +|---|---| +| `HAVE_GETHOSTBYNAME` | `gethostbyname()` | +| `HAVE_GETHOSTBYNAME_R` | Thread-safe hostname resolution | +| `HAVE_STRERROR_R` | Thread-safe error string | +| `HAVE_SCHED_GET_PRIORITY_MAX/MIN` | Thread scheduling priority | + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile thread-safe DNS resolution */ +#ifdef HAVE_GETHOSTBYNAME_R + gethostbyname_r(hostname, &result, buf, buflen, &hp, &err); +#elif defined(HAVE_GETHOSTBYNAME) + hp = gethostbyname(hostname); +#endif + +/* Log package version at startup */ +printf("Thrift version: %s\n", PACKAGE_VERSION); +``` + +> **Note:** This file is auto-generated by CMake from `config.h.in`. Do not edit manually — regenerate via `cmake ..` in your build directory. Some macros (e.g., `ARITHMETIC_RIGHT_SHIFT`) are hardcoded pending full CMake macro port from the original autoconf scripts. \ No newline at end of file diff --git a/libraries/cmake/source/thrift/config/macos/aarch64/thrift/.config.md b/libraries/cmake/source/thrift/config/macos/aarch64/thrift/.config.md new file mode 100644 index 00000000000..f2c5b77248f --- /dev/null +++ b/libraries/cmake/source/thrift/config/macos/aarch64/thrift/.config.md @@ -0,0 +1,53 @@ + +CMake-generated build configuration header that captures compile-time feature detection results, package version metadata, and platform capability flags for the Apache Thrift library (v0.17.0). + +## Key Components + +### Package Version Macros +| Macro | Value | +|---|---| +| `PACKAGE_VERSION` | `"0.17.0"` | +| `PACKAGE_STRING` | `" 0.17.0"` | + +### Arithmetic Behavior Defines +| Macro | Purpose | +|---|---| +| `ARITHMETIC_RIGHT_SHIFT` | Marks right-shift behavior as arithmetic (value: `1`) | +| `SIGNED_RIGHT_SHIFT_IS` | Indicates signed integer right-shift effect (value: `1`) | + +### Header Availability Flags (`HAVE_*`) +Confirms presence of POSIX/system headers including: +- **Networking:** `arpa/inet.h`, `netdb.h`, `netinet/in.h`, `sys/socket.h`, `sys/un.h` +- **I/O & Polling:** `poll.h`, `sys/poll.h`, `sys/select.h`, `fcntl.h` +- **Threading:** `pthread.h`, `sched.h` +- **System:** `unistd.h`, `stdint.h`, `inttypes.h`, `signal.h`, `sys/resource.h` + +### Function Availability Flags (`HAVE_*`) +| Macro | Function | +|---|---| +| `HAVE_GETHOSTBYNAME` | `gethostbyname` | +| `HAVE_STRERROR_R` | `strerror_r` | +| `HAVE_SCHED_GET_PRIORITY_MAX` | `sched_get_priority_max` | +| `HAVE_SCHED_GET_PRIORITY_MIN` | `sched_get_priority_min` | + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile based on detected platform features */ +#ifdef HAVE_PTHREAD_H + #include +#endif + +#ifdef HAVE_POLL_H + #include +#elif defined(HAVE_SYS_POLL_H) + #include +#endif + +/* Access version info at runtime */ +const char *version = PACKAGE_VERSION; /* "0.17.0" */ +``` + +> **Note:** This file is auto-generated by CMake from `config.h.in`. Do not edit manually — regenerate via CMake configuration instead. \ No newline at end of file diff --git a/libraries/cmake/source/thrift/config/macos/x86_64/thrift/.config.md b/libraries/cmake/source/thrift/config/macos/x86_64/thrift/.config.md new file mode 100644 index 00000000000..9d3ea0a0e6f --- /dev/null +++ b/libraries/cmake/source/thrift/config/macos/x86_64/thrift/.config.md @@ -0,0 +1,48 @@ + +CMake-generated build configuration header that declares compile-time feature detection macros for the Apache Thrift library (v0.17.0), capturing available system headers, functions, and platform-specific behavior. + +## Key Components + +### Version Identifiers +- `PACKAGE_VERSION` — Package version string (`"0.17.0"`) +- `PACKAGE_STRING` — Full package name and version string + +### Platform/Behavior Defines +- `ARITHMETIC_RIGHT_SHIFT` — Marks arithmetic right shift behavior for signed integers +- `SIGNED_RIGHT_SHIFT_IS` — Indicates effect of right-shift on negative signed integers + +### Header Availability Guards (`HAVE_*_H`) +Feature flags confirming presence of system headers including: +- Networking: `HAVE_ARPA_INET_H`, `HAVE_NETDB_H`, `HAVE_NETINET_IN_H`, `HAVE_SYS_SOCKET_H` +- I/O & polling: `HAVE_POLL_H`, `HAVE_SYS_SELECT_H`, `HAVE_FCNTL_H` +- Threading: `HAVE_PTHREAD_H` +- POSIX misc: `HAVE_UNISTD_H`, `HAVE_SCHED_H`, `HAVE_SYS_RESOURCE_H` + +### Function Availability Guards (`HAVE_*`) +- `HAVE_GETHOSTBYNAME` — DNS lookup function available +- `HAVE_STRERROR_R` — Thread-safe error string function available +- `HAVE_SCHED_GET_PRIORITY_MAX` / `HAVE_SCHED_GET_PRIORITY_MIN` — Thread priority scheduling available + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile platform-specific code */ +#ifdef HAVE_PTHREAD_H + #include +#endif + +#ifdef HAVE_POLL_H + #include +#elif defined(HAVE_SYS_POLL_H) + #include +#endif + +/* Check version at compile time */ +#if defined(PACKAGE_VERSION) + printf("Thrift version: %s\n", PACKAGE_VERSION); +#endif +``` + +> **Note:** This file is auto-generated by CMake from `config.h.in` — do not edit manually. Regenerate by re-running the CMake configure step. \ No newline at end of file diff --git a/libraries/cmake/source/thrift/config/windows/aarch64/thrift/.config.md b/libraries/cmake/source/thrift/config/windows/aarch64/thrift/.config.md new file mode 100644 index 00000000000..6aa7da8f742 --- /dev/null +++ b/libraries/cmake/source/thrift/config/windows/aarch64/thrift/.config.md @@ -0,0 +1,49 @@ + +CMake-generated build configuration header that exposes compile-time feature detection flags for platform capability, available headers, and package metadata. + +## Key Components + +### Package Metadata +| Macro | Value | +|---|---| +| `PACKAGE_VERSION` | `"0.17.0"` | +| `PACKAGE_STRING` | `" 0.17.0"` | +| Other `PACKAGE_*` macros | Undefined (undef'd) | + +### Arithmetic/Shift Behavior +- `ARITHMETIC_RIGHT_SHIFT` — Set to `1`; indicates arithmetic (sign-preserving) right shift behavior +- `SIGNED_RIGHT_SHIFT_IS` — Set to `1`; mirrors the above (both are hardcoded placeholders pending full CMake macro port) + +### Header Availability (`HAVE_*_H`) +Defines presence of system headers detected at build time. Currently **enabled**: +- `HAVE_FCNTL_H` +- `HAVE_INTTYPES_H` +- `HAVE_SIGNAL_H` +- `HAVE_STDINT_H` +- `HAVE_SYS_STAT_H` +- `HAVE_AF_UNIX_H` + +Most POSIX/Unix headers (`arpa/inet.h`, `pthread.h`, `sys/socket.h`, etc.) are **disabled** — indicating a Windows-targeted build. + +### Function Availability (`HAVE_*`) +All function detection macros (`gethostbyname`, `strerror_r`, `sched_get_priority_*`) are currently **undefined**. + +## Usage Example + +```c +#include "config.h" + +/* Conditional compilation based on detected headers */ +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef HAVE_AF_UNIX_H +#include /* Windows AF_UNIX socket support */ +#endif + +/* Version check at runtime */ +const char *version = PACKAGE_VERSION; /* "0.17.0" */ +``` + +> **Note:** This file is auto-generated by CMake from `config.h.in`. Do not edit manually — modify `config.h.in` or the corresponding `CMakeLists.txt` instead. The `ARITHMETIC_RIGHT_SHIFT` and `SIGNED_RIGHT_SHIFT_IS` macros are hardcoded stubs pending a full CMake port of `ax_signed_right_shift.m4`. \ No newline at end of file diff --git a/libraries/cmake/source/thrift/config/windows/x86_64/thrift/.config.md b/libraries/cmake/source/thrift/config/windows/x86_64/thrift/.config.md new file mode 100644 index 00000000000..a187fbbe1a1 --- /dev/null +++ b/libraries/cmake/source/thrift/config/windows/x86_64/thrift/.config.md @@ -0,0 +1,50 @@ + +CMake-generated build configuration header for the Apache Thrift library (version 0.17.0), defining platform capability flags, available header files, and function availability for conditional compilation. + +## Key Components + +### Package Version Macros +| Macro | Value | Description | +|---|---|---| +| `PACKAGE_VERSION` | `"0.17.0"` | Short version string | +| `PACKAGE_STRING` | `" 0.17.0"` | Full package name + version | + +### Integer Arithmetic Defines +| Macro | Value | Description | +|---|---|---| +| `ARITHMETIC_RIGHT_SHIFT` | `1` | Possible value for signed right shift behavior | +| `SIGNED_RIGHT_SHIFT_IS` | `1` | Indicates right shift behavior on negative integers | + +### Available Header Files (enabled) +| Macro | Description | +|---|---| +| `HAVE_FCNTL_H` | `` file control | +| `HAVE_INTTYPES_H` | `` integer types | +| `HAVE_SIGNAL_H` | `` signal handling | +| `HAVE_STDINT_H` | `` fixed-width integers | +| `HAVE_SYS_STAT_H` | `` file status | +| `HAVE_AF_UNIX_H` | `` Unix domain sockets (Windows) | + +> **Note:** Network-related headers (`arpa/inet.h`, `netdb.h`, `sys/socket.h`, etc.) and POSIX headers (`unistd.h`, `pthread.h`) are currently **disabled** — indicating a Windows-targeted build. + +## Usage Example + +```c +#include "config.h" + +/* Conditional compilation based on detected headers */ +#ifdef HAVE_STDINT_H + #include +#endif + +#ifdef HAVE_SYS_STAT_H + #include +#endif + +/* Check signed right shift behavior at compile time */ +#if SIGNED_RIGHT_SHIFT_IS == ARITHMETIC_RIGHT_SHIFT + /* Safe to use arithmetic right shift on this platform */ +#endif +``` + +> This file is **auto-generated** by CMake from `config.h.in`. Do not edit manually — modify the CMake configuration instead. \ No newline at end of file diff --git a/libraries/cmake/source/thrift/patches/.random_shuffle.md b/libraries/cmake/source/thrift/patches/.random_shuffle.md new file mode 100644 index 00000000000..b645d8d35b2 --- /dev/null +++ b/libraries/cmake/source/thrift/patches/.random_shuffle.md @@ -0,0 +1,35 @@ + +Provides a modern, cryptographically-seeded replacement for the deprecated `std::random_shuffle` using a Mersenne Twister PRNG, compatible with C++17 and later where the original was removed. + +## Key Components + +- **`std::random_shuffle`** — Template function injected into the `std` namespace that accepts a random-access iterator range (`first`, `last`) and shuffles elements in-place using: + - `std::random_device` — Non-deterministic seed source + - `std::mt19937` — Mersenne Twister engine seeded from the random device + - `std::shuffle` — The underlying modern shuffle algorithm + +## Usage Example + +```cpp +#include "random_shuffle.h" +#include +#include + +int main() { + std::vector items = {1, 2, 3, 4, 5}; + + // Drop-in replacement for deprecated std::random_shuffle + std::random_shuffle(items.begin(), items.end()); + + for (auto& i : items) { + std::cout << i << " "; + } + return 0; +} +``` + +## Notes + +- **Drop-in compatibility** — Maintains the same two-argument signature as the removed `std::random_shuffle`, allowing legacy call sites to work without modification. +- **Seeding** — Each call creates a fresh `std::random_device` and `std::mt19937` instance, ensuring independent, non-repeatable shuffles. +- **Thread safety** — A new RNG is instantiated per call; no shared state between calls. \ No newline at end of file diff --git a/libraries/cmake/source/util-linux/config/aarch64/.config.md b/libraries/cmake/source/util-linux/config/aarch64/.config.md new file mode 100644 index 00000000000..db00a066ced --- /dev/null +++ b/libraries/cmake/source/util-linux/config/aarch64/.config.md @@ -0,0 +1,48 @@ + +Autoconf-generated build configuration header for the `util-linux` package, defining platform capabilities, available headers, functions, libraries, and feature flags detected at configure time. + +## Key Components + +### Feature Flags +- `AGETTY_RELOAD` — Enables `agetty --reload` functionality +- `CHFN_CHSH_PASSWORD` — Requires password entry for `chfn`/`chsh` +- `ENABLE_NLS` — Enables native language support (i18n) +- `HAVE_BTRFS_SUPPORT` — Enables Btrfs filesystem support +- `HAVE_PTY` — Enables pseudo-terminal support + +### Path Configuration +- `CONFIG_ADJTIME_PATH` — Path to hwclock adjtime file (`/etc/adjtime`) +- `FS_SEARCH_PATH` — Search path for filesystem helpers + +### Library Detection Macros +Indicates presence of optional libraries: +- `HAVE_LIBBLKID`, `HAVE_LIBUUID` — Block device ID and UUID libraries +- `HAVE_LIBCRYPT`, `HAVE_LIBUTIL` — Crypto and utility libraries +- Various disabled optional libs: `HAVE_LIBSELINUX`, `HAVE_LIBSYSTEMD`, `HAVE_LIBNCURSES`, etc. + +### POSIX/System Function Availability +Covers detection of ~60 system functions including `clock_gettime`, `fstatat`, `openat`, `nanosleep`, `qsort_r`, `inotify_init`, and others. + +### Header Availability +Covers Linux kernel headers (`linux/blkpg.h`, `linux/capability.h`, etc.) and standard POSIX headers. + +## Usage Example + +```c +#include "config.h" + +#ifdef HAVE_INOTIFY_INIT1 + fd = inotify_init1(IN_CLOEXEC); +#elif defined(HAVE_INOTIFY_INIT) + fd = inotify_init(); +#else + #error "inotify not available" +#endif + +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); +#endif +``` + +> This file is auto-generated by `./configure` and should not be edited manually. Rebuild with `autoconf` + `configure` to regenerate. \ No newline at end of file diff --git a/libraries/cmake/source/util-linux/config/x86_64/.config.md b/libraries/cmake/source/util-linux/config/x86_64/.config.md new file mode 100644 index 00000000000..22984853c32 --- /dev/null +++ b/libraries/cmake/source/util-linux/config/x86_64/.config.md @@ -0,0 +1,51 @@ + +Auto-generated build configuration header for the `util-linux` package, produced by `autoconf`/`configure`. It defines platform capability macros that control conditional compilation across the codebase. + +## Key Components + +### Feature Flags +- **`AGETTY_RELOAD`** — Enables the `--reload` feature for `agetty` +- **`CHFN_CHSH_PASSWORD`** — Requires password entry for `chfn`/`chsh` +- **`ENABLE_NLS`** — Enables native language support (translations) +- **`HAVE_PTY`** — Indicates PTY (pseudo-terminal) support is available + +### Path Constants +- **`CONFIG_ADJTIME_PATH`** — Path to `hwclock` adjtime file (`/etc/adjtime`) +- **`FS_SEARCH_PATH`** — Search path for filesystem helpers (`/sbin:/sbin/fs.d:/sbin/fs`) + +### Library Availability Macros +Indicates presence of required libraries detected at configure time: + +| Macro | Library | +|---|---| +| `HAVE_LIBBLKID` | `libblkid` | +| `HAVE_LIBUUID` | `libuuid` | +| `HAVE_LIBCRYPT` | `libcrypt` | +| `HAVE_LIBUTIL` | `libutil` | + +### Header & Function Detection +Hundreds of `HAVE_*` macros guard optional headers (e.g., `HAVE_BYTESWAP_H`, `HAVE_CRYPT_H`) and functions (e.g., `HAVE_OPENAT`, `HAVE_FSTATAT`, `HAVE_NANOSLEEP`). + +## Usage Example + +```c +#include "config.h" + +/* Conditionally compile NLS support */ +#ifdef ENABLE_NLS +#include +setlocale(LC_ALL, ""); +#endif + +/* Conditionally use openat() if available */ +#ifdef HAVE_OPENAT +fd = openat(dirfd, path, O_RDONLY); +#else +fd = open(path, O_RDONLY); +#endif + +/* Use configured adjtime path */ +FILE *f = fopen(CONFIG_ADJTIME_PATH, "r"); +``` + +> **Note:** This file is auto-generated by `./configure` — do not edit manually. Regenerate by running `autoconf` and `./configure` with appropriate options. \ No newline at end of file diff --git a/libraries/cmake/source/util-linux/generated/aarch64/include/blkid/.blkid.md b/libraries/cmake/source/util-linux/generated/aarch64/include/blkid/.blkid.md new file mode 100644 index 00000000000..5394a076009 --- /dev/null +++ b/libraries/cmake/source/util-linux/generated/aarch64/include/blkid/.blkid.md @@ -0,0 +1,97 @@ + +Public C header for `libblkid`, defining the complete interface for identifying and probing block devices, including filesystem superblocks, partition tables, and device topology on Linux systems. + +## Key Components + +### Opaque Types +| Type | Description | +|---|---| +| `blkid_cache` | System-wide device information cache | +| `blkid_dev` | Single block device entry | +| `blkid_probe` | Low-level probing context | +| `blkid_topology` | Device I/O topology data | +| `blkid_partlist` | Detected partition list | +| `blkid_partition` | Single partition metadata | +| `blkid_parttable` | Partition table metadata | +| `blkid_loff_t` | 64-bit signed offset/size type | + +### API Groups + +- **Cache API** — `blkid_get_cache()`, `blkid_put_cache()`, `blkid_gc_cache()` manage the persistent device cache +- **Device Iteration** — `blkid_dev_iterate_begin/next/end` enumerate cached devices with optional tag-based filtering +- **Tag Resolution** — `blkid_get_tag_value()`, `blkid_evaluate_tag()`, `blkid_evaluate_spec()` resolve UUID/LABEL/TYPE lookups +- **Low-level Probe** — `blkid_new_probe()`, `blkid_probe_set_device()` configure direct device probing +- **Superblock Probing** — enable/filter filesystem detection via `BLKID_SUBLKS_*` flags +- **Topology Probing** — retrieve alignment offset, min/optimal I/O size, sector sizes +- **Partition Probing** — enumerate partitions, access table type, entry details via `BLKID_PARTS_*` flags + +### Key Constants + +```c +BLKID_SUBLKS_DEFAULT // Label + UUID + Type + SecType +BLKID_FLTR_NOTIN // Exclude listed types +BLKID_FLTR_ONLYIN // Include only listed types +BLKID_PARTS_FORCE_GPT // Force GPT interpretation +BLKID_USAGE_FILESYSTEM | BLKID_USAGE_RAID | BLKID_USAGE_CRYPTO +``` + +## Usage Example + +```c +#include +#include + +int main(void) { + blkid_cache cache; + blkid_dev dev; + blkid_dev_iterate iter; + const char *tag_type, *tag_value; + + /* Load (or create) the blkid cache */ + if (blkid_get_cache(&cache, NULL) != 0) { + fprintf(stderr, "Failed to open blkid cache\n"); + return 1; + } + + /* Probe all block devices into the cache */ + blkid_probe_all(cache); + + /* Iterate over every device in the cache */ + iter = blkid_dev_iterate_begin(cache); + while (blkid_dev_next(iter, &dev) == 0) { + printf("Device: %s\n", blkid_dev_devname(dev)); + + /* Iterate tags (UUID, LABEL, TYPE, ...) on each device */ + blkid_tag_iterate ti = blkid_tag_iterate_begin(dev); + while (blkid_tag_next(ti, &tag_type, &tag_value) == 0) + printf(" %s=%s\n", tag_type, tag_value); + blkid_tag_iterate_end(ti); + } + blkid_dev_iterate_end(iter); + + /* Quick tag lookup without iterating */ + char *uuid = blkid_get_tag_value(cache, "UUID", "/dev/sda1"); + if (uuid) { + printf("UUID of /dev/sda1: %s\n", uuid); + free(uuid); + } + + blkid_put_cache(cache); + return 0; +} +``` + +```c +/* Low-level probe example: read superblock info directly */ +blkid_probe pr = blkid_new_probe_from_filename("/dev/sda1"); +blkid_probe_enable_superblocks(pr, 1); +blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_DEFAULT); + +if (blkid_do_safeprobe(pr) == 0) { + const char *fstype; + size_t len; + blkid_probe_lookup_value(pr, "TYPE", &fstype, &len); + printf("Filesystem: %s\n", fstype); +} +blkid_free_probe(pr); +``` \ No newline at end of file diff --git a/libraries/cmake/source/util-linux/generated/x86_64/include/blkid/.blkid.md b/libraries/cmake/source/util-linux/generated/x86_64/include/blkid/.blkid.md new file mode 100644 index 00000000000..ff7fa2720e6 --- /dev/null +++ b/libraries/cmake/source/util-linux/generated/x86_64/include/blkid/.blkid.md @@ -0,0 +1,64 @@ + +Public C header for `libblkid` (version 2.35.2), defining the full API surface for identifying and probing block devices, including filesystem superblocks, partition tables, and device topology. + +## Key Components + +### Opaque Types +| Type | Purpose | +|---|---| +| `blkid_cache` | System-wide device cache | +| `blkid_dev` | Single device entry | +| `blkid_probe` | Low-level probing context | +| `blkid_topology` | Device I/O topology info | +| `blkid_partlist` / `blkid_partition` / `blkid_parttable` | Partition enumeration | +| `blkid_loff_t` | 64-bit offset/size type | + +### API Groups +- **Cache API** (`blkid_get_cache`, `blkid_put_cache`, `blkid_gc_cache`) — high-level device database +- **Device lookup** (`blkid_get_dev`, `blkid_find_dev_with_tag`, `blkid_verify`) — find/create device entries +- **Tag API** (`blkid_tag_iterate_*`, `blkid_get_tag_value`) — iterate and query key/value tags (UUID, LABEL, TYPE) +- **Low-level probe API** (`blkid_new_probe`, `blkid_probe_set_device`) — direct superblock/partition probing +- **Superblock flags** (`BLKID_SUBLKS_*`) — control which fields are extracted (label, UUID, type, magic, etc.) +- **Topology API** (`blkid_topology_get_*`) — alignment, sector sizes, optimal I/O size +- **Partition API** (`blkid_probe_get_partitions`, `blkid_partlist_*`) — enumerate partitions and tables + +### Device Lookup Flags +```text +BLKID_DEV_FIND (0x0000) - lookup only, return NULL if missing +BLKID_DEV_CREATE (0x0001) - create empty entry if not found +BLKID_DEV_VERIFY (0x0002) - verify entry matches real device +BLKID_DEV_NORMAL (CREATE | VERIFY) - get a valid, verified entry +``` + +## Usage Example + +```c +#include +#include + +int main(void) { + blkid_cache cache; + blkid_probe pr; + const char *uuid = NULL; + + /* High-level: get UUID from cache */ + blkid_get_cache(&cache, NULL); + uuid = blkid_get_tag_value(cache, "UUID", "/dev/sda1"); + if (uuid) + printf("UUID: %s\n", uuid); + blkid_put_cache(cache); + + /* Low-level: probe superblock directly */ + pr = blkid_new_probe_from_filename("/dev/sda1"); + if (pr) { + blkid_probe_enable_superblocks(pr, 1); + blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_UUID | BLKID_SUBLKS_TYPE | BLKID_SUBLKS_LABEL); + blkid_free_probe(pr); + } + + return 0; +} +``` + +> **Note:** Link with `-lblkid`. Return values follow standard Unix conventions — `0` on success, negative errno on failure. Functions marked `warn_unused_result` should always have their return values checked. \ No newline at end of file diff --git a/openframe/.openframe_authorization_manager.md b/openframe/.openframe_authorization_manager.md new file mode 100644 index 00000000000..5203e846d2e --- /dev/null +++ b/openframe/.openframe_authorization_manager.md @@ -0,0 +1,34 @@ + +Manages the OpenFrame authorization token used for authentication with OpenFrame services. This singleton-style class provides controlled access to storing and retrieving the auth token via a provider-based pattern. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `OpenframeAuthorizationManager` | Class | Non-copyable token manager; instantiation restricted to `OpenframeAuthorizationManagerProvider` | +| `updateToken(token)` | Method | Replaces the stored authorization token | +| `getToken()` | Method | Returns the current authorization token | +| `token_` | Private Field | Internal string storage for the token | +| `OpenframeAuthorizationManagerProvider` | Friend Class | Sole class permitted to construct/destroy instances | + +## Usage Example + +```cpp +// Access is granted only through the provider — never constructed directly. +// Typical usage via the provider pattern: + +OpenframeAuthorizationManager& manager = provider.getManager(); + +// Store a new token received from OpenFrame auth flow +manager.updateToken("Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."); + +// Retrieve the token for an outbound API request +std::string token = manager.getToken(); +httpClient.setHeader("Authorization", token); +``` + +## Design Notes + +- **Non-copyable** (`boost::noncopyable`): Prevents accidental token duplication across object copies. +- **Private constructor/destructor**: Enforces that only `OpenframeAuthorizationManagerProvider` can create or destroy instances, ensuring centralized lifecycle management. +- **`osquery` namespace**: Integrates with the broader osquery agent infrastructure used by the OpenFrame platform. \ No newline at end of file diff --git a/openframe/.openframe_authorization_manager_provider.md b/openframe/.openframe_authorization_manager_provider.md new file mode 100644 index 00000000000..5816f344856 --- /dev/null +++ b/openframe/.openframe_authorization_manager_provider.md @@ -0,0 +1,25 @@ + +Provides a singleton accessor for the `OpenframeAuthorizationManager`, ensuring a single, thread-safe instance is available throughout the osquery process. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `OpenframeAuthorizationManagerProvider` | Class | Non-copyable provider wrapping singleton access to the authorization manager | +| `getInstance()` | Static method | Returns a reference to the lazily-initialized `OpenframeAuthorizationManager` singleton | + +## Usage Example + +```c +#include "openframe_authorization_manager_provider.h" + +// Retrieve the singleton authorization manager and perform an authorization check +auto& authManager = osquery::OpenframeAuthorizationManagerProvider::getInstance(); +authManager.authorize(request); +``` + +## Notes + +- Inherits `boost::noncopyable` — the provider itself cannot be copied or assigned, enforcing singleton semantics +- The `static` local variable inside `getInstance()` guarantees thread-safe initialization (C++11 magic statics) +- Constructor and destructor are private, preventing direct instantiation; access is only via `getInstance()` \ No newline at end of file diff --git a/openframe/.openframe_encryption_service.md b/openframe/.openframe_encryption_service.md new file mode 100644 index 00000000000..7319497e8f5 --- /dev/null +++ b/openframe/.openframe_encryption_service.md @@ -0,0 +1,43 @@ + +AES-256-GCM decryption service header for the OpenFrame platform, providing symmetric key encryption utilities via OpenSSL. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `OpenframeEncryptionService` | Class | Main encryption service wrapping OpenSSL EVP AES-GCM operations | +| `OpenframeEncryptionService(secret)` | Constructor | Initializes the service with a symmetric secret key | +| `decrypt(data)` | Method | Decrypts a Base64-encoded AES-256-GCM ciphertext, returns plaintext string | +| `base64Decode(encoded)` | Method | Decodes a Base64 string into raw bytes | +| `handleOpenSSLError()` | Private Method | Throws `std::runtime_error` with OpenSSL error details on failure | +| `KEY_SIZE` | Constant | 32 bytes (256-bit AES key) | +| `IV_SIZE` | Constant | 12 bytes (96-bit GCM nonce) | +| `TAG_SIZE` | Constant | 16 bytes (128-bit GCM authentication tag) | + +## Usage Example + +```cpp +#include "openframe_encryption_service.h" +#include + +int main() { + // Initialize with a 256-bit secret key + OpenframeEncryptionService enc("my-32-byte-secret-key-goes-here!"); + + // Decrypt Base64-encoded AES-256-GCM payload + try { + std::string ciphertext = "BASE64_ENCODED_ENCRYPTED_PAYLOAD=="; + std::string plaintext = enc.decrypt(ciphertext); + std::cout << "Decrypted: " << plaintext << std::endl; + } catch (const std::runtime_error& e) { + std::cerr << "Decryption failed: " << e.what() << std::endl; + } + + // Standalone Base64 decode + std::vector raw = enc.base64Decode("SGVsbG8gV29ybGQ="); + + return 0; +} +``` + +> **Note:** The secret passed to the constructor must produce a 256-bit (32-byte) derived key. Any OpenSSL failure during decryption throws `std::runtime_error` via `handleOpenSSLError()`. \ No newline at end of file diff --git a/openframe/.openframe_token_extractor.md b/openframe/.openframe_token_extractor.md new file mode 100644 index 00000000000..2d3e20928e6 --- /dev/null +++ b/openframe/.openframe_token_extractor.md @@ -0,0 +1,37 @@ + +Provides a class interface for reading and decrypting authentication tokens stored in encrypted files, bridging file I/O with the OpenFrame encryption layer. + +## Key Components + +### `OpenframeTokenExtractor` (class) + +| Member | Type | Description | +|--------|------|-------------| +| `OpenframeTokenExtractor(encryption_service, token_file_path)` | Constructor | Initializes the extractor with a shared encryption service and the path to the token file | +| `extractToken()` | `std::string` | Reads the token file, decrypts its contents via the injected `OpenframeEncryptionService`, and returns the plaintext token | +| `token_file_path_` | `std::string` (private) | Stored path to the encrypted token file on disk | +| `encryption_service_` | `std::shared_ptr` (private) | Shared ownership of the encryption service used for decryption | + +## Usage Example + +```cpp +#include "openframe_token_extractor.h" +#include "openframe_encryption_service.h" + +// Create a shared encryption service +auto enc_service = std::make_shared(/* config */); + +// Instantiate the extractor with the service and token file path +OpenframeTokenExtractor extractor(enc_service, "/etc/openframe/auth.token"); + +// Extract and decrypt the token +std::string token = extractor.extractToken(); + +// Use the plaintext token for authenticated API calls +``` + +## Notes + +- Uses shared ownership (`std::shared_ptr`) for the encryption service, making it safe to share one service instance across multiple extractors. +- The destructor is defaulted, so no manual resource cleanup is required. +- Depends on `openframe_encryption_service.h` — ensure the encryption service is properly initialized before calling `extractToken()`. \ No newline at end of file diff --git a/openframe/.openframe_token_refresher.md b/openframe/.openframe_token_refresher.md new file mode 100644 index 00000000000..3ec04ff3e39 --- /dev/null +++ b/openframe/.openframe_token_refresher.md @@ -0,0 +1,38 @@ + +Manages background token refresh for OpenFrame authentication by running a periodic refresh loop in a dedicated thread. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `OpenframeTokenRefresher` | Class | Background service that periodically refreshes authentication tokens | +| `OpenframeTokenRefresher(extractor)` | Constructor | Accepts a shared `OpenframeTokenExtractor` instance | +| `start()` | Method | Spawns the background refresh thread | +| `stop()` | Method | Signals the refresh loop to terminate and joins the thread | +| `process()` | Private Method | Core refresh loop executed on the background thread | +| `running_` | `std::atomic` | Thread-safe flag controlling the refresh loop lifecycle | +| `extractor_` | `shared_ptr` | Token extraction dependency injected at construction | + +## Usage Example + +```cpp +#include "openframe_token_refresher.h" +#include "openframe_token_extractor.h" + +// Inject extractor and start background refresh +auto extractor = std::make_shared(); +osquery::OpenframeTokenRefresher refresher(extractor); + +refresher.start(); // begins periodic token refresh loop + +// ... application runs ... + +refresher.stop(); // gracefully stops the background thread +``` + +## Notes + +- Lives in the `osquery` namespace alongside other OpenFrame authentication components +- Depends on `OpenframeTokenExtractor` for token retrieval and `OpenframeAuthorizationManager` for token storage/validation +- Uses `std::atomic` for lock-free lifecycle control and `std::mutex` for any shared state access within `process()` +- `stop()` should always be called before destruction to avoid detached thread behavior \ No newline at end of file diff --git a/osquery/.empty.md b/osquery/.empty.md new file mode 100644 index 00000000000..97488f9c98a --- /dev/null +++ b/osquery/.empty.md @@ -0,0 +1,14 @@ + +A minimal placeholder source file containing only the standard osquery license header, with no functional code. + +## Key Components + +None — this file is intentionally empty. + +## Usage Example + +```cpp +// No usage — this file serves as a licensed empty compilation unit. +// Typically used as a placeholder target in build systems (e.g., CMake) +// when a source file is required but no implementation is needed yet. +``` \ No newline at end of file diff --git a/osquery/carver/.carver.md b/osquery/carver/.carver.md new file mode 100644 index 00000000000..50e1e949180 --- /dev/null +++ b/osquery/carver/.carver.md @@ -0,0 +1,40 @@ + +Defines the file carving subsystem for osquery, providing classes and utilities to copy files from disk, archive them, compress them, and POST the results to a remote endpoint. + +## Key Components + +### `CarverRunnable` +Abstract base class extending `InternalRunnable`. Manages the carver dispatch lifecycle via a static `running_` atomic flag. The `start()` method serially processes all pending carve requests; subclasses must implement `doCarve()`. + +### `CarverRunner` +Templated concrete runner that instantiates a carver of type `T` for each request. Designed to allow test-fake injection by swapping the carver type. Tracks the number of carves attempted in the current run cycle via `carves()`. + +### `Carver` +Core carving class. Constructed with a set of file paths, a GUID, and a request ID. Orchestrates the full pipeline: + +| Method | Description | +|---|---| +| `carve()` | End-to-end entry point: carve → compress → POST | +| `createPaths()` | Sets up temp directory, archive, and compressed output paths | +| `carveAll()` | Blockwise-copies source files into a temp directory | +| `blockwiseCopy()` | Low-level copy using `FLAGS_carver_block_size` (default 8 KB) | +| `postCarve()` | POSTs the compressed archive to the configured TLS endpoint | + +### `scheduleCarves()` +Free function called by the scheduler to dispatch a `CarverRunner` if one is not already running. + +## Usage Example + +```cpp +// Dispatch carves from the scheduler +osquery::scheduleCarves(); + +// Direct use of the Carver pipeline (e.g., in tests via CarverRunner) +std::set paths = {"/etc/passwd", "/var/log/syslog"}; +osquery::Carver carver(paths, "guid-1234", "request-5678"); + +Status status = carver.carve(); // carve → archive → compress → POST +if (!status.ok()) { + LOG(ERROR) << "Carve failed: " << status.getMessage(); +} +``` \ No newline at end of file diff --git a/osquery/carver/.carver_utils.md b/osquery/carver/.carver_utils.md new file mode 100644 index 00000000000..da5baa2fa91 --- /dev/null +++ b/osquery/carver/.carver_utils.md @@ -0,0 +1,40 @@ + +Utility header providing core primitives for osquery's file carving subsystem — constants, database helpers, and scheduling functions used across carver components. + +## Key Components + +### Constants + +| Constant | Value | Purpose | +|---|---|---| +| `kCarvePathPrefix` | `"osquery_carve_"` | Temp filesystem directory prefix | +| `kCarveNamePrefix` | `"carve_"` | Tar archive filename prefix | +| `kCarverDBPrefix` | `"carves."` | Database key namespace prefix | +| `kCarverStatusSuccess` | `"SUCCESS"` | Terminal success state | +| `kCarverStatusScheduled` | `"SCHEDULED"` | Pending/queued state | + +### Globals + +- **`kCarverPendingCarves`** (`std::atomic`) — Optimization flag for `CarverRunner`; avoids spinning up scheduler threads when no carves are queued. + +### Functions + +- **`updateCarveValue(guid, key, value)`** — Template function that reads a carve entry from the database by GUID, parses its JSON, updates a single key, and writes it back. +- **`createCarveGuid()`** — Generates and returns a new UUID string for identifying a carve request. +- **`carvePaths(paths, request_id, carve_guid)`** — Schedules a deferred file carve for the given paths; populates `carve_guid` on success and returns a `Status`. + +## Usage Example + +```c +// Schedule a carve for specific paths +std::set targets = {"/etc/passwd", "/var/log/auth.log"}; +std::string guid; + +Status s = carvePaths(targets, "incident-42", guid); +if (s.ok()) { + // Update a metadata field on the scheduled carve + updateCarveValue(guid, "priority", std::string("high")); +} +``` + +> **Note:** `carvePaths` is non-blocking by design — actual carving is deferred to the scheduler to avoid blocking query execution or parallelism issues. \ No newline at end of file diff --git a/osquery/carver/tests/.carver_tests.md b/osquery/carver/tests/.carver_tests.md new file mode 100644 index 00000000000..7de9363af26 --- /dev/null +++ b/osquery/carver/tests/.carver_tests.md @@ -0,0 +1,42 @@ + +Unit test suite for the osquery file carver subsystem, validating carve scheduling, local file collection, archiving, expiration handling, and compression/decompression. + +## Key Components + +| Component | Description | +|---|---| +| `FakeCarver` | Test double extending `Carver`; overrides `postCarve()` to mark carves successful without network I/O | +| `FakeCarverRunner` | Test double extending `CarverRunner` for scheduling tests | +| `CarverTests` | Main test fixture managing temp directories, file setup/teardown, and carve path tracking | + +## Test Cases + +| Test | What It Validates | +|---|---| +| `test_carve_files_locally` | Carves 3 files, archives them into a `.tar`, and verifies the archive is non-empty | +| `test_carve` | End-to-end `carve()` call succeeds | +| `test_schedule_carves` | `carvePaths()` queues work; `FakeCarverRunner` processes exactly once, then zero on repeat | +| `test_expiration` | Expired successful carves are pruned from the DB; pending carves survive | +| `test_carve_files_not_exists` | `carveAll()` returns empty set when target paths do not exist | +| `test_compression_decompression` | `compress()` → `.zst` and `decompress()` round-trips correctly (SHA-256 verified) | + +## Usage Example + +```cpp +// Run all carver tests via GTest +// From build root: +``` + +```bash +./osquery_tests --gtest_filter="CarverTests.*" +``` + +```cpp +// FakeCarver is only used in tests — example of how it mirrors real usage: +auto guid = createCarveGuid(); +FakeCarver carve({"path/to/file.txt"}, guid, "request-id"); +auto status = carve.carve(); // postCarve() sets DB status to kCarverStatusSuccess +ASSERT_TRUE(status.ok()); +``` + +> **Note:** Tests require a temp-writable filesystem and an initialized test database plugin (`initDatabasePluginForTesting()`). Temp directories under `fs::temp_directory_path()` are created in `SetUp()` and fully cleaned in `TearDown()`. \ No newline at end of file diff --git a/osquery/config/.config.md b/osquery/config/.config.md new file mode 100644 index 00000000000..c963af8f4d6 --- /dev/null +++ b/osquery/config/.config.md @@ -0,0 +1,62 @@ + +Programmatic representation of osquery's runtime configuration, providing a singleton interface for loading, updating, validating, and querying configuration state including scheduled queries, file watches, and performance metrics. + +## Key Components + +### `Config` (Singleton Class) +The core configuration manager. Access via `Config::get()`. + +**Public Methods:** + +| Method | Description | +|---|---| +| `update(config)` | Apply new configuration data from a source map | +| `load()` | Check for a registered config plugin and trigger initial load | +| `refresh()` | Re-invoke the config plugin's `genConfig` (supports periodic refresh) | +| `recordQueryPerformance(...)` | Store pre/post process metrics for a scheduled query | +| `recordQueryStart(name)` | Mark a query as "dirty" (running) for crash detection | +| `genHash(hash)` | Compute SHA1 over all config sources | +| `hashSource(source, content)` | Hash a single source; returns `true` if content changed | +| `scheduledQueries(predicate)` | Iterate scheduled queries via callback | +| `packs(predicate)` | Iterate all loaded packs via callback | +| `files(predicate)` | Iterate watched file categories via callback | +| `getPerformanceStats(name, predicate)` | Retrieve `QueryPerformance` stats for a named query | +| `getParser(name)` | Fetch a registered `ConfigParserPlugin` by name | +| `validateConfig(document)` | Validate JSON depth and root type | +| `restoreConfigBackup()` | Retrieve config persisted in the backing database | + +### `kExecutingQuery` +Global string constant identifying the currently executing scheduled query. + +--- + +## Usage Example + +```cpp +// Access the singleton +auto& cfg = Config::get(); + +// Iterate all scheduled queries +cfg.scheduledQueries([](std::string name, const ScheduledQuery& query) { + LOG(INFO) << "Query: " << name << " interval: " << query.interval; +}); + +// Iterate watched file categories +cfg.files([](const std::string& category, + const std::vector& paths) { + for (const auto& p : paths) { + LOG(INFO) << category << ": " << p; + } +}); + +// Retrieve performance stats for a specific query +Config::getPerformanceStats("my_query", [](const QueryPerformance& perf) { + LOG(INFO) << "Executions: " << perf.executions; +}); + +// Restore backed-up config from database +auto result = Config::restoreConfigBackup(); +if (result) { + cfg.update(result.take()); +} +``` \ No newline at end of file diff --git a/osquery/config/.packs.md b/osquery/config/.packs.md new file mode 100644 index 00000000000..153833cf1fe --- /dev/null +++ b/osquery/config/.packs.md @@ -0,0 +1,51 @@ + +Defines the `Pack` class and related utilities for managing osquery query packs — collections of scheduled queries with platform/version constraints and discovery logic. + +## Key Components + +### `PackStats` struct +Tracks discovery query execution statistics: `total`, `hits`, and `misses` counts. + +### `Pack` class +Non-copyable class representing a query pack loaded from configuration. + +| Method | Description | +|---|---| +| `initialize()` | Parses and loads pack content from a `rapidjson::Value` | +| `shouldPackExecute()` | Determines if the pack should run on this host | +| `checkPlatform()` | Validates platform compatibility | +| `checkVersion()` | Validates minimum osquery version requirement | +| `checkDiscovery()` | Runs discovery queries to determine pack applicability | +| `isActive()` | Returns active state without triggering discovery queries | +| `getSchedule()` | Returns the vector of `ScheduledQuery` entries | +| `getStats()` | Returns accumulated `PackStats` | + +### Free Functions + +| Function | Description | +|---|---| +| `splayValue(original, splay_percent)` | Generates a splayed interval from a base interval and percent | +| `restoreSplayedValue(name, interval)` | Retrieves or generates a cached splay for a name/interval pair | + +## Usage Example + +```cpp +#include + +// Construct a pack from parsed JSON +rapidjson::Value packObj; // populated from config +osquery::Pack pack("my_pack", "config_source", packObj); + +// Check if the pack should run on this host +if (pack.shouldPackExecute()) { + for (const auto& query : pack.getSchedule()) { + // queue query for execution + } +} + +// Generate a splayed interval (e.g., 10% splay on 60s interval) +uint64_t splayed = osquery::splayValue(60, 10); + +// Retrieve or create a stable cached splay +uint64_t stable = osquery::restoreSplayedValue("my_pack_query", 60); +``` \ No newline at end of file diff --git a/osquery/config/tests/.config_tests.md b/osquery/config/tests/.config_tests.md new file mode 100644 index 00000000000..9bfcb5c9018 --- /dev/null +++ b/osquery/config/tests/.config_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the osquery `Config` class, validating configuration loading, parsing, pack management, denylist behavior, and plugin interactions. + +## Key Components + +### Test Fixtures +- **`ConfigTests`** — Primary test fixture that initializes the platform, registry, and database; manages a mock filesystem and config refresh flag lifecycle +- **`TestConfigPlugin`** — Mock `ConfigPlugin` that reads from a test config file and tracks `genConfig`/`genPack` call counts with an optional failure mode +- **`TestDataConfigParserPlugin`** — Mock `ConfigParserPlugin` that captures parsed config keys and their source for assertion + +### Test Cases + +| Test | Description | +|---|---| +| `test_plugin` | Verifies config plugin registration and `genConfig` invocation count | +| `test_invalid_content` | Ensures malformed JSON doesn't throw exceptions | +| `test_config_not_an_object` | Rejects configs that are arrays instead of objects | +| `test_config_depth` | Validates rejection of deeply nested JSON (>10 levels), including duplicate-key edge cases | +| `test_config_too_big` | Rejects configs exceeding the size limit (~1M entries) | +| `test_strip_comments` | Confirms `//` and `#` style comments are stripped before parsing | +| `test_schedule_denylist` | Tests save/restore of denylist entries and expiration of past-time entries | +| `test_pack_noninline` | Verifies non-inline pack loading triggers `genPack` exactly once | +| `test_pack_restrictions` | Validates pack execution eligibility based on discovery and version restrictions | +| `test_pack_stats` | Tests recording of query performance statistics per pack | + +## Usage Example + +```cpp +// Running a specific test case +TEST_F(ConfigTests, test_strip_comments) { + std::string json_comments = + "// Comment\n{\"options\":{}}"; + + auto actual = json_comments; + stripConfigComments(actual); + EXPECT_EQ(actual, "{\"options\":{}}\n"); + + EXPECT_TRUE(get().update({{"data", json_comments}})); +} +``` \ No newline at end of file diff --git a/osquery/config/tests/.packs.md b/osquery/config/tests/.packs.md new file mode 100644 index 00000000000..1d150956dab --- /dev/null +++ b/osquery/config/tests/.packs.md @@ -0,0 +1,42 @@ + +Unit test suite for osquery's query pack system, validating pack parsing, platform filtering, version checking, scheduling, sharding, discovery queries, and splay value computation. + +## Key Components + +| Test | Description | +|------|-------------| +| `test_parse` | Verifies example pack config contains a `"packs"` member | +| `test_should_pack_execute` | Checks unrestricted vs. discovery-gated pack execution | +| `test_get_discovery_queries` | Validates discovery query extraction from pack definitions | +| `test_platform` / `test_version` | Asserts correct platform (`"all"`) and version (`"1.5.0"`) metadata | +| `test_sharding` | Confirms `getMachineShard` static caching and force-bypass behavior | +| `test_check_platform` | Exercises all platform values (`darwin`, `linux`, `centos`, `ubuntu`, `posix`, `windows`, SDK platform) against the current build | +| `test_check_version` | Rejects packs with fake/incompatible version constraints | +| `test_restriction_population` | Ensures platform, version, and shard fields are populated before evaluation | +| `test_schedule` | Confirms invalid-platform queries are excluded from the schedule | +| `test_discovery_cache` | Validates hit/miss statistics across repeated `scheduledQueries` calls | +| `test_multi_pack` | Tests wildcard (`"*"`) pack name expansion and malformed pack resilience | +| `test_splay` / `test_restore_splay` | Verifies `splayValue` bounds and `restoreSplayedValue` determinism and persistence | + +## Usage Example + +```cpp +// Run the full test suite via gtest +// Build and execute: +// ./osquery_packs_tests + +// Example: Manually exercising pack platform check logic +Pack fpack("my_pack", getPackWithDiscovery().doc()); +fpack.platform_ = "linux"; +if (fpack.checkPlatform()) { + // Pack will execute on this Linux host +} + +// Example: Splay a 1-hour query interval by ±10% +auto interval = splayValue(3600, 10); // Returns value in [3240, 3960] + +// Example: Restore a previously persisted splay (deterministic across calls) +auto interval = restoreSplayedValue("pack_name_query_name", 3600); +``` + +> **Note:** `PacksTests` fixture calls `platformSetup()`, `registryAndPluginInit()`, and `initDatabasePluginForTesting()` in its constructor — required for any test that exercises pack scheduling or config integration. \ No newline at end of file diff --git a/osquery/config/tests/.test_utils.md b/osquery/config/tests/.test_utils.md new file mode 100644 index 00000000000..0771b51ebad --- /dev/null +++ b/osquery/config/tests/.test_utils.md @@ -0,0 +1,37 @@ + +Utility header providing test infrastructure helpers for osquery configuration and pack loading in unit tests. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `getTestConfigDirectory()` | Function | Returns path to the test configuration directory | +| `getTestHelperScriptsDirectory()` | Function | Returns path to the test helper scripts directory | +| `getTestConfigMap()` | Function | Builds a config map from a file, mapping source name to JSON content | +| `getExamplePacksConfig()` | Function | Returns a sample packs configuration as a `JSON` object | +| `getUnrestrictedPack()` | Function | Returns a pack with no platform/version restrictions | +| `getRestrictedPack()` | Function | Returns a pack with platform/version restrictions applied | +| `getPackWithDiscovery()` | Function | Returns a pack containing discovery queries | +| `getPackWithValidDiscovery()` | Function | Returns a pack with a discovery query expected to pass | +| `getPackWithFakeVersion()` | Function | Returns a pack targeting a non-existent/fake osquery version | + +## Usage Example + +```c +#include "test_utils.h" + +// Resolve test config files at runtime +auto config_dir = osquery::getTestConfigDirectory(); +auto config_map = osquery::getTestConfigMap("example_config.conf"); + +// Load fixture packs for scheduler/config unit tests +osquery::JSON unrestricted = osquery::getUnrestrictedPack(); +osquery::JSON restricted = osquery::getRestrictedPack(); +osquery::JSON discovered = osquery::getPackWithValidDiscovery(); +``` + +## Notes + +- All functions are declared inside the `osquery` namespace. +- Path helpers return `const` references — do not store the result beyond test scope. +- Pack fixture functions return `JSON` objects (from `osquery/utils/json/json.h`) and are intended exclusively for use in unit/integration tests. \ No newline at end of file diff --git a/osquery/core/.core.md b/osquery/core/.core.md new file mode 100644 index 00000000000..5bed2769c7d --- /dev/null +++ b/osquery/core/.core.md @@ -0,0 +1,30 @@ + +Core utility header providing fundamental constants and operators used across the osquery codebase. + +## Key Components + +| Component | Type | Description | +|-----------|------|-------------| +| `EXIT_CATASTROPHIC` | Macro (`78`) | Exit code for catastrophic configuration errors that require watcher termination | +| `operator"" _sz` | User-defined literal | Custom `uint64_t` literal suffix for expressing size values as `size_t`-like quantities | + +## Usage Example + +```c +#include + +// Use the catastrophic exit code when a fatal config error occurs +if (config_invalid) { + exit(EXIT_CATASTROPHIC); +} + +// Use the _sz literal suffix for readable size expressions +uint64_t buffer_size = 1024_sz; +uint64_t max_rows = 500_sz; +``` + +## Notes + +- `EXIT_CATASTROPHIC` (`78`) is distinct from standard UNIX exit codes to avoid ambiguity with signals or common error codes +- The `_sz` literal operator is defined inside the `osquery` namespace — ensure `using namespace osquery` or a qualified call is in scope when using it +- Depends on `osquery/utils/macros/macros.h` for cross-platform macro support \ No newline at end of file diff --git a/osquery/core/.flagalias.md b/osquery/core/.flagalias.md new file mode 100644 index 00000000000..80b870421b8 --- /dev/null +++ b/osquery/core/.flagalias.md @@ -0,0 +1,32 @@ + +Provides a templated alias mechanism for deprecated osquery command-line flags, allowing old flag names to transparently forward reads and writes to their updated replacements. + +## Key Components + +### `FlagAlias` (class) +A templated wrapper that intercepts assignment and conversion operators to redirect flag access through `Flag::updateValue` and `Flag::getValue` using the canonical flag name. Accepts the alias name, type string, canonical name, and a storage pointer (unused) at construction. + +### `boost::lexical_cast` specializations +Custom template specializations for `bool ↔ std::string` conversion, required to correctly handle Gflags boolean string representations (e.g., `"true"` / `"false"`). + +### Macros + +| Macro | Description | +|---|---| +| `OSQUERY_FLAG_ALIAS(t, a, n, s, e)` | Base macro — creates a `FlagAlias` global, registers the alias with Gflags, and calls `Flag::createAlias` | +| `FLAG_ALIAS(t, a, n)` | Standard alias available everywhere | +| `SHELL_FLAG_ALIAS(t, a, n)` | Alias available only in `osqueryi` (shell mode) | +| `EXTENSION_FLAG_ALIAS(a, n)` | `std::string`-typed alias available only to extensions | + +## Usage Example + +```c +// Alias deprecated flag "worker_threads" to the current flag "num_workers" +FLAG_ALIAS(uint32_t, worker_threads, num_workers) + +// Callsites using the old name continue to work transparently: +uint32_t threads = FLAGS_worker_threads; // reads FLAGS_num_workers +FLAGS_worker_threads = 8; // writes FLAGS_num_workers +``` + +> **Note:** Prefer the derived macros (`FLAG_ALIAS`, `SHELL_FLAG_ALIAS`, `EXTENSION_FLAG_ALIAS`) over `OSQUERY_FLAG_ALIAS` directly, following the same convention as the `FLAG` / `OSQUERY_FLAG` pattern in osquery core. \ No newline at end of file diff --git a/osquery/core/.flags.md b/osquery/core/.flags.md new file mode 100644 index 00000000000..3168ef7b20f --- /dev/null +++ b/osquery/core/.flags.md @@ -0,0 +1,55 @@ + +Declares osquery's flag management system, wrapping gflags to support categorized, queryable, and runtime-updatable configuration options across shell, CLI, extension, and hidden flag contexts. + +## Key Components + +### Structs +- **`FlagDetail`** — Metadata for a flag: description, and boolean markers (`shell`, `external`, `cli`, `hidden`) +- **`FlagInfo`** — Full flag snapshot including type, description, default value, current value, and its `FlagDetail` + +### Class: `Flag` (singleton, non-copyable) +| Method | Description | +|---|---| +| `create()` | Registers a new named flag with its detail metadata | +| `createAlias()` | Registers a gflags alias pointing to an existing flag | +| `flags()` | Returns a map of all registered flags and their info | +| `getValue()` / `getInt32Value()` | Retrieves flag value by name | +| `getDefaultValue()` | Retrieves the default value for a flag | +| `updateValue()` | Updates a flag's value at runtime | +| `isDefault()` | Checks if a flag still holds its default value | +| `getType()` / `getDescription()` | Introspection helpers | +| `isCLIOnlyFlag()` | Returns true if the flag is CLI/flagfile-only | +| `printFlags()` | Prints help output, filterable by shell/external/cli | +| `resetCustomFlags()` | Clears custom flags (used by fuzzers) | + +### Macros +| Macro | Behavior | +|---|---| +| `FLAG(t,n,v,d)` | Standard flag — settable via flagfile, CLI, or config options | +| `SHELL_FLAG` | Only available in `osqueryi` | +| `EXTENSION_FLAG` | Only available to extensions | +| `CLI_FLAG` | Cannot be set via config `options` | +| `HIDDEN_FLAG` | Excluded from `--help` output | + +## Usage Example + +```cpp +// Declare flags in a .cpp file +FLAG(bool, enable_feature, false, "Enable the experimental feature"); +CLI_FLAG(string, config_path, "/etc/osquery/osquery.conf", "Path to config file"); +SHELL_FLAG(int32, query_timeout, 300, "Query timeout in seconds (osqueryi only)"); + +// Read a flag value at runtime +std::string path = Flag::getValue("config_path"); + +// Update a flag value programmatically +Status s = Flag::updateValue("enable_feature", "true"); +if (!s.ok()) { + LOG(WARNING) << "Failed to update flag: " << s.getMessage(); +} + +// Check if a flag was explicitly set or still at default +if (Flag::isDefault("query_timeout")) { + LOG(INFO) << "Using default query timeout"; +} +``` \ No newline at end of file diff --git a/osquery/core/.init.md b/osquery/core/.init.md new file mode 100644 index 00000000000..a9b30b33545 --- /dev/null +++ b/osquery/core/.init.md @@ -0,0 +1,34 @@ + +Handles osquery process initialization, including startup configuration, signal handling, OpenFrame integration, and platform-specific setup for daemon and shell tool types. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `Initializer` | Class | Main initialization class; sets up runtime environment, parses flags, and configures tool type (daemon/shell/extension) | +| `initOpenFrame()` | Function | Bootstraps OpenFrame mode: validates secret, initializes `OpenframeEncryptionService`, `OpenframeTokenExtractor`, `OpenframeAuthorizationManagerProvider`, and starts `OpenframeTokenRefresher` | +| `initWorkDirectories()` | Function | Creates required working directories (e.g., database path) if the database is not disabled | +| `signalHandler()` | Function | Handles `SIGTERM`, `SIGINT`, and `SIGUSR1`; triggers graceful shutdown or resource-limit notification | +| `validateAlarmTimeout()` | Validator | Enforces a minimum shutdown alarm timeout of 10 seconds | +| `printUsage()` | Function | Renders tool-specific CLI help output based on `ToolType` (daemon, shell, extension) | +| `kMainThreadId` | Variable | Stores the main thread ID to short-circuit signal-based shutdown | +| OpenFrame flags | `FLAG` macros | `openframe_mode`, `openframe_secret`, `openframe_token_path` — control OpenFrame runtime behavior | + +## Usage Example + +```cpp +// Typical daemon startup +int main(int argc, char* argv[]) { + osquery::Initializer runner(argc, argv, osquery::ToolType::DAEMON); + + // OpenFrame mode is activated via CLI flag: + // osqueryd --openframe_mode --openframe_secret= \ + // --openframe_token_path=/etc/openframe/token + + runner.initDaemon(); + runner.start(); + return runner.shutdown(0); +} +``` + +> **Note:** OpenFrame mode requires `--openframe_secret` to be set. If `--openframe_mode` is enabled without a secret, initialization logs an error and skips token extraction. The token refresher runs as a background service after a successful initial token extraction. \ No newline at end of file diff --git a/osquery/core/.query.md b/osquery/core/.query.md new file mode 100644 index 00000000000..5cfec8773ce --- /dev/null +++ b/osquery/core/.query.md @@ -0,0 +1,60 @@ + +Defines the `Query` class and `QueryLogItem` struct for managing scheduled query execution history, differential results, and JSON serialization within the osquery framework. + +## Key Components + +### `QueryLogItem` struct +| Field | Type | Description | +|---|---|---| +| `isSnapshot` | `bool` | Flags snapshot vs. differential results | +| `results` | `DiffResults` | Differential query results | +| `snapshot_results` | `QueryDataTyped` | Full snapshot results | +| `name` / `identifier` | `std::string` | Query name and host identifier | +| `epoch` / `counter` | `uint64_t` | Execution epoch and invocation counter | +| `decorations` | `map` | Additional log line metadata | + +### Serialization Functions +- `serializeQueryLogItem()` — Serializes a `QueryLogItem` into a JSON document +- `serializeQueryLogItemJSON()` — Serializes a `QueryLogItem` into a JSON string +- `serializeQueryLogItemAsEvents()` — Serializes as an event-list JSON document +- `serializeQueryLogItemAsEventsJSON()` — Serializes as a vector of event JSON strings + +### `Query` class +| Method | Description | +|---|---| +| `getPreviousQueryResults()` | Loads prior results from RocksDB into a `QueryDataSet` | +| `saveQueryResults()` | Persists updated results JSON and epoch to the database | +| `addNewResults()` | Stores new results and computes differential vs. last run | +| `addNewEvents()` | Variant of `addNewResults` for event-based queries | +| `getQueryCounter()` | Returns/increments the invocation counter, handling resets | +| `isNewQuerySql()` | Detects if the query SQL has changed since last run | +| `getStoredQueryNames()` | Static; returns all query names persisted in RocksDB | + +## Usage Example + +```c +// Initialize and run a scheduled query, capturing differential results +ScheduledQuery sq; +sq.query = "SELECT * FROM processes;"; + +Query q("process_monitor", sq); + +uint64_t counter = 0; +DiffResults diff; +QueryDataTyped results = executeQuery(sq.query); + +Status status = q.addNewResults(std::move(results), current_epoch, counter, diff); + +if (status.ok()) { + QueryLogItem item; + item.name = "process_monitor"; + item.results = diff; + item.epoch = current_epoch; + item.counter = counter; + item.isSnapshot = false; + + std::string json; + serializeQueryLogItemJSON(item, json); + logger.log(json); +} +``` \ No newline at end of file diff --git a/osquery/core/.shutdown.md b/osquery/core/.shutdown.md new file mode 100644 index 00000000000..6480b232439 --- /dev/null +++ b/osquery/core/.shutdown.md @@ -0,0 +1,47 @@ + +Declares the osquery shutdown coordination API, providing functions to request, wait for, and inspect application shutdown state across daemon services and threads. + +## Key Components + +| Function | Description | +|---|---| +| `requestShutdown(int, string)` | Primary shutdown trigger; signals all dispatched services to stop gracefully | +| `waitForShutdown()` | Blocks the calling thread until shutdown is requested | +| `waitTimeoutOrShutdown(milliseconds)` | Interruptible sleep — returns early if shutdown is requested | +| `shutdownRequested()` | Non-blocking check for shutdown state (shell/tool use only) | +| `getShutdownExitCode()` | Retrieves the stored exit code set during shutdown request | +| `setShutdownExitCode(int)` | Directly sets the exit code without triggering shutdown | +| `resetShutdown()` | Resets shutdown state to `false`; intended for internal/testing use only | + +## Usage Example + +```cpp +#include + +// In main thread — block until shutdown is signaled +void runDaemon() { + waitForShutdown(); + // Proceed with cleanup after shutdown is requested + Initializer::shutdown(getShutdownExitCode()); +} + +// In a retry loop — sleep interruptibly +void enrollWithRetry() { + while (!shutdownRequested()) { + bool done = attemptEnrollment(); + if (done) break; + + // Wait 30s or abort early if shutdown is requested + if (waitTimeoutOrShutdown(std::chrono::milliseconds(30000))) { + break; // Shutdown was requested + } + } +} + +// Request shutdown from an error handler +void onFatalError(const std::string& reason) { + requestShutdown(EXIT_FAILURE, reason); +} +``` + +> **Note:** Prefer `requestShutdown()` over direct `exit()` calls to ensure all event threads and Thrift service pools are properly notified before process termination. \ No newline at end of file diff --git a/osquery/core/.system.md b/osquery/core/.system.md new file mode 100644 index 00000000000..50183a002ee --- /dev/null +++ b/osquery/core/.system.md @@ -0,0 +1,52 @@ + +Defines the `Initializer` class and supporting utilities for bootstrapping osquery process lifecycle, identity, and platform setup within the `osquery` namespace. + +## Key Components + +### `Initializer` Class +The primary entry point for osquery process initialization. Non-copyable, intended for use in `main()`. + +| Method | Description | +|---|---| +| `Initializer(argc, argv, tool, init_glog)` | Constructor; configures flags, logging, and tool type | +| `initDaemon()` | Sets up process as an osquery daemon | +| `initShell()` | Sets up process as an interactive shell | +| `initWorkerWatcher(name)` | Spawns and monitors child worker processes | +| `shutdown(retcode)` | Cleanly stops all services and returns exit code | +| `requestShutdown(retcode)` | Static; requests graceful shutdown | +| `shutdownNow(retcode)` | Static; immediate exit without dispatcher teardown | +| `isWorker()` / `isWatcher()` | Static; detects process role via environment | + +### UUID & Identity Utilities +| Function | Description | +|---|---| +| `generateNewUUID()` | Generates a random UUID string | +| `getInstanceUUID(ident)` | Retrieves persistent instance UUID | +| `getEphemeralUUID(ident)` | Retrieves session-scoped UUID | +| `getHostUUID(ident)` | Retrieves hardware-based host UUID | +| `getHostIdentifier()` | Returns configured unique machine identifier | +| `isPlaceholderHardwareUUID(uuid)` | Detects invalid/placeholder UUIDs | + +### Platform & Process Utilities +- `platformSetup()` / `platformTeardown()` — COM/platform init on Windows +- `isUserAdmin()` — Checks admin/root privileges +- `setThreadName(name)` — Names the current thread +- `getStartTime()` / `setStartTime(st)` — Tracks tool start time + +## Usage Example + +```cpp +#include + +int main(int argc, char* argv[]) { + osquery::Initializer runner(argc, argv, osquery::ToolType::DAEMON); + + runner.initDaemon(); + runner.initWorkerWatcher("osqueryd"); + + // Only worker processes return here + runner.start(); + + return runner.shutdown(EXIT_SUCCESS); +} +``` \ No newline at end of file diff --git a/osquery/core/.tables.md b/osquery/core/.tables.md new file mode 100644 index 00000000000..7dabd713bd7 --- /dev/null +++ b/osquery/core/.tables.md @@ -0,0 +1,43 @@ + +Defines the core data structures and type system for osquery's virtual table infrastructure, including SQLite type affinity macros, constraint handling, and table metadata used when implementing and querying osquery tables. + +## Key Components + +### Type Affinity Macros +- `SQL_TEXT(x)`, `INTEGER(x)`, `BIGINT(x)`, `UNSIGNED_BIGINT(x)`, `DOUBLE(x)` — convert native C++ values to SQLite string representations via `__sqliteField()` +- `TEXT_LITERAL`, `INTEGER_LITERAL`, `BIGINT_LITERAL`, `UNSIGNED_BIGINT_LITERAL`, `DOUBLE_LITERAL` — C++ type aliases for table column literals + +### Enumerations +- `ConstraintOperator` — SQL predicate operators (`EQUALS`, `LESS_THAN`, `GLOB`, `REGEXP`, `IN_OP`, etc.) +- `TableAttributes` — bitflag enum describing table behavior (`CACHEABLE`, `EVENT_BASED`, `USER_BASED`, `PENDING`, etc.) + +### Structs & Classes +- `Constraint` — pairs a `ConstraintOperator` with a string expression +- `ConstraintList` — ordered set of `Constraint` objects for a single column; supports `matches()`, `exists()`, `existsAndMatches()`, `notExistsOrMatches()`, and `getAll()` + +### Type Aliases +- `TableColumns` — ordered vector of `(name, ColumnType, ColumnOptions)` tuples +- `ConstraintMap` — maps column names to their `ConstraintList` +- `ConstraintSet` — parsed predicate pairs used to populate a `ConstraintMap` +- `UsedColumns` / `UsedColumnsBitset` — tracks which columns are referenced in a query + +## Usage Example + +```cpp +// Check if a constraint exists and matches a value +ConstraintList cl; +cl.add(Constraint(EQUALS, "root")); + +if (cl.existsAndMatches(std::string("root"))) { + // Filter results to user "root" +} + +// Get all EQUALS expressions for iteration +for (const auto& expr : cl.getAll(EQUALS)) { + generateRowsForUser(expr); +} + +// Convert a native value to SQLite text representation +std::string val = INTEGER(42); // "42" +std::string txt = SQL_TEXT("hi"); // "hi" +``` \ No newline at end of file diff --git a/osquery/core/.watcher.md b/osquery/core/.watcher.md new file mode 100644 index 00000000000..93cd774f62e --- /dev/null +++ b/osquery/core/.watcher.md @@ -0,0 +1,41 @@ + +Defines the watchdog infrastructure for osquery, managing the lifecycle and performance monitoring of worker processes and autoloaded extension processes. + +## Key Components + +### Enums & Structs + +- **`WatchdogLimitType`** — Categorizes performance limits: `MEMORY_LIMIT`, `UTILIZATION_LIMIT`, `RESPAWN_LIMIT`, `RESPAWN_DELAY`, `LATENCY_LIMIT`, `INTERVAL` +- **`PerformanceState`** — Snapshot struct tracking CPU time, memory footprint, respawn timestamps, and sustained latency counters for a monitored process + +### Classes + +- **`Watcher`** — Thread-safe, noncopyable singleton managing the worker and extension process map. Tracks performance states, restart counts, and process handles. Only accessible by `WatcherRunner` +- **`WatcherRunner`** — `InternalRunnable` thread that spawns, monitors, and respawns worker/extension processes. Enforces resource limits and handles clean/forced shutdowns +- **`WatcherWatcherRunner`** — A reverse-watchdog spawned inside the worker process; monitors the parent watcher process and exits if it disappears + +### Free Functions + +- **`getWorkerLimit(WatchdogLimitType)`** — Returns a numeric performance threshold for a given limit category and configured watchdog level + +### Type Aliases + +- **`ExtensionMap`** — `std::map>` mapping extension paths to their process handles + +## Usage Example + +```cpp +// Construct and bind a Watcher, then launch the WatcherRunner thread +auto watcher = std::make_shared(); +watcher->loadExtensions(); + +auto runner = std::make_shared( + argc, argv, /*use_worker=*/true, watcher); + +// Query a specific performance limit +uint64_t memLimit = osquery::getWorkerLimit( + osquery::WatchdogLimitType::MEMORY_LIMIT); + +// Bind watcher/worker fates (watcher exit kills worker) +watcher->bindFates(); +``` \ No newline at end of file diff --git a/osquery/core/plugins/.logger.md b/osquery/core/plugins/.logger.md new file mode 100644 index 00000000000..201e0d11239 --- /dev/null +++ b/osquery/core/plugins/.logger.md @@ -0,0 +1,58 @@ + +Defines the pluggable logging interface for osquery, providing the base class and supporting types for implementing custom log destinations (e.g., Flume, Splunk, syslog). + +## Key Components + +### Enumerations +- **`LoggerFeatures`** — Opt-in feature flags: `LOGGER_FEATURE_BLANK`, `LOGGER_FEATURE_LOGSTATUS`, `LOGGER_FEATURE_LOGEVENT` +- **`StatusLogSeverity`** — Severity levels mirroring Glog: `O_INFO`, `O_WARNING`, `O_ERROR`, `O_FATAL` + +### Structs +- **`StatusLogLine`** — Intermediate log entry holding severity, filename, line number, message, timestamp, UNIX time, and host identifier + +### Classes +- **`LoggerPlugin`** — Abstract base class (extends `Plugin`) for all logger implementations + +### `LoggerPlugin` Virtual Methods + +| Method | Required | Description | +|---|---|---| +| `logString(s)` | ✅ Pure virtual | Core log output handler | +| `init(binary_name, log)` | ✅ Pure virtual | Called once at startup with buffered pre-init logs | +| `logStatus(log)` | Optional | Handles Glog status lines | +| `logSnapshot(s)` | Optional | Handles snapshot query results; defaults to `logString` | +| `logEvent(s)` | Optional | Handles individual forwarded events | +| `logStringBatch(batch)` | Optional | Handles a JSON array batch of events | + +## Usage Example + +```cpp +#include + +class MyLoggerPlugin : public osquery::LoggerPlugin { + public: + osquery::Status logString(const std::string& s) override { + writeToMyBackend(s); + return osquery::Status::success(); + } + + bool usesLogStatus() override { return true; } + + osquery::Status logStatus( + const std::vector& log) override { + for (const auto& line : log) { + writeStatusToMyBackend(line.severity, line.message); + } + return osquery::Status::success(); + } + + protected: + void init(const std::string& binary_name, + const std::vector& log) override { + setProcessName(binary_name); + logStatus(log); // flush buffered pre-init logs + } +}; + +REGISTER(MyLoggerPlugin, "logger", "my_logger"); +``` \ No newline at end of file diff --git a/osquery/core/plugins/.plugin.md b/osquery/core/plugins/.plugin.md new file mode 100644 index 00000000000..57a9afb5479 --- /dev/null +++ b/osquery/core/plugins/.plugin.md @@ -0,0 +1,57 @@ + +Defines the core `Plugin` base class and associated types for the osquery plugin/registry system, providing the interface that all registry items must implement. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `PluginRequest` | `std::map` | Key-value input map passed to a plugin call, typically includes an `"action"` key | +| `PluginResponse` | `std::vector` | Vector of key-value maps returned from a successful plugin call | +| `Plugin` | Abstract class | Non-copyable base class all plugins must inherit; requires `call()` implementation | +| `PluginRef` | `std::shared_ptr` | Shared pointer alias for registry-managed plugin instances | +| `tableRowsToPluginResponse()` | Free function | Converts a `TableRows` result into a `PluginResponse` | + +### `Plugin` Virtual Methods + +| Method | Required | Description | +|--------|----------|-------------| +| `call()` | ✅ Pure virtual | Handles plugin invocation with request/response | +| `setUp()` | ❌ Optional | One-time initialization logic | +| `tearDown()` | ❌ Optional | Cleanup/release logic | +| `configure()` | ❌ Optional | React to configuration changes | +| `routeInfo()` | ❌ Optional | Publish additional routing metadata | +| `addExternal()` | ❌ Static | Hook for when an external plugin is registered | +| `removeExternal()` | ❌ Static | Hook for when an external plugin is removed | + +## Usage Example + +```cpp +#include + +namespace osquery { + +class MyPlugin : public Plugin { + public: + Status call(const PluginRequest& request, + PluginResponse& response) override { + auto action = request.find("action"); + if (action == request.end()) { + return Status::failure("Missing action key"); + } + + if (action->second == "query") { + response.push_back({{"result", "hello from MyPlugin"}}); + return Status::success(); + } + + return Status::failure("Unknown action: " + action->second); + } + + Status setUp() override { + // Optional: initialize resources + return Status::success(); + } +}; + +} // namespace osquery +``` \ No newline at end of file diff --git a/osquery/core/plugins/.sql.md b/osquery/core/plugins/.sql.md new file mode 100644 index 00000000000..c0e5554f802 --- /dev/null +++ b/osquery/core/plugins/.sql.md @@ -0,0 +1,49 @@ + +Defines the abstract `SQLPlugin` interface that enables osquery's pluggable SQL execution layer, decoupling the SQL API from its underlying implementation (e.g., SQLite). + +## Key Components + +- **`SQLPlugin`** — Abstract base class inheriting from `Plugin` that defines the contract for SQL backend implementations registered under the `"sql"` registry key. + +| Method | Description | +|---|---| +| `query()` | Executes a SQL query string and populates a `QueryData` result set | +| `getQueryColumns()` | Parses a query and returns column metadata (`name`, `type`) | +| `getQueryTables()` | Returns the list of virtual tables scanned by a given query | +| `attach()` | Optionally attaches a virtual table at runtime (no-op default) | +| `detach()` | Optionally detaches a virtual table by name (no-op default) | +| `call()` | Dispatches plugin registry requests to the appropriate method | + +## Usage Example + +```cpp +#include + +// Implementing a custom SQL backend plugin +class MySQLPlugin : public osquery::SQLPlugin { + public: + osquery::Status query(const std::string& q, + osquery::QueryData& results, + bool use_cache) const override { + // Execute query against your SQL backend + return osquery::Status::success(); + } + + osquery::Status getQueryColumns(const std::string& q, + osquery::TableColumns& columns) const override { + // Parse and return column descriptors + return osquery::Status::success(); + } + + osquery::Status getQueryTables(const std::string& q, + std::vector& tables) const override { + // Return scanned table names + return osquery::Status::success(); + } +}; + +// Register the plugin under the "sql" registry +REGISTER(MySQLPlugin, "sql", "sql"); +``` + +> **Note:** The default `attach()` and `detach()` implementations return `Status::success()` — override them only if your backend requires runtime virtual table management (as SQLite does during initialization). \ No newline at end of file diff --git a/osquery/core/posix/.platform.md b/osquery/core/posix/.platform.md new file mode 100644 index 00000000000..f1211374552 --- /dev/null +++ b/osquery/core/posix/.platform.md @@ -0,0 +1,22 @@ + +Provides stub implementations of the platform lifecycle hooks for POSIX systems, where no platform-specific initialization or teardown logic is required. + +## Key Components + +- **`platformSetup()`** — Called during osquery startup; no-op on POSIX platforms. +- **`platformTeardown()`** — Called during osquery shutdown; no-op on POSIX platforms. + +## Usage Example + +```cpp +#include + +// Called by the osquery core framework at startup and shutdown. +// On POSIX, these are intentional no-ops; non-POSIX platforms +// provide their own implementations with platform-specific logic. + +osquery::platformSetup(); // Initialize platform internals (POSIX: no-op) +osquery::platformTeardown(); // Clean up platform resources (POSIX: no-op) +``` + +> **Note:** These functions follow a platform abstraction pattern. On Windows or other non-POSIX targets, separate `platform.cpp` implementations provide the actual initialization and teardown logic, while this file satisfies the interface contract for all POSIX systems (Linux, macOS, etc.). \ No newline at end of file diff --git a/osquery/core/sql/.column.md b/osquery/core/sql/.column.md new file mode 100644 index 00000000000..997711a199b --- /dev/null +++ b/osquery/core/sql/.column.md @@ -0,0 +1,45 @@ + +Defines column metadata types and enumerations used to describe osquery table schema — column options, column types, and related type aliases for table definitions. + +## Key Components + +### `ColumnOptions` (enum class) +Bitmask flags controlling column behavior in SQLite table implementations: + +| Flag | Value | Description | +|------|-------|-------------| +| `DEFAULT` | 0 | No special behavior | +| `INDEX` | 1 | Treat as primary key | +| `REQUIRED` | 2 | Must appear in query predicate | +| `ADDITIONAL` | 4 | Generates extra data when in predicate | +| `OPTIMIZED` | 8 | Enables query-time optimization | +| `HIDDEN` | 16 | Excluded from `SELECT *` results | +| `COLLATEBINARY` / `COLLATE*` | 32–2048 | SQLite collation sequences | + +Bitwise `|` and `&` operators are overloaded for flag composition. + +### `ColumnType` (enum) +Supported SQLite column data types: `TEXT_TYPE`, `INTEGER_TYPE`, `BIGINT_TYPE`, `UNSIGNED_BIGINT_TYPE`, `DOUBLE_TYPE`, `BLOB_TYPE`, `UNKNOWN_TYPE`. + +### Type Aliases +- `TableName` — `std::string` alias for plugin/table names +- `TableColumns` — ordered vector of `(name, ColumnType, ColumnOptions)` tuples describing a table's schema +- `ColumnAliasSet` — map of column name to a set of alias strings + +### `kColumnTypeNames` +External map from `ColumnType` to its SQLite string representation (e.g., `TEXT`, `INTEGER`). + +## Usage Example + +```cpp +#include +#include "column.h" + +osquery::TableColumns myTableColumns = { + // Column name, type, options + {"uid", osquery::INTEGER_TYPE, osquery::ColumnOptions::INDEX}, + {"name", osquery::TEXT_TYPE, osquery::ColumnOptions::DEFAULT}, + {"token", osquery::TEXT_TYPE, osquery::ColumnOptions::HIDDEN | + osquery::ColumnOptions::REQUIRED}, +}; +``` \ No newline at end of file diff --git a/osquery/core/sql/.diff_results.md b/osquery/core/sql/.diff_results.md new file mode 100644 index 00000000000..fdf548bc014 --- /dev/null +++ b/osquery/core/sql/.diff_results.md @@ -0,0 +1,44 @@ + +Defines the `DiffResults` structure and related utilities for computing and serializing the difference between two query result sets in osquery. + +## Key Components + +### `DiffResults` (struct) +A move-only struct representing the delta between two `QueryDataTyped` result sets: + +| Member | Type | Description | +|---|---|---| +| `added` | `QueryDataTyped` | Rows present in the new result but not the old | +| `removed` | `QueryDataTyped` | Rows present in the old result but not the new | + +**Methods:** +- `hasNoResults()` — Returns `true` if both `added` and `removed` are empty +- `operator==` / `operator!=` — Equality comparison between two `DiffResults` + +### Free Functions + +| Function | Description | +|---|---| +| `diff(old_, new_)` | Computes delta between a `QueryDataSet` and `QueryDataTyped`, returns a `DiffResults` | +| `serializeDiffResults(...)` | Serializes a `DiffResults` into a `rapidjson::Document` object | +| `serializeDiffResultsJSON(...)` | Serializes a `DiffResults` into a JSON string | + +## Usage Example + +```cpp +// Compute diff between old and new query results +QueryDataSet oldResults = getStoredResults(); +QueryDataTyped newResults = runQuery(); + +DiffResults delta = osquery::diff(oldResults, newResults); + +if (!delta.hasNoResults()) { + std::string jsonOutput; + auto status = osquery::serializeDiffResultsJSON(delta, jsonOutput, false); + + if (status.ok()) { + // jsonOutput contains {"added": [...], "removed": [...]} + processChanges(jsonOutput); + } +} +``` \ No newline at end of file diff --git a/osquery/core/sql/.query_data.md b/osquery/core/sql/.query_data.md new file mode 100644 index 00000000000..554cf1f10c3 --- /dev/null +++ b/osquery/core/sql/.query_data.md @@ -0,0 +1,47 @@ + +Defines the core result set types and serialization utilities for osquery SQL query results, providing `QueryData`, `QueryDataTyped`, and `QueryDataSet` container types along with JSON serialization/deserialization functions. + +## Key Components + +### Type Aliases +| Type | Definition | Purpose | +|---|---|---| +| `QueryData` | `std::vector` | Standard untyped SQL result set | +| `QueryDataTyped` | `std::vector` | Typed SQL result set | +| `QueryDataSet` | `std::multiset` | Multiset for fast row lookup | + +### Serialization Functions +- **`serializeQueryData`** — Converts `QueryData` or `QueryDataTyped` into a RapidJSON array, with optional numeric value preservation +- **`serializeQueryDataJSON`** — Serializes query results directly to a `JSON` document or `std::string` + +### Deserialization Functions +- **`deserializeQueryData`** — Overloaded for `QueryData`, `QueryDataTyped`, and `QueryDataSet` targets from a `rapidjson::Value` +- **`deserializeQueryDataJSON`** — Overloaded for `QueryData` and `QueryDataSet` targets from a JSON string or `JSON` document + +### Utility +- **`addUniqueRowToQueryData`** — Appends a `RowTyped` to a `QueryDataTyped` only if it does not already exist (linear scan) + +## Usage Example + +```cpp +#include + +// Serialize query results to JSON string +osquery::QueryData results = runMyQuery(); +std::string jsonOutput; +auto status = osquery::serializeQueryDataJSON(results, jsonOutput); +if (!status.ok()) { + LOG(ERROR) << "Serialization failed: " << status.getMessage(); +} + +// Deserialize back from JSON +osquery::QueryData restored; +status = osquery::deserializeQueryDataJSON(jsonOutput, restored); + +// Add unique rows only +osquery::QueryDataTyped typedResults; +osquery::RowTyped row = buildRow(); +bool added = osquery::addUniqueRowToQueryData(typedResults, row); +``` + +> **Note:** `addUniqueRowToQueryData` performs a linear scan — avoid using it in performance-critical loops with large result sets. \ No newline at end of file diff --git a/osquery/core/sql/.query_performance.md b/osquery/core/sql/.query_performance.md new file mode 100644 index 00000000000..2441d260de2 --- /dev/null +++ b/osquery/core/sql/.query_performance.md @@ -0,0 +1,45 @@ + +Defines the `QueryPerformance` struct within the `osquery` namespace for tracking runtime performance statistics of scheduled queries. + +## Key Components + +### `QueryPerformance` (struct) + +Aggregates cumulative and per-execution metrics for a single query: + +| Field | Type | Description | +|---|---|---| +| `executions` | `size_t` | Total number of times the query has run | +| `last_executed` | `uint64_t` | UNIX timestamp (seconds) of last successful execution | +| `wall_time` / `wall_time_ms` | `uint64_t` | Cumulative wall clock time (seconds / milliseconds) | +| `last_wall_time_ms` | `uint64_t` | Wall time of the most recent execution (ms) | +| `user_time` / `last_user_time` | `uint64_t` | Cumulative and latest user-space CPU time (ms) | +| `system_time` / `last_system_time` | `uint64_t` | Cumulative and latest kernel CPU time (ms) | +| `average_memory` | `uint64_t` | Average resident memory (bytes) after result collection | +| `last_memory` | `uint64_t` | Resident memory of the most recent execution (bytes) | +| `output_size` | `uint64_t` | Total bytes produced by the query | + +**Methods:** + +- `QueryPerformance(const std::string& csv)` — Deserializes a `QueryPerformance` from a CSV string +- `toCSV() const` — Serializes the struct to a CSV string for persistence +- `operator==` — Equality comparison between two instances + +## Usage Example + +```cpp +#include "query_performance.h" + +// Default-initialize and inspect after a query run +osquery::QueryPerformance perf; +perf.executions++; +perf.last_wall_time_ms = 42; + +// Persist to CSV +std::string serialized = perf.toCSV(); + +// Restore from CSV +osquery::QueryPerformance restored(serialized); + +assert(perf == restored); +``` \ No newline at end of file diff --git a/osquery/core/sql/.row.md b/osquery/core/sql/.row.md new file mode 100644 index 00000000000..e0e3d010bcc --- /dev/null +++ b/osquery/core/sql/.row.md @@ -0,0 +1,57 @@ + +Defines the core data types and serialization utilities for representing single database query rows in osquery, supporting both string-based and typed (SQLite affinity) row formats. + +## Key Components + +### Type Aliases + +| Type | Definition | +|------|-----------| +| `RowData` | `std::string` — raw column value | +| `RowDataTyped` | `boost::variant` — typed column value | +| `Row` | `std::map` — untyped string row | +| `RowTyped` | `std::map` — typed variant row | +| `ColumnNames` | `std::vector` — ordered column name list | + +### Serialization Functions + +- **`serializeRow`** — Serializes a `Row` or `RowTyped` into a `rapidjson::Value` object, with optional column ordering via `ColumnNames` +- **`serializeRowJSON`** — Serializes a `Row` or `RowTyped` directly to a JSON string; typed variant accepts an `asNumeric` flag to preserve numeric types + +### Deserialization Functions + +- **`deserializeRow`** — Deserializes a `rapidjson::Value` object into a `Row` or `RowTyped` +- **`deserializeRowJSON`** — Deserializes a JSON string into a `Row` or `RowTyped` + +All functions return an osquery `Status` object indicating success or failure. + +## Usage Example + +```c +#include +#include + +// Serialize a Row to JSON string +osquery::Row r; +r["pid"] = "1234"; +r["name"] = "my_process"; + +std::string json_output; +auto status = osquery::serializeRowJSON(r, json_output); +if (status.ok()) { + // json_output -> {"pid":"1234","name":"my_process"} +} + +// Deserialize back from JSON string +osquery::Row restored; +osquery::deserializeRowJSON(json_output, restored); + +// Using typed rows with numeric preservation +osquery::RowTyped typed_row; +typed_row["pid"] = (long long)1234; +typed_row["load"] = 0.75; + +std::string typed_json; +osquery::serializeRowJSON(typed_row, typed_json, /*asNumeric=*/true); +// typed_json -> {"pid":1234,"load":0.75} +``` \ No newline at end of file diff --git a/osquery/core/sql/.scheduled_query.md b/osquery/core/sql/.scheduled_query.md new file mode 100644 index 00000000000..42d5c135e19 --- /dev/null +++ b/osquery/core/sql/.scheduled_query.md @@ -0,0 +1,48 @@ + +Defines the `ScheduledQuery` struct used within osqueryd to represent all relevant parameters and metadata for a scheduled SQL query. + +## Key Components + +### `ScheduledQuery` (struct) +A move-only data structure (inherits `only_movable`) containing: + +| Field | Type | Description | +|---|---|---| +| `pack_name` | `std::string` | Name of the containing pack | +| `name` | `std::string` | Query identifier | +| `query` | `std::string` | Raw SQL query string | +| `oncall` | `std::string` | Query owner/team | +| `interval` | `uint64_t` | Execution frequency in seconds | +| `splayed_interval` | `uint64_t` | Temporary splayed interval value | +| `startup_priority` | `uint64_t` | Startup execution priority (`UINT64_MAX` = disabled) | +| `denylisted` | `bool` | Whether query is currently denylisted | +| `options` | `std::map` | Key/value query option flags | + +**Methods:** +- `isSnapshotQuery()` — Returns `true` if the `"snapshot"` option is set +- `reportRemovedRows()` — Returns `true` if the `"removed"` option is set or absent (default behavior) +- `operator==` / `operator!=` — Equality based on `query` and `interval` fields + +## Usage Example + +```cpp +#include + +// Construct with required fields +osquery::ScheduledQuery sq("my_pack", "active_users", "SELECT * FROM users;"); +sq.interval = 60; +sq.options["snapshot"] = false; +sq.options["removed"] = true; + +// Check query behavior +if (sq.isSnapshotQuery()) { + // handle snapshot mode +} + +if (sq.reportRemovedRows()) { + // include removed row diffs in results +} + +// Move semantics only — copies are disabled +osquery::ScheduledQuery sq2 = std::move(sq); +``` \ No newline at end of file diff --git a/osquery/core/sql/.table_row.md b/osquery/core/sql/.table_row.md new file mode 100644 index 00000000000..4b40290bcb5 --- /dev/null +++ b/osquery/core/sql/.table_row.md @@ -0,0 +1,52 @@ + +Defines the `TableRow` abstract interface for accessing and manipulating a single row of data within an osquery virtual table, abstracting over both map-backed and code-generated type-safe row implementations. + +## Key Components + +| Symbol | Kind | Description | +|---|---|---| +| `TableRow` | Abstract class | Base interface for all row implementations | +| `TableRowHolder` | Type alias | `std::unique_ptr` for ownership management | +| `get_rowid()` | Pure virtual method | Retrieves the SQLite row ID, returning `SQLITE_OK` or `SQLITE_ERROR` | +| `get_column()` | Pure virtual method | Invokes the appropriate `sqlite3_result_xxx` call for a given column index | +| `serialize()` | Pure virtual method | Serializes row data as key/value pairs into a RapidJSON object | +| `clone()` | Pure virtual method | Returns a deep copy of the row as a `TableRowHolder` | +| `operator Row()` | Pure virtual operator | Converts the row to a `map` (`Row`) | + +## Usage Example + +```cpp +// Implementing a custom TableRow subclass +class MyTableRow : public osquery::TableRow { + public: + int get_rowid(sqlite_int64 default_value, + sqlite_int64* pRowid) const override { + *pRowid = default_value; + return SQLITE_OK; + } + + int get_column(sqlite3_context* ctx, + sqlite3_vtab* pVtab, + int col) override { + sqlite3_result_text(ctx, data_[col].c_str(), -1, SQLITE_TRANSIENT); + return SQLITE_OK; + } + + osquery::TableRowHolder clone() const override { + return std::make_unique(*this); + } + + operator osquery::Row() const override { + return data_; + } + + // ... + private: + osquery::Row data_; +}; + +// Consuming via the interface +osquery::TableRowHolder row = std::make_unique(); +osquery::TableRowHolder copy = row->clone(); +osquery::Row map = static_cast(*row); +``` \ No newline at end of file diff --git a/osquery/core/sql/.table_rows.md b/osquery/core/sql/.table_rows.md new file mode 100644 index 00000000000..91963c102ad --- /dev/null +++ b/osquery/core/sql/.table_rows.md @@ -0,0 +1,47 @@ + +Defines the `TableRows` type alias and declares serialization/deserialization utilities for converting collections of table rows to and from JSON. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `TableRows` | Type alias | `std::vector` — an ordered collection of table row objects | +| `serializeTableRows` | Function | Serializes a `TableRows` into a `rapidjson::Document` JSON array | +| `serializeTableRowsJSON` | Function | Serializes a `TableRows` directly into a JSON string | +| `deserializeTableRows` | Function | Converts a `rapidjson::Value` JSON array back into a `TableRows` | +| `deserializeTableRowsJSON` | Function | Converts a raw JSON string back into a `TableRows` | + +All functions return `osquery::Status` to indicate success or failure. + +## Usage Example + +```c +#include "table_rows.h" + +// Serialize to JSON string +osquery::TableRows rows = getMyTableRows(); +std::string jsonOutput; + +osquery::Status s = osquery::serializeTableRowsJSON(rows, jsonOutput); +if (!s.ok()) { + LOG(ERROR) << "Serialization failed: " << s.getMessage(); +} + +// Deserialize from JSON string +osquery::TableRows restored; +osquery::Status ds = osquery::deserializeTableRowsJSON(jsonOutput, restored); +if (!ds.ok()) { + LOG(ERROR) << "Deserialization failed: " << ds.getMessage(); +} + +// Serialize into an existing JSON document/array +osquery::JSON doc; +rapidjson::Document arr; +osquery::serializeTableRows(rows, doc, arr); +``` + +## Dependencies + +- `osquery/utils/json/json.h` — JSON document wrapper +- `query_data.h` — underlying query result types +- `table_row.h` — `TableRowHolder` definition \ No newline at end of file diff --git a/osquery/core/tests/.flags_tests.md b/osquery/core/tests/.flags_tests.md new file mode 100644 index 00000000000..bfce76977a2 --- /dev/null +++ b/osquery/core/tests/.flags_tests.md @@ -0,0 +1,35 @@ + +Unit tests for the osquery flags system, verifying flag registration, value access, default tracking, flag details, shell/extension flag types, flag aliases (including typed aliases), and platform type detection. + +## Key Components + +| Test | Description | +|---|---| +| `test_set_get` | Verifies gflags integration, flag registration in the osquery tracker, and value updates via `Flag::getValue()` | +| `test_defaults` | Confirms default value persistence, `Flag::isDefault()`, and `Flag::getDefaultValue()` behavior including error handling | +| `test_details` | Validates `Flag::flags()` metadata: type, description, default value, current value, and detail bits | +| `test_flag_detail_types` | Tests `SHELL_FLAG` and `EXTENSION_FLAG` macros set the correct `.detail.shell` and `.detail.external` markers | +| `test_aliases` | Confirms `FLAG_ALIAS` creates a two-way binding — mutations on either alias or original are reflected on both | +| `test_alias_types` | Exercises typed aliases (`int32`, `int64`, `double`, `std::string`) with bidirectional read/write and lexical casting | +| `test_platform` | Validates `isPlatform()` bitmask logic and `PlatformType` enum casting | + +## Usage Example + +```cpp +// Declaring and using a flag with an alias (as tested here) +FLAG(int32, test_int32, 1, "description"); +FLAG_ALIAS(google::int32, test_int32_alias, test_int32); + +// Both variables share the same underlying value +FLAGS_test_int32_alias = 2; +assert(FLAGS_test_int32 == 2); // alias write reflects on original + +// Retrieve flag metadata via the osquery wrapper +auto all_flags = Flag::flags(); +auto info = all_flags["test_int32"]; +// info.type, info.default_value, info.value, info.description + +// Shell-only and extension-only flags carry detail markers +SHELL_FLAG(bool, shell_only, true, "shell description"); +assert(Flag::flags()["shell_only"].detail.shell == true); +``` \ No newline at end of file diff --git a/osquery/core/tests/.process_tests.md b/osquery/core/tests/.process_tests.md new file mode 100644 index 00000000000..5a40d095fcf --- /dev/null +++ b/osquery/core/tests/.process_tests.md @@ -0,0 +1,45 @@ + +Unit tests for the `PlatformProcess` abstraction layer in osquery, validating cross-platform process lifecycle operations including construction, PID retrieval, environment variable management, and subprocess launching for both worker and extension processes. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `ProcessTests` | Test fixture | GTest fixture class housing all `PlatformProcess` unit tests | +| `getProcessExitCode()` | Helper | Blocking wait for process exit; retrieves exit code via `waitpid` (POSIX) or `WaitForSingleObject` (Win32) | +| `compareArguments()` | Helper | Element-wise comparison of argument arrays to validate subprocess `argv` contents | +| `workerMain()` | Entry point | Subprocess entry point invoked when `OSQUERY_WORKER` env var is set; validates args and launcher identity | +| `extensionMain()` | Entry point | Subprocess entry point invoked when `OSQUERY_EXTENSION` env var is set; validates extension args | +| `kExpectedWorkerArgs` / `kExpectedExtensionArgs` | Constants | Expected `argv` arrays used to verify correct argument passing to spawned subprocesses | + +## Test Cases + +| Test | Description | +|---|---| +| `test_constructor` | Verifies invalid PID produces an invalid `PlatformProcess` | +| `test_constructorWin` / `test_constructorPosix` | Platform-specific handle/PID round-trip validation | +| `test_getpid` | Confirms `getCurrentProcess()->pid()` matches the OS-reported PID | +| `test_envVar` | Tests `getEnvVar`, `setEnvVar`, and `unsetEnvVar` lifecycle | +| `test_launchExtension` | Spawns extension subprocess and asserts `EXTENSION_SUCCESS_CODE` exit | +| `test_launchWorker` | Spawns worker subprocess and asserts `WORKER_SUCCESS_CODE` exit | + +## Usage Example + +```cpp +// The test binary serves as its own subprocess target. +// Normal test run: +// $ ./osquery_core_tests_processtests-test +// +// Subprocess modes triggered by environment variables: +// OSQUERY_WORKER=1 → runs workerMain(), validates argv + launcher identity +// OSQUERY_EXTENSION=1 → runs extensionMain(), validates extension argv + +// Typical PlatformProcess usage validated by these tests: +auto process = PlatformProcess::launchWorker(execPath, argc, argv); +int exitCode = 0; +if (getProcessExitCode(*process, exitCode)) { + assert(exitCode == WORKER_SUCCESS_CODE); +} +``` + +> **Note:** On Linux, `workerMain` reads `/proc//cmdline` to verify the launcher process name matches `kOsqueryTestModuleName`, ensuring the subprocess was spawned by the correct parent binary. \ No newline at end of file diff --git a/osquery/core/tests/.query_performance_tests.md b/osquery/core/tests/.query_performance_tests.md new file mode 100644 index 00000000000..3297d7cdef6 --- /dev/null +++ b/osquery/core/tests/.query_performance_tests.md @@ -0,0 +1,32 @@ + +Unit tests for the `QueryPerformance` struct, validating CSV serialization/deserialization and default state behavior. + +## Key Components + +- **`QueryPerformanceTests`** — GTest fixture class for `QueryPerformance` unit tests +- **`test_query_performance`** — Single test case covering three scenarios: + - **Default case** — verifies default-constructed `QueryPerformance` equals an empty-string-constructed instance and serializes to `"0,0,0,0,0,0,0,0,0,0,0,0"` + - **Normal case** — validates that a 12-field CSV string correctly deserializes into all struct fields and round-trips back to the same CSV + - **Invalid case** — confirms graceful handling of malformed CSV (empty fields, non-numeric values) by zeroing out invalid fields while preserving valid ones + +## Usage Example + +```cpp +// Construct from CSV string +QueryPerformance stats("1,2,3,4,5,6,7,8,9,10,11,12"); + +// Access individual fields +assert(stats.executions == 1); +assert(stats.last_executed == 2); + +// Serialize back to CSV +std::string csv = stats.toCSV(); +// csv == "1,2,3,4,5,6,7,8,9,10,11,12" + +// Invalid fields are zeroed +QueryPerformance partial("1,,bozo,4,5,6,7,8,9,10,11,12"); +assert(partial.last_executed == 0); // empty field → 0 +assert(partial.wall_time == 0); // "bozo" → 0 +``` + +The 12 tracked fields in CSV order are: `executions`, `last_executed`, `wall_time`, `wall_time_ms`, `last_wall_time_ms`, `user_time`, `last_user_time`, `system_time`, `last_system_time`, `average_memory`, `last_memory`, `output_size`. \ No newline at end of file diff --git a/osquery/core/tests/.query_tests.md b/osquery/core/tests/.query_tests.md new file mode 100644 index 00000000000..c70364fd8d6 --- /dev/null +++ b/osquery/core/tests/.query_tests.md @@ -0,0 +1,45 @@ + +Unit test suite for osquery's `Query` class and `ScheduledQuery` functionality, verifying counter management, result diffing, database storage, and query lifecycle behavior. + +## Key Components + +| Test | Description | +|------|-------------| +| `test_private_members` | Validates `Query` object stores the SQL string correctly | +| `test_increment_counter` | Verifies counter reset and increment logic across full/partial result sets | +| `test_get_query_status` | Checks new-epoch and new-SQL detection across query executions | +| `test_add_and_get_current_results` | Validates result storage and differential computation over simulated schedule runs | +| `test_get_query_results` | Tests retrieval of previously stored query results from the database | +| `test_query_name_not_found_in_db` | Confirms graceful failure when no prior results exist | +| `test_is_query_name_in_database` | Checks persistence of query names in the KV store | +| `test_query_name_updated` | Validates differential behavior and counter state when query SQL changes | +| `test_get_stored_query_names` | Ensures factory method returns all queries with stored results | +| `test_is_snapshot_query` | Tests `ScheduledQuery::isSnapshotQuery()` option toggling | +| `test_report_removed_rows` | Tests `ScheduledQuery::reportRemovedRows()` default and override behavior | + +## Usage Example + +```cpp +// Typical pattern used throughout the test suite +auto query = getOsqueryScheduledQuery(); +auto cf = Query("foobar", query); + +// Add results and compute diff against previous execution +uint64_t counter = 128; +DiffResults dr; +auto status = cf.addNewResults(getTestDBExpectedResults(), /*epoch=*/0, counter, dr); +ASSERT_TRUE(status.ok()); +EXPECT_EQ(counter, 0UL); // Reset on first run + +// Retrieve previously stored results +QueryDataSet previous_qd; +status = cf.getPreviousQueryResults(previous_qd); +ASSERT_TRUE(status.ok()); +``` + +## Dependencies + +- **GTest** — test framework +- **osquery/core/query.h** — `Query`, `DiffResults` +- **osquery/core/sql/scheduled_query.h** — `ScheduledQuery` +- **osquery/sql/tests/sql_test_utils.h** — test fixtures (`getTestDBExpectedResults`, `getTestDBResultStream`) \ No newline at end of file diff --git a/osquery/core/tests/.system_test.md b/osquery/core/tests/.system_test.md new file mode 100644 index 00000000000..7f53beb749c --- /dev/null +++ b/osquery/core/tests/.system_test.md @@ -0,0 +1,26 @@ + +Unit tests for the `isPlaceholderHardwareUUID` function from `osquery/core/system.h`, validating detection of placeholder vs. real hardware UUIDs. + +## Key Components + +- **`UUIDTests`** — GTest fixture class housing UUID validation test cases +- **`test_invalid_uuid`** — Asserts that a known placeholder UUID is correctly identified +- **`test_valid_uuid`** — Asserts that a real hardware UUID is not flagged as a placeholder + +## Usage Example + +```cpp +// Running the tests via GTest +// These tests exercise isPlaceholderHardwareUUID() from osquery/core/system.h + +std::string placeholder = "10000000-0000-8000-0040-000000000000"; +EXPECT_TRUE(isPlaceholderHardwareUUID(placeholder)); // Should be placeholder + +std::string real_uuid = "226e380e-67d1-4214-9868-5383a79af0b8"; +EXPECT_FALSE(isPlaceholderHardwareUUID(real_uuid)); // Should not be placeholder +``` + +## Notes + +- Placeholder UUIDs typically originate from virtualized or misconfigured hardware that returns zeroed or patterned identifiers instead of genuine hardware-assigned values. +- The tested placeholder (`10000000-0000-8000-0040-000000000000`) follows a known pattern used to detect non-unique or auto-generated UUIDs in osquery's system identification logic. \ No newline at end of file diff --git a/osquery/core/tests/.tables_tests.md b/osquery/core/tests/.tables_tests.md new file mode 100644 index 00000000000..37b382156b3 --- /dev/null +++ b/osquery/core/tests/.tables_tests.md @@ -0,0 +1,37 @@ + +Unit tests for osquery's core table primitives — `Constraint`, `ConstraintList`, `ConstraintMap`, `TablePlugin` caching — using Google Test. + +## Key Components + +| Component | Description | +|---|---| +| `TablesTests` | GTest fixture with platform/registry/database initialization in `SetUp()` | +| `test_constraint` | Validates basic `Constraint` construction (operator + expression assignment) | +| `test_constraint_list` | Verifies `ConstraintList::add()` and `getAll()` filtering by operator type | +| `test_constraint_matching` | Covers `exists()`, `matches()`, `notExistsOrMatches()`, `existsAndMatches()` for string and integer affinity, including all comparison operators | +| `test_constraint_map` | Tests `ConstraintMap` key-based lookup — constrained vs. unconstrained column behavior | +| `test_constraint_map_cast` | Confirms type-cast rejection: an integer-affinity column rejects a non-numeric `EQUALS` match | +| `TestTablePlugin` / `test_caching` | Exercises `TablePlugin::isCached()` and `setCache()` against `kCacheStep`/`kCacheInterval` boundaries | + +## Usage Example + +```cpp +// Typical constraint construction and matching pattern tested here +ConstraintList cl; +cl.affinity = INTEGER_TYPE; + +auto c = Constraint(GREATER_THAN); +c.expr = "1"; +cl.add(c); + +c = Constraint(LESS_THAN); +c.expr = "1000"; +cl.add(c); + +cl.matches(10); // true — within bounds +cl.matches(1); // false — exclusive lower bound +cl.matches(1000); // false — exclusive upper bound +cl.matches("10"); // true — string input auto-cast for integer affinity +``` + +> **Note:** `LESS_THAN_OR_EQUALS` / `GREATER_THAN_OR_EQUALS` are also covered; bounds become inclusive (`matches(1000)` → `true`). \ No newline at end of file diff --git a/osquery/core/tests/.watcher_tests.md b/osquery/core/tests/.watcher_tests.md new file mode 100644 index 00000000000..eb9f75b5fd4 --- /dev/null +++ b/osquery/core/tests/.watcher_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the `WatcherRunner` and `Watcher` classes in osquery, validating the watchdog process state machine including worker health checks, process monitoring, resource limit enforcement, and loop behavior. + +## Key Components + +| Class/Function | Description | +|---|---| +| `WatcherTests` | Base test fixture initializing platform, registry, and config state | +| `MockWatcherRunner` | GMock-based mock of `WatcherRunner` for isolating `stopChild`, `warnWorkerResourceLimitHit`, and `isChildSane` | +| `MockWatcherRunnerUnhealthy` | Extended mock with injectable `QueryData` to simulate unhealthy process metrics | +| `MockWithWatchWatcherRunner` | Super-mock that also mocks `watch`, `isWatcherHealthy`, `createExtension`, and `createWorker` | +| `FakePlatformProcess` | Controllable stub of `PlatformProcess` allowing tests to set `ProcessState` and status codes | +| `FakeWatcherRunner` | Concrete subclass with injectable `QueryData` via `setProcessRow` for process-table-free testing | + +## Test Coverage + +| Test | Validates | +|---|---| +| `test_watcherrunner_watch` | Healthy worker: `isChildSane` called once, no stop triggered | +| `test_watcherrunner_stop` | Errored process: `watch()` returns `false`, no sanity check run | +| `test_watcherrunner_loop` | Main loop with healthy worker: `createWorker` never called | +| `test_watcherrunner_loop_failure` | Failed watch triggers `createWorker` exactly once | +| `test_watcherrunner_loop_disabled` | No-worker mode: `watch` and `isWatcherHealthy` never called | +| `test_watcherrunner_watcherhealth` | Memory/CPU limit detection and `sustained_latency` tracking | + +## Usage Example + +```cpp +// Injecting fake process metrics to simulate resource exhaustion +Row r; +r["resident_size"] = INTEGER(1024 * 1024 * 1024); // 1GB +r["user_time"] = INTEGER(1024 * 1024 * 1024); +runner.setProcessRow({r}); + +auto status = runner.isWatcherHealthy(*test_process, state); +EXPECT_FALSE(status.ok()); +EXPECT_THAT(status.getMessage(), + ::testing::HasSubstr("Memory limits exceeded")); +``` \ No newline at end of file diff --git a/osquery/core/tests/posix/.permissions_tests.md b/osquery/core/tests/posix/.permissions_tests.md new file mode 100644 index 00000000000..18964800647 --- /dev/null +++ b/osquery/core/tests/posix/.permissions_tests.md @@ -0,0 +1,39 @@ + +Unit tests for osquery's privilege dropping (`DropPrivileges`) and permission management functionality, verifying correct behavior for explicit drops, path-based drops, functional access control, and multi-threaded scenarios. + +## Key Components + +### Test Fixture +- **`PermissionsTests`** — GTest fixture that creates/destroys a temporary directory (`/tmp/lowperms.XXXX`) for each test case via `SetUp`/`TearDown` + +### Test Cases + +| Test | Description | +|------|-------------| +| `test_explicit_drop` | Verifies `DropPrivileges::dropTo()` with current UID/GID and checks state restoration on scope exit | +| `test_path_drop` | Validates `dropToParent()` drops to the owning user of a given path (requires root) | +| `test_functional_drop` | Confirms a dropped process cannot read a `0400` file owned by root (requires root) | +| `test_nobody_drop` | Verifies dropping to `nobody` by both numeric UID/GID and string representations (requires root) | +| `test_multi_thread_permissions` | Ensures a background writer thread continues operating while the main thread drops to `nobody` | +| `test_multi_thread_poll` | Validates `poll()` on a file descriptor behaves correctly across a privilege drop in a concurrent thread | + +### Helper Classes +- **`PermissionsRunnable`** — `InternalRunnable` that continuously writes to a shared path; tracks write count via `ticks` +- **`PermissionsPollRunnable`** — Extends `PermissionsRunnable` to `poll()` a file descriptor instead of writing, tracking poll timeouts + +### Helper Function +- **`waitForTick()`** — Busy-waits up to 1000ms for a runnable's `ticks` counter to increment + +## Usage Example + +```cpp +// Tests run via GTest — root privileges required for most cases: +// sudo ./permissions_tests + +// Core pattern under test: +{ + auto dropper = DropPrivileges::get(); + dropper->dropTo(nobody->pw_uid, nobody->pw_gid); + // Reduced privileges active within this scope +} // Privileges automatically restored on destruction +``` \ No newline at end of file diff --git a/osquery/core/tests/windows/.wmi_tests.md b/osquery/core/tests/windows/.wmi_tests.md new file mode 100644 index 00000000000..573da13fa7b --- /dev/null +++ b/osquery/core/tests/windows/.wmi_tests.md @@ -0,0 +1,41 @@ + +Unit tests for osquery's Windows Management Instrumentation (WMI) wrapper, validating WMI method invocation with both input and output parameters. + +## Key Components + +### `WmiTests` (Test Fixture) +Inherits from `testing::Test`, calls `platformSetup()` in `SetUp()` to initialize the osquery platform environment before each test. + +### `test_methodcall_inparams` +Tests WMI method execution **with input parameters** by: +- Querying `Win32_Directory` for the `WINDIR` path +- Calling `GetEffectivePermission` with `Permissions = "1"` (`FILE_LIST_DIRECTORY`) +- Asserting the method returns `true`, confirming both admin and standard users can list `WINDIR` + +### `test_methodcall_outparams` +Tests WMI method execution **with output parameters** by: +- Querying `Win32_Process` for `wininit.exe` +- Calling `GetOwner` with no input parameters +- Asserting the return value is `0` (success) and the owner is `NT AUTHORITY\SYSTEM` + +## Usage Example + +```cpp +// Create a WMI query +Expected req = + WmiRequest::CreateWmiRequest("SELECT * FROM Win32_Process WHERE Name = \"explorer.exe\""); + +// Execute a method with input parameters +WmiMethodArgs args; +WmiResultItem out; +args.Put("SomeParam", "value"); + +auto& item = req->results().front(); +auto status = req->ExecMethod(item, "SomeMethod", args, out); + +// Read output parameter +std::string result; +out.GetString("OutputKey", result); +``` + +> **Note:** These tests are Windows-only and require WMI to be accessible. Backslashes in WMI path queries must be double-escaped (`\\\\`) to satisfy WQL string parsing. \ No newline at end of file diff --git a/osquery/core/windows/.global_users_groups_cache.md b/osquery/core/windows/.global_users_groups_cache.md new file mode 100644 index 00000000000..42bc31f30e6 --- /dev/null +++ b/osquery/core/windows/.global_users_groups_cache.md @@ -0,0 +1,34 @@ + +Declares the `GlobalUsersGroupsCache` class, which provides synchronized global access to Windows user and group cache data initialized by background services. + +## Key Components + +### `GlobalUsersGroupsCache` (class) + +A singleton-style accessor class for Windows users and groups cache data, designed to block until cache initialization completes before returning references. + +| Member | Type | Description | +|--------|------|-------------| +| `getUsersCache()` | `static const UsersCache&` | Blocks until the users cache is ready, then returns a const reference | +| `getGroupsCache()` | `static const GroupsCache&` | Blocks until the groups cache is ready, then returns a const reference | +| `global_users_cache_future_` | `std::shared_future` | Synchronization primitive tracking users cache initialization | +| `global_groups_cache_future_` | `std::shared_future` | Synchronization primitive tracking groups cache initialization | +| `global_users_cache_` | `std::shared_ptr` | Shared ownership pointer to the users cache instance | +| `global_groups_cache_` | `std::shared_ptr` | Shared ownership pointer to the groups cache instance | + +**Friend declarations:** `Initializer`, `initUsersAndGroupsServices`, `deinitUsersAndGroupsServices` — only these can populate the private cache state. + +## Usage Example + +```cpp +#include + +// Blocks until the background service has finished populating the cache, +// then returns a safe const reference for querying. +const UsersCache& users = GlobalUsersGroupsCache::getUsersCache(); +const GroupsCache& groups = GlobalUsersGroupsCache::getGroupsCache(); + +// Use the caches for lookup operations (Windows only) +``` + +> **Note:** Both accessors are blocking calls. They rely on `std::shared_future` to ensure the background initialization service has completed before returning. This header is Windows-specific and wraps `users_groups_cache.h`. \ No newline at end of file diff --git a/osquery/core/windows/.handle.md b/osquery/core/windows/.handle.md new file mode 100644 index 00000000000..d38911d1f47 --- /dev/null +++ b/osquery/core/windows/.handle.md @@ -0,0 +1,41 @@ + +RAII-style wrapper class for Windows kernel object handles (`HANDLE`), providing safe open/close lifecycle management for directory and symbolic link objects. + +## Key Components + +### `Handle` class + +| Member | Description | +|---|---| +| `openDirObj(directory)` | Opens a Windows object directory by name using `OPEN_DIRECTORY` access mask | +| `openSymLinkObj(symlink)` | Opens a Windows symbolic link by name using `SYMBOLIC_LINK_QUERY` access mask | +| `getAsHandle()` | Returns the underlying `HANDLE` value | +| `close()` | Explicitly releases the handle | +| `valid()` | Returns `true` if the handle is currently open and valid | +| `~Handle()` | Destructor automatically closes the handle on scope exit | + +## Usage Example + +```c +#include "handle.h" + +osquery::Handle h; + +// Open a Windows object directory +osquery::Status s = h.openDirObj(L"\\BaseNamedObjects"); +if (!s.ok()) { + // handle error +} + +HANDLE raw = h.getAsHandle(); + +// Check validity before use +if (h.valid()) { + // perform operations on raw handle +} + +// Explicitly close, or destructor will close on scope exit +h.close(); +``` + +> **Note:** This class is Windows-only and relies on `osquery/utils/system/system.h`. The RAII destructor ensures handles are not leaked even if exceptions occur, making it safer than manual `CloseHandle` calls. \ No newline at end of file diff --git a/osquery/core/windows/.ntapi.md b/osquery/core/windows/.ntapi.md new file mode 100644 index 00000000000..c37e42ee2db --- /dev/null +++ b/osquery/core/windows/.ntapi.md @@ -0,0 +1,64 @@ + +Windows NT Native API type definitions and function pointer declarations for low-level Windows kernel object and system information queries within the osquery framework. + +## Key Components + +### Constants & Macros +| Macro | Value | Purpose | +|---|---|---| +| `NTSTATUS` | `ULONG` | NT status code type alias | +| `STATUS_SUCCESS` | `0L` | Successful operation return value | +| `OBJ_CASE_INSENSITIVE` | `64L` | Object attribute flag for case-insensitive name matching | +| `DIRECTORY_QUERY` | `0x0001` | Access mask for directory object queries | +| `SYMBOLIC_LINK_QUERY` | `0x0001` | Access mask for symbolic link queries | + +### Enumerations +- **`SYSTEM_INFORMATION_CLASS`** — Classes for `ZwQuerySystemInformation` (processor, performance, time, path, process info) +- **`OBJECT_INFORMATION_CLASS`** — Classes for `ZwQueryObject` (basic, name, type, handle info) + +### Structures +- **`UNICODE_STRING`** — NT Unicode string with length, max length, and wide-char buffer +- **`OBJECT_ATTRIBUTES`** — NT object attributes including name, root directory, and security descriptors +- **`SYSTEM_HANDLE_INFORMATION`** — Per-handle info: process ID, type, flags, handle value, object pointer, and access mask +- **`OBJDIR_INFORMATION`** — Object directory entry containing object name and type name + +### Function Pointer Typedefs +| Typedef | Purpose | +|---|---| +| `ZwQueryObject` | Query kernel object information | +| `ZwQuerySystemInformation` | Query system-wide information | +| `NTCLOSE` | Close an NT handle | +| `NTOPENDIRECTORYOBJECT` | Open an NT namespace directory | +| `NTQUERYDIRECTORYOBJECT` | Enumerate entries in an NT directory object | +| `NTOPENSYMBOLICLINKOBJECT` | Open an NT symbolic link | +| `NTQUERYSYMBOLICLINKOBJECT` | Resolve an NT symbolic link target | + +## Usage Example + +```c +#include "ntapi.h" + +// Dynamically load and call NT APIs at runtime +HMODULE ntdll = GetModuleHandleA("ntdll.dll"); + +ZwQueryObject queryObject = + (ZwQueryObject)GetProcAddress(ntdll, "ZwQueryObject"); + +NTOPENDIRECTORYOBJECT openDir = + (NTOPENDIRECTORYOBJECT)GetProcAddress(ntdll, "NtOpenDirectoryObject"); + +UNICODE_STRING dirName; +// Initialize dirName, then open the object directory +OBJECT_ATTRIBUTES attrs; +attrs.Length = sizeof(OBJECT_ATTRIBUTES); +attrs.Attributes = OBJ_CASE_INSENSITIVE; +attrs.ObjectName = &dirName; + +HANDLE hDir = NULL; +NTSTATUS status = openDir(&hDir, DIRECTORY_QUERY, &attrs); +if (status == STATUS_SUCCESS) { + // enumerate directory entries ... +} +``` + +> **Note:** These NT API functions are undocumented Win32 internals loaded dynamically from `ntdll.dll`. All declarations are scoped under the `osquery` namespace to avoid collisions with system headers. \ No newline at end of file diff --git a/osquery/core/windows/.platform.md b/osquery/core/windows/.platform.md new file mode 100644 index 00000000000..685aa9809de --- /dev/null +++ b/osquery/core/windows/.platform.md @@ -0,0 +1,28 @@ + +Provides Windows-specific platform initialization and teardown routines for osquery, managing COM library lifecycle required for WMI (Windows Management Instrumentation) calls. + +## Key Components + +| Function | Description | +|---|---| +| `platformSetup()` | Initializes Windows COM libraries in multithreaded mode via `CoInitializeEx` | +| `platformTeardown()` | Releases COM library resources via `CoUninitialize` on shutdown | +| `alarm(int)` | No-op stub providing POSIX `alarm()` compatibility on Windows | + +## Usage Example + +```cpp +#include + +// Called once at application startup +osquery::platformSetup(); + +// ... application runs, WMI queries execute via COM ... + +// Called once at application shutdown +osquery::platformTeardown(); +``` + +> **Note:** `platformSetup()` calls `CoUninitialize()` as a safety rollback if `CoInitializeEx` fails, ensuring no partial COM state is left initialized. + +The `alarm()` stub exists to satisfy cross-platform code that references the POSIX `alarm()` signal function, which has no native equivalent on Windows. \ No newline at end of file diff --git a/osquery/core/windows/.wmi.md b/osquery/core/windows/.wmi.md new file mode 100644 index 00000000000..c5bf928f37f --- /dev/null +++ b/osquery/core/windows/.wmi.md @@ -0,0 +1,50 @@ + +Windows Management Instrumentation (WMI) query abstraction layer for osquery, providing C++ wrapper classes to execute WMI queries and retrieve typed results on Windows systems. + +## Key Components + +### `WmiMethodArgs` +Constructs and holds arguments for WMI method calls. Used alongside `WmiResultItem::ExecMethod`. +- `Put(name, value)` — Adds a typed argument to the call +- `GetArguments()` — Returns the underlying `WmiMethodArgsMap` + +### `WmiResultItem` +Wraps a single `IWbemClassObject` result, providing typed getters for WMI properties: +- `GetBool`, `GetString`, `GetLong`, `GetUnsignedInt32`, `GetDateTime`, etc. +- `GetVectorOfStrings`, `GetVectorOfLongs` — For multi-value properties +- `PrintType(name)` — Debug helper to inspect property VARIANT types + +### `WmiRequest` +Main entry point for executing WMI queries against a namespace. +- `CreateWmiRequest(query, nspace)` — Static factory; returns `Expected` +- `results()` — Returns `std::vector` from the query +- `getStatus()` — Retrieves execution status +- `ExecMethod(object, method, args, out)` — Executes a WMI method on a result object + +### `impl::WmiObjectDeleter` +Internal RAII deleter for COM `IUnknown` pointers, ensuring `Release()` is called on cleanup. + +## Usage Example + +```cpp +#include + +// Execute a WMI query +auto request = osquery::WmiRequest::CreateWmiRequest( + "SELECT * FROM Win32_OperatingSystem" +); + +if (request) { + for (const auto& item : request->results()) { + std::string caption; + if (item.GetString("Caption", caption).ok()) { + std::cout << "OS: " << caption << std::endl; + } + + unsigned long long totalMemory = 0; + if (item.GetUnsignedLongLong("TotalVisibleMemorySize", totalMemory).ok()) { + std::cout << "RAM (KB): " << totalMemory << std::endl; + } + } +} +``` \ No newline at end of file diff --git a/osquery/database/.database.md b/osquery/database/.database.md new file mode 100644 index 00000000000..e9e76edce13 --- /dev/null +++ b/osquery/database/.database.md @@ -0,0 +1,58 @@ + +Defines the `DatabasePlugin` abstract interface and supporting free functions for osquery's key-value backing storage system, abstracting RocksDB (and SQLite) behind a domain/key API. + +## Key Components + +### Domain Constants +| Constant | Purpose | +|---|---| +| `kPersistentSettings` | Node UUID, enrollment keys, persisted config | +| `kQueries` | Scheduled query results | +| `kEvents` | Queued event results | +| `kCarves` | File carve query results | +| `kLogs` | Buffered logger plugin output | +| `kDistributedQueries` | Distributed query storage | +| `kQueryPerformance` | Query performance statistics | + +### `DatabasePlugin` (abstract class) +Extends `Plugin`; all backing store implementations must override: +- `get()` — retrieve a string or int value by domain + key +- `put()` — store a string or int value +- `putBatch()` — write multiple key/value pairs atomically +- `remove()` / `removeRange()` — delete single or ranged keys +- `scan()` — list keys with optional prefix filter +- `call()` — registry/extension routing entrypoint + +### Free Functions +- `getDatabaseValue` / `setDatabaseValue` / `setDatabaseBatch` — primary read/write API for all osquery components +- `deleteDatabaseValue` / `deleteDatabaseRange` — key removal +- `scanDatabaseKeys` — enumerate keys in a domain +- `initDatabasePlugin` / `initDatabasePluginForTesting` — lifecycle setup +- `shutdownDatabase` / `resetDatabase` — teardown and reset +- `upgradeDatabase` — migrates legacy ptree JSON to RapidJSON format +- `getOsqueryDatabase` — returns the active `IDatabaseInterface` + +## Usage Example + +```c +#include + +// Write a persistent value +osquery::Status s = osquery::setDatabaseValue( + osquery::kPersistentSettings, "node_key", "abc-123"); + +// Read it back +std::string node_key; +s = osquery::getDatabaseValue( + osquery::kPersistentSettings, "node_key", node_key); + +// Scan all keys in a domain +std::vector keys; +osquery::scanDatabaseKeys(osquery::kQueries, keys); + +// Batch write +osquery::DatabaseStringValueList batch = { + {"key1", "value1"}, + {"key2", "value2"}}; +osquery::setDatabaseBatch(osquery::kLogs, batch); +``` \ No newline at end of file diff --git a/osquery/database/.ephemeral.md b/osquery/database/.ephemeral.md new file mode 100644 index 00000000000..8964350a11a --- /dev/null +++ b/osquery/database/.ephemeral.md @@ -0,0 +1,42 @@ + +Implements an in-memory (ephemeral) database plugin for osquery, providing a non-persistent key-value store backed by a nested `std::map` using `boost::variant` to support both `int` and `std::string` value types. + +## Key Components + +- **`EphemeralDatabasePlugin`** — Concrete `DatabasePlugin` subclass registered internally as `"ephemeral"`. All data lives in-process and is discarded on teardown. +- **`DBType`** — Internal storage type: `map>>`. +- **`getAny()`** — Templated retrieval helper used by both `get()` overloads; performs domain/key lookup and `boost::get` type extraction. +- **`get()` / `put()` / `putBatch()`** — Overloaded read/write operations for `std::string` and `int` values. +- **`remove()` / `removeRange()`** — Single-key and inclusive-range deletion within a domain. +- **`scan()`** — Key enumeration with optional prefix filtering and result count cap. +- **`setUp()`** — Resets the store by swapping in a fresh empty `DBType`. + +## Usage Example + +```cpp +// The plugin is auto-registered; access it through the DatabasePlugin interface: +std::string value; +Status s = db->get("config", "my_key", value); +if (!s.ok()) { + // Key or domain not found +} + +// Write a string value +db->put("config", "my_key", std::string("my_value")); + +// Write an int value +db->put("metrics", "count", 42); + +// Batch write +DatabaseStringValueList batch = {{"key1", "val1"}, {"key2", "val2"}}; +db->putBatch("config", batch); + +// Scan keys with prefix, max 100 results +std::vector keys; +db->scan("config", keys, "my_", 100); + +// Remove a range of keys +db->removeRange("metrics", "a", "m"); +``` + +> **Note:** This plugin is intended for testing and ephemeral use cases only. No data is persisted to disk; all state is lost when the process exits or `setUp()` is called again. \ No newline at end of file diff --git a/osquery/database/.idatabaseinterface.md b/osquery/database/.idatabaseinterface.md new file mode 100644 index 00000000000..288b926f949 --- /dev/null +++ b/osquery/database/.idatabaseinterface.md @@ -0,0 +1,50 @@ + +Abstract interface defining the contract for osquery's key-value database operations, enabling polymorphic database backends within the `osquery` namespace. + +## Key Components + +### Type Alias +- **`DatabaseStringValueList`** — `std::vector>` used for batch write operations. + +### `IDatabaseInterface` (Abstract Class) +A non-copyable pure virtual interface for domain-scoped key-value storage. All methods return `Status`. + +| Method | Description | +|---|---| +| `getDatabaseValue(domain, key, value)` | Read a `string` or `int` value by key | +| `setDatabaseValue(domain, key, value)` | Write a `string` or `int` value by key | +| `setDatabaseBatch(domain, data)` | Bulk-write a list of string key-value pairs | +| `deleteDatabaseValue(domain, key)` | Remove a single key | +| `deleteDatabaseRange(domain, low, high)` | Remove all keys within a lexicographic range | +| `scanDatabaseKeys(domain, keys, max)` | List up to `max` keys in a domain | +| `scanDatabaseKeys(domain, keys, prefix, max)` | List up to `max` keys matching a prefix | + +## Usage Example + +```cpp +// Implement a custom backend by inheriting the interface +class MyDatabase : public osquery::IDatabaseInterface { + public: + osquery::Status getDatabaseValue(const std::string& domain, + const std::string& key, + std::string& value) const override { + value = store_[domain + ":" + key]; + return osquery::Status::success(); + } + + osquery::Status setDatabaseValue(const std::string& domain, + const std::string& key, + const std::string& value) const override { + store_[domain + ":" + key] = value; + return osquery::Status::success(); + } + // ... implement remaining pure virtuals +}; + +// Use via pointer to interface +std::unique_ptr db = std::make_unique(); +std::string result; +db->getDatabaseValue("config", "refresh_interval", result); +``` + +> Copy and move construction are explicitly deleted, enforcing single-ownership semantics on concrete implementations. \ No newline at end of file diff --git a/osquery/database/benchmarks/.database_benchmarks.md b/osquery/database/benchmarks/.database_benchmarks.md new file mode 100644 index 00000000000..41a5514d48c --- /dev/null +++ b/osquery/database/benchmarks/.database_benchmarks.md @@ -0,0 +1,45 @@ + +Performance benchmarks for osquery's database layer, measuring serialization, diffing, and key-value store read/write operations using Google Benchmark. + +## Key Components + +### Helper Functions + +| Function | Description | +|---|---| +| `getExampleQueryData(x, y)` | Generates a `QueryData` vector with `y` rows, each containing `x` key-value columns | +| `getExampleQueryDataSet(x, y)` | Generates a `QueryDataSet` (set) with `y` rows of `x` columns | +| `getExampleColumnNames(x)` | Produces a `ColumnNames` list of `x` ordered column name strings | + +### Benchmark Cases + +| Benchmark | What It Measures | +|---|---| +| `DATABASE_serialize` | `serializeQueryData()` without column ordering | +| `DATABASE_serialize_column_order` | `serializeQueryData()` with explicit column ordering | +| `DATABASE_serialize_json` | `serializeQueryDataJSON()` full JSON string output | +| `DATABASE_diff` | `diff()` between a `QueryDataSet` and `QueryData` | +| `DATABASE_query_results` | Full `Query::addNewResults()` round-trip including diff | +| `DATABASE_get` | Single key read via `getDatabaseValue()` | +| `DATABASE_store` | Single key write via `setDatabaseValue()` | +| `DATABASE_store_large` | Writing a large serialized result set (~20 cols × 100 rows) | +| `DATABASE_store_append` | Sequential unique-key write+delete cycle | + +## Usage Example + +```cpp +// Run all DATABASE benchmarks with Google Benchmark runner +// Typical argument pairs used: (columns, rows) +// ArgPair(1, 1) → minimal dataset +// ArgPair(10, 10) → moderate dataset +// ArgPair(10, 100) → wide result set + +// Example data shape produced by helpers: +// getExampleQueryData(3, 2) produces: +// [ +// {"key0": "0content", "key1": "1content", "key2": "2content"}, +// {"key0": "0content", "key1": "1content", "key2": "2content"} +// ] +``` + +> **Note:** All `DATABASE_store*` benchmarks clean up via `deleteDatabaseValue()` after each run. All benchmarks share a single database handle, matching real osquery runtime behavior. \ No newline at end of file diff --git a/osquery/database/tests/.database.md b/osquery/database/tests/.database.md new file mode 100644 index 00000000000..23af482c7bf --- /dev/null +++ b/osquery/database/tests/.database.md @@ -0,0 +1,47 @@ + +Unit tests for osquery's database abstraction layer, verifying CRUD operations, batch writes, key scanning, deletion behavior, and schema migration/upgrade logic across database versions. + +## Key Components + +| Test | Description | +|------|-------------| +| `test_set_value_str` / `test_set_value_int` | Verifies string and integer writes to the `kLogs` domain succeed | +| `test_set_str_batch` / `test_get_str_batch` | Validates `setDatabaseBatch` writes and reads multiple key-value pairs atomically | +| `test_set_value_mix1` / `test_set_value_mix2` | Confirms type overwriting — a key written as int can be overwritten as string and vice versa | +| `test_get_value_does_not_exist` | Asserts that reading a missing key returns a failed status and empty output | +| `test_get_value_str` | Roundtrips the full printable ASCII range as a stored string value | +| `test_scan_values` | Tests `scanDatabaseKeys` for unbounded and limit-bounded key enumeration | +| `test_delete_values_str` / `test_delete_values_int` | Confirms `deleteDatabaseValue` removes the key and subsequent reads fail cleanly | +| `test_ptree_upgrade_to_rj_empty_v0v1` | Validates v0→v1 migration converts empty `{}` query results to a JSON array | +| `test_ptree_upgrade_to_rj_results_v0v1` | Validates v0→v1 migration repairs malformed WiFi JSON (duplicate empty keys) into a valid 3-element array, leaving non-JSON epoch values intact | +| `test_migration_v1v2` | Validates v1→v2 migration renames `data.audit.*` event keys to `data.auditeventpublisher.*` | + +## Usage Example + +```cpp +// Typical pattern used throughout these tests +#include + +// Write a string value +auto s = setDatabaseValue(kLogs, "my_key", "my_value"); +assert(s.ok()); + +// Read it back +std::string value; +s = getDatabaseValue(kLogs, "my_key", value); +assert(s.ok() && value == "my_value"); + +// Batch write +DatabaseStringValueList batch = {{"k1", "v1"}, {"k2", "v2"}}; +s = setDatabaseBatch(kLogs, batch); + +// Scan keys (limit to 10) +std::vector keys; +scanDatabaseKeys(kLogs, keys, 10); + +// Delete a key +deleteDatabaseValue(kLogs, "my_key"); + +// Trigger schema migration to version 2 +upgradeDatabase(2); +``` \ No newline at end of file diff --git a/osquery/database/tests/.results.md b/osquery/database/tests/.results.md new file mode 100644 index 00000000000..b7ffc3042cc --- /dev/null +++ b/osquery/database/tests/.results.md @@ -0,0 +1,47 @@ + +Unit tests for osquery's query result serialization, deserialization, and diff operations using Google Test. + +## Key Components + +| Test Case | Description | +|---|---| +| `test_simple_diff` | Verifies `diff()` correctly identifies added/removed rows between old and new `QueryData` sets | +| `test_serialize_row` | Validates `serializeRow()` produces correct JSON document output | +| `test_deserialize_row_json` | Round-trip test ensuring `deserializeRowJSON()` restores original `RowTyped` from serialized JSON | +| `test_serialize_query_data` | Validates `serializeQueryData()` against expected JSON array output | +| `test_serialize_query_data_json` | Validates `serializeQueryDataJSON()` string output | +| `test_deserialize_query_data_json` | Round-trip test for `QueryDataSet` JSON deserialization | +| `test_serialize_diff_results` | Validates `serializeDiffResults()` against expected JSON object | +| `test_serialize_diff_results_json` | Validates `serializeDiffResultsJSON()` string output | +| `test_serialize_query_log_item` | Validates `serializeQueryLogItem()` document output | +| `test_serialize_query_log_item_json` | Validates `serializeQueryLogItemJSON()` string output | +| `test_adding_duplicate_rows_to_query_data` | Ensures `addUniqueRowToQueryData()` rejects duplicate rows | + +## Usage Example + +```cpp +// Diff detection between old and new query results +QueryDataSet oldSet; +QueryDataTyped newData; + +RowTyped row; +row["foo"] = "bar"; +newData.push_back(row); + +auto diffResult = diff(oldSet, newData); +// diffResult.added contains new rows +// diffResult.removed contains removed rows + +// Serialize a row to JSON string +std::string json; +serializeRowJSON(row, json, true); + +// Deserialize back to a RowTyped container +RowTyped output; +auto status = deserializeRowJSON(json, output); + +// Add only unique rows to a result set +QueryDataTyped queryData; +bool wasAdded = addUniqueRowToQueryData(queryData, row); // true +bool duplicate = addUniqueRowToQueryData(queryData, row); // false +``` \ No newline at end of file diff --git a/osquery/database/tests/.test_utils.md b/osquery/database/tests/.test_utils.md new file mode 100644 index 00000000000..f27cb6fbe72 --- /dev/null +++ b/osquery/database/tests/.test_utils.md @@ -0,0 +1,41 @@ + +Provides a reusable Google Test base class and macro for testing osquery database plugin implementations. + +## Key Components + +### Macro + +- **`CREATE_DATABASE_TESTS(n)`** — Expands into a full suite of `TEST_F` cases for a given fixture class `n`, covering all standard database operations. Use this in a `.cpp` file to instantiate tests for a specific plugin. + +### Class: `DatabasePluginTests` + +A `testing::Test` subclass within the `osquery` namespace that serves as the base fixture for all database plugin test suites. + +| Member | Type | Description | +|---|---|---| +| `path_` | `std::string` | Path to the temporary test database | +| `previous_path_` | `std::string` | Database path saved before `SetUp` runs | +| `name()` | pure virtual | Must return the plugin name under test | +| `SetUp()` / `TearDown()` | lifecycle | Initializes and cleans up the plugin under test | + +**Protected test methods** (called by the macro-generated test cases): + +`testPluginCheck` · `testReset` · `testPut` · `testPutBatch` · `testGet` · `testDelete` · `testDeleteRange` · `testScan` · `testScanLimit` + +## Usage Example + +```cpp +#include "test_utils.h" + +class RocksDBDatabaseTests : public osquery::DatabasePluginTests { + protected: + std::string name() override { + return "rocksdb"; + } +}; + +// Expands all standard TEST_F cases for RocksDBDatabaseTests +CREATE_DATABASE_TESTS(RocksDBDatabaseTests); +``` + +Adding a new database plugin test suite requires only subclassing `DatabasePluginTests`, implementing `name()`, and invoking `CREATE_DATABASE_TESTS` — no boilerplate test bodies needed. \ No newline at end of file diff --git a/osquery/devtools/.devtools.md b/osquery/devtools/.devtools.md new file mode 100644 index 00000000000..a73517eb617 --- /dev/null +++ b/osquery/devtools/.devtools.md @@ -0,0 +1,45 @@ + +Header file providing developer tools for the osquery interactive SQL shell, including query result formatting utilities and shell launch functionality. + +## Key Components + +### Flags +| Flag | Type | Description | +|------|------|-------------| +| `FLAGS_L` | `bool` | List all tables and exit | +| `FLAGS_A` | `string` | Select all from a table and exit | +| `FLAGS_pack` | `string` | Execute all queries in a pack | +| `FLAGS_disable_events` | `bool` | Disable events for fast operations | + +### Functions + +| Function | Description | +|----------|-------------| +| `launchIntoShell()` | Starts an interactive SQL query shell session | +| `prettyPrint()` | Prints `QueryData` as a formatted ASCII table to stdout | +| `jsonPrint()` | Prints `QueryData` as compact JSON to stdout | +| `jsonPrettyPrint()` | Prints `QueryData` as indented JSON to stdout | +| `computeRowLengths()` | Calculates max column widths from a `Row` for table formatting | +| `generateToken()` | Builds a separator line string for table output | +| `generateHeader()` | Builds a column header string for table output | +| `generateRow()` | Builds a formatted data row string for table output | + +## Usage Example + +```c +#include +#include + +// Launch interactive shell +int main(int argc, char* argv[]) { + osquery::initOsquery(argc, argv); + return osquery::launchIntoShell(argc, argv); +} + +// Pretty-print query results +void printResults(const osquery::QueryData& results, + const std::vector& columns) { + std::map lengths; + osquery::prettyPrint(results, columns, lengths); +} +``` \ No newline at end of file diff --git a/osquery/devtools/.printer.md b/osquery/devtools/.printer.md new file mode 100644 index 00000000000..97b98cd2749 --- /dev/null +++ b/osquery/devtools/.printer.md @@ -0,0 +1,39 @@ + +Formats and renders osquery query results to stdout in multiple output styles, including pretty-printed ASCII tables and JSON formats. + +## Key Components + +| Function | Description | +|---|---| +| `prettyPrint()` | Renders `QueryData` as a formatted ASCII table with column headers and separators | +| `jsonPrint()` | Outputs `QueryData` as a compact JSON array | +| `jsonPrettyPrint()` | Outputs `QueryData` as an indented, human-readable JSON array | +| `generateToken()` | Builds horizontal separator rows (`+---+---+`); supports emoji rendering via `ENHANCE` env var | +| `generateHeader()` | Builds the column header row, padding to match computed widths | +| `generateRow()` | Builds a single data row string, padding values to align columns; falls back to `FLAGS_nullvalue` for missing fields | +| `computeRowLengths()` | Calculates maximum column widths across rows for consistent alignment; operates on column names or cell values | + +## Usage Example + +```cpp +#include + +QueryData results = runQuery("SELECT pid, name FROM processes"); + +// Compute column widths incrementally across all rows +std::map lengths; +for (const auto& row : results) { + computeRowLengths(row, lengths, false); +} + +// Ordered column list +std::vector columns = {"pid", "name"}; + +// Pretty-print as ASCII table +prettyPrint(results, columns, lengths); + +// Or output as formatted JSON +jsonPrettyPrint(results); +``` + +> **Note:** Setting the `ENHANCE` environment variable before running replaces ASCII table borders with emoji characters for a decorative terminal output — a non-functional Easter egg. \ No newline at end of file diff --git a/osquery/devtools/.shell.md b/osquery/devtools/.shell.md new file mode 100644 index 00000000000..53b9face7ed --- /dev/null +++ b/osquery/devtools/.shell.md @@ -0,0 +1,39 @@ + +Brief description of the file's purpose: `shell.cpp` implements the interactive SQLite-based command-line shell for osquery, providing a REPL interface for querying OS data using SQL syntax, including output formatting, timer utilities, and extension socket connectivity. + +## Key Components + +| Component | Description | +|---|---| +| `SHELL_FLAG` macros | Define CLI flags: `--csv`, `--json`, `--line`, `--list`, `--separator`, `--pack`, `--connect`, etc. | +| `shellstaticFunc` | SQLite UDF that exposes a global C-string (`zShellStatic`) to SQL statements | +| `beginTimer` / `endTimer` | Cross-platform (Win32 + POSIX) CPU and wall-clock timing utilities | +| `timeOfDay` | Returns current wall-clock time via SQLite VFS interface | +| `local_getline` | Reads a line from a `FILE*` with dynamic buffer resizing | +| `one_input_line` | Retrieves a single input line; uses `linenoise` for interactive prompts or `local_getline` for file input | +| `connect_socket` / `disconnect_socket` | Manages connection to an osquery extension socket, updating shell prompts with `[*]` indicator | +| `print_bold` | Outputs bold-formatted text to the console using ANSI escape codes | +| Mode constants | `MODE_Line`, `MODE_Column`, `MODE_List`, `MODE_Csv`, `MODE_Pretty` define output formatting modes | +| `zHelp` | Static help text listing all dot-commands (`.tables`, `.schema`, `.mode`, etc.) | + +## Usage Example + +```cpp +// Typical shell invocation flow (simplified) + +// 1. Timer wraps query execution +BEGIN_TIMER; +sqlite3_exec(db, "SELECT * FROM processes;", callback, nullptr, &errMsg); +END_TIMER; +// Output: Run Time: real 0.012 user 0.008000 sys 0.002000 + +// 2. Interactive line reading with linenoise +char* line = one_input_line(nullptr, nullptr, 0 /* main prompt */); +// Displays: "osquery> " and waits for input + +// 3. Extension socket connection +osquery::FLAGS_connect = "/var/osquery/osquery.em"; +connect_socket(); +// Output: Connected to extension socket /var/osquery/osquery.em for debugging +// Prompt changes to: [*]osquery> +``` \ No newline at end of file diff --git a/osquery/devtools/tests/.printer_tests.md b/osquery/devtools/tests/.printer_tests.md new file mode 100644 index 00000000000..8478bfd8f87 --- /dev/null +++ b/osquery/devtools/tests/.printer_tests.md @@ -0,0 +1,39 @@ + +Unit tests for osquery's devtools printer/formatting functions, validating table generation utilities used to render query results in the interactive shell. + +## Key Components + +### `PrinterTests` Fixture +A `testing::Test` subclass that provides a shared `QueryData` dataset (3 rows × 4 columns: `name`, `age`, `food`, `number`) and a column `order` vector, initialized in `SetUp()`. + +### Test Cases + +| Test | Function Tested | Validates | +|---|---|---| +| `test_compute_query_data_lengths` | `computeRowLengths()` | Max value widths across all rows; column name length promotion | +| `test_generate_separator` | `generateToken()` | ASCII `+---+` separator row output | +| `test_generate_header` | `generateHeader()` | Column name header row with padding | +| `test_generate_row` | `generateRow()` | Data row formatting with correct padding | +| `test_unicode` | `computeRowLengths()` | Unicode multi-byte character (`À`) counted as 1 display character | + +## Usage Example + +```cpp +// The tests exercise the full table rendering pipeline: +// 1. Compute column widths +std::map lengths; +computeRowLengths(row, lengths); // pass values +computeRowLengths(row, lengths, true); // promote to column name widths + +// 2. Build table components +auto separator = generateToken(lengths, order); +// → "+------------+------+-------------------------+----+\n" + +auto header = generateHeader(lengths, order); +// → "| name | age | food | number |\n" + +auto dataRow = generateRow(q.front(), lengths, order); +// → "| Mike Jones | 39 | mac and cheese | 1 |\n" +``` + +> **Note:** The unicode test confirms that `À` (a 2-byte UTF-8 sequence) is treated as a single display-width character, ensuring column width calculations are visually accurate rather than byte-count based. \ No newline at end of file diff --git a/osquery/dispatcher/.dispatcher.md b/osquery/dispatcher/.dispatcher.md new file mode 100644 index 00000000000..6df777c8bb4 --- /dev/null +++ b/osquery/dispatcher/.dispatcher.md @@ -0,0 +1,74 @@ + +Manages thread lifecycle and parallel task execution for osquery services, providing a singleton dispatcher that coordinates interruptible background threads. + +## Key Components + +### `InterruptibleRunnable` +Base class for any runnable that supports clean interruption. + +| Member | Description | +|---|---| +| `interrupt()` | Signals the runnable to stop (final, non-overridable) | +| `interrupted()` | Checks if an interrupt has been requested | +| `pause(milliseconds)` | Interruptible sleep using a condition variable | +| `stop()` | Pure virtual — subclasses define their shutdown logic | +| `runnable_name_` | Thread display name | + +### `InternalRunnable` +Extends `InterruptibleRunnable` with a thread entrypoint. Non-copyable. + +| Member | Description | +|---|---| +| `run()` | Thread entrypoint called by `Dispatcher` (final) | +| `start()` | Pure virtual — subclasses define their work loop | +| `hasRun()` | Returns whether the thread context has started | + +### `Dispatcher` (Singleton) +Manages all `InternalRunnable` service threads. + +| Method | Description | +|---|---| +| `instance()` | Returns the singleton instance | +| `addService(ref)` | Spawns a new service thread (unlimited pool) | +| `joinServices()` | Blocks until all services complete | +| `stopServices()` | Interrupts and destroys all running services | +| `serviceCount()` | Returns the number of active services | + +### Type Aliases + +```c +using InternalRunnableRef = std::shared_ptr; +using InternalThreadRef = std::unique_ptr; +``` + +## Usage Example + +```c +// Define a custom background service +class MyService : public InternalRunnable { + public: + MyService() : InternalRunnable("MyService") {} + + protected: + void start() override { + while (!interrupted()) { + // do work + pause(std::chrono::milliseconds(500)); + } + } + + void stop() override { + // cleanup + } +}; + +// Register and launch the service +auto svc = std::make_shared(); +osquery::Dispatcher::addService(svc); + +// Later, cleanly shut everything down +osquery::Dispatcher::stopServices(); +osquery::Dispatcher::joinServices(); +``` + +> **Note:** `Dispatcher` is a singleton — never construct it directly. Use `Dispatcher::instance()`. Services are not subject to a fixed thread pool size, unlike the underlying Thrift pool. \ No newline at end of file diff --git a/osquery/dispatcher/.distributed_runner.md b/osquery/dispatcher/.distributed_runner.md new file mode 100644 index 00000000000..c52d1a98aff --- /dev/null +++ b/osquery/dispatcher/.distributed_runner.md @@ -0,0 +1,32 @@ + +Declares the `DistributedRunner` service thread responsible for executing distributed queries within the osquery dispatcher framework. + +## Key Components + +- **`DistributedRunner`** — A `InternalRunnable` subclass that runs as a long-lived Dispatcher service thread, implementing the distributed query polling/execution loop. + - `start()` — Entry point invoked by the Dispatcher when the thread is scheduled. +- **`startDistributed()`** — Factory/helper function to initialize and register the `DistributedRunner` with the osquery Dispatcher. + +## Usage Example + +```cpp +#include + +// Start the distributed query service (typically called during osquery startup) +Status s = osquery::startDistributed(); +if (!s.ok()) { + LOG(ERROR) << "Failed to start distributed runner: " << s.getMessage(); +} +``` + +Internally, the runner is registered via the Dispatcher: + +```cpp +// Inside startDistributed() — conceptual implementation +Status startDistributed() { + Dispatcher::addService(std::make_shared()); + return Status::success(); +} +``` + +> **Note:** `DistributedRunner` is intended to be managed exclusively by the osquery `Dispatcher`. Do not instantiate or call `start()` directly — use `startDistributed()` instead to ensure proper lifecycle management. \ No newline at end of file diff --git a/osquery/dispatcher/.scheduler.md b/osquery/dispatcher/.scheduler.md new file mode 100644 index 00000000000..01692c14232 --- /dev/null +++ b/osquery/dispatcher/.scheduler.md @@ -0,0 +1,54 @@ + +Defines the `SchedulerRunner` class and supporting utilities for executing scheduled osquery SQL queries at configurable intervals within the osquery dispatcher framework. + +## Key Components + +### `SchedulerRunner` (class) +A `InternalRunnable` dispatcher service thread that drives osquery's schedule execution loop. + +| Member | Description | +|---|---| +| `SchedulerRunner(timeout, interval, max_time_drift)` | Constructor; configures loop timeout, step interval, and optional drift cap | +| `start()` | Thread entry point; runs the scheduling loop | +| `stop()` | Interrupt/stop hook (no-op implementation) | +| `getCurrentTimeDrift()` | Returns accumulated time drift in milliseconds | + +### Private Helpers +| Method | Description | +|---|---| +| `calculateTimeDriftAndMaybePause()` | Tracks loop step duration and compensates for drift | +| `maybeRunDecorators()` | Fires interval-based query decorators | +| `maybeReloadSchedule()` | Checks and applies config schedule changes | +| `maybeFlushLogs()` | Conditionally flushes buffered status logs | +| `maybeScheduleCarves()` | Triggers file carve requests on schedule | + +### Free Functions +| Function | Description | +|---|---| +| `monitor(name, query)` | Executes a named `ScheduledQuery` and returns an `SQLInternal` result | +| `startScheduler()` | Starts the scheduler using config-defined settings | +| `startScheduler(timeout, interval)` | Starts the scheduler with explicit timeout and interval (used in tests) | + +## Usage Example + +```cpp +#include "osquery/scheduler/scheduler.h" + +// Start the scheduler using the loaded osquery config schedule +osquery::startScheduler(); + +// Or start with explicit parameters (e.g., in tests) +osquery::startScheduler( + /*timeout=*/3600UL, // run for 3600 steps + /*interval=*/60 // execute every 60 seconds +); + +// Manually monitor a specific scheduled query +osquery::ScheduledQuery sq; +sq.query = "SELECT * FROM processes;"; +sq.interval = 60; + +osquery::SQLInternal result = osquery::monitor("process_monitor", sq); +``` + +> **Note:** `request_shutdown_on_expiration` defaults to `true` in production but is suppressed during unit tests via `FRIEND_TEST(TLSConfigTests, test_runner_and_scheduler)` to prevent premature process exit. \ No newline at end of file diff --git a/osquery/dispatcher/tests/.dispatcher.md b/osquery/dispatcher/tests/.dispatcher.md new file mode 100644 index 00000000000..3c808864a54 --- /dev/null +++ b/osquery/dispatcher/tests/.dispatcher.md @@ -0,0 +1,38 @@ + +Unit test suite for the `Dispatcher` class, validating singleton behavior, service lifecycle management, runnable execution, and interruption handling within the osquery dispatcher system. + +## Key Components + +- **`DispatcherTests`** — Test fixture with `TearDown` that resets dispatcher stopping state between tests +- **`InternalTestableRunnable`** — Subclass of `InternalRunnable` that skips the first interruption check, enabling controlled test scenarios +- **`TestRunnable`** — Simple runnable with a static atomic counter (`i`) to track execution count; supports `reset()` and `count()` accessors +- **`BlockingTestRunnable`** — Runnable that sleeps for 100 seconds, used to test interruption and early termination + +## Test Cases + +| Test | What It Validates | +|---|---| +| `test_singleton` | `Dispatcher::instance()` returns the same object on repeated calls | +| `test_service_count` | Service count returns to baseline after a service completes and joins | +| `test_run` | Runnable executes exactly once; re-adding an already-run service fails | +| `test_independent_run` | Two separate instances of the same runnable class run independently | +| `test_interruption` | Calling `interrupt()` on a blocking runnable causes early exit | +| `test_stop_dispatcher` | After `stopServices()`, adding new services returns a failure status | + +## Usage Example + +```cpp +// Typical pattern used in these tests +auto runnable = std::make_shared(); + +// Add and execute a service +auto status = Dispatcher::addService(runnable); +EXPECT_TRUE(status); + +// Block until all services finish +Dispatcher::joinServices(); + +// Verify execution +EXPECT_EQ(1U, runnable->count()); +EXPECT_TRUE(runnable->hasRun()); +``` \ No newline at end of file diff --git a/osquery/dispatcher/tests/.scheduler.md b/osquery/dispatcher/tests/.scheduler.md new file mode 100644 index 00000000000..e8477bb1245 --- /dev/null +++ b/osquery/dispatcher/tests/.scheduler.md @@ -0,0 +1,33 @@ + +Unit test suite for the osquery `SchedulerRunner` and `monitor()` function, validating query execution, performance tracking, database persistence, drift accumulation, and config purge behavior. + +## Key Components + +| Test | Description | +|---|---| +| `test_monitor` | Verifies the `monitor()` wrapper executes a scheduled query, records `QueryPerformance` stats, and writes an execution timestamp to the database | +| `test_output_size` | Confirms `output_size` and `executions` counters accumulate correctly across multiple `monitor()` calls | +| `test_config_results_purge` | Validates that `Config::purge()` removes stale query data (timestamp, interval splay, differential results) only when the last run exceeds 7 days ago | +| `test_scheduler` | Loads a multi-query pack into `Config`, runs `SchedulerRunner` for one second, and asserts the table cache step advanced | +| `test_scheduler_zero_drift` | Asserts `getCurrentTimeDrift()` returns zero when queries complete well within the tick window | +| `test_scheduler_drift_accumulation` | Verifies drift accumulates (`>= 1ms`) when queries—including a `sleep(1)`—exceed the scheduler interval | +| `test_scheduler_reload` | Smoke-tests `SchedulerRunner` construction and teardown with `schedule_reload` enabled | + +## Usage Example + +```cpp +// Minimal reproduction of the monitor() test pattern +ScheduledQuery query("my_pack", "my_query", "select * from time"); +query.interval = 10; +query.splayed_interval = 11; + +auto results = monitor("pack_test_my_query", query); + +QueryPerformance perf; +Config::get().getPerformanceStats( + "pack_test_my_query", + [&perf](const QueryPerformance& r) { perf = r; }); + +assert(perf.executions == 1U); +assert(perf.output_size > 0U); +``` \ No newline at end of file diff --git a/osquery/distributed/.distributed.md b/osquery/distributed/.distributed.md new file mode 100644 index 00000000000..e9b7444e2a4 --- /dev/null +++ b/osquery/distributed/.distributed.md @@ -0,0 +1,51 @@ + +Defines the interfaces and data structures for osquery's distributed query system, enabling remote query execution, result serialization, and denylist management across endpoints. + +## Key Components + +### Structs +- **`DistributedQueryRequest`** — Holds a query string and its unique ID for a pending distributed query +- **`DistributedQueryResult`** — Holds the request, result rows, column names, status, and message from a completed query execution + +### Free Functions + +| Function | Description | +|---|---| +| `serializeDistributedQueryRequest[JSON]` | Serialize a request to JSON tree or string | +| `deserializeDistributedQueryRequest[JSON]` | Deserialize a request from JSON tree or string | +| `serializeDistributedQueryResult[JSON]` | Serialize a result to JSON tree or string | +| `deserializeDistributedQueryResult[JSON]` | Deserialize a result from JSON tree or string | +| `hashQuery` | Returns SHA-256 hex digest of a query string | +| `denylistDuration` | Returns configured denylist duration in seconds | +| `denylistedQueryTimestampExpired` | Checks if a denylisted query's timeout has elapsed | + +### Classes +- **`DistributedPlugin`** — Abstract plugin base; implementors must provide `getQueries()` and `writeResults()` to integrate with a remote query backend +- **`Distributed`** — Orchestrates the full distributed query lifecycle: pulling, executing, tracking running state, recording performance, and flushing results + +## Usage Example + +```cpp +// Basic distributed query polling loop +auto dist = osquery::Distributed(); + +while (true) { + // Pull pending queries from remote server + dist.pullUpdates(); + + if (dist.getPendingQueryCount() > 0) { + // Execute all pending queries and queue results + dist.runQueries(); + } + + // Serialize and inspect buffered results + std::string json; + dist.serializeResults(json); +} +``` + +## Notes + +- Query deduplication is enforced via SHA-256 hashing (`hashQuery`) combined with denylist tracking (`checkAndSetAsRunning`) to prevent re-execution within the configured denylist window +- Discovery queries gate execution: if a discovery query returns no rows, its associated distributed query is skipped +- Performance statistics per query are recorded in `performance_` and reported alongside results \ No newline at end of file diff --git a/osquery/distributed/tests/.distributed_tests.md b/osquery/distributed/tests/.distributed_tests.md new file mode 100644 index 00000000000..0715050c077 --- /dev/null +++ b/osquery/distributed/tests/.distributed_tests.md @@ -0,0 +1,31 @@ + +Unit test suite for osquery's distributed query functionality, covering serialization, deserialization, query lifecycle, denylisting, and TLS-based workflow integration. + +## Key Components + +- **`DistributedTests`** — Main test fixture that initializes the platform, registry, and database; optionally starts a mock TLS server for integration tests. +- **`DistributedMock`** — GMock subclass of `Distributed` that mocks `flushCompleted()` to isolate query execution from network I/O. +- **Test cases:** + - `test_serialize_distributed_query_request` — Verifies `DistributedQueryRequest` serializes correctly to a JSON document. + - `test_deserialize_distributed_query_request` / `_json` — Verifies deserialization from both a JSON object and a raw JSON string. + - `test_serialize_distributed_query_result` — Verifies `DistributedQueryResult` (including nested request and results array) serializes correctly. + - `test_deserialize_distributed_query_result` / `_json` — Verifies full result deserialization roundtrip. + - `test_workflow` — End-to-end integration test: pulls queries from a TLS server, runs them, and confirms results are flushed. + - `test_check_and_set_as_running` — Tests the denylist mechanism: marking queries as running, detecting duplicates, and expiry/cleanup. + - `test_run_queries_with_denylisted_query` — Verifies that a denylisted query returns a `"Denylisted"` status while other queries in the batch still execute; also validates expiry behavior. + - `test_accept_work_basic` — Confirms `acceptWork()` correctly stores queries in the distributed query database. + - `test_accept_work_with_discovery` — Tests discovery query filtering (queries with zero-result discovery queries are skipped). + +## Usage Example + +```cpp +// Run the full distributed test suite via gtest +./osquery_tests --gtest_filter="DistributedTests.*" + +// Example: manually test denylist logic +auto dist = Distributed(); +const auto query = "SELECT * FROM system_info;"; +bool firstCall = dist.checkAndSetAsRunning(query); // false — not yet denylisted +bool secondCall = dist.checkAndSetAsRunning(query); // true — now denylisted +auto status = dist.cleanupExpiredRunningQueries(); // removes expired entries +``` \ No newline at end of file diff --git a/osquery/events/.audit_flags.md b/osquery/events/.audit_flags.md new file mode 100644 index 00000000000..701cd8f561e --- /dev/null +++ b/osquery/events/.audit_flags.md @@ -0,0 +1,36 @@ + +Defines runtime configuration flags for the audit subsystem (Auditd on Linux, OpenBSM on macOS), controlling whether audit event collection and rule management are enabled. + +## Key Components + +| Flag | Default | Description | +|------|---------|-------------| +| `disable_audit` | `true` | Master switch to disable all audit subsystem event receiving | +| `audit_allow_config` | `false` | Permits the audit publisher to modify auditing configuration | +| `audit_allow_sockets` | `false` | Permits installation of socket-related audit rules | +| `audit_allow_process_events` | `true` | Permits installation of process-related audit rules | +| `audit_allow_user_events` | `true` | Permits installation of user-related audit rules | +| `audit_allow_fim_events` | `false` | Permits installation of filesystem integrity monitoring (FIM) rules | + +## Usage Example + +Flags are passed as CLI arguments to the osquery daemon: + +```bash +# Enable audit subsystem with FIM and socket rules +osqueryd \ + --disable_audit=false \ + --audit_allow_config=true \ + --audit_allow_fim_events=true \ + --audit_allow_sockets=true +``` + +```bash +# Minimal audit: process and user events only (default behavior) +osqueryd \ + --disable_audit=false \ + --audit_allow_process_events=true \ + --audit_allow_user_events=true +``` + +> **Note:** `disable_audit` defaults to `true`, meaning the audit subsystem is **off by default**. It must be explicitly enabled. Enabling audit features may have a measurable performance impact on the host system. \ No newline at end of file diff --git a/osquery/events/.eventer.md b/osquery/events/.eventer.md new file mode 100644 index 00000000000..739769904da --- /dev/null +++ b/osquery/events/.eventer.md @@ -0,0 +1,51 @@ + +Defines the base `Eventer` class and `EventState` enum used to track the lifecycle state of event publishers and subscribers within osquery's eventing system. + +## Key Components + +### `EventState` (enum class) +Represents the possible lifecycle states of an event publisher or subscriber: + +| Value | Description | +|---|---| +| `EVENT_NONE` | Default uninitialized state | +| `EVENT_SETUP` | Subscriber attached and setup complete | +| `EVENT_RUNNING` | Ready to receive events | +| `EVENT_PAUSED` | Initialized but not accepting events | +| `EVENT_FAILED` | Initialization failed or offline | + +### `Eventer` (class) +Base class providing state management for event publishers and subscribers. + +| Member | Access | Description | +|---|---|---| +| `state() const` | `public` | Returns current `EventState` | +| `state(EventState)` | `protected` | Sets the current state | +| `state_` | `private` | Internal state storage, defaults to `EVENT_NONE` | + +`EventFactory` is declared a `friend` class, granting it direct access to internal state. + +## Usage Example + +```cpp +#include + +class MySubscriber : public Eventer { + public: + void initialize() { + // Transition to running after setup + state(EventState::EVENT_SETUP); + if (setupSucceeded()) { + state(EventState::EVENT_RUNNING); + } else { + state(EventState::EVENT_FAILED); + } + } + + void checkStatus() { + if (state() == EventState::EVENT_RUNNING) { + // Accept and process events + } + } +}; +``` \ No newline at end of file diff --git a/osquery/events/.eventfactory.md b/osquery/events/.eventfactory.md new file mode 100644 index 00000000000..c83bdcca4f3 --- /dev/null +++ b/osquery/events/.eventfactory.md @@ -0,0 +1,47 @@ + +Singleton factory class for managing osquery event publishers, subscribers, and subscriptions throughout the application lifecycle. + +## Key Components + +| Member | Type | Description | +|--------|------|-------------| +| `getInstance()` | Static method | Returns the singleton `EventFactory` instance | +| `registerEventPublisher()` | Static method | Registers an `EventPublisher` plugin with the factory | +| `registerEventSubscriber()` | Template method | Registers an `EventSubscriber` by type or instance | +| `addSubscription()` | Static method | Binds a `SubscriptionContext` and `EventCallback` to a publisher | +| `deregisterEventPublisher()` | Static method | Halts a publisher's run loop and removes it from the factory | +| `getEventPublisher()` | Static method | Retrieves a registered publisher by name | +| `getEventSubscriber()` | Static method | Retrieves a registered subscriber by name | +| `fire()` | Template method | Fires an `EventContext` into a publisher's subscriber callbacks | +| `getType()` | Template method | Resolves the registry plugin name for a publisher type | +| `delay()` | Static method | Spawns all registered publisher run loop threads | +| `end()` | Static method | Terminates all publisher run loops and optionally joins threads | +| `configUpdate()` | Static method | Notifies publishers/subscribers of configuration changes | + +## Usage Example + +```cpp +// Register a publisher plugin +EventFactory::registerEventPublisher(pub_plugin_ref); + +// Register a subscriber by type +EventFactory::registerEventSubscriber(); + +// Add a subscription with context and callback +auto sc = std::make_shared(); +EventFactory::addSubscription("my_publisher", "my_subscriber", sc, + [](const EventContextRef& ec, const SubscriptionContextRef& sc) { + // Handle event + return Status::success(); + }); + +// Fire an event from a static publisher callback +EventContextRef ec = std::make_shared(); +EventFactory::fire(ec); + +// Start all publisher run loops +EventFactory::delay(); + +// Shutdown all publishers (with thread join) +EventFactory::end(true); +``` \ No newline at end of file diff --git a/osquery/events/.eventpublisher.md b/osquery/events/.eventpublisher.md new file mode 100644 index 00000000000..d0ef779588d --- /dev/null +++ b/osquery/events/.eventpublisher.md @@ -0,0 +1,67 @@ + +Templated base class for osquery event publishers, providing type-safe wrappers around `EventPublisherPlugin` for generating and dispatching OS-level events (filesystem, network, syscalls, ioctl). + +## Key Components + +### Macro + +- **`DECLARE_PUBLISHER(TYPE)`** — Injects a `type()` override returning the publisher's string identifier. Required in every concrete publisher class. + +### Template Class: `EventPublisher` + +Inherits from `EventPublisherPlugin`. Parameterized by: +- `SC` — SubscriptionContext type (filter/selector criteria) +- `EC` — EventContext type (event payload data) + +**Type Aliases** + +| Alias | Type | +|---|---| +| `SCRef` | `std::shared_ptr` | +| `ECRef` | `std::shared_ptr` | + +**Static Factory Methods** + +| Method | Description | +|---|---| +| `createEventContext()` | Allocates a new `EC` instance | +| `createSubscriptionContext()` | Allocates a new `SC` instance | +| `getEventContext(ec)` | Up-casts `EventContextRef` → `ECRef` | +| `getSubscriptionContext(sc)` | Up-casts `SubscriptionContextRef` → `SCRef` | + +**Virtual Methods** + +- **`shouldFire(sc, ec)`** — Override to filter events against subscription selectors. Returns `true` by default (fires all events). +- **`fireCallback(sub, ec)`** — Internal dispatch; calls `shouldFire` then invokes the subscription callback if matched. + +## Usage Example + +```cpp +// Define typed context structs +struct MySubscriptionContext : public SubscriptionContext { + std::string path_filter; +}; + +struct MyEventContext : public EventContext { + std::string path; +}; + +// Declare the publisher +class MyPublisher + : public EventPublisher { + DECLARE_PUBLISHER("my_publisher"); + + public: + Status run() override { + auto ec = createEventContext(); + ec->path = "/etc/changed_file"; + fire(ec); + return Status::success(); + } + + protected: + bool shouldFire(const SCRef& sc, const ECRef& ec) const override { + return ec->path.find(sc->path_filter) != std::string::npos; + } +}; +``` \ No newline at end of file diff --git a/osquery/events/.eventpublisherplugin.md b/osquery/events/.eventpublisherplugin.md new file mode 100644 index 00000000000..9903fac46e1 --- /dev/null +++ b/osquery/events/.eventpublisherplugin.md @@ -0,0 +1,62 @@ + +Defines the `EventPublisherPlugin` base class for osquery's event-driven architecture, providing the lifecycle interface that all event publishers must implement to register OS-level callbacks, manage subscriptions, and fire events to subscribers. + +## Key Components + +### Class: `EventPublisherPlugin` +Inherits from `Plugin`, `InterruptibleRunnable`, and `Eventer`. Serves as the abstract base for all event publisher implementations. + +**Lifecycle Methods** +| Method | Description | +|--------|-------------| +| `setUp()` | Initialize handles and register OS API callbacks (called before run loop) | +| `configure()` | Re-optimize subscriptions (e.g., dedup inotify paths) when subscriptions change | +| `tearDown()` | Release handles and clean up resources before exit | +| `run()` | Single iteration of the publisher's run loop; return `FAILED` to exit | +| `stop()` | Signal the run loop to terminate (called by `EventFactory`) | + +**Subscription Management** +| Method | Description | +|--------|-------------| +| `addSubscription(sub)` | Register a new `SubscriptionRef` with this publisher | +| `removeSubscriptions(subscriber)` | Remove all subscriptions for a named subscriber | +| `numSubscriptions()` | Returns active subscription count | + +**State Inspection** +- `isEnding()` / `hasStarted()` — get/set publisher lifecycle state +- `numEvents()` — total fired event count +- `restartCount()` — run loop restart iterations + +### Protected Members +- `fire(ec, time)` — dispatches an `EventContext` to all matching subscribers +- `fireCallback(sub, ec)` — pure virtual; typed publishers implement actual dispatch +- `subscriptions_` — the internal `SubscriptionVector` +- `next_ec_id_` — atomic event context ID counter + +## Usage Example + +```cpp +class MyPublisher : public EventPublisherPlugin { + public: + Status setUp() override { + // Open OS handle or register callback + return Status::success(); + } + + Status run() override { + auto ec = createEventContext(); + fire(ec); // dispatches to all subscribers + return Status::success(); + } + + void tearDown() override { + // Close handles + } + + protected: + void fireCallback(const SubscriptionRef& sub, + const EventContextRef& ec) const override { + // Invoke subscriber's typed callback + } +}; +``` \ No newline at end of file diff --git a/osquery/events/.events.md b/osquery/events/.events.md new file mode 100644 index 00000000000..185e2df9624 --- /dev/null +++ b/osquery/events/.events.md @@ -0,0 +1,26 @@ + +Utility header declaring event system lifecycle functions within the `osquery` namespace for managing event publisher run loops and query denylist enforcement. + +## Key Components + +| Function | Description | +|---|---| +| `attachEvents()` | Iterates the event publisher registry and creates run loops for each publisher via the event factory | +| `enforceEventsDenylist(query)` | Returns `true` if the given query operates exclusively on event-based tables and should be denylisted | + +## Usage Example + +```c +#include "events.h" + +// Initialize all registered event publishers +osquery::attachEvents(); + +// Check if a query should be denied based on event-table usage +std::string query = "SELECT * FROM process_events"; +if (osquery::enforceEventsDenylist(query)) { + // Query is denylist-eligible; skip or block execution +} +``` + +> **Note:** `attachEvents()` is typically called once during daemon startup to wire up all registered publishers. `enforceEventsDenylist` is used during query scheduling to prevent high-frequency polling on purely event-driven tables. \ No newline at end of file diff --git a/osquery/events/.eventsubscriber.md b/osquery/events/.eventsubscriber.md new file mode 100644 index 00000000000..bf6ed1f5f8d --- /dev/null +++ b/osquery/events/.eventsubscriber.md @@ -0,0 +1,37 @@ + +Defines a templated base class for binding event subscriptions to publisher-specific contexts and table generation within the osquery event framework. + +## Key Components + +- **`EventSubscriber`** — Template class inheriting from `EventSubscriberPlugin`. The `PUB` template parameter specifies the associated `EventPublisher` type, allowing strong-typed access to its subscription and event context types. +- **`SCRef` / `ECRef`** — Type aliases derived from the publisher, representing the strongly-typed subscription context reference and event context reference respectively. +- **`getType()`** — Returns the registry plugin name of the bound publisher, identifying the subscriber's event type at factory initialization time. +- **`createSubscriptionContext()`** — Helper that delegates to the publisher's subscription context factory method. +- **`subscribe(entry, sc)`** — Binds a member function callback of a concrete subscriber subclass to a subscription context. Internally downcasts the subscriber and wraps the call in a type-erased lambda before registering with `EventFactory::addSubscription`. + +## Usage Example + +```cpp +// Define a concrete subscriber bound to a specific publisher +class MySubscriber : public EventSubscriber { + public: + void init() override { + auto sc = createSubscriptionContext(); + sc->filter = "some_filter"; + subscribe(&MySubscriber::onEvent, sc); + } + + Status onEvent(const ECRef& ec, const SCRef& sc) { + // Handle the event and optionally call add() to persist rows + Row r; + r["column"] = ec->some_field; + add(r); + return Status::success(); + } +}; + +// Register the subscriber with the plugin registry +REGISTER(MySubscriber, "event_subscriber", "my_subscriber"); +``` + +> **Note:** The `subscribe()` method increments `subscription_count_` only on successful registration. Subscribers without at least one successful subscription will not receive events at query time. \ No newline at end of file diff --git a/osquery/events/.eventsubscriberplugin.md b/osquery/events/.eventsubscriberplugin.md new file mode 100644 index 00000000000..9338289cef4 --- /dev/null +++ b/osquery/events/.eventsubscriberplugin.md @@ -0,0 +1,49 @@ + +Defines the `EventSubscriberPlugin` class, the base interface for osquery event subscribers that receive, store, and expose event data as queryable table rows via a backing database store. + +## Key Components + +### Class: `EventSubscriberPlugin` +Inherits from `Plugin` and `Eventer`. Manages event ingestion, indexed storage, expiration, and query-time row generation. + +### Core Methods + +| Method | Description | +|---|---| +| `init()` | Override to register `Subscription`s with an `EventPublisher` | +| `call(PluginRequest, PluginResponse)` | Plugin dispatch entrypoint | +| `addBatch(row_list)` | Store a batch of parsed event rows to the backing store (preferred API) | +| `add(row)` | *(Deprecated)* Store a single event row — use `addBatch()` instead | +| `genTable(yield, ctx)` | Table generation entrypoint; retrieves stored events filtered by query time window | +| `generateRows(...)` | Static: enumerate stored events within a `[start_time, end_time]` range | +| `expireEventBatches(...)` | Static: prune old event batches from backing store | +| `removeOverflowingEventBatches(...)` | Static: enforce maximum batch count | +| `getOptimizeData(...)` / `setOptimizeData(...)` | Static: manage query optimization checkpoints | + +### Supporting Structures + +- **`Context`** — Holds the database namespace, event index, last query/event timestamps +- **`GenerateRowsResult`** — Returns pagination state (`isEnd`, `last_time`, `last_id`) from `generateRows` + +## Usage Example + +```cpp +class MySubscriber : public EventSubscriber { + public: + Status init() override { + auto sub = createSubscription(); + return EventFactory::addSubscription(getType(), sub); + } + + Status Callback(const EventContextRef& ec, const SubscriptionContextRef& sc) { + Row r; + r["action"] = ec->action; + r["time"] = INTEGER(ec->time); + + std::vector batch = {r}; + return addBatch(batch); // preferred over deprecated add() + } +}; +``` + +> **Note:** `add()` is deprecated. Always use `addBatch()` to group events together for efficient indexed storage and reduced write overhead. \ No newline at end of file diff --git a/osquery/events/.file_events_flags.md b/osquery/events/.file_events_flags.md new file mode 100644 index 00000000000..4fa64466555 --- /dev/null +++ b/osquery/events/.file_events_flags.md @@ -0,0 +1,28 @@ + +Defines a compile-time configuration flag to enable or disable the `file_events` publisher within the osquery framework. + +## Key Components + +- **`enable_file_events`** — A boolean `FLAG` (defaulting to `false`) that controls whether the file events publisher is active. When set to `true`, osquery will begin monitoring and publishing file system events. + +## Usage Example + +```cpp +// Enable file events via CLI flag at startup: +// osqueryd --enable_file_events=true + +// Access the flag value programmatically within osquery: +#include + +namespace osquery { + DECLARE_bool(enable_file_events); + + void checkFileEvents() { + if (FLAGS_enable_file_events) { + // File events publisher is active + } + } +} // namespace osquery +``` + +> **Note:** This flag must be set at startup. The `file_events` publisher relies on this flag being enabled to subscribe to and emit file system change events in osquery tables. \ No newline at end of file diff --git a/osquery/events/.pathset.md b/osquery/events/.pathset.md new file mode 100644 index 00000000000..67d75069b4d --- /dev/null +++ b/osquery/events/.pathset.md @@ -0,0 +1,51 @@ + +Thread-safe, policy-based container for filesystem path lookups with glob wildcard support, used in osquery's file monitoring and path matching subsystem. + +## Key Components + +### `PathSet` (class template) +A thread-safe multiset wrapper for storing and querying filesystem paths. The `PathType` policy parameter controls tokenization and comparison behavior. + +| Method | Description | +|--------|-------------| +| `insert(str)` | Normalizes glob wildcards, tokenizes, and inserts one or more path variants | +| `find(str)` | Returns `true` if the path matches any stored entry (wildcard-aware) | +| `clear()` | Removes all stored paths under write lock | +| `empty()` | Returns `true` if the set contains no paths | + +### `patternedPath` (policy class) +Implements the `PathType` concept for `PathSet`. Supports `*` (single-component wildcard) and `**` (recursive wildcard) glob patterns. + +| Member | Description | +|--------|-------------| +| `Path` | `std::vector` — tokenized path components | +| `VPath` | `std::vector` — multiple path variants (used for `**` expansion) | +| `Compare` | Custom comparator enabling wildcard-aware equivalence in the multiset | +| `createPath(str)` | Tokenizes a path string into a `Path` | +| `createVPath(str)` | Tokenizes and expands `**` into multiple `Path` entries | + +## Usage Example + +```cpp +#include + +// Create a PathSet using the patterned policy +osquery::PathSet ps; + +// Insert paths with glob wildcards +ps.insert("/etc/cron.d/*"); +ps.insert("/var/log/**"); + +// Query for a specific path +if (ps.find("/etc/cron.d/daily")) { + // matched via '*' wildcard +} + +if (ps.find("/var/log/nginx/access.log")) { + // matched via '**' recursive wildcard +} + +ps.clear(); +``` + +> **Thread safety:** All operations are protected by an internal `Mutex` using `ReadLock`/`WriteLock`, making `PathSet` safe for concurrent access across osquery worker threads. \ No newline at end of file diff --git a/osquery/events/.subscription.md b/osquery/events/.subscription.md new file mode 100644 index 00000000000..b7de73f51af --- /dev/null +++ b/osquery/events/.subscription.md @@ -0,0 +1,40 @@ + +Defines the `Subscription` struct used to configure an `EventPublisher` and bind event callbacks to a `SubscriptionContext` within osquery's event framework. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `EventCallback` | `using` alias | Function signature `Status(const EventContextRef&, const SubscriptionContextRef&)` invoked when a matching event fires | +| `SubscriptionRef` | `using` alias | `shared_ptr` for shared ownership of a subscription | +| `SubscriptionVector` | `using` alias | `vector` used by publishers to track all active subscriptions | +| `Subscription` | `struct` | Non-copyable aggregate holding a subscriber name, context, and callback | +| `Subscription::create()` | static factory | Two overloads — name-only, or name + context + optional callback | + +## Usage Example + +```cpp +#include + +// Define a callback matching the EventCallback signature +Status onFileEvent(const EventContextRef& ec, + const SubscriptionContextRef& sc) { + // Handle the event + return Status::success(); +} + +// Create a subscription with context and callback +auto ctx = std::make_shared(); +ctx->path = "/etc/passwd"; + +SubscriptionRef sub = Subscription::create( + "my_subscriber", // EventSubscriber name + ctx, // Publisher-specific context + onFileEvent // Callback fired on matching events +); + +// Register via EventFactory +EventFactory::addSubscription("MyEventPublisher", sub); +``` + +> **Note:** `Subscription` inherits `boost::noncopyable` — always pass instances via `SubscriptionRef` (`shared_ptr`). The default constructor is explicitly deleted; use the static `create()` factory methods. \ No newline at end of file diff --git a/osquery/events/.types.md b/osquery/events/.types.md new file mode 100644 index 00000000000..3541ae06ea5 --- /dev/null +++ b/osquery/events/.types.md @@ -0,0 +1,44 @@ + +Defines the core type aliases and base structures used throughout osquery's event publish-subscribe system. + +## Key Components + +### Type Aliases + +| Type | Definition | Purpose | +|------|-----------|---------| +| `EventContextID` | `uint64_t` | Unique ID for each fired event | +| `EventTime` | `uint64_t` | Timestamp of event occurrence | +| `EventRecord` | `pair` | Associates an event string with a timestamp | +| `EventID` | `uint64_t` | Unique event identifier | +| `EventIDList` | `vector` | Ordered collection of event IDs | +| `EventIndex` | `map` | Time-indexed event lookup structure | +| `EventContextRef` | `shared_ptr` | Shared ownership of an event context | +| `SubscriptionContextRef` | `shared_ptr` | Shared ownership of a subscription context | +| `EventPublisherRef` | `shared_ptr` | Shared ownership of a publisher plugin | +| `EventSubscriberRef` | `shared_ptr` | Shared ownership of a subscriber plugin | + +### Structs + +- **`EventContext`** — Non-copyable base struct passed to subscriber callbacks when an event fires. Carries a unique `id` and `time` field populated by the publisher. +- **`SubscriptionContext`** — Non-copyable base struct extended by each `EventPublisher` to define the filtering/registration parameters subscribers use (e.g., filesystem paths for inotify, network protocols for libpcap). + +## Usage Example + +```c +// Extend EventContext for a custom publisher +struct FileEventContext : public osquery::EventContext { + std::string path; + std::string action; // "CREATED", "DELETED", etc. +}; + +// Extend SubscriptionContext to filter by path +struct FileSubscriptionContext : public osquery::SubscriptionContext { + std::string watch_path; + bool recursive{false}; +}; + +// Use shared_ptr aliases for safe ownership passing +osquery::EventContextRef ctx = std::make_shared(); +osquery::SubscriptionContextRef sub = std::make_shared(); +``` \ No newline at end of file diff --git a/osquery/events/benchmarks/.events_benchmarks.md b/osquery/events/benchmarks/.events_benchmarks.md new file mode 100644 index 00000000000..13377137260 --- /dev/null +++ b/osquery/events/benchmarks/.events_benchmarks.md @@ -0,0 +1,37 @@ + +Performance benchmarks for the osquery event system, measuring publisher registration, event subscription/firing, event storage, and table generation throughput using Google Benchmark. + +## Key Components + +### `BenchmarkEventPublisher` +A minimal `EventPublisher` implementation used as a benchmark fixture. Exposes `benchmarkFire()` to manually trigger event dispatch via `fire()`. + +### `BenchmarkEventSubscriber` +A minimal `EventSubscriber` fixture with helper methods: +- `benchmarkInit()` — creates and registers a subscription with a no-op callback +- `benchmarkAdd(int t)` — inserts a single test row via `addBatch()` +- `benchmarkGet(int low, int high)` — retrieves events over a time range using the pull-based `RowGenerator` +- `clearRows()` — purges stored events by forcing expiry + +### Benchmark Functions + +| Benchmark | What It Measures | +|---|---| +| `EVENTS_register` | Publisher registration + lookup via `EventFactory` | +| `EVENTS_subscribe_fire` | End-to-end event fire → subscriber callback cycle | +| `EVENTS_add_events` | Raw event storage throughput via `addBatch()` | +| `EVENTS_retrieve_events` | Event retrieval across 100 / 1,000 / 10,000 stored rows | +| `EVENTS_gentable` | `genRows()` table generation across varying event counts | +| `EVENTS_add_and_gentable` | Combined add + table generation under varying loads | + +## Usage Example + +```bash +# Build and run all event benchmarks +./osquery_benchmarks --benchmark_filter=EVENTS_ + +# Run a specific benchmark with custom repetitions +./osquery_benchmarks --benchmark_filter=EVENTS_retrieve_events --benchmark_repetitions=5 +``` + +> **Note:** `EVENTS_retrieve_events`, `EVENTS_gentable`, and `EVENTS_add_and_gentable` are parameterized with `ArgPair(0, N)` for N = 100, 1,000, and 10,000 to profile scaling behavior under increasing event volumes. \ No newline at end of file diff --git a/osquery/events/darwin/.diskarbitration.md b/osquery/events/darwin/.diskarbitration.md new file mode 100644 index 00000000000..fe2b809a169 --- /dev/null +++ b/osquery/events/darwin/.diskarbitration.md @@ -0,0 +1,50 @@ + +Provides the osquery event publisher interface for monitoring macOS disk mount/unmount events via the DiskArbitration framework. + +## Key Components + +### Macros + +| Macro | Purpose | +|---|---| +| `kIOPropertyProtocolCharacteristicsKey_` | IOKit key for protocol characteristics lookup | +| `kVirtualInterfaceLocation_` | Key identifying virtual disk interface paths | +| `kDAAppearanceTime_` | Key for disk appearance timestamp | + +### Structs + +- **`DiskArbitrationSubscriptionContext`** — Subscription filter; `physical_disks` flag limits events to physical vs. virtual (DMG) disks +- **`DiskArbitrationEventContext`** — Event payload carrying disk metadata: `action`, `path`, `device_path`, `uuid`, `size`, `filesystem`, `vendor`, `checksum`, and mount capability flags + +### Type Aliases + +- `DiskArbitrationEventContextRef` — `shared_ptr` +- `DiskArbitrationSubscriptionContextRef` — `shared_ptr` + +### Class: `DiskArbitrationEventPublisher` + +Extends `EventPublisher` to wrap macOS `DASession` callbacks into osquery events. + +| Member | Description | +|---|---| +| `run()` | Starts the `CFRunLoop` and registers DA session callbacks | +| `tearDown()` | Releases DA session and run loop resources | +| `shouldFire()` | Filters events against subscription context | +| `DiskAppearedCallback()` | Static DA callback for disk mount events | +| `DiskDisappearedCallback()` | Static DA callback for disk unmount events | +| `extractUdifChecksum()` | Reads checksum from UDIF (DMG) disk images | +| `fire()` | Builds and dispatches `DiskArbitrationEventContext` from a `CFDictionaryRef` | + +## Usage Example + +```cpp +// Subscribe to physical disk events only +auto sc = std::make_shared(); +sc->physical_disks = true; + +auto sub = EventFactory::addSubscription( + "diskarbitration", + EventSubscriberPlugin::SubscriptionAction::ADD, + sc, + callback); +``` \ No newline at end of file diff --git a/osquery/events/darwin/.endpointsecurity.md b/osquery/events/darwin/.endpointsecurity.md new file mode 100644 index 00000000000..4e48c562096 --- /dev/null +++ b/osquery/events/darwin/.endpointsecurity.md @@ -0,0 +1,49 @@ + +macOS Endpoint Security event publisher and subscriber definitions for osquery, providing process and file system monitoring via Apple's EndpointSecurity framework (requires macOS 10.15+). + +## Key Components + +### Context Structs + +| Struct | Purpose | +|---|---| +| `EndpointSecuritySubscriptionContext` | Holds ES event type subscriptions and row buffer for process events | +| `EndpointSecurityEventContext` | Full process event payload — PID, credentials, code signing, exec args, fork/exit data | +| `EndpointSecurityFileSubscriptionContext` | Holds ES event type subscriptions and row buffer for file events | +| `EndpointSecurityFileEventContext` | Extends process context with `filename` and `dest_filename` for file operations | + +### Publishers + +- **`EndpointSecurityPublisher`** — Registers as `"endpointsecurity"`, manages an `es_client_s*` handle, dispatches process-level ES events (`exec`, `fork`, `exit`) to subscribers +- **`EndpointSecurityFileEventPublisher`** — Registers as `"endpointsecurity_fim"`, manages a separate `es_file_client_s*` handle with configurable path muting/filtering for file integrity monitoring (FIM); ships with a default muted path list covering high-noise system daemons (e.g., `WindowServer`, `tccd`, `securityd`) + +### Subscribers + +- **`ESProcessEventSubscriber`** — Subscribes to `EndpointSecurityPublisher`, populates the `es_process_events` osquery table +- **`ESProcessFileEventSubscriber`** — Subscribes to `EndpointSecurityFileEventPublisher`, populates the `es_process_file_events` osquery table + +## Usage Example + +```cpp +// Subscribing to process events in a custom subscriber +class MyProcessSubscriber + : public EventSubscriber { + public: + MyProcessSubscriber() { setName("my_process_events"); } + + Status init() override { + auto sc = createSubscriptionContext(); + sc->es_event_subscriptions_.push_back(ES_EVENT_TYPE_NOTIFY_EXEC); + subscribe(&MyProcessSubscriber::Callback, sc); + return Status::success(); + } + + Status Callback(const EndpointSecurityEventContextRef& ec, + const EndpointSecuritySubscriptionContextRef& sc) { + // Access ec->pid, ec->path, ec->args, ec->signing_id, etc. + return Status::success(); + } +}; +``` + +> **Platform note:** All publisher and subscriber methods are gated with `API_AVAILABLE(macos(10.15))`. This header has no effect on non-macOS builds. \ No newline at end of file diff --git a/osquery/events/darwin/.endpointsecurity_fim.md b/osquery/events/darwin/.endpointsecurity_fim.md new file mode 100644 index 00000000000..dc572c59426 --- /dev/null +++ b/osquery/events/darwin/.endpointsecurity_fim.md @@ -0,0 +1,46 @@ + +Implements the macOS EndpointSecurity-based File Integrity Monitoring (FIM) event publisher for osquery, subscribing to kernel-level file system events via the Apple EndpointSecurity framework on macOS 10.15+. + +## Key Components + +### `EndpointSecurityFileEventPublisher` +The primary event publisher class registered under `"endpointsecurity_fim"`. + +| Method | Description | +|---|---| +| `setUp()` | Initializes the ES client, parses muted path flags, and registers the message handler | +| `configure()` | Subscribes to file events, applies path muting/inversion rules, and resolves monitored paths from config | +| `tearDown()` | Unsubscribes and destroys the ES client | +| `handleMessage()` | Processes incoming `es_message_t` events and fires typed event contexts | +| `shouldFire()` | Subscription filter (always returns `true`) | + +### Supported ES Event Types +| Event | `ec->event_type` | +|---|---| +| `ES_EVENT_TYPE_NOTIFY_CREATE` | `"create"` | +| `ES_EVENT_TYPE_NOTIFY_WRITE` | `"write"` | +| `ES_EVENT_TYPE_NOTIFY_RENAME` | `"rename"` | +| `ES_EVENT_TYPE_NOTIFY_TRUNCATE` | `"truncate"` | +| `ES_EVENT_TYPE_NOTIFY_OPEN` | `"open"` | + +### Configuration Flags +- `--disable_endpointsecurity` / `--disable_endpointsecurity_fim` — disable the publisher +- `--es_fim_mute_path_literal` — comma-separated exact paths to suppress +- `--es_fim_mute_path_prefix` — comma-separated path prefixes to suppress + +## Usage Example + +```cpp +// Subscriber registering for FIM write and create events +auto sc = createSubscriptionContext(); +sc->es_file_event_subscriptions_ = { + ES_EVENT_TYPE_NOTIFY_WRITE, + ES_EVENT_TYPE_NOTIFY_CREATE, +}; + +EventFactory::addSubscription( + "endpointsecurity_fim", + Subscription::create("my_fim_subscriber", sc, callback)); +``` + +> **Note:** Requires macOS 10.15+. Path mute inversion (selective monitoring via `file_paths` config) is only active on macOS 13.0+. The publisher automatically mutes its own process to avoid self-monitoring loops. \ No newline at end of file diff --git a/osquery/events/darwin/.es_utils.md b/osquery/events/darwin/.es_utils.md new file mode 100644 index 00000000000..ee9049cde42 --- /dev/null +++ b/osquery/events/darwin/.es_utils.md @@ -0,0 +1,40 @@ + +Utility header for extracting and formatting process and string properties from macOS Endpoint Security (`es_*`) data structures within the osquery framework. + +## Key Components + +| Function | Description | +|---|---| +| `getEsNewClientErrorMessage` | Converts an `es_new_client_result_t` error code into a human-readable string | +| `getPath` | Extracts the executable path from an `es_process_t` process struct | +| `getSigningId` | Retrieves the code-signing identifier from an `es_process_t` | +| `getTeamId` | Retrieves the Apple Developer Team ID from an `es_process_t` | +| `getStringFromToken` | Converts a mutable or const `es_string_token_t` to a `std::string` | +| `getCwdPathFromPid` | Resolves the current working directory path for a given PID | +| `getCDHash` | Extracts the code-directory hash from an `es_process_t` | +| `getProcessProperties` | Populates an `EndpointSecurityEventContextRef` with properties from a process | +| `appendQuotedString` | Appends a delimiter-quoted string to an output stream | + +## Usage Example + +```c +#include + +// Inside an Endpoint Security event handler: +void handleEvent(const es_message_t* msg) { + const es_process_t* proc = msg->process; + + std::string path = osquery::getPath(proc); + std::string signingId = osquery::getSigningId(proc); + std::string teamId = osquery::getTeamId(proc); + std::string cdHash = osquery::getCDHash(proc); + std::string cwd = osquery::getCwdPathFromPid(audit_token_to_pid(proc->audit_token)); + + osquery::getProcessProperties(proc, eventContext); + + std::ostringstream out; + osquery::appendQuotedString(out, path, '"'); +} +``` + +> **Note:** This header is macOS-only and depends on the Apple Endpoint Security framework (`es_process_t`, `es_string_token_t`). It is used internally by osquery's EndpointSecurity event publisher to normalize raw ES API types into standard C++ types before query processing. \ No newline at end of file diff --git a/osquery/events/darwin/.event_taps.md b/osquery/events/darwin/.event_taps.md new file mode 100644 index 00000000000..f8b09845338 --- /dev/null +++ b/osquery/events/darwin/.event_taps.md @@ -0,0 +1,46 @@ + +macOS event tap publisher that monitors and intercepts system-level input events (keyboard, mouse, etc.) using the CoreGraphics `CGEventTap` API, exposing them to osquery subscribers. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `EventTappingSubscriptionContext` | Struct | Empty subscription context for event tap subscriptions | +| `EventTappingEventContext` | Struct | Empty event context carrying tapped event data | +| `EventTappingEventPublisher` | Class | Core publisher managing the CGEventTap lifecycle and run loop | +| `EventTappingConsumerRunner` | Class (forward) | Dispatched service that processes published event tap events | + +### `EventTappingEventPublisher` Methods + +| Method | Description | +|---|---| +| `setUp()` | Initializes the CGEventTap and run loop source | +| `tearDown()` | Cleans up tap resources | +| `stop()` | Stops the run loop | +| `run()` | Starts the publisher run loop | +| `restart()` | Reinitializes the event tap (e.g., after system re-enables it) | +| `eventCallback()` | Static CGEventTap callback receiving raw `CGEventRef` events | +| `shouldFire()` | Filters events against subscription context | + +## Usage Example + +```cpp +// Subscribing to event tap events in an osquery EventSubscriber +class MyEventTapSubscriber + : public EventSubscriber { + public: + Status init() override { + auto sc = createSubscriptionContext(); + subscribe(&MyEventTapSubscriber::onEvent, sc); + return Status::success(); + } + + Status onEvent(const EventTappingEventContextRef& ec, + const EventTappingSubscriptionContextRef& sc) { + // Process intercepted macOS input event + return Status::success(); + } +}; +``` + +> **Note:** This publisher is macOS-only and requires the `ApplicationServices` framework. The process may need Accessibility permissions (`TCC`) for the event tap to function correctly in macOS 10.15+. \ No newline at end of file diff --git a/osquery/events/darwin/.fsevents.md b/osquery/events/darwin/.fsevents.md new file mode 100644 index 00000000000..68064e595fb --- /dev/null +++ b/osquery/events/darwin/.fsevents.md @@ -0,0 +1,47 @@ + +Wraps Apple's FSEvents API as an osquery `EventPublisher`, enabling filesystem change monitoring with path globbing, recursive watching, and event filtering on macOS. + +## Key Components + +### `FSEventsSubscriptionContext` +Defines what to watch: +- `path` — filesystem path to monitor (supports glob wildcards) +- `mask` — optional `FSEventStreamEventFlags` filter +- `recursive` — enables recursive directory watching +- `category` — config-originated category label +- `requireAction()` — appends a required action filter +- Private fields `discovered_` and `recursive_match` used internally by the publisher for path resolution and event matching + +### `FSEventsEventContext` +Carries data for a fired event: +- `fsevent_stream`, `fsevent_flags`, `transaction_id` — raw FSEvents metadata +- `path`, `action` — resolved path and human-readable action string + +### `FSEventsEventPublisher` +Core publisher class inheriting `EventPublisher`: + +| Method | Purpose | +|---|---| +| `configure()` | Rebuilds watch paths and exclude sets on config change | +| `run()` | Starts the CFRunLoop-based event loop | +| `tearDown()` / `stop()` | Stops the stream and cleans up | +| `Callback()` | Static FSEvents client callback; fires event contexts | +| `shouldFire()` | Filters events against subscription criteria | +| `transformSubscription()` | Resolves globs/wildcards at configure time | +| `buildExcludePathsSet()` | Populates `ExcludePathSet` for suppressed paths | + +## Usage Example + +```cpp +// Subscribe to all changes under /etc recursively +auto sc = std::make_shared(); +sc->path = "/etc"; +sc->recursive = true; +sc->category = "config_files"; +sc->requireAction("UPDATED"); + +// The publisher matches fired events against this context via shouldFire() +// and delivers FSEventsEventContext to the subscriber callback +``` + +> **Note:** This publisher is macOS-only and requires `CoreServices.framework`. Path inputs should use filesystem glob syntax (e.g., `**`), not SQL wildcards. \ No newline at end of file diff --git a/osquery/events/darwin/.iokit.md b/osquery/events/darwin/.iokit.md new file mode 100644 index 00000000000..d99f0934fde --- /dev/null +++ b/osquery/events/darwin/.iokit.md @@ -0,0 +1,57 @@ + +Provides the `IOKitEventPublisher` class and associated context structures for subscribing to and handling macOS IOKit hardware device attach/detach events within the osquery event framework. + +## Key Components + +### Structs + +| Struct | Description | +|---|---| +| `IOKitSubscriptionContext` | Subscription filter with `model_id`, `vendor_id`, and bus `type` (e.g., `"USB"`) | +| `IOKitEventContext` | Event payload carrying device metadata: `action`, `vendor`, `model`, `path`, `driver`, `version`, `serial` | + +### Enums + +- **`IOKitEventContext::Action`** — `DEVICE_ATTACH (0)` or `DEVICE_DETACH` + +### Type Aliases + +- `IOKitEventContextRef` — `std::shared_ptr` +- `IOKitSubscriptionContextRef` — `std::shared_ptr` + +### Class: `IOKitEventPublisher` + +Extends `EventPublisher`. Manages a `CFRunLoop` and `IONotificationPort` to listen for IOKit device notifications. + +| Method | Description | +|---|---| +| `run()` | Starts the CFRunLoop and notification monitoring | +| `tearDown()` | Cleans up run loop and IOKit resources | +| `shouldFire()` | Filters events against subscription context | +| `deviceAttach()` *(static)* | IOKit callback for device connect events | +| `deviceDetach()` *(static)* | IOKit callback for device disconnect events | +| `newEvent()` | Constructs and fires an `IOKitEventContext` | +| `restart()` | Resets the run loop and re-registers notifications | + +## Usage Example + +```cpp +// Subscribe to USB device events +auto sc = std::make_shared(); +sc->type = "USB"; +sc->vendor_id = "05ac"; // Apple vendor ID + +EventFactory::addSubscription( + "iokit", + Subscription::create("usb_monitor", sc, + [](const EventContextRef& ec, const void*) { + auto event = std::static_pointer_cast(ec); + if (event->action == IOKitEventContext::DEVICE_ATTACH) { + LOG(INFO) << "Device attached: " << event->model + << " path=" << event->path; + } + }) +); +``` + +> **Note:** `publisher_started_` is intentionally set `false` during initial iterator seeding to suppress synthetic attach events at startup — only real plug/unplug events are emitted after `restart()` completes. \ No newline at end of file diff --git a/osquery/events/darwin/.openbsm.md b/osquery/events/darwin/.openbsm.md new file mode 100644 index 00000000000..4e4167c36be --- /dev/null +++ b/osquery/events/darwin/.openbsm.md @@ -0,0 +1,37 @@ + +Defines the OpenBSM event publisher and associated context structures for subscribing to and processing macOS/BSD audit log events within the osquery eventing framework. + +## Key Components + +| Name | Type | Description | +|---|---|---| +| `OpenBSMSubscriptionContext` | Struct | Holds the `event_id` a subscriber wants to monitor (e.g., `23` for `execve`) | +| `OpenBSMEventContext` | Struct | Carries a fired event's `event_id`, parsed `tokenstr_t` tokens, and a shared memory buffer | +| `OpenBSMEventContextRef` | Type alias | `std::shared_ptr` | +| `OpenBSMSubscriptionContextRef` | Type alias | `std::shared_ptr` | +| `OpenBSMEventPublisher` | Class | Core publisher that reads from the audit pipe, filters events by subscribed IDs, and fires matching event contexts | + +### `OpenBSMEventPublisher` Methods + +| Method | Description | +|---|---| +| `setUp()` | Initializes the publisher | +| `configure()` | Rebuilds the set of subscribed event IDs | +| `tearDown()` | Closes the audit pipe and cleans up resources | +| `run()` | Polling loop — reads audit records until interrupted | +| `acquireMessages()` | Dequeues and parses raw records from the audit pipe | +| `configureAuditPipe()` | Opens and configures `/dev/auditpipe` | +| `shouldFire()` | Matches incoming events against subscription contexts | + +## Usage Example + +```c +// Subscribe to execve events (event_id = 23) +auto sub_ctx = std::make_shared(); +sub_ctx->event_id = 23; + +// Register the subscription — the publisher handles routing +EventFactory::addSubscription("openbsm", sub_ctx, callback); +``` + +> **Note:** `OpenBSMEventPublisher` is a dispatched service — `run()` is called by the osquery eventing thread, not directly by subscribers. Access to `audit_pipe_` and `event_ids_` is guarded by dedicated `Mutex` members. \ No newline at end of file diff --git a/osquery/events/darwin/.scnetwork.md b/osquery/events/darwin/.scnetwork.md new file mode 100644 index 00000000000..4f5dc1b2f1c --- /dev/null +++ b/osquery/events/darwin/.scnetwork.md @@ -0,0 +1,46 @@ + +Provides the osquery event publisher interface for monitoring macOS network reachability changes using Apple's `SCNetworkReachability` API. + +## Key Components + +### Enumerations + +- **`SCNetworkSubscriptionType`** — Defines target types: `ADDRESS_TARGET` (IP address) or `NAME_TARGET` (hostname) + +### Structs + +- **`SCNetworkSubscriptionContext`** — Subscription configuration containing target type, hostname/address string, address family, and a reachability flags bitmask filter +- **`SCNetworkEventContext`** — Event payload carrying the originating subscription context and observed `SCNetworkReachabilityFlags` + +### Class: `SCNetworkEventPublisher` + +An `EventPublisher` subclass registered under the `"scnetwork"` identifier. Manages a CoreFoundation run loop with registered reachability targets. + +| Method | Description | +|---|---| +| `configure()` | Rebuilds and registers reachability targets | +| `run()` | Starts the CFRunLoop for async callbacks | +| `tearDown()` | Cleans up all registered targets | +| `shouldFire()` | Filters events against subscription flag masks | +| `Callback()` (static) | SCNetwork callback invoked on reachability change | +| `addHostname()` / `addAddress()` | Register name or address targets | +| `clearAll()` | Removes all tracked targets and contexts | + +## Usage Example + +```cpp +// Define a subscription context to monitor a hostname +auto sc = std::make_shared(); +sc->type = NAME_TARGET; +sc->target = "example.com"; +sc->mask = kSCNetworkReachabilityFlagsReachable; + +// Subscribe an event subscriber using this context +auto sub = EventFactory::createSubscription("scnetwork", sc); +``` + +## Notes + +- macOS-only; requires `SystemConfiguration.framework` +- `setUp()` always returns `Status(1, "Publisher not used")` — publisher lifecycle is driven by `configure()` and `run()` +- Internal state is protected by a `Mutex` for thread-safe target management \ No newline at end of file diff --git a/osquery/events/linux/.apparmor_events.md b/osquery/events/linux/.apparmor_events.md new file mode 100644 index 00000000000..741ba4b96cc --- /dev/null +++ b/osquery/events/linux/.apparmor_events.md @@ -0,0 +1,25 @@ + +Defines constants for AppArmor Linux Security Module (LSM) audit event handling, mapping Linux audit codes to their string labels for log parsing. + +## Key Components + +- **`kAppArmorEventSet`** — A `std::set` containing the audit event codes that AppArmor writes to the Linux audit subsystem. Currently includes `AUDIT_AVC` (Access Vector Cache denial events). +- **`kAppArmorRecordLabels`** — A `std::map` mapping audit event codes to their short string labels used during audit record parsing (e.g., `AUDIT_AVC` → `"AA"`). + +## Usage Example + +```c +#include "apparmor_events.h" + +// Check if an incoming audit event is an AppArmor event +if (kAppArmorEventSet.count(event_code)) { + // Look up the record label for this event + auto it = kAppArmorRecordLabels.find(event_code); + if (it != kAppArmorRecordLabels.end()) { + std::string label = it->second; // "AA" + // Process AppArmor audit record... + } +} +``` + +> **Note:** All AppArmor LSM audit messages use `AUDIT_AVC` as defined by the Linux kernel's ``. The label `"AA"` is the conventional short-form identifier used when tagging AppArmor records during audit log processing. \ No newline at end of file diff --git a/osquery/events/linux/.auditdnetlink.md b/osquery/events/linux/.auditdnetlink.md new file mode 100644 index 00000000000..d1fb75c544f --- /dev/null +++ b/osquery/events/linux/.auditdnetlink.md @@ -0,0 +1,43 @@ + +Header file defining the audit netlink interface for osquery's Linux audit subsystem integration, providing classes and data structures for reading, parsing, and consuming kernel audit events via the Linux netlink socket. + +## Key Components + +### Enums & Type Aliases +- **`NetlinkStatus`** — Enum indicating netlink handle state: `ActiveMutable`, `ActiveImmutable`, `Disabled`, or `Error` +- **`AuditRuleDataObject`** — `std::vector` wrapping raw audit rule data +- **`AuditdContextRef`** — `shared_ptr` for shared ownership across services + +### Structs +- **`AuditEventRecord`** — A parsed audit event containing type, timestamp, audit ID, key-value fields, and raw message data +- **`AuditdContext`** — Thread-safe shared state between reader and parser services; holds unprocessed/processed queues, mutexes, condition variables, and throttling counters + +### Classes +- **`AuditdNetlinkReader`** — `InternalRunnable` service that acquires the netlink handle, configures audit rules, reads raw `audit_reply` records, and manages audit service lifecycle +- **`AuditdNetlinkParser`** — `InternalRunnable` service that parses raw `audit_reply` structures into `AuditEventRecord` objects; exposes static `ParseAuditReply()` and `AdjustAuditReply()` utilities +- **`AuditdNetlink`** — Top-level consumer interface; calls `getEvents()` to retrieve fully processed `AuditEventRecord` vectors + +### Utility +- **`DecodeAuditPathValues()`** — Inline function that strips surrounding quotes or hex-decodes audit field values using `boost::algorithm::unhex` + +## Usage Example + +```cpp +#include "auditdnetlink.h" + +// Initialize shared context and the top-level netlink interface +osquery::AuditdNetlink netlink; + +// Poll for new audit events in a publisher loop +while (running) { + std::vector events = netlink.getEvents(); + for (const auto& record : events) { + // Access parsed fields + auto it = record.fields.find("exe"); + if (it != record.fields.end()) { + std::string exe = osquery::DecodeAuditPathValues(it->second); + // process exe path... + } + } +} +``` \ No newline at end of file diff --git a/osquery/events/linux/.auditeventpublisher.md b/osquery/events/linux/.auditeventpublisher.md new file mode 100644 index 00000000000..b3b36217a6a --- /dev/null +++ b/osquery/events/linux/.auditeventpublisher.md @@ -0,0 +1,60 @@ + +Header file defining the Linux audit event publisher for osquery, providing data structures and interfaces for processing kernel audit events including syscalls, AppArmor, SELinux, and seccomp notifications via the Linux Audit netlink subsystem. + +## Key Components + +### Event Data Structures + +| Struct | Description | +|---|---| +| `UserAuditEventData` | Holds a user-space audit event ID | +| `SyscallAuditEventData` | Captures syscall metadata: PID, UID/GID sets, executable path, success status | +| `AppArmorAuditEventData` | Key-value fields for AppArmor audit records (profile, operation, masks, etc.) | +| `SeccompAuditEventData` | Key-value fields for seccomp audit records (arch, syscall, signal, instruction pointer, etc.) | +| `AuditEvent` | Top-level event descriptor wrapping all event types via `boost::variant` with a raw `record_list` | + +### Publisher Infrastructure + +| Type | Description | +|---|---| +| `AuditEventPublisher` | Core `EventPublisher` subclass; manages netlink I/O, assembles audit records into events | +| `AuditEventContext` | Carries a `vector` dispatched to subscribers | +| `AuditSubscriptionContext` | Subscription handle (opaque; controlled by the publisher) | +| `AuditTraceContext` | `map` tracking in-flight multi-record audit sequences | + +### Free Functions + +| Function | Description | +|---|---| +| `ProcessEvents()` | Aggregates raw `AuditEventRecord` lists into typed `AuditEvent` objects | +| `GetEventRecord()` | Extracts a record of a specific type from an `AuditEvent` | +| `GetStringFieldFromMap()` | Safe string field lookup with default value | +| `GetIntegerFieldFromMap()` | Safe integer field lookup with configurable base and default | +| `CopyFieldFromMap()` | Copies a named field into an osquery `Row` | +| `StripQuotes()` | Removes surrounding quotes from audit field values | +| `parseSeccompEvent()` | Parses a raw seccomp record into `SeccompAuditEventData` | + +## Usage Example + +```cpp +// Subscribing to audit events +auto sub_ctx = AuditEventPublisher::createSubscriptionContext(); +EventFactory::addSubscription("auditeventpublisher", sub_ctx, + [](const AuditEventContextRef& ctx, const AuditSubscriptionContextRef&) { + for (const auto& event : ctx->audit_events) { + if (event.type == AuditEvent::Type::Syscall) { + const auto& data = boost::get(event.data); + LOG(INFO) << "Syscall " << data.syscall_number + << " by PID " << data.process_id + << " exe: " << data.executable_path; + } + } + return Status::success(); + }); + +// Assembling raw records into typed events (used internally / in tests) +AuditEventContextRef ctx = std::make_shared(); +AuditTraceContext trace; +std::set allowed_failures = {257}; // e.g. openat allowed to fail +AuditEventPublisher::ProcessEvents(ctx, raw_records, trace, allowed_failures); +``` \ No newline at end of file diff --git a/osquery/events/linux/.inotify.md b/osquery/events/linux/.inotify.md new file mode 100644 index 00000000000..920966d4189 --- /dev/null +++ b/osquery/events/linux/.inotify.md @@ -0,0 +1,44 @@ + +Linux `inotify`-based filesystem event publisher header for osquery, defining subscription contexts, event contexts, and the `INotifyEventPublisher` class used to monitor file and directory changes on Linux. + +## Key Components + +### Constants & Type Aliases +- `kMaskActions` — maps `inotify` mask bits to human-readable action strings +- `kFileDefaultMasks` / `kFileAccessMasks` — predefined `inotify` event mask sets +- `PathDescriptorMap` / `DescriptorPathMap` — bidirectional path ↔ watch descriptor lookup maps +- `ExcludePathSet` — pattern-aware set of paths excluded from event propagation + +### `INotifySubscriptionContext` +Subscription configuration struct holding the watched path, optional action mask, recursive flag, category label, and lazy-deletion marker. Provides `requireAction()` to map string action names to `inotify` bitmask values. + +### `INotifyEventContext` +Event payload struct carrying the raw `inotify_event`, resolved path string, action string, transaction ID, and a reference back to the originating subscription context. + +### `INotifyEventPublisher` +Core publisher class (inherits `EventPublisher`) managing the `inotify` file descriptor lifecycle, watch descriptor bookkeeping, and event dispatch loop. Key methods: + +| Method | Purpose | +|---|---| +| `setUp()` | Opens the `inotify` handle | +| `configure()` | Applies updated subscriptions from config | +| `run()` | Main event polling loop | +| `addMonitor()` | Registers a path watch, optionally recursive | +| `needMonitoring()` | Guards `addMonitor` using status-change time | +| `removeMonitor()` | Unregisters a watch descriptor | +| `shouldFire()` | Matches an event against a subscription's path/mask | +| `handleOverflow()` | Recovers from `IN_Q_OVERFLOW` by increasing read batch size | + +## Usage Example + +```cpp +// Subscribe to file creation events under a directory +auto sc = std::make_shared(); +sc->path = "/etc/myapp/"; +sc->recursive = true; +sc->requireAction("CREATED"); + +// The publisher dispatches INotifyEventContext to matching subscribers +// ec->path → absolute path of the affected file +// ec->action → "CREATED", "UPDATED", "DELETED", etc. +``` \ No newline at end of file diff --git a/osquery/events/linux/.process_events.md b/osquery/events/linux/.process_events.md new file mode 100644 index 00000000000..70eab58b647 --- /dev/null +++ b/osquery/events/linux/.process_events.md @@ -0,0 +1,38 @@ + +Defines constant syscall sets used to classify Linux process-related events by category within the osquery framework. + +## Key Components + +| Constant | Syscalls Included | Description | +|---|---|---| +| `kExecProcessEventsSyscalls` | `execve`, `execveat` | Tracks process execution events | +| `kKillProcessEventsSyscalls` | `kill`, `tkill`, `tgkill` | Tracks process termination/signal events | +| `kForkProcessEventsSyscalls` | `clone`, `fork`*, `vfork`* | Tracks process creation events (*x86_64 only) | + +> **Note:** `fork` and `vfork` syscalls are conditionally included only on `__x86_64__` architectures, as they are not available on all Linux platforms (e.g., ARM uses `clone` exclusively). + +## Usage Example + +```c +#include "process_events.h" + +// Check if a captured syscall number is a fork-type event +int syscall_nr = __NR_clone; + +if (osquery::kForkProcessEventsSyscalls.count(syscall_nr)) { + // Handle fork/clone process creation +} + +if (osquery::kExecProcessEventsSyscalls.count(syscall_nr)) { + // Handle exec-based process launch +} + +if (osquery::kKillProcessEventsSyscalls.count(syscall_nr)) { + // Handle kill/signal event +} +``` + +## Architecture Notes + +- All sets use raw kernel syscall numbers from `` for direct BPF/tracepoint matching +- `fork`/`vfork` inclusion is guarded by `#ifdef __x86_64__`, ensuring portability across Linux architectures \ No newline at end of file diff --git a/osquery/events/linux/.process_file_events.md b/osquery/events/linux/.process_file_events.md new file mode 100644 index 00000000000..2ea813d9177 --- /dev/null +++ b/osquery/events/linux/.process_file_events.md @@ -0,0 +1,37 @@ + +Defines the set of Linux system calls monitored for process file event tracking in osquery's event subsystem. + +## Key Components + +### `kProcessFileEventsSyscalls` +A compile-time constant `std::set` containing syscall numbers relevant to file and process operations, sourced from ``. The set covers: + +| Category | Syscalls | +|---|---| +| File linking/creation | `linkat`, `symlinkat`, `mknodat`, `openat`, `creat`, `mknod` | +| File deletion/rename | `unlinkat`, `renameat`, `renameat2`, `unlink`, `rename` | +| File handles | `open_by_handle_at`, `name_to_handle_at` | +| Descriptors | `close`, `dup`, `dup2`, `dup3` | +| Read operations | `read`, `readv`, `pread64`, `preadv` | +| Write operations | `write`, `writev`, `pwrite64`, `pwritev`, `truncate`, `ftruncate` | +| Memory | `mmap` | +| Process creation | `clone`, `fork`, `vfork` | + +> **Note:** Syscalls under `#ifdef __x86_64__` (`symlink`, `unlink`, `rename`, `open`, `dup2`, `fork`, `vfork`, `creat`, `mknod`) are included only on x86_64, as they are deprecated on newer architectures (e.g., ARM64/AArch64). + +## Usage Example + +```c +#include "process_file_events.h" + +// Check if a given syscall number should trigger a file event +bool shouldTrack(int syscallNr) { + return kProcessFileEventsSyscalls.count(syscallNr) > 0; +} + +// Example: filter audit records by monitored syscalls +if (kProcessFileEventsSyscalls.find(event.syscall) + != kProcessFileEventsSyscalls.end()) { + processFileEvent(event); +} +``` \ No newline at end of file diff --git a/osquery/events/linux/.selinux_events.md b/osquery/events/linux/.selinux_events.md new file mode 100644 index 00000000000..dafb7f131d6 --- /dev/null +++ b/osquery/events/linux/.selinux_events.md @@ -0,0 +1,35 @@ + +Defines shared constants for SELinux audit event tracking, providing the event ID-to-label mappings and event set used by the SELinux event subscriber in osquery. + +## Key Components + +### `kSELinuxRecordLabels` +A `std::map` mapping Linux audit event codes (from ``) to their human-readable string labels. Covers 19 SELinux-related audit record types including AVC decisions, MAC policy changes, and IPsec events. + +> **Important:** This map must remain in exact sync with `SELinuxEventSubscriber::GetEventSet()`. Adding or removing entries here without updating that method will cause inconsistent behavior. + +### `kSELinuxEventList` +A `std::set` containing the same audit event codes as `kSELinuxRecordLabels`, used for event subscription filtering. Includes `AUDIT_USER_AVC`, which falls outside the standard documented numeric range (1400–1499). + +**Notable omissions** (documented events not found in kernel headers): +- `USER_SELINUX_ERR` +- `USER_MAC_POLICY_LOAD` +- `USER_ROLE_CHANGE` +- `USER_LABEL_EXPORT` + +## Usage Example + +```c +// Look up a human-readable label for an incoming audit record type +int record_type = AUDIT_AVC; + +auto it = kSELinuxRecordLabels.find(record_type); +if (it != kSELinuxRecordLabels.end()) { + std::string label = it->second; // "AVC" +} + +// Check if a record type is a tracked SELinux event +if (kSELinuxEventList.count(record_type) > 0) { + // process the SELinux event +} +``` \ No newline at end of file diff --git a/osquery/events/linux/.socket_events.md b/osquery/events/linux/.socket_events.md new file mode 100644 index 00000000000..e6e1e3b6741 --- /dev/null +++ b/osquery/events/linux/.socket_events.md @@ -0,0 +1,26 @@ + +Declares the interface for retrieving the set of system call numbers monitored by the socket events subsystem in osquery. + +## Key Components + +- **`getSocketEventsSyscalls()`** — Returns a `std::set` containing the syscall numbers relevant to socket event monitoring (e.g., `connect`, `bind`, `accept`). + +## Usage Example + +```c +#include "socket_events.h" +#include + +// Retrieve all syscalls tracked for socket events +std::set syscalls = osquery::getSocketEventsSyscalls(); + +for (int syscall_nr : syscalls) { + std::cout << "Monitoring syscall: " << syscall_nr << std::endl; +} +``` + +## Notes + +- Lives within the `osquery` namespace. +- Typically consumed by the audit-based event publisher to register which syscalls to subscribe to for network socket activity tracking. +- The returned set drives kernel-level audit rule registration — only syscalls in this set will generate socket events in the osquery event pipeline. \ No newline at end of file diff --git a/osquery/events/linux/.syslog.md b/osquery/events/linux/.syslog.md new file mode 100644 index 00000000000..cd8b52ae6e8 --- /dev/null +++ b/osquery/events/linux/.syslog.md @@ -0,0 +1,40 @@ + +Declares the event publisher and supporting types for ingesting syslog entries forwarded from rsyslog via a named pipe, publishing parsed log fields to osquery subscribers. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `SyslogSubscriptionContext` | struct | Empty subscription context; no filtering criteria needed | +| `SyslogEventContext` | struct | Holds a syslog message as a `map` of parsed fields | +| `NonBlockingFStream` | class | Non-blocking pipe reader with internal ring buffer; exposes `openReadOnly`, `getline`, and `close` | +| `SyslogEventPublisher` | class | osquery event publisher; reads JSON syslog lines from a named pipe, parses them, and fires events | +| `RsyslogCsvSeparator` | class | Boost `TokenizerFunction` functor that handles rsyslog's CSV dialect (`""` escaping, no backslash escaping) | + +## Usage Example + +```cpp +// Subscribing to syslog events in an EventSubscriber +class MySyslogSubscriber + : public EventSubscriber { + public: + Status init() override { + // No filter criteria needed; SyslogSubscriptionContext is empty + auto sc = createSubscriptionContext(); + subscribe(&MySyslogSubscriber::onEvent, sc); + return Status::success(); + } + + Status onEvent(const SyslogEventContextRef& ec, + const SyslogSubscriptionContextRef&) { + // Access parsed syslog fields + auto it = ec->fields.find("message"); + if (it != ec->fields.end()) { + LOG(INFO) << "syslog message: " << it->second; + } + return Status::success(); + } +}; +``` + +> **Note:** `SyslogEventPublisher` requires rsyslog to be configured to write JSON-formatted entries to the named pipe path defined in osquery's configuration. Only the first osquery process to acquire the pipe lock (`lockPipe`) will consume events; subsequent processes (e.g. `osqueryi`) are silently skipped to avoid corrupting `osqueryd` reads. \ No newline at end of file diff --git a/osquery/events/linux/.udev.md b/osquery/events/linux/.udev.md new file mode 100644 index 00000000000..2dcfc678f43 --- /dev/null +++ b/osquery/events/linux/.udev.md @@ -0,0 +1,49 @@ + +Defines the Linux `udev` event publisher interface for osquery, enabling subscribers to monitor hardware device events (add, remove, change) via the `libudev` API. + +## Key Components + +### Enums +- **`udev_event_action`** — Device event types: `ADD`, `REMOVE`, `CHANGE`, `UNKNOWN`, and `ALL` (subscriber catch-all) + +### Structs +- **`UdevSubscriptionContext`** — Subscription filter criteria; restrict events by `action`, `subsystem`, `devnode`, `devtype`, or `driver` +- **`UdevEventContext`** — Fired event payload carrying a `udev_device*` pointer, resolved `action`, and device metadata strings + +### Type Aliases +- **`UdevEventContextRef`** — `std::shared_ptr` +- **`UdevSubscriptionContextRef`** — `std::shared_ptr` + +### Class: `UdevEventPublisher` +Extends `EventPublisher` and manages the udev socket monitor lifecycle. + +| Method | Description | +|---|---| +| `setUp()` | Initializes the udev handle and monitor | +| `tearDown()` | Releases udev resources | +| `run()` | Polls the monitor and dispatches events | +| `getValue(device, property)` | Returns a udev property string | +| `getAttr(device, attr)` | Returns a udev sysattr string | +| `shouldFire(...)` | Filters events against subscription context | +| `createEventContextFrom(device)` | Builds an `UdevEventContextRef` from a raw device pointer | + +## Usage Example + +```cpp +// Create a subscription context filtering USB add events +auto sc = createSubscriptionContext(); +sc->action = UDEV_EVENT_ACTION_ADD; +sc->subsystem = "usb"; + +// Subscribe with a callback +subscribe(&MySubscriber::onUsbAdd, sc); + +// In the callback, inspect event context +void onUsbAdd(const UdevEventContextRef& ec) { + auto node = ec->devnode; // e.g. "/dev/bus/usb/001/002" + auto driver = ec->driver; + auto device = ec->device; // raw udev_device* for deeper inspection + + auto vendor = UdevEventPublisher::getValue(device, "ID_VENDOR"); +} +``` \ No newline at end of file diff --git a/osquery/events/linux/bpf/.bpferrorstate.md b/osquery/events/linux/bpf/.bpferrorstate.md new file mode 100644 index 00000000000..219a15712c6 --- /dev/null +++ b/osquery/events/linux/bpf/.bpferrorstate.md @@ -0,0 +1,38 @@ + +Defines the `BPFErrorState` structure and related utilities for tracking and reporting errors encountered during BPF-based event collection in the osquery BPF publisher. + +## Key Components + +### `BPFErrorState` (struct) +A container aggregating all BPF publisher error counters: + +| Field | Type | Description | +|---|---|---| +| `perf_error_counters` | `IPerfEventReader::ErrorCounters` | Errors from `perf_output` ring buffer reads | +| `probe_error_counter` | `std::size_t` | Failures from BPF probes (e.g. string/buffer capture failures) | +| `errored_tracer_list` | `std::unordered_set` | IDs of tracers that produced unprocessable events | + +### `updateBpfErrorState()` +Merges incoming `perf_output` error counters into an existing `BPFErrorState` instance. + +### `reportAndClearBpfErrorState()` +Logs the current error state (typically to the osquery logger) and resets all counters and sets to their zero/empty defaults. + +## Usage Example + +```c +BPFErrorState error_state{}; + +// After each perf reader poll cycle, merge new counters in +IPerfEventReader::ErrorCounters new_counters = reader->getErrorCounters(); +updateBpfErrorState(error_state, new_counters); + +// Periodically flush errors to logs and reset state +reportAndClearBpfErrorState(error_state); +``` + +## Notes + +- `probe_error_counter` covers soft failures such as truncated strings in `open` syscall captures or malformed `sockaddr_in` buffers — not fatal crashes. +- `errored_tracer_list` deduplicates affected tracer IDs, making it useful for identifying persistently failing probes. +- Both functions operate on `BPFErrorState` by reference, making them safe to use with a long-lived state object across polling intervals. \ No newline at end of file diff --git a/osquery/events/linux/bpf/.bpfeventpublisher.md b/osquery/events/linux/bpf/.bpfeventpublisher.md new file mode 100644 index 00000000000..1e344a84b2d --- /dev/null +++ b/osquery/events/linux/bpf/.bpfeventpublisher.md @@ -0,0 +1,54 @@ + +Header defining the `BPFEventPublisher` class for osquery's Linux BPF-based event publishing system, which captures and processes kernel syscall events via eBPF tracing. + +## Key Components + +### Structs + +| Name | Base | Description | +|------|------|-------------| +| `BPFEventSC` | `SubscriptionContext` | Subscription context for BPF event subscriptions (opaque to subscribers) | +| `BPFEventEC` | `EventContext` | Event context carrying a list of system state events (`ISystemStateTracker::EventList`) | + +### Class: `BPFEventPublisher` + +Extends `EventPublisher`. Manages the eBPF perf event reader lifecycle and dispatches processed syscall events to subscribers. + +**Lifecycle methods:** + +| Method | Description | +|--------|-------------| +| `setUp()` | Initializes eBPF tracers and perf event reader | +| `configure()` | Applies runtime configuration | +| `run()` | Poll loop consuming BPF perf events | +| `tearDown()` | Cleans up eBPF resources | + +**Static helper — `getEventMapValue`** + +Extracts a typed value from a `IFunctionTracer::Event::FieldMap` by key using `std::variant`. Returns `false` if the key is missing or the type doesn't match. + +**Static syscall processors** + +One handler per traced syscall, each updating an `ISystemStateTracker` with the parsed event: + +- **Process lifecycle:** `processForkEvent`, `processVforkEvent`, `processCloneEvent`, `processExecveEvent`, `processExecveatEvent` +- **File descriptors:** `processCloseEvent`, `processDupEvent`, `processDup2Event`, `processDup3Event`, `processFcntlEvent` +- **File system:** `processCreatEvent`, `processMknodatEvent`, `processOpenEvent`, `processOpenatEvent`, `processOpenat2Event`, `processChdirEvent`, `processFchdirEvent`, `processNameToHandleAtEvent`, `processOpenByHandleAtEvent` +- **Networking:** `processSocketEvent`, `processConnectEvent`, `processAcceptEvent`, `processAccept4Event`, `processBindEvent`, `processListenEvent` + +## Usage Example + +```cpp +// Retrieve a typed field from a BPF event map +uint64_t fd = 0; +bool ok = BPFEventPublisher::getEventMapValue( + fd, event.field_map, "fd"); + +if (ok) { + // Use fd value +} + +// Dispatch a clone syscall event to the state tracker +bool handled = BPFEventPublisher::processCloneEvent( + stateTracker, bpfEvent); +``` \ No newline at end of file diff --git a/osquery/events/linux/bpf/.filesystem.md b/osquery/events/linux/bpf/.filesystem.md new file mode 100644 index 00000000000..b295a6a391e --- /dev/null +++ b/osquery/events/linux/bpf/.filesystem.md @@ -0,0 +1,48 @@ + +Concrete implementation of the `IFilesystem` interface for Linux BPF event processing, providing real filesystem operations backed by system calls. + +## Key Components + +**`Filesystem`** — Final class implementing `IFilesystem` with the following methods: + +| Method | Description | +|---|---| +| `open()` | Opens a file at the given path with specified flags, returning a unique fd | +| `openAt()` | Opens a file relative to a directory fd (`dirfd`) | +| `readLinkAt()` | Reads a symbolic link target relative to a directory fd | +| `read()` | Reads up to `max_size` bytes from an open fd into a buffer | +| `enumFiles()` | Enumerates files in a directory, invoking a callback per entry | +| `fileExists()` | Checks whether a named file exists within a directory fd | + +> Construction is private (`Filesystem() = default`), enforcing instantiation exclusively through the `IFilesystem` factory (friend class pattern). + +## Usage Example + +```cpp +#include + +// Instantiate via the IFilesystem factory (not directly) +auto fs = IFilesystem::create(); + +// Open a file +tob::utils::UniqueFd fd; +if (fs->open(fd, "/proc/self/maps", O_RDONLY)) { + std::vector buffer; + fs->read(buffer, fd.get(), 4096); +} + +// Check file existence relative to a directory fd +bool exists = false; +fs->fileExists(exists, AT_FDCWD, "config.json"); + +// Enumerate directory entries +fs->enumFiles(AT_FDCWD, [](const std::string& name) { + // process each entry +}); +``` + +## Notes + +- Scoped to the `osquery` namespace +- Designed for Linux only (BPF event subsystem) +- All methods return `bool` indicating success/failure; output is via reference parameters \ No newline at end of file diff --git a/osquery/events/linux/bpf/.ifilesystem.md b/osquery/events/linux/bpf/.ifilesystem.md new file mode 100644 index 00000000000..dc93996b9d7 --- /dev/null +++ b/osquery/events/linux/bpf/.ifilesystem.md @@ -0,0 +1,50 @@ + +Abstract interface for file system operations using file descriptor-based access, designed to minimize race conditions in path traversals (particularly for procfs) and enable easy test mocking. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `IFilesystem` | Abstract class | Pure virtual interface for fd-based filesystem operations | +| `IFilesystem::Ref` | Type alias | `std::unique_ptr` for ownership management | +| `create()` | Static factory | Instantiates a concrete implementation | +| `EnumFilesCallback` | Type alias | Callback `void(name, is_directory)` used by `enumFiles` | + +### Virtual Methods + +| Method | Wraps | Description | +|---|---|---| +| `open()` | `open()` | Opens a file by absolute path | +| `openAt()` | `openat()` | Opens a file relative to a directory fd | +| `readLinkAt()` | `readlinkat()` | Reads a symlink target relative to a directory fd | +| `read()` | `read()` | Reads up to `max_size` bytes from an fd | +| `enumFiles()` | `getdents` | Enumerates entries in a directory fd | +| `fileExists()` | — | Checks existence of a file relative to a directory fd | + +## Usage Example + +```cpp +#include "ifilesystem.h" + +osquery::IFilesystem::Ref fs; +auto status = osquery::IFilesystem::create(fs); + +if (!status.ok()) { + // handle creation error +} + +// Open a directory fd first +tob::utils::UniqueFd dirfd; +fs->open(dirfd, "/proc/1234", O_RDONLY | O_DIRECTORY); + +// Enumerate entries relative to that fd +fs->enumFiles(dirfd.get(), [](const std::string& name, bool isDir) { + std::cout << (isDir ? "[dir] " : "[file] ") << name << "\n"; +}); + +// Read a symlink relative to the directory fd +std::string target; +fs->readLinkAt(target, dirfd.get(), "exe"); +``` + +> **Note:** Copy construction and assignment are deleted — use `IFilesystem::Ref` (`unique_ptr`) for ownership transfer. \ No newline at end of file diff --git a/osquery/events/linux/bpf/.iprocesscontextfactory.md b/osquery/events/linux/bpf/.iprocesscontextfactory.md new file mode 100644 index 00000000000..ce9c5415c41 --- /dev/null +++ b/osquery/events/linux/bpf/.iprocesscontextfactory.md @@ -0,0 +1,56 @@ + +Interface header defining the `IProcessContextFactory` abstract class and associated data structures for capturing and tracking Linux process state via BPF and procfs within the osquery framework. + +## Key Components + +### Data Structures + +| Structure | Description | +|---|---| +| `ProcessContext` | Top-level container holding all state for a single process | +| `ProcessContext::FileDescriptor` | Represents an open file descriptor (file or socket) | +| `ProcessContext::FileDescriptor::FileData` | Path information for file/directory descriptors | +| `ProcessContext::FileDescriptor::SocketData` | Full socket metadata (domain, type, local/remote addr+port) | +| `ProcessContextMap` | Alias for `unordered_map` | + +### `ProcessContext` Fields + +- `parent_process_id` — PID of the parent process +- `binary_path` — Path to the running executable +- `argv` — Program argument list +- `cwd` — Current working directory +- `fd_map` — Inherited file descriptor map (`FileDescriptorMap`) + +### `IProcessContextFactory` Interface + +| Member | Description | +|---|---| +| `create(Ref&)` | Static factory method returning a `unique_ptr` instance | +| `captureSingleProcess(...)` | Reads a single process state from procfs by PID | +| `captureAllProcesses(...)` | Snapshots all running processes via repeated `captureSingleProcess` calls | + +## Usage Example + +```c +// Create a factory instance +IProcessContextFactory::Ref factory; +Status s = IProcessContextFactory::create(factory); + +// Snapshot all running processes +ProcessContextMap process_map; +factory->captureAllProcesses(process_map); + +// Capture a single process by PID +ProcessContext ctx; +factory->captureSingleProcess(ctx, 1234); + +// Inspect an open file descriptor +for (auto& [fd, descriptor] : ctx.fd_map) { + if (auto* file = std::get_if( + &descriptor.data)) { + // Access file->path + } +} +``` + +> **Note:** The factory is non-copyable by design. BPF events keep the `ProcessContextMap` up to date after the initial snapshot; `captureSingleProcess` handles processes discovered mid-flight that were absent from the initial capture. \ No newline at end of file diff --git a/osquery/events/linux/bpf/.isystemstatetracker.md b/osquery/events/linux/bpf/.isystemstatetracker.md new file mode 100644 index 00000000000..496a441160d --- /dev/null +++ b/osquery/events/linux/bpf/.isystemstatetracker.md @@ -0,0 +1,63 @@ + +Abstract interface defining a system state tracker that monitors OS-level process and network events using eBPF, maintaining a live process snapshot initialized from `/proc` and emitting structured events for process lifecycle and socket operations. + +## Key Components + +### `ISystemStateTracker` +Pure virtual interface class (non-copyable) with a `unique_ptr` alias `Ref`. + +### `Event` +Nested struct representing a captured system event, containing: +- **`Type`** — enum of `Fork`, `Exec`, `Connect`, `Bind`, `Listen`, `Accept` +- **`ExecData`** — argv vector for `execve`/`execveat` events +- **`SocketData`** — socket domain, type, protocol, fd, local/remote address and port +- **`Data`** — `std::variant` payload + +### Pure Virtual Methods + +| Method | Triggered By | +|---|---| +| `restart()` | Re-snapshots `/proc` state | +| `createProcess()` | `fork` / `vfork` / `clone` | +| `executeBinary()` | `execve` / `execveat` | +| `setWorkingDirectory()` | `chdir` / `fchdir` | +| `openFile()` | `openat` | +| `duplicateHandle()` | `dup`, `dup2`, `dup3`, `fcntl` | +| `closeHandle()` | `close` | +| `createSocket()` | `socket` | +| `bind()` / `listen()` / `connect()` / `accept()` | Socket lifecycle syscalls | +| `nameToHandleAt()` / `openByHandleAt()` | Opaque file handle tracking | +| `eventList()` | Retrieve accumulated events | + +## Usage Example + +```cpp +// Implement the interface +class SystemStateTracker : public osquery::ISystemStateTracker { + public: + bool createProcess( + const tob::ebpfpub::IFunctionTracer::Event::Header& header, + pid_t process_id, + pid_t child_process_id) override { + // update internal state, push Fork event + return true; + } + + EventList eventList() override { + return std::move(pending_events_); + } + // ... implement remaining pure virtuals +}; + +// Consume events +ISystemStateTracker::Ref tracker = std::make_unique(); +tracker->restart(); + +// After processing eBPF callbacks... +for (const auto& event : tracker->eventList()) { + if (event.type == ISystemStateTracker::Event::Type::Exec) { + auto& exec = std::get(event.data); + // handle exec event + } +} +``` \ No newline at end of file diff --git a/osquery/events/linux/bpf/.processcontextfactory.md b/osquery/events/linux/bpf/.processcontextfactory.md new file mode 100644 index 00000000000..dad39b819ef --- /dev/null +++ b/osquery/events/linux/bpf/.processcontextfactory.md @@ -0,0 +1,51 @@ + +Concrete implementation of `IProcessContextFactory` for capturing Linux process context data by reading `/proc` filesystem entries via BPF event handling. + +## Key Components + +### Class: `ProcessContextFactory` + +Final implementation of `IProcessContextFactory` with both instance and static utility methods. + +**Instance Methods** + +| Method | Description | +|---|---| +| `captureSingleProcess(ProcessContext&, pid_t)` | Captures context for a single process by PID | +| `captureAllProcesses(ProcessContextMap&)` | Captures context for all running processes | + +**Static Utility Methods** + +| Method | Description | +|---|---| +| `captureSingleProcess(IFilesystem&, ProcessContext&, pid_t)` | Filesystem-injectable single process capture | +| `captureAllProcesses(IFilesystem&, ProcessContextMap&)` | Filesystem-injectable bulk process capture | +| `getArgvFromCmdlineFile(IFilesystem&, vector&, int fd)` | Parses argv from `/proc//cmdline` | +| `getParentPidFromStatFile(IFilesystem&, pid_t&, int fd)` | Extracts parent PID from `/proc//stat` | + +**Private Members** +- `IFilesystem::Ref fs` — injected filesystem abstraction for testability +- Private constructor (instantiated via `IProcessContextFactory` friend) + +## Usage Example + +```c +// Typically instantiated through IProcessContextFactory +auto factory = IProcessContextFactory::create(fs_ref); + +// Capture a single process by PID +ProcessContext ctx; +if (factory->captureSingleProcess(ctx, 1234)) { + // ctx now contains argv, parent PID, etc. +} + +// Capture all running processes +ProcessContextMap all_procs; +factory->captureAllProcesses(all_procs); + +// Static form — useful in tests with mock filesystem +ProcessContext ctx2; +ProcessContextFactory::captureSingleProcess(mock_fs, ctx2, 1234); +``` + +> **Note:** The private constructor and `friend class IProcessContextFactory` pattern enforces creation through the factory interface, enabling dependency injection of `IFilesystem` for unit testing. \ No newline at end of file diff --git a/osquery/events/linux/bpf/.serializers.md b/osquery/events/linux/bpf/.serializers.md new file mode 100644 index 00000000000..e55d7396631 --- /dev/null +++ b/osquery/events/linux/bpf/.serializers.md @@ -0,0 +1,28 @@ + +Declares the `ParameterListMap` type and the `kParameterListMap` lookup table used to map eBPF function tracer names to their corresponding parameter lists. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `ParameterListMap` | Type alias | `unordered_map` keyed by `std::string`, mapping to `IFunctionTracer::ParameterList` | +| `kParameterListMap` | `extern const` | Global registry of eBPF-traced function names to their parameter definitions | + +## Usage Example + +```cpp +#include "serializers.h" + +// Look up parameters for a known syscall tracer +auto it = osquery::kParameterListMap.find("open"); +if (it != osquery::kParameterListMap.end()) { + const auto& params = it->second; + // iterate or pass params to a function tracer +} +``` + +## Notes + +- Depends on `ebpfpub/ifunctiontracer.h` from the `tob::ebpfpub` namespace. +- `kParameterListMap` is defined externally (in the corresponding `.cpp`); this header only declares it. +- Intended for use within osquery's eBPF event tracing subsystem to drive syscall parameter serialization. \ No newline at end of file diff --git a/osquery/events/linux/bpf/.setrlimit.md b/osquery/events/linux/bpf/.setrlimit.md new file mode 100644 index 00000000000..7af86cfb756 --- /dev/null +++ b/osquery/events/linux/bpf/.setrlimit.md @@ -0,0 +1,27 @@ + +Declares a utility function for configuring BPF (Berkeley Packet Filter) memory resource limits within the osquery framework. + +## Key Components + +- **`configureBPFMemoryLimits()`** — Sets system resource limits (`rlimit`) required for BPF operations. Returns a `Status` object indicating success or failure. + +## Usage Example + +```c +#include "setrlimit.h" + +void initBPF() { + osquery::Status status = osquery::configureBPFMemoryLimits(); + if (!status.ok()) { + // Handle failure: status.getMessage() provides details + return; + } + // Proceed with BPF initialization +} +``` + +## Notes + +- Part of the `osquery` namespace. +- Relies on `osquery::Status` from `osquery/utils/status/status.h` for error propagation. +- Typically called during BPF subsystem initialization to ensure adequate locked memory (`RLIMIT_MEMLOCK`) is available for eBPF map and program loading. \ No newline at end of file diff --git a/osquery/events/linux/bpf/.systemstatetracker.md b/osquery/events/linux/bpf/.systemstatetracker.md new file mode 100644 index 00000000000..5c345dc8594 --- /dev/null +++ b/osquery/events/linux/bpf/.systemstatetracker.md @@ -0,0 +1,52 @@ + +Concrete implementation of `ISystemStateTracker` that tracks Linux process and system state via BPF event hooks, maintaining per-process context maps and generating an event list from observed kernel activity. + +## Key Components + +### Factory Methods +- `create()` — Instantiates tracker with a default `IProcessContextFactory` +- `create(IProcessContextFactory::Ref)` — Instantiates tracker with an injected factory (for testing) + +### Instance Methods (virtual overrides) +| Method | Description | +|---|---| +| `restart()` | Reinitializes tracker state | +| `createProcess()` | Records a fork/clone event | +| `executeBinary()` | Records an exec event with argv | +| `setWorkingDirectory()` | Updates CWD by `dirfd` or path string | +| `openFile()` / `closeHandle()` / `duplicateHandle()` | Tracks file descriptor lifecycle | +| `createSocket()` / `bind()` / `listen()` / `connect()` / `accept()` | Tracks socket state transitions | +| `nameToHandleAt()` / `openByHandleAt()` | Tracks opaque file handle resolution | +| `eventList()` | Returns accumulated BPF events | + +### Internal Static Helpers +- `getProcessContext()` — Looks up or creates a `ProcessContext` for a given PID +- `expireProcessContexts()` — Prunes stale process entries +- `parseUnixSockaddr()` / `parseInetSockaddr()` / `parseInet6Sockaddr()` / `parseNetlinkSockaddr()` — Deserializes raw sockaddr bytes into address/port strings +- `parseSocketAddress()` — Dispatches to the appropriate sockaddr parser +- `saveFileHandle()` / `expireFileHandleEntries()` — Manages the file handle index/map + +### Key Data Structures +- `Context` — Holds the `ProcessContextMap`, accumulated `EventList`, and file handle index/map +- `FileHandleStruct` — Associates a handle with its originating `dfd`, name, and flags +- `PrivateData` — Opaque pImpl struct holding internal state + +## Usage Example + +```cpp +// Default construction +auto tracker = osquery::SystemStateTracker::create(); + +// Process a fork event +tracker->createProcess(event_header, parent_pid, child_pid); + +// Track a binary execution +tracker->executeBinary(event_header, pid, AT_FDCWD, 0, "/usr/bin/ls", argv); + +// Retrieve accumulated events +auto events = tracker->eventList(); + +// Inject a custom factory (e.g., in unit tests) +auto mock_factory = std::make_shared(); +auto tracker = osquery::SystemStateTracker::create(mock_factory); +``` \ No newline at end of file diff --git a/osquery/events/linux/bpf/.uniquedir.md b/osquery/events/linux/bpf/.uniquedir.md new file mode 100644 index 00000000000..27901527a95 --- /dev/null +++ b/osquery/events/linux/bpf/.uniquedir.md @@ -0,0 +1,33 @@ + +Provides a RAII-managed unique pointer type for POSIX directory handles, ensuring automatic cleanup of `DIR*` resources within the `osquery` namespace. + +## Key Components + +- **`UniqueDir`** — A `std::unique_ptr` typedef that wraps a POSIX `DIR*` handle and automatically calls `closedir` when the pointer goes out of scope. + +## Usage Example + +```c +#include "uniquedir.h" +#include + +namespace osquery { + +void listDirectory(const std::string& path) { + UniqueDir dir(opendir(path.c_str()), closedir); + if (!dir) { + // handle error + return; + } + + struct dirent* entry = nullptr; + while ((entry = readdir(dir.get())) != nullptr) { + // process entry->d_name + } + // closedir called automatically when dir goes out of scope +} + +} // namespace osquery +``` + +> **Note:** Always pass `closedir` as the deleter when constructing a `UniqueDir` instance, since `std::unique_ptr` with a custom deleter requires it to be provided at construction time. \ No newline at end of file diff --git a/osquery/events/tests/.events_tests.md b/osquery/events/tests/.events_tests.md new file mode 100644 index 00000000000..545a5db0707 --- /dev/null +++ b/osquery/events/tests/.events_tests.md @@ -0,0 +1,42 @@ + +Unit tests for the osquery events framework, validating the lifecycle and behavior of `EventPublisher`, `EventFactory`, and `Subscription` components. + +## Key Components + +### Test Fixtures & Mock Types +- **`EventsTests`** — GTest fixture that initializes the registry, database, and config parser, then tears down the `EventFactory` after each test +- **`BasicEventPublisher`** / **`AnotherBasicEventPublisher`** — Minimal publishers using default `SubscriptionContext`/`EventContext` +- **`FakeEventPublisher`** / **`AnotherFakeEventPublisher`** — Named publishers using custom `FakeSubscriptionContext` and `FakeEventContext` +- **`TestEventPublisher`** — Publisher with overridden `setUp()`, `configure()`, and `tearDown()` lifecycle hooks for behavioral validation + +### Test Cases + +| Test | What It Validates | +|---|---| +| `test_event_publisher` | Publisher type naming via `DECLARE_PUBLISHER` and `setName()` | +| `test_register_event_publisher` | Register/deregister lifecycle, duplicate detection | +| `test_event_publisher_types` | Lookup by name, deregister by base pointer | +| `test_duplicate_event_publisher` | Only first registration is recorded | +| `test_create_using_registry` | `attachEvents()` wiring from `RegistryFactory` into `EventFactory` | +| `test_create_subscription` | Subscription creation and publisher association | +| `test_multiple_subscriptions` | Multiple subscriptions to a single publisher | +| `test_create_custom_event_publisher` | `setUp()` is called on registration | +| `test_custom_subscription` | `configure()` is triggered with correct subscription context values | +| `test_tear_down` | `setUp()`/`tearDown()` call counts via `getTestValue()` | + +## Usage Example + +```cpp +// Typical pattern used across tests: register → subscribe → verify → deregister +auto pub = std::make_shared(); +EventFactory::registerEventPublisher(pub); + +auto sc = std::make_shared(); +sc->smallest = -1; +EventFactory::addSubscription("TestPublisher", "TestSubscriber", sc); + +pub->configure(); +EXPECT_EQ(pub->getTestValue(), -1); + +EventFactory::deregisterEventPublisher(pub->type()); +``` \ No newline at end of file diff --git a/osquery/events/tests/.eventsubscriberplugin.md b/osquery/events/tests/.eventsubscriberplugin.md new file mode 100644 index 00000000000..0bdce0e2e51 --- /dev/null +++ b/osquery/events/tests/.eventsubscriberplugin.md @@ -0,0 +1,51 @@ + +Brief unit test suite for `EventSubscriberPlugin`, validating core event subscriber functionality including identifier generation, database indexing, event expiry, overflow handling, and row generation using a mocked osquery database. + +## Key Components + +| Component | Description | +|---|---| +| `EventSubscriberPluginTests` | GTest fixture class housing all unit tests | +| `FakeEventSubscriberPlugin` | Concrete stub subclass of `EventSubscriberPlugin` for integration-level tests with controllable time, expiry, and optimize settings | +| `MockedOsqueryDatabase` | In-memory database mock used to simulate osquery key-value storage | + +## Test Coverage + +| Test Case | What It Validates | +|---|---| +| `generateEventIdentifier` | Sequential identifier increment within a shared context | +| `setDatabaseNamespace` | Namespace format `"type.name"` | +| `generateEventDataIndex` | Malformed key pruning; only 10 of 20 keys survive reindex | +| `toIndex` | Zero-padded 10-digit string formatting | +| `setOptimizeData` / `getOptimizeData` | Optimize time and EID round-trip persistence | +| `timeFromRecord` | String-to-`EventTime` deserialization | +| `databaseKeyForEventId` | Key format `data..` | +| `removeOverflowingEventBatches` | Batch count capped at specified limit; reducing limit removes oldest | +| `expireEventBatches` | Time-range expiry; one event always retained (osquery issue #8524) | +| `generateRows` | Time-range filtering, callback invocation count, `isEnd`/`last_time` result fields | +| `getExpireTime` | Returns least-recent executed query time, falls back to current time | +| `getEventsExpiry` / `getMinExpiry` | `min(setMinExpiry, eventsExpiry)` ceiling logic | +| `generateRowsWithExpiry` | Full expiry cycle via `FakeEventSubscriberPlugin` | +| `generateRowsWithOptimize` | Optimize-mode row deduplication (partial in file) | + +## Usage Example + +```cpp +// Instantiate the fake subscriber with a mocked database +MockedOsqueryDatabase mocked_database; +FakeEventSubscriberPlugin subscriber(mocked_database); + +// Populate mock events and build the index +mocked_database.generateEvents(subscriber.getType(), subscriber.getName()); +subscriber.setDatabaseNamespace(); +subscriber.generateEventDataIndex(); + +// Set expiry and collect rows via callback +subscriber.setEventsExpiry(100); +subscriber.setTime(20); +subscriber.resetQueryCount(0); // marks all queries as executed + +size_t count = 0; +subscriber.generateRows([&count](Row) { ++count; }, true, 0, 0); +// count == 10 (all events within range) +``` \ No newline at end of file diff --git a/osquery/events/tests/.mockedosquerydatabase.md b/osquery/events/tests/.mockedosquerydatabase.md new file mode 100644 index 00000000000..adf5afcc7e9 --- /dev/null +++ b/osquery/events/tests/.mockedosquerydatabase.md @@ -0,0 +1,41 @@ + +In-memory mock implementation of `IDatabaseInterface` for use in osquery unit tests, providing a simple `std::map`-backed database without requiring a real storage backend. + +## Key Components + +- **`MockedOsqueryDatabase`** — Final class implementing `IDatabaseInterface` with an in-memory `std::map key_map` as the backing store. +- **`generateEvents()`** — Helper to simulate event generation for a given publisher and event name during tests. +- **`getDatabaseValue()`** — Overloaded for `std::string` and `int` return types; reads from `key_map`. +- **`setDatabaseValue()`** — Overloaded for `std::string` and `int` values; writes to `key_map`. +- **`setDatabaseBatch()`** — Bulk insert of string key-value pairs into the mock store. +- **`deleteDatabaseValue()`** / **`deleteDatabaseRange()`** — Remove single or range-bounded entries from `key_map`. +- **`scanDatabaseKeys()`** — Overloaded to list keys within a domain, with optional prefix filtering and a result size limit. + +## Usage Example + +```cpp +#include "mockedosquerydatabase.h" + +using namespace osquery; + +// Instantiate mock database for a unit test +MockedOsqueryDatabase db; + +// Write a value +db.setDatabaseValue("config", "refresh_interval", std::string("60")); + +// Read it back +std::string value; +Status s = db.getDatabaseValue("config", "refresh_interval", value); +assert(s.ok()); +assert(value == "60"); + +// Scan keys in a domain +std::vector keys; +db.scanDatabaseKeys("config", keys, 100); + +// Simulate event generation +db.generateEvents("file_events", "file_created"); +``` + +> **Note:** `key_map` is `mutable` to allow `const`-qualified read/write methods to modify the in-memory store — a deliberate design choice for test convenience. Not intended for production use. \ No newline at end of file diff --git a/osquery/events/tests/darwin/.fsevents_tests.md b/osquery/events/tests/darwin/.fsevents_tests.md new file mode 100644 index 00000000000..a0cfa99c33c --- /dev/null +++ b/osquery/events/tests/darwin/.fsevents_tests.md @@ -0,0 +1,55 @@ + +Unit tests for the macOS FSEvents event publisher in osquery, verifying event registration, subscription management, path matching, event firing, and action detection using Google Test. + +## Key Components + +### `FSEventsTests` (Test Fixture) +Base test fixture providing shared setup/teardown and helper utilities: + +| Method | Description | +|---|---| +| `SetUp()` / `TearDown()` | Initializes/cleans temp paths, registry, and database | +| `StartEventLoop()` / `EndEventLoop()` | Manages the FSEvents publisher thread lifecycle | +| `WaitForStream(int max)` | Blocks until the FSEvents stream is running | +| `WaitForEvents(size_t max, size_t num_events)` | Polls until expected event count is reached | +| `CreateEvents(int num)` | Triggers filesystem events by writing to the watched test path | + +### `TestFSEventsEventSubscriber` +A concrete `EventSubscriber` used to capture and validate fired events: +- `SimpleCallback` — increments a counter on each event +- `Callback` — records event action strings for assertion +- `WaitForEvents(int max, int initial)` — spins until callback count threshold is met + +### Test Cases + +| Test | Purpose | +|---|---| +| `test_register_event_pub` | Verifies publisher registration and deregistration | +| `test_fsevents_add_subscription_missing_path` | Confirms fake paths are accepted without error | +| `test_fsevents_add_subscription_success` | Validates path deduplication on `configure()` | +| `test_fsevents_match_subscription` | Tests `shouldFire()` against exclude path patterns | +| `test_fsevents_run` | End-to-end: starts loop, writes file, asserts events received | +| `test_fsevents_fire_event` | Verifies subscriber callback is invoked | +| `test_fsevents_event_action` | Asserts event actions are `CREATED`, `ATTRIBUTES_MODIFIED`, or `UNKNOWN` | + +## Usage Example + +```cpp +// Run all FSEvents tests via Google Test +TEST_F(FSEventsTests, test_fsevents_fire_event) { + StartEventLoop(); + + auto sub = std::make_shared(); + EventFactory::registerEventSubscriber(sub); + + auto sc = sub->GetSubscription(real_test_path, 0); + sub->subscribe(&TestFSEventsEventSubscriber::SimpleCallback, sc); + event_pub_->configure(); + + CreateEvents(); + sub->WaitForEvents(kMaxEventLatency, 1); + + EXPECT_TRUE(sub->callback_count_ > 0); + EndEventLoop(); +} +``` \ No newline at end of file diff --git a/osquery/events/tests/linux/.audit_tests.md b/osquery/events/tests/linux/.audit_tests.md new file mode 100644 index 00000000000..68a1fcf9689 --- /dev/null +++ b/osquery/events/tests/linux/.audit_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the Linux audit subsystem, specifically validating `AuditdNetlinkParser` reply parsing and audit path value decoding functionality. + +## Key Components + +| Component | Description | +|---|---| +| `AuditTests` | GTest fixture class with a shared `Row` object reset between tests | +| `generateAuditId()` | Helper that generates a fake audit ID string from a timestamp and event ID | +| `test_handle_reply` | Validates `AuditdNetlinkParser::ParseAuditReply()` against a synthetic audit message | +| `test_audit_value_decode` | Validates `DecodeAuditPathValues()` for quoted strings, hex-encoded values, and decode failures | +| `SimpleUpdate()` | Utility callback that increments `kAuditCounter` and merges field maps | + +## Usage Example + +```cpp +// Constructing a fake audit_reply and verifying parsed fields +std::string message = + "audit(1440542781.644:403030): argc=3 a0=\"H=1 \" a1=\"/bin/sh\" a2=c"; + +struct audit_reply reply; +reply.type = 1; +reply.len = message.size(); +reply.message = (char*)malloc(sizeof(char) * (message.size() + 1)); +memcpy((void*)reply.message, message.c_str(), message.size()); + +AuditEventRecord audit_event_record = {}; +bool ok = AuditdNetlinkParser::ParseAuditReply(reply, audit_event_record); + +// Expect 4 parsed fields: argc, a0, a1, a2 +assert(audit_event_record.fields.size() == 4U); +assert(audit_event_record.fields["a1"] == "\"/bin/sh\""); + +// Decoding audit path values +DecodeAuditPathValues("\"/bin/ls\""); // → "/bin/ls" +DecodeAuditPathValues("736C6565702031"); // → "sleep 1" (hex-decoded) +DecodeAuditPathValues("7"); // → "7" (fallback on decode failure) +``` + +> **Note:** These tests target Linux-only audit infrastructure. They depend on `auditdnetlink.h` and require the `audit_allow_unix` flag to be available at runtime. \ No newline at end of file diff --git a/osquery/events/tests/linux/.inotify_tests.md b/osquery/events/tests/linux/.inotify_tests.md new file mode 100644 index 00000000000..9eeeadee969 --- /dev/null +++ b/osquery/events/tests/linux/.inotify_tests.md @@ -0,0 +1,54 @@ + +Unit tests for the Linux inotify event publisher in osquery, validating filesystem event monitoring including subscription management, path matching, event firing, and publisher lifecycle. + +## Key Components + +| Component | Description | +|---|---| +| `INotifyTests` | Main GTest fixture class providing setup/teardown, helper methods, and shared state for all inotify tests | +| `TestINotifyEventSubscriber` | Test subscriber implementation that counts callbacks and records event actions | +| `StartEventLoop()` / `StopEventLoop()` | Helpers to spin up and tear down the inotify event publisher thread | +| `SubscriptionAction()` | Registers a path subscription with optional mask and callback | +| `WaitForEvents()` | Polling helper that blocks until a minimum number of events is received or timeout occurs | +| `TriggerEvent()` | Writes to a file to produce an inotify filesystem event | +| `addMonitor()` / `RemoveAll()` | Low-level monitor management helpers wrapping publisher internals | + +## Test Cases + +| Test | What It Validates | +|---|---| +| `test_register_event_pub` | Publisher registers/deregisters cleanly via `EventFactory` | +| `test_inotify_init` | Handle opens on registration, closes on deregistration | +| `test_inotify_add_subscription_missing_path` | Subscriptions on non-existent paths succeed without error | +| `test_inotify_add_subscription_success` | Valid path (`/`) subscription succeeds | +| `test_inotify_match_subscription` | Path normalization, trailing slash handling, wildcard matching, and exclude-path filtering via `shouldFire()` | +| `test_inotify_run` | End-to-end: write to watched file triggers event, publisher increments event count | + +## Usage Example + +```cpp +// Minimal pattern used throughout these tests: +event_pub_ = std::make_shared(true); +EventFactory::registerEventPublisher(event_pub_); + +auto mc = std::make_shared(); +mc->path = "/tmp/watched-file.txt"; +mc->mask = IN_ALL_EVENTS; + +EventFactory::addSubscription( + "inotify", + Subscription::create("TestSubscriber", mc)); + +event_pub_->configure(); + +// Trigger an event +FILE* fd = fopen("/tmp/watched-file.txt", "w"); +fputs("data", fd); +fclose(fd); + +// Poll for results +WaitForEvents(kMaxEventLatency, 1); +EXPECT_GT(event_pub_->numEvents(), 0); + +EventFactory::deregisterEventPublisher("inotify"); +``` \ No newline at end of file diff --git a/osquery/events/tests/linux/.process_file_events_tests.md b/osquery/events/tests/linux/.process_file_events_tests.md new file mode 100644 index 00000000000..2566514acf0 --- /dev/null +++ b/osquery/events/tests/linux/.process_file_events_tests.md @@ -0,0 +1,40 @@ + +Unit test file for the `process_file_events` Linux audit-based File Integrity Monitoring (FIM) subscriber. It validates the full pipeline from raw audit netlink messages through event assembly to row emission. + +## Key Components + +- **`AuditdFimTests`** — GTest fixture that resets a `Row` object before each test case +- **`DumpRow` / `DumpRowList`** — Debug helpers that print operation and path fields from emitted rows to stdout +- **`TEST_F(AuditdFimTests, row_emission)`** — Primary test case covering the end-to-end audit event processing pipeline +- **`complete_event_list`** — Large static dataset of 243 raw audit netlink records (types 1300/1302/1307/1320/1323) captured from a real Linux system +- **`included_file_paths`** — Allowlist of file paths used to configure the FIM context filter + +## Usage Example + +```cpp +// The test exercises this pipeline: + +// 1. Parse raw audit replies into AuditEventRecords +AuditEventRecord audit_event_record = {}; +bool ok = AuditdNetlinkParser::ParseAuditReply(reply, audit_event_record); + +// 2. Assemble records into grouped AuditEvents +AuditEventPublisher::ProcessEvents( + event_context, event_record_list, + audit_trace_context, kSyscallsAllowedToFail); + +// 3. Configure FIM path filter +AuditdFimContext fim_context; +fim_context.included_path_list = included_file_paths; + +// 4. Emit osquery rows from matched events +std::vector emitted_row_list; +Status status = ProcessFileEventSubscriber::ProcessEvents( + emitted_row_list, fim_context, event_context->audit_events); +``` + +## Notes + +- Expects exactly **243** parsed records collapsing into **71** audit events +- A row count assertion (`EXPECT_EQ(emitted_row_list.size(), 15U)`) is currently commented out pending a fix +- Test data covers syscalls: `open` (2), `close` (3), `read` (0), `mmap` (9), and `mmap` with `mmap_fd` records (1323) \ No newline at end of file diff --git a/osquery/events/tests/linux/.socket_events.md b/osquery/events/tests/linux/.socket_events.md new file mode 100644 index 00000000000..915c88d904a --- /dev/null +++ b/osquery/events/tests/linux/.socket_events.md @@ -0,0 +1,41 @@ + +Unit test file for the `SocketEventSubscriber` class, validating socket event parsing and processing logic for Linux audit events (connect, bind, and accept syscalls). + +## Key Components + +### Test Fixture +- **`SocketEventsTableTests`** — GTest fixture class wrapping all socket event unit tests. + +### Test Cases + +| Test | Description | +|---|---| +| `test_parse_sock_addr` | Validates `parseSockAddr()` for IPv4, IPv6, and Unix domain socket address hex strings | +| `succeeded_blocking_connect_syscall` | Verifies a successful blocking `connect()` emits a row with `success=1`, `status=succeeded` | +| `failed_blocking_connect_syscall` | Verifies a failed `connect()` (e.g., `EBADF`) is emitted only when `allow_failed_events=true` | +| `succeeded_non_blocking_connect_syscall` | Verifies `EINPROGRESS` connect exit is treated as `success=1`, `status=in_progress` | +| `succeeded_bind_syscall` | Verifies a successful `bind()` emits expected success row | +| `failed_bind_syscall` | Verifies failed `bind()` rows are filtered based on `allow_failed_events` flag | +| `succeeded_accept_syscall` | Verifies `accept`/`accept4` rows are emitted only when `allow_accept_events=true` | +| `failed_accept_syscall` | Verifies failed `accept`/`accept4` rows respect the `allow_failed_events` flag | +| `succeeded_non_blocking_accept_syscall` | Verifies non-blocking accept with `EAGAIN` respects `allow_null_accept_events` flag | + +## Usage Example + +```cpp +// Running a specific test +./osquery_tests --gtest_filter=SocketEventsTableTests.test_parse_sock_addr + +// Key method under test +SocketEventSubscriber::parseSockAddr(hex_string, row, unix_socket); +// Populates row fields: remote_address, remote_port, family, socket + +SocketEventSubscriber::ProcessEvents( + emitted_row_list, + audit_events, + allow_failed_events, + allow_unix_socket_events, + allow_accept_events, + allow_null_accept_events +); +``` \ No newline at end of file diff --git a/osquery/events/tests/linux/.syslog_tests.md b/osquery/events/tests/linux/.syslog_tests.md new file mode 100644 index 00000000000..98e63ec3dd0 --- /dev/null +++ b/osquery/events/tests/linux/.syslog_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the Linux syslog event subsystem in osquery, covering non-blocking file stream behavior, event context population from rsyslog CSV lines, and CSV tokenization edge cases. + +## Key Components + +- **`SyslogTests`** — GTest fixture that creates/removes a temporary working directory per test and exposes a `splitCsv()` helper using `RsyslogCsvSeparator` tokenizer +- **`test_nonblockingfstream`** — Validates `NonBlockingFStream` behavior over a named pipe: line buffering, partial reads, overflow handling, and multiple consecutive lines +- **`test_populate_event_context`** — Validates `SyslogEventPublisher::populateEventContext()` correctly parses rsyslog CSV fields (`datetime`, `host`, `severity`, `facility`, `tag`, `message`) and rejects malformed input with too few or too many fields +- **`test_csv_separator`** — Exercises `RsyslogCsvSeparator` tokenizer across edge cases: empty fields, quoted commas, backslash escapes, escaped quotes, and multi-byte UTF-8 characters + +## Usage Example + +```cpp +// Run a specific test suite +// $ ctest -R SyslogTests + +// Typical fixture lifecycle +class SyslogTests : public testing::Test { + public: + void SetUp() override { + // Creates isolated temp dir under /tmp + test_working_dir_ = fs::temp_directory_path() / + fs::unique_path("osquery.test_working_dir.%%%%.%%%%"); + fs::create_directories(test_working_dir_); + } + + // Helper used in test_csv_separator + std::vector splitCsv(std::string line) { + boost::tokenizer tokenizer(line); + return std::vector(tokenizer.begin(), tokenizer.end()); + } +}; + +// Example assertion validated by test_populate_event_context +ASSERT_EQ("vagrant-ubuntu-trusty-64", ec->fields.at("host")); +ASSERT_EQ("CRON[16538]", ec->fields.at("tag")); +``` + +> **Note:** `NonBlockingFStream` is initialized with a buffer size in bytes (e.g., `NonBlockingFStream nbfs(20)`). Writes exceeding this buffer without a newline trigger a failure status, allowing the publisher to safely handle high-throughput syslog pipes without blocking osquery's event loop. \ No newline at end of file diff --git a/osquery/events/tests/linux/bpf/.bpfeventpublisher.md b/osquery/events/tests/linux/bpf/.bpfeventpublisher.md new file mode 100644 index 00000000000..f22363c8a18 --- /dev/null +++ b/osquery/events/tests/linux/bpf/.bpfeventpublisher.md @@ -0,0 +1,39 @@ + +Unit test file for `BPFEventPublisher`, validating BPF event processing logic for Linux process lifecycle syscalls using a mocked process context factory and `SystemStateTracker`. + +## Key Components + +- **`kBaseBPFEventHeader`** — Baseline BPF event header with default timestamp, PID, UID, GID, and cgroup values used across all tests +- **`kBaseBPFEvent`** — Base BPF event structure initialized from the header, serving as a template for individual test cases +- **`getMockedProcessContextFactory()`** — Helper that returns a `MockedProcessContextFactory` instance for isolated state tracking + +## Test Cases + +| Test | Syscall(s) Covered | Key Assertions | +|---|---|---| +| `processForkEvent_and_processVforkEvent` | `fork`, `vfork` | Failed exits are ignored; successful forks grow `process_map` | +| `processCloneEvent` | `clone` | Missing `clone_flags` fails; `CLONE_THREAD` flag is ignored; new processes added on success | +| `processExecveEvent` | `execve` | Fails with incomplete fields; succeeds with `filename` + `argv` | +| `processExecveatEvent` | `execveat` | Requires all 4 fields (`filename`, `argv`, `flags`, `fd`); validates `AT_FDCWD` handling | +| `processCloseEvent` | `close` | Invalid fd (`-1`) skipped; valid fd reduces `fd_map` to 7 entries | +| `processDupEvent` | `dup` | Missing/invalid `fildes` handled gracefully; validates `fd_map` size | + +## Usage Example + +```cpp +// Pattern used throughout — create isolated tracker with mocked factory +auto state_tracker_ref = + SystemStateTracker::create(getMockedProcessContextFactory()); +auto& state_tracker = + static_cast(*state_tracker_ref.get()); + +// Build a test event from the base template +auto bpf_event = kBaseBPFEvent; +bpf_event.name = "fork"; +bpf_event.header.exit_code = 1001; // child PID +bpf_event.header.process_id = 1000; // parent PID + +auto succeeded = BPFEventPublisher::processForkEvent(state_tracker, bpf_event); +EXPECT_TRUE(succeeded); +EXPECT_EQ(state_tracker.getContextCopy().process_map.size(), 3U); +``` \ No newline at end of file diff --git a/osquery/events/tests/linux/bpf/.bpftestsmain.md b/osquery/events/tests/linux/bpf/.bpftestsmain.md new file mode 100644 index 00000000000..0f92bbbb643 --- /dev/null +++ b/osquery/events/tests/linux/bpf/.bpftestsmain.md @@ -0,0 +1,40 @@ + +Header file defining GTest test fixture classes for osquery's BPF (Berkeley Packet Filter) subsystem testing infrastructure. + +## Key Components + +| Class | Base | Purpose | +|---|---|---| +| `SystemStateTrackerTests` | `testing::Test` | Test fixture for system state tracking logic | +| `BPFEventPublisherTests` | `testing::Test` | Test fixture for BPF event publisher | +| `ProcessContextFactoryTests` | `testing::Test` | Test fixture for process context factory | + +All three classes reside in the `osquery` namespace and provide empty `SetUp()` overrides, serving as base fixtures for individual test case implementations. + +## Usage Example + +```cpp +#include "bpftestsmain.h" + +namespace osquery { + +TEST_F(BPFEventPublisherTests, testEventSubscription) { + // Test BPF event publisher behavior + EXPECT_TRUE(publisher.isRunning()); +} + +TEST_F(SystemStateTrackerTests, testProcessTracking) { + // Test system state tracker state + EXPECT_EQ(tracker.getProcessCount(), 0U); +} + +TEST_F(ProcessContextFactoryTests, testContextCreation) { + // Test process context factory output + auto ctx = factory.create(1234); + EXPECT_NE(ctx, nullptr); +} + +} // namespace osquery +``` + +> **Note:** The `SetUp()` methods are intentionally empty stubs. Concrete test files extend these fixtures with initialization logic suited to each component under test. This header is intended to be included by BPF-related test translation units within the osquery build system. \ No newline at end of file diff --git a/osquery/events/tests/linux/bpf/.mockedfilesystem.md b/osquery/events/tests/linux/bpf/.mockedfilesystem.md new file mode 100644 index 00000000000..8f8daa9374c --- /dev/null +++ b/osquery/events/tests/linux/bpf/.mockedfilesystem.md @@ -0,0 +1,37 @@ + +A test double implementation of the `IFilesystem` interface that provides a mockable filesystem abstraction for unit testing BPF-related Linux event components without requiring real filesystem access. + +## Key Components + +### `MockedFilesystem` class + +Concrete final class inheriting from `IFilesystem`, overriding all virtual methods: + +| Method | Description | +|---|---| +| `open()` | Mocked file open by path with flags | +| `openAt()` | Mocked file open relative to a directory fd | +| `readLinkAt()` | Mocked symlink resolution relative to a directory fd | +| `read()` | Mocked buffered file read up to a max size | +| `enumFiles()` | Mocked directory file enumeration via callback | +| `fileExists()` | Mocked existence check relative to a directory fd | + +## Usage Example + +```cpp +#include "mockedfilesystem.h" + +// Inject MockedFilesystem into a component under test +osquery::MockedFilesystem mockFs; + +// Use with a BPF event processor that depends on IFilesystem +tob::utils::UniqueFd fd; +bool success = mockFs.open(fd, "/proc/self/maps", O_RDONLY); + +// Typical usage: inject via IFilesystem interface pointer +std::unique_ptr fs = + std::make_unique(); +MyBpfComponent component(std::move(fs)); +``` + +> **Note:** Method bodies are defined in the accompanying `.cpp` file where mock behavior (e.g., via GoogleMock `MOCK_METHOD` macros or manual stubs) is implemented. This header only declares the override surface. \ No newline at end of file diff --git a/osquery/events/tests/linux/bpf/.mockedprocesscontextfactory.md b/osquery/events/tests/linux/bpf/.mockedprocesscontextfactory.md new file mode 100644 index 00000000000..24fb05c7b91 --- /dev/null +++ b/osquery/events/tests/linux/bpf/.mockedprocesscontextfactory.md @@ -0,0 +1,42 @@ + +Provides a test double implementation of `IProcessContextFactory` for unit testing BPF process context capture logic in osquery's Linux event system. + +## Key Components + +| Member | Description | +|---|---| +| `MockedProcessContextFactory` | Concrete mock class implementing `IProcessContextFactory` | +| `invocationCount()` | Returns the number of times capture methods have been called | +| `failNextRequest()` | Configures the mock to return failure on the next capture call | +| `captureSingleProcess()` | Mock override; captures context for a single PID | +| `captureAllProcesses()` | Mock override; captures context for all processes | + +## Usage Example + +```c +#include "mockedprocesscontextfactory.h" + +// Basic happy-path test +MockedProcessContextFactory factory; +ProcessContext ctx; + +bool ok = factory.captureSingleProcess(ctx, 1234); +assert(ok); +assert(factory.invocationCount() == 1); + +// Simulate capture failure +factory.failNextRequest(); +bool failed = factory.captureSingleProcess(ctx, 5678); +assert(!failed); + +// Bulk capture +ProcessContextMap process_map; +factory.captureAllProcesses(process_map); +assert(factory.invocationCount() == 3); +``` + +## Notes + +- Both `fail_next_request` and `invocation_count` are `mutable`, allowing state tracking inside `const` method overrides. +- Marked `final` — not intended to be subclassed further. +- Depends on `IProcessContextFactory` from `osquery/events/linux/bpf/`. \ No newline at end of file diff --git a/osquery/events/tests/linux/bpf/.processcontextfactory.md b/osquery/events/tests/linux/bpf/.processcontextfactory.md new file mode 100644 index 00000000000..fb79c2a7e72 --- /dev/null +++ b/osquery/events/tests/linux/bpf/.processcontextfactory.md @@ -0,0 +1,44 @@ + +Unit tests for the `ProcessContextFactory` class, validating process context capture and parsing functionality within osquery's Linux BPF event system. + +## Key Components + +### Test Fixtures +- **`ProcessContextFactoryTests`** — Test fixture class (defined in `bpftestsmain.h`) providing shared setup for all test cases, using a `MockedFilesystem` to simulate `/proc` reads without requiring a live Linux system. + +### Test Cases + +| Test | Description | +|------|-------------| +| `captureSingleProcess` | Validates capturing process context for a single PID, including parent PID, binary path, argv, cwd, and file descriptor map | +| `captureAllProcesses` | Validates bulk process context capture across all processes, asserting the resulting map is populated | +| `getArgvFromCmdlineFile` | Validates parsing of `/proc//cmdline` into an argv vector | +| `getParentPidFromStatFile` | Validates extraction of the parent PID from `/proc//stat` | + +## Usage Example + +```cpp +// Typical integration usage of the tested factory methods +MockedFilesystem mocked_filesystem; + +// Capture a single process by PID +ProcessContext process_context; +bool ok = ProcessContextFactory::captureSingleProcess( + mocked_filesystem, process_context, 1001); + +if (ok) { + // Access parsed fields + pid_t parent = process_context.parent_process_id; // 33616 + std::string binary = process_context.binary_path; // "/usr/bin/zsh" + std::vector args = process_context.argv; // ["zsh", "-i", "-H"] + std::string cwd = process_context.cwd; // "/home/alessandro" +} + +// Capture all running processes +ProcessContextMap all_processes; +ProcessContextFactory::captureAllProcesses(mocked_filesystem, all_processes); +``` + +## Notes +- Invalid PIDs (e.g., `1234567`) return `false` — callers must check the return value before accessing context fields. +- File descriptors in `ProcessContext::fd_map` are validated via the `validateFileDescriptor` utility helper from `utils.h`. \ No newline at end of file diff --git a/osquery/events/tests/linux/bpf/.systemstatetracker.md b/osquery/events/tests/linux/bpf/.systemstatetracker.md new file mode 100644 index 00000000000..362d8a27bdd --- /dev/null +++ b/osquery/events/tests/linux/bpf/.systemstatetracker.md @@ -0,0 +1,48 @@ + +Unit tests for the `SystemStateTracker` BPF-based Linux process event tracking subsystem in osquery, validating process context management, fork/exec event generation, and socket address handling. + +## Key Components + +### Test Fixtures & Constants +- `kBaseBPFEventHeader` — baseline BPF event header used across all test cases (PID 1001, UID/GID 1000) +- `kTestUnixSocketAddress`, `kTestIPv4Address`, `kTestIPv6Address`, `kTestNetlinkSockaddr` — raw byte vectors representing different socket address families for connection-tracking tests + +### Test Cases + +| Test | Purpose | +|---|---| +| `getProcessContext` | Verifies lazy process context creation, caching, and graceful handling of factory failures | +| `create_process` | Validates fork/vfork/clone event generation, parent-child context inheritance (binary path, argv, cwd, fd_map), and process map population | +| `execute_binary_with_absolute_path` | Confirms exec events are emitted, binary path and argv are updated, and close-on-exec FDs are removed | +| `execute_binary_at_cwd` | Validates relative-path binary resolution against the process's current working directory | + +### Dependencies +- `MockedProcessContextFactory` — controls process context creation and simulates failures +- `MockedFilesystem` — intercepts filesystem operations during tests +- `SystemStateTracker` — the system under test, from `osquery/events/linux/bpf/` + +## Usage Example + +```cpp +// Typical pattern used across all tests +SystemStateTracker::Context context; +auto process_context_factory = std::make_unique(); + +// Retrieve or create a process context entry +auto& proc_ctx = SystemStateTracker::getProcessContext( + context, *process_context_factory, /*pid=*/1000); + +// Simulate a fork event (parent=1000, child=1001) +auto bpf_header = kBaseBPFEventHeader; +bpf_header.process_id = 1001; + +bool ok = SystemStateTracker::createProcess( + context, *process_context_factory, + bpf_header, + /*parent_pid=*/1000, + /*child_pid=*/1001); + +// Verify a Fork event was appended +assert(context.event_list.at(0).type == + ISystemStateTracker::Event::Type::Fork); +``` \ No newline at end of file diff --git a/osquery/events/tests/linux/bpf/.utils.md b/osquery/events/tests/linux/bpf/.utils.md new file mode 100644 index 00000000000..e03ac74cf11 --- /dev/null +++ b/osquery/events/tests/linux/bpf/.utils.md @@ -0,0 +1,53 @@ + +Utility header for managing and validating file and socket descriptors within BPF process context tracking on Linux. + +## Key Components + +All functions reside in the `osquery` namespace and operate on either a single `ProcessContext` or a `ProcessContextMap` (keyed by `pid_t`). + +### Set Functions + +| Function | Description | +|---|---| +| `setFileDescriptor` | Registers a file descriptor with its path and `close_on_exec` flag into a process context | +| `setSocketDescriptor` | Registers a socket descriptor with full network metadata (domain, type, protocol, local/remote address and port) | + +Both setters are overloaded to accept either a direct `ProcessContext&` or a `ProcessContextMap&` + `pid_t` pair. + +### Validate Functions + +| Function | Description | +|---|---| +| `validateFileDescriptor` | Returns `true` if the given fd matches the expected path and `close_on_exec` state in the context | +| `validateSocketDescriptor` | Returns `true` if the given fd matches all expected socket properties in the context | + +Same overload pattern as the setters. + +## Usage Example + +```cpp +#include "utils.h" + +osquery::ProcessContext ctx; +osquery::ProcessContextMap ctx_map; + +// Register a file descriptor on a single context +osquery::setFileDescriptor(ctx, 3, false, "/etc/passwd"); + +// Register a socket on a context map entry for pid 1234 +osquery::setSocketDescriptor(ctx_map, 1234, 5, false, + AF_INET, SOCK_STREAM, IPPROTO_TCP, + "127.0.0.1", 8080, + "10.0.0.1", 443); + +// Validate the file descriptor +bool ok = osquery::validateFileDescriptor(ctx, 3, false, "/etc/passwd"); + +// Validate the socket via map +bool valid = osquery::validateSocketDescriptor(ctx_map, 1234, 5, false, + AF_INET, SOCK_STREAM, IPPROTO_TCP, + "127.0.0.1", 8080, + "10.0.0.1", 443); +``` + +> Primarily used in BPF event processing tests to populate and assert process context state without requiring live kernel events. \ No newline at end of file diff --git a/osquery/events/tests/windows/.usn_journal_reader_tests.md b/osquery/events/tests/windows/.usn_journal_reader_tests.md new file mode 100644 index 00000000000..fc0fb31e10f --- /dev/null +++ b/osquery/events/tests/windows/.usn_journal_reader_tests.md @@ -0,0 +1,42 @@ + +Unit tests for the Windows USN (Update Sequence Number) Journal Reader, validating parsing of USN record fields across V2 and V3 record formats using the `USNParsers` utility class. + +## Key Components + +### Test Fixture +- **`UsnJournalReaderTests`** — GTest fixture class wrapping all USN journal parser unit tests. + +### Test Cases + +| Test | Description | +|---|---| +| `test_get_native_file_id` | Validates conversion from `USNFileReferenceNumber` to `FILE_ID_DESCRIPTOR`, checking both `ObjectIdType` and `FileIdType` | +| `test_Get_update_sequence_number` | Verifies `USNParsers::GetUpdateSequenceNumber` correctly extracts USN from V2/V3 records and rejects unsupported versions | +| `test_get_file_ref_number` | Tests `USNParsers::GetFileReferenceNumber` for V2 (64-bit) and V3 (128-bit) reference number extraction | +| `test_get_parent_file_ref` | Tests `USNParsers::GetParentFileReferenceNumber` for both record versions | +| `test_get_timestamp` | Validates FILETIME-to-Unix epoch conversion (epoch anchor: `11644473600` seconds) for V2/V3 | +| `test_get_attributes` | Checks `USNParsers::GetAttributes` correctly reads `FileAttributes` from both record versions | +| `test_get_reason` | Verifies `USNParsers::GetReason` extracts change reason flags from V2/V3 records | +| `test_get_event_type` | Tests `USNParsers::GetEventType` maps reason bits + file attributes to `USNJournalEventRecord::Type` (e.g. `DirectoryCreation`, `FileCreation`) | +| `test_get_event_string` | Validates wide-char filename extraction and UTF-8 conversion for V2/V3 records, including zero-length filenames | + +## Usage Example + +```cpp +// All parsers follow the same pattern: return false for unsupported versions +USN usn = {}; +USN_RECORD_V2 record = {}; +record.MajorVersion = 2U; +record.Usn = 42; + +if (USNParsers::GetUpdateSequenceNumber(usn, (USN_RECORD*)&record)) { + // usn == 42 +} + +// Version 4+ is unsupported and returns false +record.MajorVersion = 4U; +bool ok = USNParsers::GetUpdateSequenceNumber(usn, (USN_RECORD*)&record); +// ok == false +``` + +> All `USNParsers` functions return `false` for record versions other than V2 and V3, providing a consistent version-guard pattern across all field extractors. \ No newline at end of file diff --git a/osquery/events/windows/.evtsubscription.md b/osquery/events/windows/.evtsubscription.md new file mode 100644 index 00000000000..524810ccc31 --- /dev/null +++ b/osquery/events/windows/.evtsubscription.md @@ -0,0 +1,36 @@ + +Defines the `EvtSubscription` class and its Windows Event Log callback dispatcher for subscribing to and collecting Windows Event Log entries within osquery. + +## Key Components + +- **`EvtSubscriptionCallbackDispatcher`** — Windows callback function (`DWORD WINAPI`) invoked by the WinEvt API on new events; dispatches to the appropriate `EvtSubscription` instance via the `context` pointer. +- **`EvtSubscription`** — RAII-style, non-copyable class managing a Windows Event Log channel subscription. + - `Ref` — `std::unique_ptr` alias for ownership semantics. + - `Event` / `EventList` — Type aliases for `std::wstring` and `std::vector` representing collected events. + - `create(Ref&, channel)` — Static factory method; constructs a subscription for the given channel name, returning an osquery `Status`. + - `channel()` — Returns the subscribed channel name. + - `getEvents()` — Drains and returns all buffered events collected since the last call. + - `processEvent(EVT_HANDLE)` — Private handler called by the dispatcher to render and buffer an incoming event. + - `PrivateData` — Pimpl struct hiding WinEvt handles and internal state. + +## Usage Example + +```cpp +#include "evtsubscription.h" + +osquery::EvtSubscription::Ref sub; +auto status = osquery::EvtSubscription::create(sub, "System"); + +if (!status.ok()) { + LOG(ERROR) << "Subscription failed: " << status.getMessage(); + return; +} + +// Later, poll for collected events +auto events = sub->getEvents(); +for (const auto& event : events) { + // process wide-string XML event data +} +``` + +> **Platform note:** This header is Windows-only. It depends on `` and `` and must only be compiled in Windows build targets. \ No newline at end of file diff --git a/osquery/events/windows/.ntfs_event_publisher.md b/osquery/events/windows/.ntfs_event_publisher.md new file mode 100644 index 00000000000..bc37d986214 --- /dev/null +++ b/osquery/events/windows/.ntfs_event_publisher.md @@ -0,0 +1,54 @@ + +Declares the NTFS event publisher for osquery on Windows, which consumes raw USN journal records and emits structured file change events to subscribers. + +## Key Components + +### Structs + +| Name | Purpose | +|---|---| +| `NTFSEventSubscriptionContext` | Subscriber filter: paths/FRNs for write-only or always-include (read+write) monitoring | +| `NTFSEventRecord` | Single normalized NTFS event with path, type, timestamps, FRNs, and drive letter | +| `NTFSEventContext` | Batch event context holding a `vector` emitted to subscribers | +| `VolumeData` | Holds volume/root folder HANDLE and root FRN for a monitored drive | +| `USNJournalReaderInstance` | Tracks a running reader service, its shared context, parent FRN cache, and rename merge map | + +### Type Aliases + +- `NTFSEventPublisherConfiguration` — `unordered_set` of drive letters to monitor +- `ParentFRNCache` — `unordered_map` for resolving parent directory paths + +### Class: `NTFSEventPublisher` + +Extends `EventPublisher`. Core lifecycle methods: + +| Method | Role | +|---|---| +| `setUp()` | One-time initialization | +| `configure()` | Reads drive list from config; called on reload | +| `run()` | Main loop: acquires journal records, resolves paths, fires events | +| `tearDown()` | Releases handles and cleans up resources | + +### Free Function + +- `GetNativeFileIdFromUSNReference()` — Converts a `USNFileReferenceNumber` to a `FILE_ID_DESCRIPTOR` for use with the `OpenFileById` Win32 API + +## Usage Example + +```cpp +// Subscribing to NTFS write events on C:\Windows +auto sc = std::make_shared(); +sc->category = "system_files"; +sc->write_paths.insert("C:\\Windows\\System32"); + +// The publisher resolves FRNs to full paths internally +// and emits NTFSEventContext batches containing NTFSEventRecord entries +// Subscribers inspect each record's type, path, and old_path (renames) +for (const auto& record : context->event_list) { + if (record.type == USNJournalEventRecord::Type::FileWrite) { + LOG(INFO) << "Modified: " << record.path; + } +} +``` + +> **Note:** This publisher is Windows-only and depends on the USN change journal. Partial events (`record.partial == true`) indicate only a filename was resolved, not a full path. \ No newline at end of file diff --git a/osquery/events/windows/.usn_journal_reader.md b/osquery/events/windows/.usn_journal_reader.md new file mode 100644 index 00000000000..144bfdc2d1e --- /dev/null +++ b/osquery/events/windows/.usn_journal_reader.md @@ -0,0 +1,52 @@ + +Windows NTFS USN (Update Sequence Number) Journal reader interface for osquery's file change event monitoring on Windows. + +## Key Components + +### Structs + +| Name | Description | +|------|-------------| +| `USNFileReferenceNumber` | Uniquely identifies a file within a volume; supports 64-bit, 128-bit (`FILE_ID_128`), and GUID formats | +| `USNJournalEventRecord` | Represents a single file system change event, including type, path, timestamps, and attributes | +| `USNJournalReaderContext` | Shared state between `USNJournalReader` service and `FileChangePublisher`; thread-safe via mutex and condition variable | + +### Classes + +- **`USNJournalReader`** — Background service thread (`InternalRunnable`) that asynchronously reads USN journal records from a volume, decompresses compound events, and dispatches them to the publisher. + +### Namespace `USNParsers` + +Utility functions for extracting individual fields from raw `USN_RECORD` pointers: + +- `GetUpdateSequenceNumber`, `GetFileReferenceNumber`, `GetParentFileReferenceNumber` +- `GetTimeStamp`, `GetAttributes`, `GetReason`, `GetEventType`, `GetEventString` + +### Key Type Aliases + +- `USNPerFileLastRecordType` — `std::map` used to deduplicate event types per file reference +- `USNJournalReaderRef` / `USNJournalReaderContextRef` — `shared_ptr` wrappers for shared ownership + +## Usage Example + +```cpp +// Create a shared context for the reader and publisher +auto context = std::make_shared(); +context->drive_letter = 'C'; + +// Instantiate and start the reader service +auto reader = std::make_shared(context); +reader->start(); + +// Consume processed records (publisher side) +{ + std::lock_guard lock(context->processed_records_mutex); + for (const auto& record : context->processed_record_list) { + std::cout << record << "\n"; // uses operator<< overload + } + context->processed_record_list.clear(); +} + +// Signal shutdown +context->terminate.store(true); +``` \ No newline at end of file diff --git a/osquery/events/windows/.windowseventlogparser.md b/osquery/events/windows/.windowseventlogparser.md new file mode 100644 index 00000000000..9c48abcf6f1 --- /dev/null +++ b/osquery/events/windows/.windowseventlogparser.md @@ -0,0 +1,57 @@ + +Windows Event Log parser interface for osquery, providing data structures and parsing utilities to convert raw Windows Event Log XML into structured C++ objects. + +## Key Components + +### `WELEvent` Struct +A plain data container representing a parsed Windows Event Log entry with the following fields: + +| Field | Type | Description | +|---|---|---| +| `osquery_time` | `std::time_t` | Timestamp when osquery captured the event | +| `datetime` | `std::string` | Human-readable event datetime | +| `source` | `std::string` | Event log source channel | +| `provider_name` | `std::string` | Publisher/provider name | +| `provider_guid` | `std::string` | Provider GUID | +| `computer_name` | `std::string` | Originating machine name | +| `event_id` | `int64_t` | Windows Event ID | +| `task_id` | `int64_t` | Task category ID | +| `level` | `int64_t` | Severity level | +| `pid` / `tid` | `int64_t` | Process and thread IDs | +| `keywords` | `std::string` | Event keywords bitmask string | +| `data` | `std::string` | Raw event data payload | + +### `parseWindowsEventLogXML()` +Parses a raw Windows Event Log XML string (`std::wstring`) into a Boost `property_tree` object for further processing. + +### `parseWindowsEventLogPTree()` +Converts a populated Boost `property_tree` into a `WELEvent` struct, extracting all structured fields. + +## Usage Example + +```cpp +#include "windowseventlogparser.h" + +// Step 1: Parse raw XML into a property tree +boost::property_tree::ptree event_object; +std::wstring raw_xml = /* ... raw WEL XML ... */; + +osquery::Status parse_status = + osquery::parseWindowsEventLogXML(event_object, raw_xml); + +if (!parse_status.ok()) { + // handle error +} + +// Step 2: Populate a WELEvent struct from the property tree +osquery::WELEvent wel_event; +osquery::Status map_status = + osquery::parseWindowsEventLogPTree(wel_event, event_object); + +if (map_status.ok()) { + // Access structured fields + std::cout << wel_event.provider_name << " - ID: " << wel_event.event_id; +} +``` + +The two-step design separates XML deserialization from field extraction, allowing either stage to be used independently or tested in isolation. \ No newline at end of file diff --git a/osquery/events/windows/.windowseventlogparserservice.md b/osquery/events/windows/.windowseventlogparserservice.md new file mode 100644 index 00000000000..03bf850b8cd --- /dev/null +++ b/osquery/events/windows/.windowseventlogparserservice.md @@ -0,0 +1,50 @@ + +Parses raw Windows Event Log subscription data into structured property trees for consumption by osquery event publishers. + +## Key Components + +### Class: `WindowsEventLogParserService` + +Extends `InternalRunnable` (osquery dispatcher thread). Runs as a background service that deserializes Windows Event Log entries from active `EvtSubscription` callbacks. + +**Type Aliases** + +| Type | Definition | +|---|---| +| `PropertyTreeList` | `std::vector` | +| `ChannelEventObjects` | `std::unordered_map` | + +**Public Methods** + +| Method | Description | +|---|---| +| `start()` | Begins the parser service loop (called by dispatcher) | +| `stop()` | Signals the service loop to terminate | +| `addEventList(channel, event_list)` | Queues raw `EvtSubscription::EventList` entries for a named channel | +| `getChannelEventObjects()` | Returns parsed property trees grouped by channel name, draining the internal buffer | + +**Implementation Detail** + +Uses the PIMPL idiom (`struct PrivateData` + `std::unique_ptr d_`) to hide internal state (likely mutex, event queue, and parse buffers) from the header. + +## Usage Example + +```cpp +// Instantiated and managed by the osquery dispatcher +auto parser = std::make_shared(); + +// From a subscription callback, queue raw events for a channel +parser->addEventList("Security", std::move(event_list)); + +// From the publisher, drain parsed results +WindowsEventLogParserService::ChannelEventObjects parsed = + parser->getChannelEventObjects(); + +for (const auto& [channel, trees] : parsed) { + for (const auto& tree : trees) { + // Process each boost::property_tree representing one event record + } +} +``` + +> **Note:** `addEventList` and `getChannelEventObjects` are intended to be called from different threads — the subscription callback thread writes, while the publisher thread reads. Internal synchronization is handled inside `PrivateData`. \ No newline at end of file diff --git a/osquery/events/windows/.windowseventlogpublisher.md b/osquery/events/windows/.windowseventlogpublisher.md new file mode 100644 index 00000000000..e9c5eeae94a --- /dev/null +++ b/osquery/events/windows/.windowseventlogpublisher.md @@ -0,0 +1,49 @@ + +Windows Event Log event publisher interface for osquery, providing subscription-based access to Windows Event Log channels with channel filtering and character frequency analysis for event matching. + +## Key Components + +### Structs + +| Name | Description | +|------|-------------| +| `WindowsEventLogSubscriptionContext` | Holds subscriber configuration — a set of channel names to monitor and a character frequency map used for cosine similarity filtering | +| `WindowsEventLogEC` | Event context payload carrying the source channel name and a list of parsed property tree event objects | + +### Type Aliases + +| Alias | Underlying Type | +|-------|----------------| +| `WindowsEventLogECRef` | `std::shared_ptr` | +| `WindowsEventLogSCRef` | `std::shared_ptr` | + +### Class: `WindowsEventLogPublisher` + +Extends `EventPublisher`. + +| Method | Description | +|--------|-------------| +| `shouldFire()` | Determines if an event should be dispatched to a given subscriber based on channel and frequency matching | +| `configure()` | Sets up event log channel subscriptions | +| `tearDown()` | Cleans up active subscriptions and resources | +| `run()` | Main loop — polls/receives Windows Event Log entries and fires events | +| `cosineSimilarity()` | Static utility computing cosine similarity between a buffer's character frequency and a reference frequency map | + +## Usage Example + +```cpp +// Define a subscriber with specific channels to monitor +auto sc = std::make_shared(); +sc->channel_list.insert("Security"); +sc->channel_list.insert("System"); + +// Compute similarity between an event buffer and known frequency baseline +std::vector baseline = loadBaselineFrequencies(); +double score = WindowsEventLogPublisher::cosineSimilarity(rawEventBuffer, baseline); + +if (score > threshold) { + // Process suspicious event +} +``` + +> **Note:** The `PrivateData` pimpl pattern hides platform-specific WinAPI subscription handles, keeping the public interface clean and ABI-stable. \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_concurrent_queue.md b/osquery/events/windows/etw/.etw_concurrent_queue.md new file mode 100644 index 00000000000..5a4236e7fd6 --- /dev/null +++ b/osquery/events/windows/etw/.etw_concurrent_queue.md @@ -0,0 +1,47 @@ + +Thread-safe concurrent queue implementation used within osquery's ETW (Event Tracing for Windows) event processing pipeline to safely pass event data between producer and consumer threads. + +## Key Components + +### `ConcurrentQueue` (class template) +A mutex-protected, non-copyable FIFO queue built on `std::queue` with condition variable signaling. + +| Method | Description | +|---|---| +| `size()` | Returns current queue depth (thread-safe) | +| `empty()` | Returns `true` if the queue has no elements (thread-safe) | +| `pop()` | **Blocking** — waits indefinitely until an element is available, then removes and returns it | +| `popWait(T&, ms)` | **Timed blocking** — waits up to `timeoutMS` (default `300ms`), returns `false` if queue is still empty | +| `push(const T&)` | Enqueues an item and notifies one waiting consumer | + +### Type Aliases + +| Alias | Definition | +|---|---| +| `ConcurrentEventQueue` | `ConcurrentQueue` | +| `ConcurrentEventQueueRef` | `std::shared_ptr` | + +## Usage Example + +```cpp +#include + +// Create a shared ETW event queue +auto queue = std::make_shared(); + +// Producer thread: push an ETW event +queue->push(etwEventDataRef); + +// Consumer thread: blocking pop (waits indefinitely) +osquery::EtwEventDataRef event = queue->pop(); + +// Consumer thread: timed pop with 500ms timeout +osquery::EtwEventDataRef timedEvent; +if (queue->popWait(timedEvent, 500)) { + // process timedEvent +} else { + // no event arrived within 500ms +} +``` + +> **Note:** `ConcurrentQueue` is non-copyable (inherits `boost::noncopyable`) but supports move construction, making it safe to transfer ownership while holding the internal mutex lock. \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_controller.md b/osquery/events/windows/etw/.etw_controller.md new file mode 100644 index 00000000000..a386ff138e5 --- /dev/null +++ b/osquery/events/windows/etw/.etw_controller.md @@ -0,0 +1,49 @@ + +Singleton controller class for managing Windows Event Tracing (ETW) sessions and event processing pipelines within the osquery framework. + +## Key Components + +| Member | Type | Description | +|---|---|---| +| `instance()` | Static method | Returns the singleton `EtwController` instance | +| `addProvider()` | Public method | Registers an ETW provider with pre/post-processor callbacks | +| `dispatchETWEvents()` | Public method | Routes captured ETW events into the processing pipeline | +| `startProcessing()` | Private method | Starts ETW session listening and event processing | +| `initialize()` | Private method | Bootstraps ETW sessions and the post-processing engine | + +**Internal Sessions:** + +| Field | Purpose | +|---|---| +| `etwUserSession_` | Manages userspace ETW provider session | +| `etwKernelSession_` | Manages kernel ETW provider session | +| `etwPostProcessingEngine_` | Runs post-processor callbacks on captured events | +| `concurrentQueue_` | Thread-safe event queue bridging capture and processing | + +## Usage Example + +```cpp +#include + +// Retrieve the singleton instance +EtwController& controller = EtwController::instance(); + +// Build a provider configuration +EtwProviderConfig config; +// ... populate config with provider GUID, pre/post-processor callbacks ... + +// Register the provider — starts sessions if not already running +Status status = controller.addProvider(config); +if (!status.ok()) { + LOG(ERROR) << "Failed to add ETW provider: " << status.getMessage(); +} + +// Events are automatically dispatched internally via: +// controller.dispatchETWEvents(eventDataRef); +``` + +## Notes + +- Inherits `boost::noncopyable` — copy and assignment are explicitly disabled. +- Thread-safety is enforced via `std::mutex` and `std::atomic initialized_`. +- Session names are fixed: `OsqueryUserETWSession`, `OsqueryKernelETWSession`, and `EtwPostProcessingEngine`. \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_data_event.md b/osquery/events/windows/etw/.etw_data_event.md new file mode 100644 index 00000000000..bf19543e369 --- /dev/null +++ b/osquery/events/windows/etw/.etw_data_event.md @@ -0,0 +1,55 @@ + +Defines the core ETW (Event Tracing for Windows) data structures used to represent parsed event payloads within the osquery ETW event pipeline. + +## Key Components + +### Event Payload Structs + +| Struct | Description | +|---|---| +| `EtwProcessStartData` | Process creation event: PID, PPID, image name, cmdline, user/session info, elevation type, sequence numbers, kernel/user data readiness flags | +| `EtwProcessStopData` | Process termination event: PID, PPID, exit code, image name, session, user info | +| `EtwDnsRequestData` | DNS query event: PID, process image path, query name/type/status/results, user info | + +### Variant & Enum Types + +- **`EtwPayloadVariant`** — `std::variant` holding one of the three payload refs (or `std::monostate` for empty/uninitialized state) +- **`EtwEventType`** — Enum tagging an event to its payload type: `Invalid`, `ProcessStart`, `ProcessStop`, `DnsRequest` +- **`kEtwEventTypeStrings`** — Lookup map from `EtwEventType` to human-readable string + +### Event Container Structs + +- **`EtwHeaderData`** — Wraps the raw `EVENT_HEADER`, typed event kind, and both Windows/Unix timestamps +- **`EtwEventData`** — Top-level event container combining `EtwHeaderData` + `EtwPayloadVariant` + +### Type Aliases + +```cpp +using EtwProcStartDataRef = std::shared_ptr; +using EtwProcStopDataRef = std::shared_ptr; +using EtwDnsRequestDataRef = std::shared_ptr; +using EtwEventDataRef = std::shared_ptr; +using EtwEventTypes = std::vector; +``` + +## Usage Example + +```cpp +// Populate a process start event +auto payload = std::make_shared(); +payload->ProcessId = 1234; +payload->ImageName = "notepad.exe"; +payload->KernelDataReady = true; + +// Wrap in top-level event container +auto event = std::make_shared(); +event->Header.Type = EtwEventType::ProcessStart; +event->Header.UnixTimestamp = currentUnixTime(); +event->Payload = payload; + +// Dispatch via variant +if (std::holds_alternative(event->Payload)) { + auto& data = std::get(event->Payload); + // process start handling logic +} +``` \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_kernel_session.md b/osquery/events/windows/etw/.etw_kernel_session.md new file mode 100644 index 00000000000..26982a77877 --- /dev/null +++ b/osquery/events/windows/etw/.etw_kernel_session.md @@ -0,0 +1,42 @@ + +Manages the ETW (Event Tracing for Windows) kernel-space trace session as a dedicated runnable thread within osquery, wrapping the KrabsETW library to listen for and process kernel-level ETW events. + +## Key Components + +### Class: `KernelEtwSessionRunnable` +Extends `InternalRunnable` to run a kernel ETW trace session on a dedicated thread. + +| Member | Type | Description | +|--------|------|-------------| +| `addProvider()` | `Status` | Registers a kernel ETW provider with pre/post-processor callbacks | +| `start()` | `void` | Starts the InternalRunnable thread | +| `stop()` | `void` | Stops the InternalRunnable thread | +| `pause()` / `resume()` | `void` | Suspends or resumes event listening | +| `initKernelTraceSession()` | `void` | Best-effort initialization of the kernel trace session | +| `stopKernelTraceSession()` | `void` | Best-effort teardown of the kernel trace session | +| `kernelTraceSession_` | `shared_ptr` | Underlying KrabsETW kernel trace object | +| `runningProviders_` | `KernelProvidersCollection` | Cache of active kernel-space providers | + +## Usage Example + +```c +// Instantiate and start the kernel ETW session runnable +auto kernelSession = std::make_shared("KernelEtwSession"); + +// Register a kernel provider with its event config +EtwProviderConfig providerConfig = /* ... */; +Status status = kernelSession->addProvider(providerConfig); + +if (!status.ok()) { + LOG(ERROR) << "Failed to add ETW kernel provider: " << status.getMessage(); +} + +// Thread lifecycle is managed via InternalRunnable (Dispatcher) +Dispatcher::addService(kernelSession); +``` + +## Thread Safety + +- All shared state is protected by `std::mutex mutex_` +- Session lifecycle flags use `std::atomic` (`endTraceSession_`, `traceSessionStopped_`) +- A `std::condition_variable` coordinates pause/resume signaling \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_krabs.md b/osquery/events/windows/etw/.etw_krabs.md new file mode 100644 index 00000000000..7706de5f291 --- /dev/null +++ b/osquery/events/windows/etw/.etw_krabs.md @@ -0,0 +1,35 @@ + +C header file that defines ETW (Event Tracing for Windows) session configuration constants used within the osquery framework, wrapping the `krabs.hpp` library for Windows event trace sessions. + +## Key Components + +| Constant | Value | Description | +|---|---|---| +| `kEventTraceBufferSize` | `256` | Buffer size in KB allocated per ETW session | +| `kEventTraceMinimumBuffers` | `12` | Minimum buffer pool reservation | +| `kEventTraceMaximumBuffers` | `48` | Maximum buffer pool allocation | +| `kEventTraceFlushTimer` | `1` | Flush interval in seconds for non-empty trace buffers | +| `kEventTraceLogFileMode` | `EVENT_TRACE_REAL_TIME_MODE \| EVENT_TRACE_INDEPENDENT_SESSION_MODE` | Combined logging mode flags | + +All constants are defined within the `osquery` namespace as `static const ULONG`. + +## Usage Example + +```c +#include "etw_krabs.h" + +// Use constants when configuring an ETW trace session +EVENT_TRACE_PROPERTIES traceProps = {}; +traceProps.BufferSize = osquery::kEventTraceBufferSize; +traceProps.MinimumBuffers = osquery::kEventTraceMinimumBuffers; +traceProps.MaximumBuffers = osquery::kEventTraceMaximumBuffers; +traceProps.FlushTimer = osquery::kEventTraceFlushTimer; +traceProps.LogFileMode = osquery::kEventTraceLogFileMode; +``` + +## Notes + +- Depends on `krabs.hpp` (Microsoft's [krabsetw](https://github.com/microsoft/krabsetw) C++ wrapper for ETW). +- `EVENT_TRACE_INDEPENDENT_SESSION_MODE` ensures trace session stability — `EventWrite` failures in unrelated sessions won't interrupt this session. +- Real-time mode (`EVENT_TRACE_REAL_TIME_MODE`) delivers events directly to consumers without intermediate file storage. +- Windows-only header; not applicable on non-Windows platforms. \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_post_processing_pipeline.md b/osquery/events/windows/etw/.etw_post_processing_pipeline.md new file mode 100644 index 00000000000..2b4f5be28aa --- /dev/null +++ b/osquery/events/windows/etw/.etw_post_processing_pipeline.md @@ -0,0 +1,44 @@ + +Manages the ETW (Event Tracing for Windows) post-processing pipeline by collecting and dispatching ETW events to subscribers via registered callback handlers. + +## Key Components + +### `EtwPostProcessorsRunnable` +A thread-safe runnable class (extending `InternalRunnable`) that processes ETW events from a shared concurrent queue and dispatches them to registered event subscribers. + +| Member | Type | Description | +|---|---|---| +| `addProvider()` | `Status` | Registers a post-processor callback for specific ETW event types | +| `start()` | `void` | Starts the processing thread | +| `stop()` | `void` | Signals the thread to stop | +| `CommonPostProcessing()` | `bool` (private) | Applies common enrichment logic to every ETW event | +| `etwPostProcessors_` | `ProviderProcessors` | Map of ETW event types to their post-processor callbacks | +| `shouldRun_` | `atomic` | Atomic flag controlling thread lifecycle | +| `concurrentQueue_` | `ConcurrentEventQueueRef&` | Reference to the shared concurrent event queue | + +## Usage Example + +```cpp +// Create a shared concurrent queue +ConcurrentEventQueueRef sharedQueue = std::make_shared(); + +// Instantiate the post-processing pipeline runnable +auto postProcessor = std::make_shared( + "etw_post_processor", sharedQueue); + +// Register an ETW provider with its post-processing callback +EtwProviderConfig providerConfig; +// ... configure providerConfig with event types and callback ... +Status status = postProcessor->addProvider(providerConfig); + +if (status.ok()) { + // Dispatcher manages the thread lifecycle + Dispatcher::addService(postProcessor); +} +``` + +## Notes + +- Uses `ProviderProcessors` (`std::map`) to route events to their handlers. +- `CommonPostProcessing()` runs on **every** event before provider-specific callbacks, enabling consistent event enrichment across all ETW sources. +- Thread safety is enforced via `std::atomic` for the run flag and the shared `ConcurrentEventQueueRef`. \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_provider_config.md b/osquery/events/windows/etw/.etw_provider_config.md new file mode 100644 index 00000000000..0374d01d841 --- /dev/null +++ b/osquery/events/windows/etw/.etw_provider_config.md @@ -0,0 +1,51 @@ + +Defines the `EtwProviderConfig` class, which encapsulates the configurable parameters and callback hooks for an ETW (Event Tracing for Windows) provider within osquery's Windows event pipeline. + +## Key Components + +### `EtwKernelProviderType` (enum) +Enumerates supported kernel provider categories: +- `File`, `ImageLoad`, `Network`, `Process`, `Registry`, `ObjectManager`, `Invalid` + +### Type Aliases +| Alias | Underlying Type | +|---|---| +| `EtwProviderName` | `std::string` | +| `EtwBitmask` | `boost::optional` | +| `EtwLevel` | `boost::optional` | +| `EventProviderPreProcessor` | `krabs::c_provider_callback` | +| `EventProviderPostProcessor` | `std::function` | + +### Core Methods +- **`setPreProcessor` / `getPreProcessor`** — Attach a raw `krabs` callback for low-level event pre-processing +- **`setPostProcessor` / `getPostProcessor`** — Attach a `std::function` for structured post-processing of `EtwEventDataRef` +- **`setEventTypes` / `addEventTypeToHandle`** — Define which ETW event types this provider handles +- **`isValid()`** — Validates the configuration before use +- **Optional flag setters/getters** — `AnyBitmask`, `AllBitmask`, `Level`, `TraceFlags`, `Tag` + +### `EtwProviderConfigRef` +Convenience alias: `std::shared_ptr` + +## Usage Example + +```cpp +auto config = std::make_shared(); + +// Configure a user-mode provider +config->setName("Microsoft-Windows-Kernel-Process"); +config->setLevel(osquery::EtwProviderConfig::EtwLevel(4)); // Informational +config->setAnyBitmask(osquery::EtwProviderConfig::EtwBitmask(0x10)); + +// Attach processing callbacks +config->setPreProcessor(myKrabsCallback); +config->setPostProcessor([](const osquery::EtwEventDataRef& event) { + // Handle structured event data +}); + +config->addEventTypeToHandle(osquery::EtwEventType::ProcessStart); + +auto status = config->isValid(); +if (!status.ok()) { + // Handle invalid configuration +} +``` \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_publisher.md b/osquery/events/windows/etw/.etw_publisher.md new file mode 100644 index 00000000000..c9744ec7b2c --- /dev/null +++ b/osquery/events/windows/etw/.etw_publisher.md @@ -0,0 +1,46 @@ + +Windows ETW (Event Tracing for Windows) publisher base class that abstracts event collection and dispatching infrastructure for osquery's ETW event system. + +## Key Components + +### Macros + +| Macro | Purpose | +|---|---| +| `REGISTER_ETW_PUBLISHER` | Registers a class as an ETW event publisher plugin | +| `REGISTER_ETW_SUBSCRIBER` | Registers a class as an ETW event subscriber plugin | +| `getPreProcessorCallback()` | Generates a lambda wrapping `providerPreProcessor` with structured error logging for raw ETW events | + +### Class: `EtwPublisherBase` + +Abstract base class for all ETW publishers. Concrete publishers must inherit from this and implement `providerPostProcessor`. + +| Member | Type | Description | +|---|---|---| +| `EtwEngine()` | Method | Returns reference to the global `EtwController` instance | +| `run()` | Method | Signals dispatcher that no dedicated running thread is required | +| `getPostProcessorCallback()` | Method | Returns a `std::function` wrapping the publisher's post-processor | +| `updateUserInfo()` | Protected | Resolves and caches username from a SID string | +| `updateHardVolumeWithLogicalDrive()` | Protected | Converts device paths (e.g., `\Device\HarddiskVolume3\`) to logical drive paths (e.g., `C:\`) | +| `providerPostProcessor()` | Private/Pure virtual | Must be implemented by concrete ETW publishers to handle parsed event data | + +## Usage Example + +```cpp +class MyEtwPublisher : public EtwPublisherBase { + public: + MyEtwPublisher() : EtwPublisherBase("my_etw_publisher") {} + + private: + void providerPostProcessor(const EtwEventDataRef& data) override { + // Process and dispatch parsed ETW event data + std::string username; + updateUserInfo(data->userSid, username); + + std::string path = data->imagePath; + updateHardVolumeWithLogicalDrive(path); + } +}; + +REGISTER_ETW_PUBLISHER(MyEtwPublisher, "my_etw_publisher"); +``` \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_publisher_dns.md b/osquery/events/windows/etw/.etw_publisher_dns.md new file mode 100644 index 00000000000..99ba0945ddf --- /dev/null +++ b/osquery/events/windows/etw/.etw_publisher_dns.md @@ -0,0 +1,51 @@ + +ETW publisher header defining the DNS event collection pipeline for Windows ETW-based DNS monitoring in osquery. + +## Key Components + +### Structs +- **`EtwDNSEventSubContext`** — Subscription context for DNS ETW events; friend-scoped to `EtwPublisherDNS` +- **`EtwDNSEventContext`** — Event context carrying an `EtwEventDataRef` payload dispatched to subscribers + +### Type Aliases +- **`EtwDNSEventContextRef`** — `shared_ptr` +- **`EtwDNSEventSubContextRef`** — `shared_ptr` + +### Constants +- **`kEtwDNSPublisherName`** — Publisher identifier string `"etw_dns_publisher"` + +### Class: `EtwPublisherDNS` +Inherits from `EtwPublisherBase` and `EventPublisher`. + +| Member | Description | +|---|---| +| `setUp()` | Configures ETW providers, parameters, and processing callbacks | +| `providerPreProcessor()` | Static C-function callback; lightweight entry point for raw ETW events from the OS | +| `providerPostProcessor()` | Post-processing hook for enriching/aggregating event data before dispatch | +| `hardVolumeDrives_` | Maps volume paths for drive resolution | +| `usernamesBySIDs_` | Caches SID-to-username lookups | + +**Supported event versions:** +- DNS Start events: `Version0–3` +- DNS Stop events: `Version0–2` +- Kernel DNS events: `Version3–4` + +## Usage Example + +```cpp +#include + +// Register and start the DNS ETW publisher +auto publisher = std::make_shared(); +publisher->setUp(); + +// Subscribe to DNS events +auto sub = std::make_shared(); +auto callback = [](const EtwDNSEventContextRef& ctx, + const EtwDNSEventSubContextRef&) { + // Access enriched DNS event data + auto& data = ctx->data; + // process data... + return Status::success(); +}; +``` \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_publisher_processes.md b/osquery/events/windows/etw/.etw_publisher_processes.md new file mode 100644 index 00000000000..8578b68d793 --- /dev/null +++ b/osquery/events/windows/etw/.etw_publisher_processes.md @@ -0,0 +1,59 @@ + +ETW publisher header for collecting and dispatching Windows process lifecycle events (start/stop) using the Event Tracing for Windows (ETW) infrastructure within osquery. + +## Key Components + +### Structs / Type Aliases + +| Type | Description | +|---|---| +| `EtwProcEventSubContext` | Subscription context for process event subscribers | +| `EtwProcessEventContext` | Event context carrying `EtwEventDataRef` payload data | +| `EtwProcessEventContextRef` | `shared_ptr` alias for `EtwProcessEventContext` | +| `EtwProcEventSubContextRef` | `shared_ptr` alias for `EtwProcEventSubContext` | + +### Constants + +- `kEtwProcessPublisherName` — Publisher identifier string: `"etw_process_publisher"` + +### Class: `EtwPublisherProcesses` + +Inherits from `EtwPublisherBase` and `EventPublisher`. Listens to kernel and user-mode ETW providers for process start/stop events. + +| Member | Description | +|---|---| +| `setUp()` | Registers ETW providers, configuration, and callbacks | +| `providerPreProcessor()` | Static C-function callback; lightweight ETW event entry point called per OS event | +| `providerPostProcessor()` | Enriches and aggregates event data before dispatching to subscribers | +| `cleanOldAggregationCacheEntries()` | Purges stale cache entries | +| `updateTokenInfo()` | Resolves token type metadata | +| `updateImagePath()` | Resolves process image paths via composite key lookup | +| `processStartAggregationCache_` | Cache keyed by composed `uint64` key for process start correlation | +| `processImageCache_` | Cache mapping composite keys to image path strings | + +**Supported event versions tracked internally:** +- User process start: `Version0–3` +- User process stop: `Version0–2` +- Kernel process events: `Version3–4` + +## Usage Example + +```cpp +// Register and start the ETW process publisher +auto publisher = std::make_shared(); + +Status status = publisher->setUp(); +if (!status.ok()) { + LOG(ERROR) << "ETW process publisher setup failed: " << status.getMessage(); +} + +// Subscribe to process events +auto subscription = EtwProcEventSubContext::create(); +EventFactory::addSubscription(kEtwProcessPublisherName, subscription, + [](const EtwProcessEventContextRef& ctx, + const EtwProcEventSubContextRef& /*sub*/) -> Status { + auto& data = ctx->data; + // Handle process start/stop event data + return Status::success(); + }); +``` \ No newline at end of file diff --git a/osquery/events/windows/etw/.etw_user_session.md b/osquery/events/windows/etw/.etw_user_session.md new file mode 100644 index 00000000000..96784ea8cb2 --- /dev/null +++ b/osquery/events/windows/etw/.etw_user_session.md @@ -0,0 +1,49 @@ + +Manages the ETW (Event Tracing for Windows) user-space trace session for osquery, running on a dedicated thread via the `InternalRunnable` abstraction to listen for and process user-space ETW events. + +## Key Components + +### `UserEtwSessionRunnable` (class) +Inherits from `InternalRunnable`. Owns and manages a `krabs::user_trace` session and a collection of registered ETW providers. + +| Member | Type | Description | +|---|---|---| +| `addProvider()` | `Status` | Registers an ETW user-space provider with pre/post-processor callbacks | +| `start()` | `void` | Starts the dedicated trace session thread | +| `stop()` | `void` | Stops the dedicated trace session thread | +| `pause()` / `resume()` | `void` | Pauses and resumes event listening | +| `initUserTraceSession()` | `void` | Best-effort initialization of the KrabsETW user trace | +| `stopUserTraceSession()` | `void` | Best-effort teardown of the KrabsETW user trace | + +**Internal state:** +- `userTraceSession_` — KrabsETW `krabs::user_trace` instance +- `runningProviders_` — vector of active `krabs::provider<>` references +- `mutex_` — thread-safety guard +- `endTraceSession_` / `traceSessionStopped_` — atomic lifecycle flags +- `condition_` — condition variable for pause/resume signaling + +## Usage Example + +```cpp +#include + +// Create and start a user ETW session runnable +auto session = std::make_shared("MyUserSession"); + +// Register a provider using its configuration +osquery::EtwProviderConfig config; +// ... populate config with provider GUID, callbacks, etc. + +auto status = session->addProvider(config); +if (!status.ok()) { + LOG(ERROR) << "Failed to add ETW provider: " << status.getMessage(); +} + +// Start listening (runs on dedicated internal thread) +session->start(); + +// Stop when done +session->stop(); +``` + +> **Note:** This class is Windows-only and depends on the [KrabsETW](https://github.com/microsoft/krabsetw) library (`krabs::user_trace`). It is `final` and not intended for subclassing. \ No newline at end of file diff --git a/osquery/experimental/events_stream/.events_stream.md b/osquery/experimental/events_stream/.events_stream.md new file mode 100644 index 00000000000..8e0dc05217d --- /dev/null +++ b/osquery/experimental/events_stream/.events_stream.md @@ -0,0 +1,18 @@ + +Declares a single function for dispatching serialized events within the osquery events subsystem. + +## Key Components + +- **`dispatchSerializedEvent(const std::string& event)`** — Accepts a serialized event string and dispatches it through the osquery event pipeline. + +## Usage Example + +```c +#include "events_stream.h" + +// Dispatch a pre-serialized event payload +std::string serialized = serializeMyEvent(data); +osquery::events::dispatchSerializedEvent(serialized); +``` + +> **Note:** The function expects the event to already be serialized before being passed in. Serialization format is determined by the calling subsystem. \ No newline at end of file diff --git a/osquery/experimental/events_stream/.events_stream_registry.md b/osquery/experimental/events_stream/.events_stream_registry.md new file mode 100644 index 00000000000..2f172cfff4c --- /dev/null +++ b/osquery/experimental/events_stream/.events_stream_registry.md @@ -0,0 +1,38 @@ + +Declares the `EventsStreamPlugin` class and supporting utilities for the osquery events stream plugin registry, enabling event data to be dispatched through the plugin framework. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `EventsStreamPlugin` | Class | Plugin subclass that handles event stream dispatch via the osquery plugin registry | +| `EventsStreamPlugin::call` | Method | Processes incoming `PluginRequest` and populates a `PluginResponse`; overrides `Plugin::call` | +| `events::streamRegistryName` | Function | Returns the canonical string name used to register the events stream in the plugin registry | + +## Usage Example + +```c +// Registering a custom events stream plugin +class MyEventsStreamPlugin : public EventsStreamPlugin { + public: + Status call(const PluginRequest& request, + PluginResponse& response) override { + // Handle the incoming event stream request + auto event_data = request.at("data"); + response.push_back({{"status", "ok"}}); + return Status::success(); + } +}; + +// Register using the canonical registry name +REGISTER(MyEventsStreamPlugin, + events::streamRegistryName(), + "my_events_stream"); +``` + +## Dependencies + +- `osquery/core/plugins/plugin.h` — base `Plugin` class and `PluginRequest`/`PluginResponse` types +- `osquery/core/query.h` — core query types shared across the osquery plugin framework +- `osquery/utils/expected/expected.h` — `Expected<>` utility for error-propagating return types +- `osquery/numeric_monitoring/numeric_monitoring.h` — numeric metrics instrumentation support \ No newline at end of file diff --git a/osquery/experimental/experiments/linuxevents/include/osquery/experiments/.linuxevents.md b/osquery/experimental/experiments/linuxevents/include/osquery/experiments/.linuxevents.md new file mode 100644 index 00000000000..91ed1271829 --- /dev/null +++ b/osquery/experimental/experiments/linuxevents/include/osquery/experiments/.linuxevents.md @@ -0,0 +1,15 @@ + +Declares the initialization interface for Linux-specific event monitoring within the osquery framework. + +## Key Components + +- **`initializeLinuxEvents()`** — Function declaration that bootstraps Linux kernel event subscriptions and related event publisher infrastructure at startup. + +## Usage Example + +```c +#include "linuxevents.h" + +// Called during osquery daemon startup to register Linux event publishers +osquery::initializeLinuxEvents(); +``` \ No newline at end of file diff --git a/osquery/experimental/experiments/linuxevents/src/.bpfprocesseventstable.md b/osquery/experimental/experiments/linuxevents/src/.bpfprocesseventstable.md new file mode 100644 index 00000000000..136755aa829 --- /dev/null +++ b/osquery/experimental/experiments/linuxevents/src/.bpfprocesseventstable.md @@ -0,0 +1,43 @@ + +Header defining the `BPFProcessEventsTable` plugin, an osquery table that collects Linux process events captured via BPF (Berkeley Packet Filter) tracing. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `BPFProcessEventsTable` | Class | osquery `TablePlugin` that exposes BPF process events as a queryable table | +| `ErrorCode` | Enum | Factory error states — currently `MemoryAllocationFailure` | +| `Ptr` | Type alias | `std::shared_ptr` for safe ownership | +| `create()` | Static factory | Returns `Expected` — preferred construction path | +| `addEvents()` | Method | Ingests a batch of `ILinuxEvents::EventList` events into the table buffer | +| `name()` | Method | Returns the registered osquery table name | +| `columns()` | Override | Declares the table schema to the osquery runtime | +| `generate()` | Override | Produces `TableRows` in response to SQL queries | + +## Usage Example + +```cpp +#include "bpfprocesseventstable.h" + +// Create the table plugin via factory +auto result = osquery::BPFProcessEventsTable::create(); +if (result.isError()) { + // Handle MemoryAllocationFailure + return; +} + +osquery::BPFProcessEventsTable::Ptr table = result.take(); + +// Push captured BPF events into the table +tob::linuxevents::ILinuxEvents::EventList events = captureEvents(); +table->addEvents(std::move(events)); + +// osquery runtime calls generate() internally when +// a query like `SELECT * FROM bpf_process_events` is executed +``` + +## Notes + +- Non-copyable by design — copy constructor and assignment operator are explicitly deleted +- Uses the **PIMPL idiom** (`PrivateData`) to hide implementation details and reduce compile-time dependencies +- Depends on `tob::linuxevents::ILinuxEvents` from the `linuxevents` library for raw event ingestion \ No newline at end of file diff --git a/osquery/experimental/experiments/linuxevents/src/.linuxevents.md b/osquery/experimental/experiments/linuxevents/src/.linuxevents.md new file mode 100644 index 00000000000..f85de2d85ac --- /dev/null +++ b/osquery/experimental/experiments/linuxevents/src/.linuxevents.md @@ -0,0 +1,37 @@ + +Initializes and registers the Linux BPF process events system within the osquery plugin registry and dispatcher. + +## Key Components + +- **`initializeLinuxEvents()`** — Entry point that orchestrates the full setup sequence for Linux BPF-based process event monitoring: + 1. Creates a `BPFProcessEventsTable` instance via its factory method + 2. Registers the table with osquery's `"table"` registry + 3. Attaches the table to the SQL engine via a `Registry::call` + 4. Spawns a `LinuxEventsService` background service through the `Dispatcher` + +## Usage Example + +```cpp +#include + +int main() { + // Call once during osquery startup to enable BPF process event tracking + osquery::initializeLinuxEvents(); + + // The bpf_process_events_v2 table is now queryable via SQL: + // SELECT * FROM bpf_process_events_v2; +} +``` + +## Error Handling + +If `BPFProcessEventsTable::create()` fails (e.g., missing kernel BPF support), an error is logged and initialization is aborted early — no table is registered and no background service is started. + +## Dependencies + +| Dependency | Role | +|---|---| +| `BPFProcessEventsTable` | Provides the virtual table implementation | +| `LinuxEventsService` | Background dispatcher service for event polling | +| `RegistryFactory` | Manages the osquery plugin/table registry | +| `Registry::call` | Attaches the table to the SQL query engine | \ No newline at end of file diff --git a/osquery/experimental/experiments/linuxevents/src/.linuxeventsservice.md b/osquery/experimental/experiments/linuxevents/src/.linuxeventsservice.md new file mode 100644 index 00000000000..b8afb1daee8 --- /dev/null +++ b/osquery/experimental/experiments/linuxevents/src/.linuxeventsservice.md @@ -0,0 +1,35 @@ + +A background service class that runs as an osquery internal thread, consuming BPF-sourced Linux process events and feeding them into the `BPFProcessEventsTable`. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `LinuxEventsService` | Class | Final class inheriting from `InternalRunnable`; manages the lifecycle of the BPF event processing loop | +| `LinuxEventsService(BPFProcessEventsTable&)` | Constructor | Binds the service to a specific `BPFProcessEventsTable` instance | +| `start()` | Protected method | Entry point called by the dispatcher to begin event consumption | +| `stop()` | Protected method | Signals the service to halt gracefully | +| `PrivateData` | Private struct (PIMPL) | Opaque implementation detail; hides internal state behind a `unique_ptr` | + +## Usage Example + +```cpp +#include "linuxeventsservice.h" +#include "bpfprocesseventstable.h" +#include + +// Instantiate the table that will receive BPF process events +osquery::BPFProcessEventsTable bpfTable; + +// Create and register the service with the osquery dispatcher +auto service = std::make_shared(bpfTable); +osquery::Dispatcher::addService(service); + +// The dispatcher calls start() internally; stop() is invoked on shutdown +``` + +## Notes + +- Uses the **PIMPL idiom** (`PrivateData` + `unique_ptr`) to keep implementation details out of the header and reduce recompilation overhead. +- Marked `final` — not intended to be subclassed. +- Depends on `osquery::InternalRunnable` (from `dispatcher/dispatcher.h`) for thread lifecycle management. \ No newline at end of file diff --git a/osquery/experimental/experiments/loader/include/osquery/experiments/.loader.md b/osquery/experimental/experiments/loader/include/osquery/experiments/.loader.md new file mode 100644 index 00000000000..32f73fd251a --- /dev/null +++ b/osquery/experimental/experiments/loader/include/osquery/experiments/.loader.md @@ -0,0 +1,24 @@ + +Declares the experiment-loading interface for osquery's configuration system, providing a single entry point to initialize any configured experiments at startup. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `loadExperiments()` | `void` function | Reads and activates configured experiments for the current osquery instance | + +## Usage Example + +```c +#include "loader.h" + +int main() { + // Load any configured experiments before starting core services + osquery::loadExperiments(); + + // Continue with osquery initialization... + return 0; +} +``` + +> **Note:** `loadExperiments()` is typically called once during the osquery startup sequence. It has no return value; errors or missing experiment configurations are handled internally. \ No newline at end of file diff --git a/osquery/experimental/experiments/loader/src/.loader.md b/osquery/experimental/experiments/loader/src/.loader.md new file mode 100644 index 00000000000..fd6b9eb21dc --- /dev/null +++ b/osquery/experimental/experiments/loader/src/.loader.md @@ -0,0 +1,34 @@ + +Implements the experiment loader for osquery, enabling opt-in experimental features at runtime via a command-line flag. + +## Key Components + +- **`FLAG(experiment_list)`** — Defines the `--experiment_list` CLI flag accepting a comma-separated list of experiment names to enable. +- **`kExperimentMap`** — Static registry mapping experiment name strings to their initializer function pointers. Currently includes `linuxevents` on Linux builds. +- **`loadExperiments()`** — Entry point that parses the flag, deduplicates entries, and invokes each registered experiment's initializer. + +## Usage Example + +```cpp +// Enable experiments at launch via CLI flag: +// osqueryd --experiment_list=linuxevents + +// Or register a new experiment by adding to kExperimentMap: +static std::unordered_map kExperimentMap = { +#ifdef __linux__ + {"linuxevents", initializeLinuxEvents}, + {"myexperiment", initializeMyExperiment} // Add new experiments here +#endif +}; + +// Call during osquery startup: +osquery::loadExperiments(); +``` + +## Behavior Notes + +- If `--experiment_list` is empty, the loader exits silently with a `VLOG(1)` trace. +- Duplicate experiment names in the flag are silently deduplicated before processing. +- Unknown experiment names log an `ERROR` and are skipped; known ones log `INFO` and call their initializer. +- A prominent warning is logged when any experiment is active: *"This osquery instance is not officially supported"*. +- Experiment availability is **platform-gated** — `linuxevents` only compiles and registers on `__linux__` targets. \ No newline at end of file diff --git a/osquery/extensions/.extensions.md b/osquery/extensions/.extensions.md new file mode 100644 index 00000000000..d9e0be1f5b4 --- /dev/null +++ b/osquery/extensions/.extensions.md @@ -0,0 +1,52 @@ + +Defines the public API for osquery's extension system, providing interfaces for loading, managing, and communicating with external extension processes via UNIX domain sockets. + +## Key Components + +### Flags +| Flag | Type | Description | +|------|------|-------------| +| `extensions_socket` | string | Path to the extension manager socket | +| `extensions_autoload` | string | Search path for autoloading extensions | +| `extensions_timeout` | string | Global timeout for extension operations | +| `disable_extensions` | bool | Toggle to disable the extension subsystem | + +### Structs & Types +- **`ExtensionInfo`** — Metadata container (name, version, sdk_version, min_sdk_version) matching Thrift's `InternalExtensionInfo` +- **`ExtensionList`** — `std::map` mapping route IDs to extension metadata + +### Classes +- **`ExternalSQLPlugin`** — `SQLPlugin` implementation that routes SQL queries through an extension registry, exposing `query()` and `getQueryColumns()` + +### Key Functions + +| Function | Description | +|----------|-------------| +| `getExtensionSocket()` | Derives a socket path for a given `RouteUUID` | +| `loadExtensions()` | Reads autoload flags and returns valid extension paths | +| `startExtension()` | Launches an `ExtensionRunner` thread for an extension process | +| `startExtensionManager()` | Launches the `ExtensionManagerRunner` thread | +| `startExtensionWatcher()` | Starts a watcher thread monitoring a manager connection | +| `callExtension()` | Invokes a plugin exposed by an extension over a socket | +| `getExtensions()` | Retrieves the list of currently active extensions | +| `pingExtension()` | Health-checks an extension or manager socket | +| `applyExtensionDelay()` | Runs a predicate in a retry loop bounded by the extension timeout | +| `initShellSocket()` | Resolves a unique socket path for `osqueryi` shell sessions | + +## Usage Example + +```c +// Autoload extensions at startup +std::set paths = osquery::loadExtensions(); + +// Start the extension manager +osquery::startExtensionManager(); + +// Call a plugin exposed by a registered extension +osquery::PluginRequest req = {{"action", "query"}}; +osquery::PluginResponse resp; +osquery::callExtension(uuid, "table", "my_table", req, resp); + +// Start an extension process and connect to the manager +osquery::startExtension("my_extension", "1.0.0", "2.0.0"); +``` \ No newline at end of file diff --git a/osquery/extensions/.impl_thrift.md b/osquery/extensions/.impl_thrift.md new file mode 100644 index 00000000000..e2b4b7b6e28 --- /dev/null +++ b/osquery/extensions/.impl_thrift.md @@ -0,0 +1,58 @@ + +Implements the Apache Thrift-based IPC layer for osquery's extension system, providing both server-side handlers and client transport setup for cross-platform extension communication (Unix sockets on Linux/macOS, named pipes on Windows). + +## Key Components + +### Flags +- `thrift_verbose` — Enables the Thrift log handler +- `thrift_timeout` — Socket operation timeout (default: 300s) +- `thrift_string_size_limit` — Maximum string size in Thrift messages (0 = unlimited) + +### Classes + +| Class | Description | +|---|---| +| `ThriftServerEventHandler` | Monitors server lifecycle; exposes `waitUntilServerIsListening()` with a 1-minute guard timeout | +| `ExtensionHandler` | Thrift-side implementation of `ExtensionIf`; bridges `ping`, `call`, and `shutdown` to `ExtensionInterface` | +| `ExtensionManagerHandler` | Extends `ExtensionHandler` with manager-specific RPCs: `extensions`, `options`, `registerExtension`, `deregisterExtension`, `query`, `getQueryColumns` | + +### Structs + +| Struct | Description | +|---|---| +| `ImplExtensionRunner` | Holds Thrift server transport, `TThreadedServer`, processor, and event handler | +| `ImplExtensionClient` | Holds Thrift client stubs (`ExtensionClient`, `ExtensionManagerClient`) and socket transport | + +### Platform Abstractions + +```cpp +// Unix/Linux/macOS +using TPlatformServerSocket = TServerSocket; +using TPlatformSocket = TSocket; + +// Windows +using TPlatformServerSocket = TPipeServer; +using TPlatformSocket = TPipe; +``` + +## Usage Example + +```cpp +// Server-side: initialize and start an extension runner +ExtensionRunnerInterface runner; +runner.init(uuid, /*manager=*/false); +runner.connect(); + +// Blocks until the Thrift server's event handler signals readiness +runner.serve(); + +// Client-side: connect to the extension socket +ImplExtensionClient client; +auto socket = std::make_shared(path_); +auto transport = std::make_shared(socket); +auto protocol = std::make_shared(transport); +client.e = std::make_shared(protocol); +transport->open(); +``` + +> **Windows note:** The named pipe server enforces a SDDL security descriptor granting access only to `SYSTEM`, `Built-in Administrators`, and the current user's SID. Extensions must run as Administrator if user SID resolution fails. \ No newline at end of file diff --git a/osquery/extensions/.interface.md b/osquery/extensions/.interface.md new file mode 100644 index 00000000000..5b471e23d4f --- /dev/null +++ b/osquery/extensions/.interface.md @@ -0,0 +1,40 @@ + +Defines the abstract interfaces, data structures, and concrete classes for osquery's Thrift-based extension IPC system, providing both server-side handlers and client-side accessors for extension/extension-manager communication. + +## Key Components + +| Class / Type | Role | +|---|---| +| `ExtensionAPI` | Pure abstract base defining `ping()`, `call()`, and `shutdown()` | +| `ExtensionManagerAPI` | Pure abstract base for manager operations: register, deregister, query, options | +| `ExtensionInterface` | Concrete `ExtensionAPI` server implementation with a transient `RouteUUID` | +| `ExtensionManagerInterface` | Full server implementation combining both APIs; manages route table and extension list | +| `ExtensionRunnerInterface` | PIMPL wrapper around the Thrift server setup (`serve`, `connect`, `init`) | +| `ExtensionRunnerCore` | `InternalRunnable` base for dispatcher threads with start/stop lifecycle | +| `ExtensionRunner` | Dispatcher thread serving a single extension handler | +| `ExtensionManagerRunner` | Dispatcher thread serving the extension manager handler | +| `ExtensionClientCore` | Non-copyable PIMPL base for UNIX socket clients | +| `ExtensionClient` | Client to an individual extension (from the manager side) | +| `ExtensionManagerClient` | Client to the extension manager (from an extension side) | +| `Option` | Holds a flag's current value, default, and type string | +| `ExtensionCode` | Enum mirroring Thrift IDL status codes (`EXT_SUCCESS`, `EXT_FAILED`, `EXT_FATAL`) | + +## Usage Example + +```cpp +// Start an extension manager server +auto manager_runner = std::make_unique( + "/var/osquery/osquery.em"); +Dispatcher::addService(std::move(manager_runner)); + +// Connect a client from an extension back to the manager +ExtensionManagerClient client("/var/osquery/osquery.em"); +RouteUUID uuid; +ExtensionRegistry registry; +Status s = client.registerExtension(info, registry, uuid); + +// Call a plugin on a connected extension from the manager +ExtensionClient ext_client("/var/osquery/ext.sock"); +PluginResponse response; +ext_client.call("table", "users", {{"action", "generate"}}, response); +``` \ No newline at end of file diff --git a/osquery/extensions/tests/.extensions.md b/osquery/extensions/tests/.extensions.md new file mode 100644 index 00000000000..b014d2bd189 --- /dev/null +++ b/osquery/extensions/tests/.extensions.md @@ -0,0 +1,40 @@ + +Provides GTest-based integration tests for the osquery extension system, verifying extension manager lifecycle, socket communication, plugin broadcasting, and registry routing behavior. + +## Key Components + +- **`ExtensionsTest`** — Primary test fixture managing socket path setup/teardown, dispatcher reset, and helper utilities +- **Helper Methods** + - `ping()` — Attempts to connect and ping the extension manager via `ExtensionManagerClient` + - `query()` — Executes SQL through the extension manager socket + - `registeredExtensions()` — Retrieves the list of active extensions + - `socketExistsLocal()` — Polls until a socket path becomes available (up to `kTimeout` ms) +- **`ExtensionPlugin` / `TestExtensionPlugin`** — Minimal plugin implementations used to exercise registry broadcast and alias resolution +- **Test Cases** + - `test_manager_runnable` — Confirms the extension manager starts and creates its socket + - `test_manager_bad_socket` — Verifies failure on an invalid socket path + - `test_manager_bad_require_extension` — Validates `--extensions_require` enforcement + - `test_extension_runnable` — Confirms the manager responds to `ping` after startup + - `test_extension_start` — Tests `startExtension`, UUID assignment, and `versionAtLeast` comparisons + - `test_extension_broadcast` — Validates plugin registration, alias resolution, and `callExtension` round-trips + +## Usage Example + +```cpp +// Run a specific test case +// From the osquery build directory: +``` + +```bash +./osquery_tests --gtest_filter="ExtensionsTest.test_extension_broadcast" +``` + +```cpp +// Core flow exercised by the tests: +startExtensionManager(socket_path); // Start EM on socket +startExtension(socket_path, "test", "0.1", // Register extension + "0.0.0", "9.9.9"); +callExtension(ext_socket, // Call plugin via extension + "extension_test", "test_alias", + {{"key", "value"}}, response); +``` \ No newline at end of file diff --git a/osquery/extensions/thrift/gen/.Extension.md b/osquery/extensions/thrift/gen/.Extension.md new file mode 100644 index 00000000000..9af9061d1fe --- /dev/null +++ b/osquery/extensions/thrift/gen/.Extension.md @@ -0,0 +1,51 @@ + +Autogenerated Apache Thrift RPC interface header for the osquery Extension service, defining the client, processor, and service interface for inter-process communication between osquery core and its extensions. + +## Key Components + +| Class | Description | +|---|---| +| `ExtensionIf` | Pure virtual service interface defining the three RPC methods all extensions must implement | +| `ExtensionNull` | No-op implementation of `ExtensionIf`; useful as a stub or placeholder | +| `ExtensionIfFactory` / `ExtensionIfSingletonFactory` | Factory abstractions for creating/releasing handler instances per connection | +| `ExtensionClient` | Thrift-generated RPC client for calling remote extension methods over a protocol transport | +| `ExtensionProcessor` | Server-side dispatcher that routes incoming RPC calls to the appropriate handler method | +| Arg/Result structs | Serialization containers (`Extension_ping_args`, `Extension_call_args`, etc.) for each RPC method's parameters and return values | + +## RPC Methods + +- **`ping`** — Health check; returns an `ExtensionStatus` +- **`call`** — Invokes a plugin by registry name and item key, passing an `ExtensionPluginRequest`; returns an `ExtensionResponse` +- **`shutdown`** — Signals the extension to terminate + +## Usage Example + +```cpp +#include "Extension.h" +#include +#include + +// Connect to a running extension over a socket +auto socket = std::make_shared("localhost", 9090); +auto transport = std::make_shared(socket); +auto protocol = std::make_shared(transport); + +transport->open(); + +osquery::extensions::ExtensionClient client(protocol); + +// Health check +osquery::extensions::ExtensionStatus status; +client.ping(status); + +// Invoke a plugin +osquery::extensions::ExtensionResponse response; +osquery::extensions::ExtensionPluginRequest req; +client.call(response, "table", "processes", req); + +// Shut down the extension +client.shutdown(); +transport->close(); +``` + +> **Note:** This file is autogenerated by Thrift Compiler 0.13.0. Do not edit manually — regenerate from the `.thrift` IDL definition instead. \ No newline at end of file diff --git a/osquery/extensions/thrift/gen/.ExtensionManager.md b/osquery/extensions/thrift/gen/.ExtensionManager.md new file mode 100644 index 00000000000..39e9729e8f1 --- /dev/null +++ b/osquery/extensions/thrift/gen/.ExtensionManager.md @@ -0,0 +1,52 @@ + +Autogenerated Thrift RPC header (Thrift Compiler 0.13.0) defining the `ExtensionManager` service interface for osquery's inter-process extension communication within the `osquery::extensions` namespace. + +## Key Components + +| Class | Description | +|---|---| +| `ExtensionManagerIf` | Pure virtual interface extending `ExtensionIf`; defines all RPC method contracts | +| `ExtensionManagerIfFactory` | Abstract factory for creating/releasing `ExtensionManagerIf` handler instances | +| `ExtensionManagerIfSingletonFactory` | Concrete factory wrapping a shared singleton handler instance | +| `ExtensionManagerNull` | No-op implementation of all interface methods; useful for testing/stubbing | +| `ExtensionManager_*_args / _pargs` | Thrift-generated argument containers for each RPC call | +| `ExtensionManager_*_result / _presult` | Thrift-generated result containers with `__isset` tracking for optional fields | + +### RPC Methods (via `ExtensionManagerIf`) + +| Method | Description | +|---|---| +| `extensions()` | Returns list of registered extensions (`InternalExtensionList`) | +| `options()` | Returns available options (`InternalOptionList`) | +| `registerExtension()` | Registers an extension with its info and route registry | +| `deregisterExtension()` | Removes an extension by UUID | +| `query()` | Executes a SQL query via an extension | +| `getQueryColumns()` | Returns column metadata for a SQL query | + +## Usage Example + +```cpp +#include "ExtensionManager.h" + +// Implement the interface +class MyExtensionManager : virtual public osquery::extensions::ExtensionManagerIf { +public: + void extensions(osquery::extensions::InternalExtensionList& _return) override { + // populate _return with registered extensions + } + + void query(osquery::extensions::ExtensionResponse& _return, + const std::string& sql) override { + // execute SQL and populate _return + } + + // ... implement remaining pure virtual methods +}; + +// Wrap in singleton factory for Thrift server +auto handler = std::make_shared(); +auto factory = std::make_shared< + osquery::extensions::ExtensionManagerIfSingletonFactory>(handler); +``` + +> **Note:** This file is autogenerated — do not edit manually. Regenerate using Thrift Compiler 0.13.0 from the corresponding `.thrift` service definition. \ No newline at end of file diff --git a/osquery/extensions/thrift/gen/.ExtensionManager_server.skeleton.md b/osquery/extensions/thrift/gen/.ExtensionManager_server.skeleton.md new file mode 100644 index 00000000000..6a2e3bb42e1 --- /dev/null +++ b/osquery/extensions/thrift/gen/.ExtensionManager_server.skeleton.md @@ -0,0 +1,36 @@ + +Autogenerated Apache Thrift server skeleton for the `ExtensionManager` service, demonstrating the minimal boilerplate required to stand up an osquery extension manager server over a binary protocol. + +## Key Components + +- **`ExtensionManagerHandler`** — Stub implementation of `ExtensionManagerIf` with placeholder methods to be filled in: + - `extensions()` — Returns the list of registered extensions + - `options()` — Returns current internal option list + - `registerExtension()` — Registers a new extension with its route/registry info + - `deregisterExtension()` — Removes an extension by its route UUID + - `query()` — Executes a SQL query through the extension interface + - `getQueryColumns()` — Returns column metadata for a given SQL statement + +- **`main()`** — Wires up and starts a `TSimpleServer` on port `9090` using `TBinaryProtocol` and `TBufferedTransport` + +## Usage Example + +Copy this file and implement the handler methods before building: + +```cpp +// 1. Copy the skeleton (never edit it directly) +// cp ExtensionManager_server.skeleton.cpp MyExtensionManagerServer.cpp + +// 2. Implement a handler method, e.g.: +void query(ExtensionResponse& _return, const std::string& sql) { + // Execute sql and populate _return + _return.status.code = 0; + _return.status.message = "OK"; + // ... populate _return.response +} + +// 3. Build and run — server listens on port 9090 by default +// The TSimpleServer handles one client at a time (single-threaded) +``` + +> **Note:** `TSimpleServer` is single-threaded and suitable for development only. For production use, replace it with `TThreadedServer` or `TThreadPoolServer`. \ No newline at end of file diff --git a/osquery/extensions/thrift/gen/.Extension_server.skeleton.md b/osquery/extensions/thrift/gen/.Extension_server.skeleton.md new file mode 100644 index 00000000000..5ae5afd42bd --- /dev/null +++ b/osquery/extensions/thrift/gen/.Extension_server.skeleton.md @@ -0,0 +1,48 @@ + +Autogenerated skeleton demonstrating how to implement an Apache Thrift-based osquery Extension server using the `ExtensionIf` interface. + +## Key Components + +- **`ExtensionHandler`** — Concrete implementation of `ExtensionIf` with three required methods: + - `ping()` — Health check endpoint returning an `ExtensionStatus` + - `call()` — Dispatches plugin calls by registry name, item, and request payload + - `shutdown()` — Handles graceful extension teardown +- **`main()`** — Wires up the Thrift server stack (transport → protocol → processor) and begins serving on port `9090` + +## Usage Example + +Copy this file to a new name before editing to avoid overwriting: + +```bash +cp Extension_server.skeleton.cpp MyExtension_server.cpp +``` + +Then implement the handler methods: + +```cpp +class ExtensionHandler : virtual public ExtensionIf { + public: + ExtensionHandler() { + // Initialize resources, logging, etc. + } + + void ping(ExtensionStatus& _return) { + _return.code = 0; + _return.message = "OK"; + } + + void call(ExtensionResponse& _return, + const std::string& registry, + const std::string& item, + const ExtensionPluginRequest& request) { + // Route request to appropriate plugin logic + _return.status.code = 0; + } + + void shutdown() { + // Clean up resources + } +}; +``` + +> **Note:** The default port is `9090`. Update `port` in `main()` to match the socket path negotiated with the osquery core process in production deployments. \ No newline at end of file diff --git a/osquery/extensions/thrift/gen/.osquery_constants.md b/osquery/extensions/thrift/gen/.osquery_constants.md new file mode 100644 index 00000000000..2d1f433767c --- /dev/null +++ b/osquery/extensions/thrift/gen/.osquery_constants.md @@ -0,0 +1,21 @@ + +Auto-generated Thrift constants header for the `osquery` extensions namespace. Declares the `osqueryConstants` class and its global instance used across the osquery Thrift service interface. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `osqueryConstants` | Class | Thrift-generated constants container for the osquery extensions namespace | +| `g_osquery_constants` | `extern const osqueryConstants` | Global singleton instance of the constants class | + +## Usage Example + +```cpp +#include "osquery_constants.h" + +// Access the global constants instance +const osquery::extensions::osqueryConstants& constants = + osquery::extensions::g_osquery_constants; +``` + +> **⚠️ Do not manually edit this file.** It is auto-generated by the Thrift Compiler (v0.13.0). Any changes will be overwritten on regeneration. Modify the upstream `.thrift` schema definition instead. \ No newline at end of file diff --git a/osquery/extensions/thrift/gen/.osquery_types.md b/osquery/extensions/thrift/gen/.osquery_types.md new file mode 100644 index 00000000000..117febb4c4b --- /dev/null +++ b/osquery/extensions/thrift/gen/.osquery_types.md @@ -0,0 +1,51 @@ + +Autogenerated Thrift header defining the core data types and structures used for osquery extension IPC communication within the `osquery::extensions` namespace. + +## Key Components + +### Enum +- **`ExtensionCode`** — Status codes: `EXT_SUCCESS` (0), `EXT_FAILED` (1), `EXT_FATAL` (2) + +### Type Aliases +| Typedef | Underlying Type | +|---|---| +| `ExtensionPluginRequest` | `map` | +| `ExtensionPluginResponse` | `vector>` | +| `ExtensionRouteUUID` | `int64_t` | +| `ExtensionRouteTable` | `map` | +| `ExtensionRegistry` | `map` | +| `InternalOptionList` | `map` | +| `InternalExtensionList` | `map` | + +### Classes +- **`InternalOptionInfo`** — Holds a config option's `value`, `default_value`, and `type` strings +- **`InternalExtensionInfo`** — Describes a registered extension: `name`, `version`, `sdk_version`, `min_sdk_version` +- **`ExtensionStatus`** — Response status with integer `code`, `message`, and `uuid` +- **`ExtensionResponse`** — Combines an `ExtensionStatus` with an `ExtensionPluginResponse` payload +- **`ExtensionException`** — Throwable Thrift exception carrying `code`, `message`, and `uuid` + +All classes inherit from `apache::thrift::TBase` and support Thrift protocol `read()`/`write()`, equality operators, and `std::ostream` output. + +## Usage Example + +```cpp +#include "osquery_types.h" + +osquery::extensions::ExtensionStatus status; +status.__set_code(osquery::extensions::ExtensionCode::EXT_SUCCESS); +status.__set_message("OK"); +status.__set_uuid(42); + +osquery::extensions::ExtensionResponse resp; +resp.__set_status(status); + +// Serialize over a Thrift protocol +resp.write(protocol); + +// Check result +if (resp.status.code == osquery::extensions::ExtensionCode::EXT_SUCCESS) { + // handle success +} +``` + +> **Note:** This file is autogenerated by the Thrift 0.13.0 compiler. Do not edit manually — regenerate from the `.thrift` IDL source instead. \ No newline at end of file diff --git a/osquery/filesystem/.file_compression.md b/osquery/filesystem/.file_compression.md new file mode 100644 index 00000000000..2cde700e339 --- /dev/null +++ b/osquery/filesystem/.file_compression.md @@ -0,0 +1,41 @@ + +Provides file compression, decompression, and archiving utilities for osquery using Zstandard (ZSTD) streaming compression and libarchive TAR creation. + +## Key Components + +| Function | Signature | Description | +|----------|-----------|-------------| +| `compress` | `(path& in, path& out) → Status` | Compresses a file using ZSTD streaming compression (level 1) | +| `decompress` | `(path& in, path& out) → Status` | Decompresses a ZSTD-compressed file via streaming | +| `archive` | `(set& paths, path& out, size_t block_size) → Status` | Creates a PAX-restricted TAR archive from a set of file paths | + +All functions return an `osquery::Status` indicating success or a descriptive failure message. + +## Usage Example + +```cpp +#include + +// Compress a single file +auto status = osquery::compress("/var/log/osquery.log", "/tmp/osquery.log.zst"); +if (!status.ok()) { + LOG(ERROR) << status.getMessage(); +} + +// Decompress it back +status = osquery::decompress("/tmp/osquery.log.zst", "/tmp/osquery_restored.log"); + +// Archive multiple files into a single TAR +std::set files = { + "/var/log/osquery.log", + "/etc/osquery/osquery.conf" +}; +status = osquery::archive(files, "/tmp/osquery_bundle.tar", 4096); +``` + +## Notes + +- **ZSTD streaming** is used for both compress/decompress to handle large files without loading them fully into memory. +- A file-change guard (`readSoFar > inFileSize`) detects if a file is modified mid-operation. +- `archive()` uses `pax_restricted` format for broad TAR compatibility; files are written with `0644` permissions. +- On Windows, `LIBARCHIVE_STATIC` must be defined before including `` (already handled internally). \ No newline at end of file diff --git a/osquery/filesystem/.fileops.md b/osquery/filesystem/.fileops.md new file mode 100644 index 00000000000..1deaad06254 --- /dev/null +++ b/osquery/filesystem/.fileops.md @@ -0,0 +1,68 @@ + +Cross-platform file operations abstraction layer for osquery, providing unified POSIX-compatible file I/O primitives across Windows and Unix systems. + +## Key Components + +### Types & Constants +- `PlatformHandle` — `HANDLE` (Windows) or `int` (POSIX) file descriptor alias +- `PlatformTimeType` — `FILETIME` (Windows) or `struct timeval` (POSIX) +- `kInvalidHandle` — sentinel value for an uninitialized/invalid handle +- `PlatformTime` — wraps access/modification time pair +- `WINDOWS_STAT` — Windows-specific stat structure with extended metadata (ACL, version info, etc.) + +### File Access Flags +| Macro | Description | +|---|---| +| `PF_READ` / `PF_WRITE` | Read/write mode bits | +| `PF_CREATE_NEW` | Fail if file exists | +| `PF_CREATE_ALWAYS` | Truncate or create | +| `PF_OPEN_EXISTING` | Fail if not found | +| `PF_OPEN_ALWAYS` | Open or create | +| `PF_NONBLOCK` / `PF_APPEND` | Non-blocking / append mode | + +### `PlatformFile` Class +Non-copyable RAII file wrapper with cross-platform semantics: +- `read()` / `write()` — byte-level I/O +- `seek()` — position control via `SeekMode` enum (`PF_SEEK_BEGIN`, `PF_SEEK_CURRENT`, `PF_SEEK_END`) +- `isOwnerRoot()`, `isOwnerCurrentUser()`, `isExecutable()`, `hasSafePermissions()` — permission checks +- `hasPendingIo()` — async I/O state (Windows async emulation via `AsyncEvent`/`OVERLAPPED`) +- `getFileTimes()` — retrieve file timestamps +- `size()` — file size inspection + +### Free Functions +- `platformChmod()` — POSIX `chmod` equivalent with Windows ACL approximation +- `platformGlob()` — glob pattern expansion (POSIX/Windows) +- `platformAccess()` — permission check wrapper (`access()`/`_access()`) +- `platformIsTmpDir()` — temporary directory detection +- `platformIsFileAccessible()` — existence and accessibility check +- `getHomeDirectory()` — home directory resolution via env vars or OS APIs +- `windowsShortPathToLongPath()` — 8.3 path expansion (Windows only) +- `windowsGetVersionInfo()` — PE version metadata (Windows only) + +## Usage Example + +```c +#include + +// Open an existing file for reading +osquery::PlatformFile pf("/etc/osquery/osquery.conf", + PF_OPEN_EXISTING | PF_READ); + +if (!pf.isValid()) { + // handle error +} + +char buf[1024]; +ssize_t bytes = pf.read(buf, sizeof(buf)); + +// Check ownership before trusting file +if (pf.isOwnerRoot().ok() && pf.hasSafePermissions().ok()) { + // safe to process contents +} + +// Glob for config files +auto files = osquery::platformGlob("/etc/osquery/*.conf"); +for (const auto& f : files) { + // process each matched path +} +``` \ No newline at end of file diff --git a/osquery/filesystem/.filesystem.md b/osquery/filesystem/.filesystem.md new file mode 100644 index 00000000000..0867794ccee --- /dev/null +++ b/osquery/filesystem/.filesystem.md @@ -0,0 +1,71 @@ + +Filesystem utility header for osquery providing cross-platform file I/O, directory traversal, glob pattern resolution, and Linux-specific `/proc` filesystem helpers. + +## Key Components + +### Enums & Constants +- **`GlobLimits`** — Bitmask enum controlling glob traversal: `GLOB_FILES`, `GLOB_FOLDERS`, `GLOB_ALL`, `GLOB_NO_CANON` +- **`kSQLGlobWildcard`** / **`kSQLGlobRecursive`** — SQL `%` wildcard constants used in path pattern matching + +### File I/O +- **`readFile()`** — Reads file contents into a string or streams chunks via callback; respects `read_max` flag and handles special files (FIFOs, named pipes) safely +- **`writeTextFile()`** — Writes text to disk with configurable permissions and open mode flags +- **`isWritable()` / `isReadable()`** — Permission checks with optional effective UID support + +### Path Utilities +- **`pathExists()`** — Checks disk presence; returns `-1` (no input), `0` (not found), or `1` (found) +- **`isDirectory()`** — Tests if a path is a directory +- **`removePath()` / `movePath()`** — Delete or relocate files and directories +- **`createDirectory()`** — Creates directories with optional recursive and ignore-existing flags +- **`safePermissions()`** — Validates file ownership is root or current process UID (not world-writable) + +### Glob & Listing +- **`resolveFilePattern()`** — Expands SQL-style glob patterns into matching path lists +- **`replaceGlobWildcards()`** — Converts SQL `%` wildcards to filesystem `*` with optional path canonicalization +- **`listFilesInDirectory()` / `listDirectoriesInDirectory()`** — Enumerate directory contents, optionally recursive + +### System Helpers +- **`getHomeDirectories()`** — Returns all user home directories on the system +- **`osqueryHomeDirectory()`** — Returns osquery's protected local storage path +- **`parseJSON()`** — Parses a JSON file from disk into a Boost property tree +- **`lsperms()`** — Converts numeric permission mode to `ls`-style string + +### Linux-Only (`#ifdef __linux__`) +- **`procProcesses()`** — Lists running PIDs from `/proc` +- **`procDescriptors()`** — Enumerates open file descriptors for a given PID +- **`procReadDescriptor()`** — Resolves a descriptor's virtual symlink path + +## Usage Example + +```c +#include + +// Read a file +std::string content; +auto status = osquery::readFile("/etc/os-release", content); +if (!status.ok()) { + LOG(ERROR) << status.getMessage(); +} + +// Stream large file in chunks +osquery::readFile("/var/log/syslog", [](std::string_view chunk) { + processChunk(chunk); +}); + +// Resolve glob pattern (SQL-style wildcards) +std::vector matches; +osquery::resolveFilePattern("/home/%/.ssh/%.pub", matches, osquery::GLOB_FILES); + +// Write with explicit permissions +osquery::writeTextFile("/tmp/report.txt", "data", 0644); + +// Linux: iterate /proc +#ifdef __linux__ +std::set pids; +osquery::procProcesses(pids); +for (const auto& pid : pids) { + std::map fds; + osquery::procDescriptors(pid, fds); +} +#endif +``` \ No newline at end of file diff --git a/osquery/filesystem/.mock_file_structure.md b/osquery/filesystem/.mock_file_structure.md new file mode 100644 index 00000000000..0ecfcc8c2c9 --- /dev/null +++ b/osquery/filesystem/.mock_file_structure.md @@ -0,0 +1,34 @@ + +Provides a test utility for generating a temporary mock directory structure used in osquery filesystem-related unit tests. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kTopLevelMockFolderName` | `extern const std::string` | Name constant for the top-level mock folder | +| `createMockFileStructure()` | `boost::filesystem::path` | Creates a small temporary directory tree for testing | + +## Usage Example + +```c +#include "mock_file_structure.h" +#include + +namespace osquery { + +void myFilesystemTest() { + // Create a temporary mock directory tree + boost::filesystem::path mockRoot = createMockFileStructure(); + + // Use mockRoot in test assertions + // e.g., verify files exist under kTopLevelMockFolderName + boost::filesystem::path topLevel = mockRoot / kTopLevelMockFolderName; + + // Clean up after test + boost::filesystem::remove_all(mockRoot); +} + +} // namespace osquery +``` + +> **Note:** This header is intended exclusively for test environments. Do not use `createMockFileStructure()` in production code paths, as it generates temporary filesystem artifacts on the host. \ No newline at end of file diff --git a/osquery/filesystem/darwin/.bsd_file_flags.md b/osquery/filesystem/darwin/.bsd_file_flags.md new file mode 100644 index 00000000000..94e3d778f97 --- /dev/null +++ b/osquery/filesystem/darwin/.bsd_file_flags.md @@ -0,0 +1,48 @@ + +Parses and describes BSD file flags (from `st_flags` in `struct stat`) into human-readable string labels, mapping known flag bits to their named constants and representing any unrecognized bits as a hex value. + +## Key Components + +### Internal Map — `kBsdFlagMap` +A `std::map` containing all supported BSD file flags as documented in `man 2 chflags` and the Darwin XNU kernel headers. Covers both user-settable (`UF_*`) and superuser-only (`SF_*`) flags. + +| Flag | Hex Value | +|---|---| +| `UF_NODUMP` | `0x00000001` | +| `UF_IMMUTABLE` | `0x00000002` | +| `UF_APPEND` | `0x00000004` | +| `SF_IMMUTABLE` | `0x00020000` | +| `SF_APPEND` | `0x00040000` | +| *(and others)* | | + +### Internal Helper — `getBsdFlagMask()` +Computes a bitmask of all known flags by OR-ing every key in `kBsdFlagMap`. Used to isolate undocumented bits. + +### Public Function — `describeBSDFileFlags()` +```cpp +Status describeBSDFileFlags(std::string& output, std::uint32_t st_flags); +``` +- Iterates `kBsdFlagMap` and collects labels for each active bit in `st_flags` +- Any bits not covered by the known flag map are appended as a zero-padded hex string (e.g., `0x00000100`) +- Returns `Status::success()` if all flags are documented, or `Status::failure("undocumented flags present")` if unknown bits are detected + +## Usage Example + +```cpp +#include + +struct stat file_stat; +stat("/path/to/file", &file_stat); + +std::string flag_description; +auto status = osquery::describeBSDFileFlags(flag_description, file_stat.st_flags); + +if (status.ok()) { + // e.g., "UF_IMMUTABLE, HIDDEN" + std::cout << "Flags: " << flag_description << "\n"; +} else { + // e.g., "UF_IMMUTABLE, 0x00000100" + std::cerr << "Warning: " << status.getMessage() << "\n"; + std::cout << "Flags: " << flag_description << "\n"; +} +``` \ No newline at end of file diff --git a/osquery/filesystem/darwin/benchmarks/.plist_benchmarks.md b/osquery/filesystem/darwin/benchmarks/.plist_benchmarks.md new file mode 100644 index 00000000000..cf6f6b5c3ef --- /dev/null +++ b/osquery/filesystem/darwin/benchmarks/.plist_benchmarks.md @@ -0,0 +1,38 @@ + +Benchmarks for osquery's plist parsing utilities, measuring performance of both in-memory content parsing and direct file parsing using Google Benchmark. + +## Key Components + +| Function | Description | +|---|---| +| `PLIST_parse_content` | Benchmarks `parsePlistContent()` by pre-loading a test `.plist` file into a string buffer, then repeatedly parsing the in-memory content | +| `PLIST_parse_file` | Benchmarks `parsePlist()` by repeatedly parsing a `.plist` file directly from disk on each iteration | + +## Usage Example + +```cpp +// Run all plist benchmarks +// From build directory: +// ./plist_benchmarks + +// Run a specific benchmark with filtering +// ./plist_benchmarks --benchmark_filter=PLIST_parse_content + +// Example of what each benchmark measures internally: +std::string content; +readFile(kTestDataPath + "test.plist", content); + +// Content benchmark - file I/O excluded from timing loop +pt::ptree tree; +auto status = parsePlistContent(content, tree); + +// File benchmark - full disk I/O included in timing loop +pt::ptree tree; +auto status = parsePlist(kTestDataPath + "test.plist", tree); +``` + +## Notes + +- Uses `kTestDataPath + "test.plist"` as the test fixture — ensure the test data path is correctly configured before running +- The key distinction between the two benchmarks is I/O isolation: `PLIST_parse_content` isolates pure parsing performance by buffering the file before the loop, while `PLIST_parse_file` includes disk read overhead in every iteration +- Depends on `boost::property_tree` for the parsed output tree and `osquery::readFile` / `parsePlistContent` / `parsePlist` from the osquery filesystem utilities \ No newline at end of file diff --git a/osquery/filesystem/linux/.mem.md b/osquery/filesystem/linux/.mem.md new file mode 100644 index 00000000000..10bfbb03e66 --- /dev/null +++ b/osquery/filesystem/linux/.mem.md @@ -0,0 +1,41 @@ + +Provides Linux physical memory reading functionality for osquery, enabling low-level access to `/dev/mem` with safety guards and mmap-based I/O. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kLinuxMaxMemRead` | constant | Maximum read size cap (`0x10000` / 64KB) to prevent oversized reads | +| `kLinuxMemPath` | constant | Path to the Linux raw memory device (`/dev/mem`) | +| `disable_memory` | flag | CLI/config flag to globally disable physical memory reads | +| `readMem()` | function | Low-level fallback reader using `lseek` + `read` on an open fd | +| `readRawMem()` | function | Primary entry point; opens `/dev/mem`, allocates a buffer, and reads via `mmap` (falls back to `readMem` on failure) | + +## Usage Example + +```cpp +#include + +void* buffer = nullptr; +size_t base = 0xE0000; // physical base address +size_t length = 0x1000; // bytes to read (must be ≤ 0x10000) + +osquery::Status status = osquery::readRawMem(base, length, &buffer); +if (!status.ok()) { + LOG(ERROR) << "Memory read failed: " << status.getMessage(); + return; +} + +// Use buffer contents ... +uint8_t* data = static_cast(buffer); + +free(buffer); // caller is responsible for freeing +``` + +## Notes + +- Requires root/su privileges; `/dev/mem` must be readable. +- Reads are skipped entirely when the `--disable_memory` flag is set. +- `mmap` is preferred for portability; `readMem` is the fallback for devices where mapping fails. +- Page-alignment is handled automatically using `_SC_PAGESIZE` (or `getpagesize()` as a fallback). +- The caller **owns** the allocated buffer and must call `free()` after use. \ No newline at end of file diff --git a/osquery/filesystem/linux/.mounts.md b/osquery/filesystem/linux/.mounts.md new file mode 100644 index 00000000000..d3dfe9984dd --- /dev/null +++ b/osquery/filesystem/linux/.mounts.md @@ -0,0 +1,52 @@ + +Declares data structures and a utility function for querying mounted filesystem information within the osquery framework. + +## Key Components + +### Structs + +- **`MountInformation::StatFsInfo`** — Nested struct holding low-level `statfs` metrics: + - `block_size` — Optimal transfer block size (`f_bsize`) + - `block_count` — Total data blocks (`f_blocks`) + - `free_block_count` — Free blocks (`f_bfree`) + - `unprivileged_free_block_count` — Free blocks available to unprivileged users (`f_bavail`) + - `inode_count` — Total inodes (`f_files`) + - `free_inode_count` — Free inodes (`f_ffree`) + +- **`MountInformation`** — Describes a single mounted filesystem: + - `type` — Filesystem type (e.g., `ext4`, `tmpfs`) + - `device` — Raw device path + - `device_alias` — Canonicalized device path + - `path` — Mount point + - `flags` — Mount options + - `optional_statfs_info` — Optional `StatFsInfo`; absent if `statfs` failed + +### Type Aliases + +- **`MountedFilesystems`** — `std::vector` representing all active mounts + +### Functions + +- **`getMountedFilesystems(MountedFilesystems&)`** — Populates the provided vector with information about all currently mounted filesystems; returns an osquery `Status` + +## Usage Example + +```c +#include "mounts.h" + +osquery::MountedFilesystems mounts; +auto status = osquery::getMountedFilesystems(mounts); + +if (status.ok()) { + for (const auto& mount : mounts) { + std::cout << mount.path << " -> " << mount.device + << " [" << mount.type << "]\n"; + + if (mount.optional_statfs_info) { + auto& fs = *mount.optional_statfs_info; + std::cout << " Blocks: " << fs.block_count + << ", Free: " << fs.free_block_count << "\n"; + } + } +} +``` \ No newline at end of file diff --git a/osquery/filesystem/linux/.proc.md b/osquery/filesystem/linux/.proc.md new file mode 100644 index 00000000000..b43d18e1a1f --- /dev/null +++ b/osquery/filesystem/linux/.proc.md @@ -0,0 +1,57 @@ + +Linux `/proc` filesystem parsing utilities for osquery, providing socket enumeration, process namespace inspection, and file descriptor traversal on Linux systems. + +## Key Components + +### Data Structures + +| Type | Description | +|------|-------------| +| `SocketInfo` | Holds socket metadata: family, protocol, local/remote address+port, state, and network namespace | +| `SocketProcessInfo` | Maps a socket to its owning process (`pid` + `fd`) | +| `SocketInodeToProcessInfoMap` | `map` keyed by socket inode | +| `ProcessNamespaceList` | `map` of namespace name to inode | + +### Constants + +- `kLinuxProcPath` — `/proc` root path +- `kLinuxProtocolNames` — maps `IPPROTO_*` values to `/proc/net` filenames +- `tcp_states` — ordered TCP state name lookup by index + +### Functions + +| Function | Description | +|----------|-------------| +| `procGetSocketList` | Builds `SocketInfoList` from `/proc//net` for a given family/protocol | +| `procGetSocketListPacket` | Parses `/proc/net/packet` content into `SocketInfoList` | +| `procGetSocketInodeToProcessInfoMap` | Maps socket inodes to owning process via `/proc//fd` | +| `procGetProcessNamespaces` | Reads namespace inodes from `/proc//ns` | +| `procDecodeAddressFromHex` | Decodes hex-encoded addresses from `/proc/net` files for `AF_INET`/`AF_INET6` | +| `procDecodePortFromHex` | Decodes hex-encoded port numbers | +| `getProcRSS` | Returns RSS memory usage for a process | + +### Templates + +- `procEnumerateProcesses` — Iterates all PIDs under `/proc`, invoking a callback per process; stops on first `false` return +- `procEnumerateProcessDescriptors` — Iterates `/proc//fd`, resolving symlinks and invoking a callback per descriptor + +## Usage Example + +```cpp +// Enumerate all processes and collect socket info +struct MyData { SocketInfoList sockets; }; + +MyData data; +procEnumerateProcesses(data, [](const std::string& pid, MyData& d) -> bool { + procGetSocketList(AF_INET, IPPROTO_TCP, 0, pid, d.sockets); + return true; // continue iteration +}); + +// Decode a hex address from /proc/net/tcp +std::string addr = procDecodeAddressFromHex("0100007F", AF_INET); +// addr == "127.0.0.1" + +// Map socket inodes to processes +SocketInodeToProcessInfoMap inodeMap; +procGetSocketInodeToProcessInfoMap("1234", inodeMap); +``` \ No newline at end of file diff --git a/osquery/filesystem/posix/.fileops.md b/osquery/filesystem/posix/.fileops.md new file mode 100644 index 00000000000..7d7d3c2e699 --- /dev/null +++ b/osquery/filesystem/posix/.fileops.md @@ -0,0 +1,60 @@ + +POSIX-specific implementation of cross-platform file operation primitives for osquery, providing file handle management, permission checks, path utilities, and filesystem queries on Linux and macOS. + +## Key Components + +### `PlatformFile` Class Methods +| Method | Description | +|---|---| +| `PlatformFile(path, mode, perms)` | Opens/creates a file using POSIX `open()` with flags derived from `PF_*` mode constants | +| `~PlatformFile()` | Closes the file handle via `::close()` | +| `read(buf, nbyte)` | Reads bytes, sets `has_pending_io_` on `EAGAIN` or partial reads | +| `write(buf, nbyte)` | Writes bytes, sets `has_pending_io_` on `EAGAIN` | +| `seek(offset, mode)` | Wraps `::lseek()` with `PF_SEEK_BEGIN/CURRENT/END` modes | +| `size()` | Returns file size via `fstat()` | +| `isOwnerRoot()` | Checks if file UID is 0 | +| `isOwnerCurrentUser()` | Checks if file UID matches `getuid()` | +| `isExecutable()` | Checks `S_IXUSR` permission bit | +| `hasSafePermissions()` | Returns success if `S_IWOTH` (world-writable) bit is **not** set | +| `getFileTimes(times)` | Populates access/modification times (handles Linux vs. BSD `stat` field names) | + +### Free Functions +| Function | Description | +|---|---| +| `getHomeDirectory()` | Returns writable `$HOME` or `pw_dir` from `getpwuid()` | +| `platformChmod(path, perms)` | Wraps `::chmod()` | +| `platformSetSafeDbPerms(path)` | Sets path to `S_IRWXU` (owner-only) | +| `platformGlob(find_path)` | Expands glob patterns using `GLOB_TILDE|GLOB_MARK|GLOB_BRACE` | +| `platformAccess(path, mode)` | Wraps `::access()` | +| `platformIsTmpDir(dir)` | Checks sticky bit (bit 9) of directory mode | +| `platformIsFileAccessible(path)` | Uses `lstat()` to detect symlinks and missing paths | +| `socketExists(path, remove_socket)` | Validates extension socket path writability and existence | +| `platformLstat(path, d_stat)` | Wraps `::lstat()` returning `Status` | +| `platformIsFile(fd)` | Returns `boost::optional` checking `S_ISREG` | +| `platformFileno(file, fd)` | Wraps `::fileno()` returning `Status` | + +## Usage Example + +```cpp +// Open a file for reading +PlatformFile pf("/etc/osquery/osquery.conf", PF_OPEN_EXISTING | PF_READ); +if (pf.isValid()) { + // Verify ownership and permissions before reading + if (pf.isOwnerRoot().ok() && pf.hasSafePermissions().ok()) { + char buf[4096]; + ssize_t bytes = pf.read(buf, sizeof(buf)); + } +} + +// Glob for config files +auto matches = platformGlob("/etc/osquery/*.conf"); +for (const auto& path : matches) { + // process each matched path +} + +// Validate a socket before binding +auto status = socketExists("/var/osquery/osquery.em", true); +if (!status.ok()) { + LOG(ERROR) << status.getMessage(); +} +``` \ No newline at end of file diff --git a/osquery/filesystem/posix/.xattrs.md b/osquery/filesystem/posix/.xattrs.md new file mode 100644 index 00000000000..e9ae5a43997 --- /dev/null +++ b/osquery/filesystem/posix/.xattrs.md @@ -0,0 +1,38 @@ + +Provides declarations for reading extended attributes (xattrs) from files on supported filesystems, exposing typed error enums and `Expected`-based result types for safe attribute retrieval. + +## Key Components + +### Error Enums +| Enum | Values | Description | +|------|--------|-------------| +| `XAttrFileError` | `NoLength`, `List`, `SizeChanged` | Errors when listing attribute names | +| `XAttrValueError` | `NoLength`, `Get`, `SizeChanged` | Errors when reading an attribute value | +| `XAttrGetError` | `GenericError`, `NoFile` | Top-level errors for the combined get operation | + +### Type Aliases +- **`ExtendedAttributeValue`** — `vector` raw byte buffer for an attribute's value +- **`ExtendedAttributeMap`** — `unordered_map` mapping attribute names to their values +- **`XAttrGetResult`** / **`XAttrNameListResult`** / **`XAttrValueResult`** — `Expected` wrappers for fallible return types + +### Functions +- **`xAttrFileErrorToString`** — Converts a `XAttrFileError` to a human-readable string given a file path +- **`xAttrValueErrorToString`** — Converts a `XAttrValueError` to a human-readable string given a path and attribute name +- **`getExtendedAttributesNames(int fd)`** — Lists all xattr names on an open file descriptor +- **`getExtendedAttributeValue(int fd, name)`** — Reads a single xattr value by name from an open file descriptor +- **`getExtendedAttributes(path)`** — High-level call: opens the file by path and returns all attributes as a map + +## Usage Example + +```c +auto result = osquery::getExtendedAttributes("/path/to/file"); +if (result.isError()) { + // handle XAttrGetError + return; +} + +for (const auto& [name, value] : result.get()) { + // name -> std::string (e.g. "user.comment") + // value -> std::vector +} +``` \ No newline at end of file diff --git a/osquery/filesystem/tests/.fileops.md b/osquery/filesystem/tests/.fileops.md new file mode 100644 index 00000000000..93c2cb573de --- /dev/null +++ b/osquery/filesystem/tests/.fileops.md @@ -0,0 +1,47 @@ + +Cross-platform file operations unit test suite for osquery, validating `PlatformFile` behavior including open modes, read/write I/O, seek operations, async I/O, file sharing, chmod permissions, and glob pattern matching. + +## Key Components + +### Test Fixture: `FileOpsTests` +- Inherits from `testing::Test`; sets up a mock filesystem in `SetUp()` and cleans it in `TearDown()` +- `globResultsMatch()` — helper to compare glob results against expected paths using set-based matching + +### Helper Class: `TempFile` +- RAII wrapper that generates a unique temporary file path (via `fs::unique_path`) and auto-deletes it on destruction + +### Helper Function: `openRWSharedFile()` +- Creates a `PlatformFile` with both `FILE_SHARE_READ` and `FILE_SHARE_WRITE` on Windows; falls back to standard open on POSIX + +### Test Cases + +| Test | What it validates | +|---|---| +| `test_openFile` | `PF_CREATE_NEW`, `PF_CREATE_ALWAYS`, `PF_OPEN_EXISTING` mode combinations | +| `test_shareRead` | Concurrent read/write with shared file handles | +| `test_fileIo` | Basic write-then-read correctness | +| `test_append` | `PF_APPEND` mode accumulates data correctly | +| `test_asyncIo` | `PF_NONBLOCK` reads/writes, including chunked reads | +| `test_seekFile` | `PF_SEEK_BEGIN`, `PF_SEEK_END`, `PF_SEEK_CURRENT` offsets | +| `test_large_read_write` | 20 MB write/read round-trip | +| `test_chmod_no_exec/read/write` | `platformChmod` + `isExecutable()` enforcement | + +## Usage Example + +```cpp +// Typical pattern used throughout these tests +TempFile tmp_file; +std::string path = tmp_file.path(); + +// Write data +PlatformFile writer(path, PF_CREATE_NEW | PF_WRITE); +if (writer.isValid()) { + writer.write("HELLO", 5); +} + +// Read it back +PlatformFile reader(path, PF_OPEN_EXISTING | PF_READ); +std::vector buf(5); +reader.read(buf.data(), 5); +// buf now contains "HELLO" +``` \ No newline at end of file diff --git a/osquery/filesystem/tests/.filesystem.md b/osquery/filesystem/tests/.filesystem.md new file mode 100644 index 00000000000..c5233248f52 --- /dev/null +++ b/osquery/filesystem/tests/.filesystem.md @@ -0,0 +1,52 @@ + +Filesystem test suite for osquery's filesystem utilities, providing comprehensive test coverage for file I/O, directory operations, glob pattern resolution, and cross-platform path handling. + +## Key Components + +### `FilesystemTests` (Test Fixture) +A `testing::Test` subclass that manages test lifecycle and provides helper utilities: + +- **`SetUp()`** — Initializes locale, creates a mock directory structure, and configures platform-specific paths (`etc_hosts_path_`, `etc_path_`, `tmp_path_`, `system_root_`, `line_ending_`) +- **`TearDown()`** — Cleans up temporary directories and mock file structure + +### Helper Methods +| Method | Purpose | +|---|---| +| `contains()` | Checks if a path exists in a results vector | +| `genRandomName()` | Generates a UUID-based random string | +| `createNestedDirectories()` | Builds multi-level directory trees | +| `createDirectorySymlink()` | Creates directory symlinks with error handling | +| `deleteDirectoryContent()` | Recursively removes a directory | +| `getRandomNumber()` | Returns a seeded random integer (1–30) | +| `legacyListDirectoriesInDirectory()` | Wraps `listInAbsoluteDirectory` for backward-compat testing | + +### Test Cases +| Test | What It Validates | +|---|---| +| `test_read_file` | Reads files including Unicode filenames (e.g., `辞書.txt`) | +| `test_remove_path` | Recursive path removal | +| `test_write_file` | File creation with POSIX permission modes (0400, 0000) | +| `test_readwrite_file` | Round-trip read/write across 4K chunk boundaries | +| `test_read_limit` | Enforces `FLAGS_read_max` byte cap | +| `test_list_files_*` | Directory listing — missing, invalid, and valid paths | +| `test_intermediate_globbing_directories` | Multi-level glob resolution | +| `test_canonicalization` | Path normalization with `..` and wildcard stops | +| `test_simple_globs` | Shell `*`, CSH `{}`, and `~` expansion | +| `test_wildcard_single_all` | `%` wildcard with `GLOB_ALL` flag | + +## Usage Example + +```cpp +// Running a specific test +// Build with CMake, then: +// ./osquery_tests --gtest_filter=FilesystemTests.test_read_file + +// The fixture automatically sets up cross-platform paths: +// Windows: etc_hosts_path_ = "C:\\Windows\\System32\\drivers\\etc\\hosts" +// POSIX: etc_hosts_path_ = "/etc/hosts" + +// Unicode filenames are tested via kFileNameList: +// { "辞書.txt", "test_file.txt" } +``` + +> **Note:** Tests require the `createMockFileStructure()` helper from `osquery/filesystem/mock_file_structure.h` and run on both POSIX and Windows platforms via conditional path logic. \ No newline at end of file diff --git a/osquery/filesystem/tests/darwin/.bsd_file_flags_tests.md b/osquery/filesystem/tests/darwin/.bsd_file_flags_tests.md new file mode 100644 index 00000000000..5ef1e6f7615 --- /dev/null +++ b/osquery/filesystem/tests/darwin/.bsd_file_flags_tests.md @@ -0,0 +1,31 @@ + +Unit tests for the `describeBSDFileFlags` function, validating correct parsing and string description of BSD file flags on Darwin/macOS systems. + +## Key Components + +- **`DarwinBsdFlags`** — GTest fixture class for BSD file flag tests +- **`testAllFlags`** — Verifies that all known BSD flags (`UF_*` and `SF_*`) are correctly described as a comma-separated string, and that the function returns `true` when no undocumented bits are present +- **`foreignFlags`** — Verifies that unknown/undocumented flag bits (e.g., `0xFF000000`) are included in the output as a hex value, and that the function returns `false` to signal unrecognized bits + +## Usage Example + +```cpp +// Describing a combination of known BSD flags +std::string description; +auto flags = UF_NODUMP | UF_IMMUTABLE | SF_ARCHIVED; +bool success = describeBSDFileFlags(description, flags); +// success == true +// description == "NODUMP, UF_IMMUTABLE, ARCHIVED" + +// Handling unknown/foreign flag bits +auto mixed_flags = UF_NODUMP | 0xFF000000U; +bool success = describeBSDFileFlags(description, mixed_flags); +// success == false +// description == "NODUMP, 0xff000000" +``` + +## Notes + +- Depends on `osquery/filesystem/fileops.h` for the `describeBSDFileFlags` function and BSD flag constants +- A `true` return value indicates all bits in `flags` are documented; `false` signals at least one unrecognized bit +- Covers both user-level (`UF_*`) and superuser-level (`SF_*`) BSD file flag categories \ No newline at end of file diff --git a/osquery/filesystem/tests/darwin/.plist_tests.md b/osquery/filesystem/tests/darwin/.plist_tests.md new file mode 100644 index 00000000000..338d059b37c --- /dev/null +++ b/osquery/filesystem/tests/darwin/.plist_tests.md @@ -0,0 +1,46 @@ + +Unit tests for osquery's plist parsing utilities, covering XML plist content parsing, file-based parsing, array plists, and binary plist blobs with base64-encoded data. + +## Key Components + +| Test Case | Description | +|---|---| +| `test_parse_plist_content` | Parses a plist from raw string content, validates key types (`bool`, `string`), missing key exceptions, and array child nodes | +| `test_parse_plist_from_file` | Reads and parses a plist directly from disk, validates tree size and dot-delimited key traversal | +| `test_parse_plist_array` | Validates successful parsing of array-structured plist files | +| `test_parse_plist_content_with_blobs` | Parses a binary plist, validates nested key access and decodes a base64 binary blob | + +## Usage Example + +```cpp +#include +#include + +// Parse plist from file path +boost::property_tree::ptree tree; +auto s = parsePlist("/path/to/file.plist", tree); +if (s.ok()) { + // Access scalar values + auto label = tree.get("Label"); + + // Access nested keys using dot notation + auto wait = tree.get("inetdCompatibility.Wait", ""); + + // Iterate array children + auto args = tree.get_child("ProgramArguments"); + for (const auto& arg : args) { + auto value = arg.second.get(""); + } +} + +// Parse plist from raw string content +std::string content; // pre-loaded plist XML +boost::property_tree::ptree tree2; +auto s2 = parsePlistContent(content, tree2); +``` + +## Notes + +- Tests rely on fixture files (`test.plist`, `test_array.plist`, `test_binary.plist`) located in the osquery test config directory +- Dot (`.`) in plist keys is used as the `boost::property_tree` level delimiter — tests explicitly verify this does not corrupt key traversal +- Binary blob fields are returned as base64-encoded strings and must be decoded with `base64::decode()` before use \ No newline at end of file diff --git a/osquery/filesystem/tests/linux/.proc_tests.md b/osquery/filesystem/tests/linux/.proc_tests.md new file mode 100644 index 00000000000..d90237f15af --- /dev/null +++ b/osquery/filesystem/tests/linux/.proc_tests.md @@ -0,0 +1,34 @@ + +Unit tests for Linux `/proc` filesystem parsing utilities, specifically validating `procGetSocketListPacket` (packet socket parsing with filtering) and `procGetNamespaceInode` (namespace inode extraction from symlinks). + +## Key Components + +| Test | Description | +|---|---| +| `testProcGetSocketListPacketValidInput` | Verifies correct parsing of well-formed `/proc/net/packet` data, including protocol filtering by `ETH_P_ALL` and `ETH_P_LLDP` | +| `testProcGetSocketListPacketValidInvalidHeader` | Ensures parsing fails gracefully when the header line is malformed | +| `testProcGetSocketListPacketValidInvalidContent` | Confirms partial results are returned when only some entries are parseable | +| `test_user_namespace_parser` | Validates inode extraction from a namespace symlink (e.g., `namespace:[112233]`) | + +## Usage Example + +```cpp +// Parse all packet sockets (no protocol filter) +SocketInfoList socket_list; +Status status = procGetSocketListPacket(AF_PACKET, 0, pid, raw_proc_input, socket_list); + +// Filter by specific protocol +status = procGetSocketListPacket(AF_PACKET, ETH_P_LLDP, pid, raw_proc_input, socket_list); + +// Extract namespace inode from /proc//ns/ +ino_t namespace_inode; +status = procGetNamespaceInode(namespace_inode, "net", "/proc/1234/ns"); +// namespace_inode == 4026531992 +``` + +## Notes + +- Protocol filter value `0` returns all sockets regardless of protocol +- Malformed headers cause an immediate error return with an empty list +- Partially malformed content (bad trailing entries) still returns successfully parsed entries +- Namespace inode test creates a real temporary symlink on disk to exercise the actual symlink-reading code path \ No newline at end of file diff --git a/osquery/filesystem/tests/posix/.filesystem_tests.md b/osquery/filesystem/tests/posix/.filesystem_tests.md new file mode 100644 index 00000000000..2df430348a5 --- /dev/null +++ b/osquery/filesystem/tests/posix/.filesystem_tests.md @@ -0,0 +1,26 @@ + +Unit test file for POSIX filesystem operations in osquery, specifically validating non-blocking behavior when reading from unopened FIFOs (named pipes). + +## Key Components + +- **`PosixFilesystemTests`** — GTest fixture class that manages a temporary working directory, created in `SetUp()` and cleaned up via `TearDown()` using `boost::filesystem`. +- **`test_read_unopened_fifo`** — Test case verifying that `readFile()` handles unopened named pipes gracefully without blocking, returning an empty string and a success status. + +## Usage Example + +```cpp +// The test simulates this usage pattern: +auto fifo_path = "/tmp/test_fifo"; +::mkfifo(fifo_path, S_IRUSR | S_IWUSR); + +std::string content; +bool success = readFile(fifo_path, content); +// success == true, content == "" (non-blocking, no writer attached) +::unlink(fifo_path); +``` + +## Notes + +- Tests are scoped to `namespace osquery` and target POSIX-only behavior (uses `::mkfifo`, `::unlink`). +- The fixture uses `fs::unique_path` with a randomized suffix to avoid test directory collisions. +- The core assertion validates two things: `readFile()` must not hang (non-blocking mode), and it must return `true` with empty content when no writer has opened the FIFO. \ No newline at end of file diff --git a/osquery/filesystem/tests/posix/.xattrs.md b/osquery/filesystem/tests/posix/.xattrs.md new file mode 100644 index 00000000000..e8ffc42d15a --- /dev/null +++ b/osquery/filesystem/tests/posix/.xattrs.md @@ -0,0 +1,35 @@ + +Unit test file for the POSIX extended attributes (xattr) filesystem utilities in osquery, validating the reading and retrieval of file extended attributes on macOS and Linux. + +## Key Components + +### Test Fixture: `XattrTests` +- **`SetUp()`** — Creates a temporary file and sets two known extended attributes (`testValue1`, `testValue2`) using `setxattr`. Handles platform differences: macOS uses no prefix; Linux uses the `user.` namespace prefix. +- **`TearDown()`** — Removes the temporary test file via `boost::filesystem::remove`. + +### Test Cases + +| Test | Description | +|---|---| +| `getExtendedAttributesNames` | Opens the temp file by fd and verifies `getExtendedAttributesNames()` returns a list containing all expected attribute names | +| `getExtendedAttributeValue` | Reads each attribute by fd + name and asserts the returned byte vector matches the expected value | +| `getExtendedAttributes` | Calls the higher-level `getExtendedAttributes()` by file path and validates the full name→value map | + +### Platform Compatibility +Uses a preprocessor macro to normalize the `setxattr` signature and attribute name prefix across macOS (`APPLE`) and Linux. + +## Usage Example + +```cpp +// The tests validate these public xattr API functions: + +// 1. Get all attribute names from an open file descriptor +auto names = getExtendedAttributesNames(fd); + +// 2. Get a single attribute's value by name +auto value = getExtendedAttributeValue(fd, "user.myattr"); + +// 3. Get all attributes as a map from a file path +auto xattrs = getExtendedAttributes("/path/to/file"); +// xattrs == { "user.myattr": {0x01, 0x02, ...}, ... } +``` \ No newline at end of file diff --git a/osquery/filesystem/tests/windows/.filesystem_tests.md b/osquery/filesystem/tests/windows/.filesystem_tests.md new file mode 100644 index 00000000000..a627cfe17fd --- /dev/null +++ b/osquery/filesystem/tests/windows/.filesystem_tests.md @@ -0,0 +1,34 @@ + +Windows-specific filesystem test suite that validates non-blocking pipe read behavior using the osquery filesystem abstraction layer. + +## Key Components + +- **`WindowsFilesystemTests`** — GTest fixture class that creates a temporary working directory under the system temp path for test isolation +- **`test_read_empty_named_pipe`** — Verifies that `readFile()` does not hang (deadlock) when attempting to read from an empty, unconnected named pipe in non-blocking mode + +## Usage Example + +```cpp +// The test simulates this real-world scenario: +// 1. Create a named pipe (server-side handle) +// 2. Attempt to read from it before any client connects or writes data +// 3. Confirm readFile() returns false and produces no content + +std::wstring pipe_name = LR"(\\.\pipe\osquery_test_pipe)"; +HANDLE pipe_handle = CreateNamedPipe(pipe_name.c_str(), + PIPE_ACCESS_DUPLEX, + PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + 0, 0, 1000, 0); + +std::string content; +bool result = readFile(pipe_name, content); +// Expected: result == false, content.empty() == true +``` + +## Notes + +- **Windows-only** — relies on `CreateNamedPipe` and `HANDLE` Win32 APIs; not compiled on other platforms +- The core assertion guards against a previously known hang bug where `readFile()` would block indefinitely on empty named pipes +- `SetUp()` creates a unique temp directory per test run; `TearDown()` is a no-op (cleanup left to the OS) +- Named pipe path follows the Windows UNC convention: `\\.\pipe\` \ No newline at end of file diff --git a/osquery/filesystem/windows/.fileops.md b/osquery/filesystem/windows/.fileops.md new file mode 100644 index 00000000000..63fee4a50a0 --- /dev/null +++ b/osquery/filesystem/windows/.fileops.md @@ -0,0 +1,49 @@ + +Windows-specific file operations implementation for osquery, providing filesystem utilities including file finding, version info retrieval, ACL management, glob pattern matching, and async I/O event handling on Windows platforms. + +## Key Components + +### Classes +- **`WindowsFindFiles`** — RAII wrapper around `FindFirstFileW`/`FindNextFileW` for enumerating files and directories matching a path pattern +- **`AsyncEvent`** — RAII wrapper for Windows overlapped I/O events using `CreateEventW` + +### Core Functions + +| Function | Description | +|---|---| +| `windowsShortPathToLongPath` | Converts 8.3 short paths to full long paths via `GetLongPathNameW` | +| `windowsGetVersionInfo` | Retrieves `ProductVersion` and `FileVersion` strings from a PE's version resource | +| `windowsGetOriginalFilename` | Extracts `OriginalFilename` from a PE's version resource, with multi-codepage fallback heuristics | +| `getLanguagesAndCodepages` | Parses `\VarFileInfo\Translation` from a version info block | +| `globToRegex` | Converts glob patterns (including `{}` brace groups) to `std::wregex`-compatible strings | +| `hasGlobBraces` | Validates balanced brace groups in a glob pattern | +| `getNewAclSize` | Calculates required buffer size for a modified DACL when adding allow/deny ACEs | + +### Constants & Macros +- `CHMOD_READ`, `CHMOD_WRITE`, `CHMOD_EXECUTE` — Windows ACL permission bit groups mapping Unix-style chmod semantics +- `kTrustedInstallerSID` — Well-known SID for the Windows TrustedInstaller service account + +## Usage Example + +```cpp +// Enumerate files matching a glob pattern +WindowsFindFiles finder(fs::path(L"C:\\Windows\\System32\\*.exe")); +auto files = finder.get(); +auto dirs = finder.getDirectories(); + +// Retrieve PE version metadata +std::string product_ver, file_ver; +auto status = windowsGetVersionInfo("C:\\Windows\\notepad.exe", + product_ver, file_ver); +if (status.ok()) { + // product_ver == "10.0.19041.1" +} + +// Extract OriginalFilename from a PE +std::string original_name; +auto s = windowsGetOriginalFilename("C:\\path\\to\\binary.exe", original_name); + +// Convert a glob to a regex for manual matching +std::wstring regex = globToRegex(L"C:\\Users\\{alice,bob}\\*.log"); +// result: ^C:\\Users\\(alice|bob)\\.*\.log$ +``` \ No newline at end of file diff --git a/osquery/hashing/.hashing.md b/osquery/hashing/.hashing.md new file mode 100644 index 00000000000..dc6e457077c --- /dev/null +++ b/osquery/hashing/.hashing.md @@ -0,0 +1,49 @@ + +Provides hashing utilities for osquery, supporting MD5, SHA1, and SHA256 algorithms with hex or Base64 encoding, for buffers and filesystem paths. + +## Key Components + +### Enums +- **`HashType`** — Algorithm selector: `HASH_TYPE_MD5`, `HASH_TYPE_SHA1`, `HASH_TYPE_SHA256` +- **`HashEncodingType`** — Output encoding: `HASH_ENCODING_TYPE_HEX`, `HASH_ENCODING_TYPE_BASE64` + +### Structs +- **`MultiHashes`** — Holds results for simultaneous multi-algorithm hashing (`md5`, `sha1`, `sha256` strings + a bitmask) + +### Class: `Hash` +Non-copyable (move-only) streaming hash context. + +| Method | Description | +|---|---| +| `Hash(HashType)` | Init with algorithm, defaults to hex encoding | +| `Hash(HashType, HashEncodingType)` | Init with algorithm and encoding | +| `update(buffer, size)` | Feed data incrementally | +| `digest()` | Finalize and return encoded hash string | + +### Free Functions + +| Function | Description | +|---|---| +| `hashFromFile(hash_type, path)` | Hash a file, returns hex string | +| `hashMultiFromFile(mask, path)` | Hash a file with multiple algorithms simultaneously | +| `hashFromBuffer(hash_type, buffer, size)` | Hash an in-memory buffer, returns hex string | + +## Usage Example + +```cpp +// Streaming hash (large file chunks) +osquery::Hash hasher(HASH_TYPE_SHA256, HASH_ENCODING_TYPE_HEX); +hasher.update(chunk_ptr, chunk_size); +std::string result = hasher.digest(); + +// One-shot file hash +std::string md5 = osquery::hashFromFile(HASH_TYPE_MD5, "/etc/hosts"); + +// Multiple algorithms in a single file pass +osquery::MultiHashes hashes = osquery::hashMultiFromFile( + HASH_TYPE_MD5 | HASH_TYPE_SHA256, "/var/log/syslog"); + +// Buffer hash +std::string sha1 = osquery::hashFromBuffer( + HASH_TYPE_SHA1, data_ptr, data_size); +``` \ No newline at end of file diff --git a/osquery/hashing/tests/.hashing.md b/osquery/hashing/tests/.hashing.md new file mode 100644 index 00000000000..161a7462d0e --- /dev/null +++ b/osquery/hashing/tests/.hashing.md @@ -0,0 +1,36 @@ + +Unit test suite for the osquery hashing module, validating MD5, SHA1, and SHA256 hash computation across in-memory buffers, small files, and large binary files. + +## Key Components + +### Test Fixtures & Constants +- **`HashingFilesystemTests`** — GTest fixture that creates and tears down a temporary working directory for file-based tests +- **Known digest constants** — Pre-computed MD5, SHA1, and SHA256 digests for both the string `"HELLO"` and a 10MB binary file filled with `0xDEADBEEF` + +### Test Cases + +| Test | Description | +|---|---| +| `test_multi_hashing_file_small` | Hashes a small text file (`"HELLO"`) using `hashMultiFromFile` and `hashFromFile` | +| `test_multi_hashing_file_big` | Hashes a 10MB binary file (2560 × 4KB blocks of `0xDEADBEEF`) to validate chunked/streaming correctness | +| `test_hashing_md5` | Validates `Hash` class and `hashFromBuffer` for MD5 | +| `test_hashing_sha1` | Validates `Hash` class and `hashFromBuffer` for SHA1 | +| `test_hashing_sha256` | Validates `Hash` class and `hashFromBuffer` for SHA256 | + +## Usage Example + +```cpp +// Single algorithm from a buffer +std::string digest = hashFromBuffer( + HASH_TYPE_SHA256, data.c_str(), data.length()); + +// Multiple algorithms in one pass from a file +const auto mask = HASH_TYPE_MD5 | HASH_TYPE_SHA1 | HASH_TYPE_SHA256; +const auto hashes = hashMultiFromFile(mask, "/path/to/file"); +// hashes.md5, hashes.sha1, hashes.sha256 + +// Incremental hashing via Hash class +Hash hash(HASH_TYPE_MD5); +hash.update(chunk.data(), chunk.size()); +std::string result = hash.digest(); +``` \ No newline at end of file diff --git a/osquery/logger/.data_logger.md b/osquery/logger/.data_logger.md new file mode 100644 index 00000000000..29f14598bfa --- /dev/null +++ b/osquery/logger/.data_logger.md @@ -0,0 +1,58 @@ + +Header file defining osquery's data logging interface, providing functions to initialize, route, and emit log messages across multiple logger backends and system facilities. + +## Key Components + +### Enums +- **`LoggerRelayMode`** — Controls relay behavior: `Sync` (blocking) or `Async` (non-blocking) + +### Initialization Functions +- **`initStatusLogger(name, init_glog)`** — Starts buffered status logging before the logger plugin is online; optionally initializes Google Logging +- **`initLogger(name)`** — Finalizes logger setup by flushing buffered logs and configuring status log forwarding +- **`setVerboseLevel()`** — Configures Glog verbosity and affects plugin sink behavior + +### Logging Functions +- **`logString(...)`** — Logs a string to the default or a named receiver with a category key +- **`logQueryLogItem(item)`** / **`logQueryLogItem(item, receiver)`** — Logs scheduled query results to the default or a named receiver +- **`logSnapshotQuery(item)`** — Logs raw snapshot query results + +### Relay & Utility +- **`relayStatusLogs(relay_mode)`** — Flushes buffered watcher/worker status logs to the configured plugin +- **`waitLogRelay()`** — Blocks until the async relay thread completes +- **`queuedStatuses()`** — Returns the count of buffered status log entries +- **`systemLog(line)`** — Bypasses osquery logging and writes directly to the OS system log (syslog / Windows Event Log) +- **`googleLogCustomPrefix(...)`** — Custom Glog prefix formatter (omits year field) + +## Usage Example + +```cpp +#include + +// Initialize logging early in process startup +osquery::initStatusLogger("my_process"); + +// After plugins are registered, finalize logger +osquery::initLogger("my_process"); + +// Log a result string to the default receiver +auto status = osquery::logString( + R"({"key":"value"})", + "snapshot" +); + +if (!status.ok()) { + LOG(ERROR) << "Logging failed: " << status.getMessage(); +} + +// Log scheduled query results +osquery::QueryLogItem item; +item.name = "running_processes"; +osquery::logQueryLogItem(item); + +// Flush buffered watcher logs synchronously +osquery::relayStatusLogs(osquery::LoggerRelayMode::Sync); +osquery::waitLogRelay(); + +// Write directly to OS system log (bypasses osquery config) +osquery::systemLog("Critical startup event"); +``` \ No newline at end of file diff --git a/osquery/logger/.logger.md b/osquery/logger/.logger.md new file mode 100644 index 00000000000..89cb679344b --- /dev/null +++ b/osquery/logger/.logger.md @@ -0,0 +1,37 @@ + +Thin wrapper around `glog` that provides osquery-specific logging macros for consistent verbosity and log-line referencing across the codebase. + +## Key Components + +| Macro | Description | +|-------|-------------| +| `TLOG` | Alias for `VLOG(1)` — verbose logging intended for table-level parsing events and non-critical edge cases | +| `RLOG(n)` | Prepends a formatted reference tag (e.g., `[Ref #42]`) to a log line for external search and triage | + +## Usage Example + +```c +#include + +// Table-level verbose log (only shown when verbosity >= 1) +TLOG << "Parsed unexpected value in users table, skipping row."; + +// Log with a reference number for documentation/issue tracking +LOG(WARNING) << RLOG(101) << "Unexpected null pointer in process cache."; +``` + +**Output examples:** + +```text +// TLOG output (verbosity level 1): +I0101 12:00:00 table.cpp:42] Parsed unexpected value in users table, skipping row. + +// RLOG output: +W0101 12:00:00 cache.cpp:88] [Ref #101] Unexpected null pointer in process cache. +``` + +## Notes + +- `TLOG` is intended for **table developers** — use it for routine parse warnings, not critical errors. +- `RLOG(n)` is purely a string prefix; combine it with any standard `glog` severity macro (`LOG(WARNING)`, `LOG(ERROR)`, etc.). +- Verbosity for `TLOG` is controlled at runtime via CLI flags or osquery config, keeping table noise out of production logs by default. \ No newline at end of file diff --git a/osquery/logger/benchmarks/.logger_benchmarks.md b/osquery/logger/benchmarks/.logger_benchmarks.md new file mode 100644 index 00000000000..8873b16b064 --- /dev/null +++ b/osquery/logger/benchmarks/.logger_benchmarks.md @@ -0,0 +1,31 @@ + +Benchmarks for osquery's logger plugin system, measuring throughput of status log relay and string logging operations using a no-op `DummyLoggerPlugin`. + +## Key Components + +| Component | Description | +|---|---| +| `DummyLoggerPlugin` | A minimal `LoggerPlugin` implementation that discards all log output, used to isolate benchmark overhead from I/O | +| `LOGGER_logstatus_plugin` | Benchmarks the full GLog `LOG(INFO)` → `relayStatusLogs()` pipeline via the plugin registry | +| `LOGGER_logstring_plugin` | Benchmarks direct `logString()` dispatch through the plugin registry | + +## Usage Example + +```cpp +// Run all logger benchmarks +./osquery_benchmarks --benchmark_filter=LOGGER_ + +// Run only the logstatus benchmark +./osquery_benchmarks --benchmark_filter=LOGGER_logstatus_plugin + +// Run with iteration count and output format +./osquery_benchmarks --benchmark_filter=LOGGER_ \ + --benchmark_format=json \ + --benchmark_out=logger_results.json +``` + +## Notes + +- Both benchmarks register `DummyLoggerPlugin` under the `"dummy"` logger key in the `RegistryFactory`, then restore the previously active logger after the run to avoid side effects. +- `LOGGER_logstatus_plugin` temporarily suppresses `logtostderr` and raises `stderrthreshold` to `WARNING` to prevent GLog from writing to stderr during measurement, keeping results clean. +- `FLAGS_disable_logging` is toggled around each benchmark to ensure the logger pipeline is active only during measurement. \ No newline at end of file diff --git a/osquery/logger/tests/.logger.md b/osquery/logger/tests/.logger.md new file mode 100644 index 00000000000..a635afb5b58 --- /dev/null +++ b/osquery/logger/tests/.logger.md @@ -0,0 +1,49 @@ + +Unit test suite for osquery's logging subsystem, validating logger plugin registration, status forwarding, log string dispatch, snapshot queries, and multi-logger configurations. + +## Key Components + +### `LoggerTests` (Test Fixture) +Base `testing::Test` class that initializes the platform, registry, and database before each test. Tracks static counters for log lines, status messages, and snapshot rows across test cases. + +### `TestLoggerPlugin` +A mock `LoggerPlugin` implementation used across most tests. Supports toggling status and event logging via `shouldLogStatus` / `shouldLogEvent` flags. Captures log output into shared static vectors. + +### `SecondTestLoggerPlugin` +A secondary mock logger used in `test_multiple_loggers` to verify that multiple active loggers each receive dispatched log entries. + +### Helper: `placeStatuses` +Inline function that appends incoming `StatusLogLine` entries to `LoggerTests::status_messages` and increments `statuses_logged`. + +## Test Cases + +| Test | What It Validates | +|---|---| +| `test_plugin` | Plugin registration and basic `logString` dispatch via registry | +| `test_logger_init` | Buffered warning forwarded after `initLogger` | +| `test_log_string` | Direct `logString` API, disabled logging, unknown plugin handling | +| `test_logger_log_status` | Severity, line, message, time, and host in `StatusLogLine` | +| `test_logger_status_level` | `logger_min_status` / `logger_min_stderr` flag filtering | +| `test_feature_request` | `LOGGER_FEATURE_LOGSTATUS` feature negotiation via registry call | +| `test_logger_variations` | Logger skips Glog forwarding when status logging disabled at init | +| `test_logger_snapshots` | Snapshot query logging and `logger_snapshot_event_type` flag | +| `test_multiple_loggers` | Dual active loggers, deadlock safety, and status forwarding order | +| `test_logger_scheduled_query` | `logQueryLogItem` dispatch with `logger_event_type` toggle | + +## Usage Example + +```cpp +// Register and activate the test logger plugin +auto& rf = RegistryFactory::get(); +rf.registry("logger")->add("test", std::make_shared()); +rf.setActive("logger", "test"); + +// Initialize and emit a log string +initLogger("my_test"); +logString("{\"key\": \"value\"}", "event"); +// LoggerTests::log_lines.back() == "{\"key\": \"value\"}" + +// Verify status forwarding +LOG(WARNING) << "Test warning"; +// LoggerTests::statuses_logged == 1 +``` \ No newline at end of file diff --git a/osquery/main/.benchmarks.md b/osquery/main/.benchmarks.md new file mode 100644 index 00000000000..84316b00ff5 --- /dev/null +++ b/osquery/main/.benchmarks.md @@ -0,0 +1,38 @@ + +Benchmark harness entry point for osquery, initializing the testing environment and a no-op logger plugin before running Google Benchmark suites. + +## Key Components + +- **`NoneLoggerPlugin`** — A stub `LoggerPlugin` implementation that silently discards all log output (`logString`, `logStatus`, `init`), registered under the `"none"` logger name to suppress logging noise during benchmarks. +- **`REGISTER` macro** — Registers `NoneLoggerPlugin` into the osquery plugin registry as `logger/none`. +- **`main()`** — Configures the logger plugin to `"none"`, calls `osquery::initTesting()` to bootstrap the osquery environment, initializes Google Logging, then delegates to the Google Benchmark runner. + +## Usage Example + +```cpp +// Benchmarks are defined in separate translation units and linked against +// this harness. Example benchmark definition: + +#include +#include + +namespace osquery { + +static void BM_ExampleQuery(benchmark::State& state) { + for (auto _ : state) { + auto results = SQL::selectAllFrom("time"); + benchmark::DoNotOptimize(results); + } +} + +BENCHMARK(BM_ExampleQuery); + +} // namespace osquery + +// Build and run: +// cmake --build . --target osquery_benchmarks +// ./osquery_benchmarks --benchmark_filter=BM_ExampleQuery +// ./osquery_benchmarks --benchmark_format=json +``` + +> **Note:** The `NoneLoggerPlugin` ensures benchmark timing is not skewed by log I/O. All osquery log calls become no-ops for the duration of the benchmark run. \ No newline at end of file diff --git a/osquery/main/.empty.md b/osquery/main/.empty.md new file mode 100644 index 00000000000..51065b5a94e --- /dev/null +++ b/osquery/main/.empty.md @@ -0,0 +1,13 @@ + +A copyright-only placeholder file containing no executable code, serving as an empty compilation unit within the osquery project. + +## Key Components + +None — this file contains only the standard osquery license header (Apache-2.0 OR GPL-2.0-only) and no declarations, definitions, or exports. + +## Usage Example + +```cpp +// No symbols are defined or exported from this file. +// It exists solely as a placeholder compilation unit. +``` \ No newline at end of file diff --git a/osquery/main/.main.md b/osquery/main/.main.md new file mode 100644 index 00000000000..9da5b3c6709 --- /dev/null +++ b/osquery/main/.main.md @@ -0,0 +1,32 @@ + +Platform entry point header that declares service installation/uninstallation functions and the primary osquery startup routine. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `installService` | Function | Installs osqueryd as a platform service (currently Windows-only) | +| `uninstallService` | Function | Removes the previously installed osqueryd service | +| `startOsquery` | Function | Platform-agnostic entry point that initializes the shell and daemon | + +## Usage Example + +```c +#include "main.h" + +// Install osqueryd as a Windows service +osquery::Status status = osquery::installService("C:\\osquery\\osqueryd.exe"); +if (!status.ok()) { + // handle error +} + +// Start the osquery shell/daemon (called from main()) +int result = osquery::startOsquery(argc, argv); +``` + +## Notes + +- `installService` / `uninstallService` are **Windows-only** at runtime. POSIX platforms rely on an external `osqueryctl` companion script to handle daemon registration (launchd, systemd, init). +- Refactoring POSIX install flows into these methods is a known limitation tracked by the osquery authors. +- Both service functions return `osquery::Status` (from `osquery/utils/status/status.h`), enabling structured error handling. +- `startOsquery` accepts standard `argc`/`argv` and serves as the single cross-platform bootstrap call shared by both the shell and daemon binaries. \ No newline at end of file diff --git a/osquery/main/.tests.md b/osquery/main/.tests.md new file mode 100644 index 00000000000..c7ab5bee0e1 --- /dev/null +++ b/osquery/main/.tests.md @@ -0,0 +1,35 @@ + +Test harness entry point for osquery process-related unit tests, handling three execution modes: worker subprocess, extension subprocess, and standard Google Test runner. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kProcessTestExecPath` | `std::string` | Exported path of the current executable, shared with `process_tests.cpp` | +| `kOsqueryTestModuleName` | `const char*` | Expected module name of the launcher process (`osquery_tests.exe`) | +| `kExpectedWorkerArgs` | `const char*[]` | Expected CLI arguments for the worker subprocess (`--socket fake-socket`) | +| `kExpectedExtensionArgs` | `const char*[]` | Expected CLI arguments for the extension subprocess (`--verbose --socket ...`) | +| `compareArguments()` | `static bool` | Validates argv count and content against an expected argument list | +| `workerMain()` | `int` | Worker mode entrypoint; validates args and verifies the launcher process identity | +| `extensionMain()` | `int` | Extension mode entrypoint; validates args and returns success/failure codes | +| `main()` | `int` | Dispatches to worker, extension, or GTest runner based on environment variables | + +## Usage Example + +The binary self-forks into different roles via environment variables: + +```cpp +// Standard test runner mode (no env var set) +// $ ./osquery_tests +// Runs all Google Test suites normally. + +// Worker subprocess mode +// $ OSQUERY_WORKER=1 ./osquery_tests --socket fake-socket +// → workerMain() validates args, then checks launcher PID/image name. + +// Extension subprocess mode +// $ OSQUERY_EXTENSION=1 ./osquery_tests --verbose --socket socket-name --timeout 100 --interval 5 +// → extensionMain() validates args and returns EXTENSION_SUCCESS_CODE. +``` + +On Windows, `workerMain()` uses `QueryFullProcessImageNameA` + `PathStripPathA` to verify the launcher matches `kOsqueryTestModuleName`. On POSIX, it simply compares the launcher's native handle against `getppid()`. \ No newline at end of file diff --git a/osquery/main/harnesses/.fuzz_config.md b/osquery/main/harnesses/.fuzz_config.md new file mode 100644 index 00000000000..c7fc0e56daa --- /dev/null +++ b/osquery/main/harnesses/.fuzz_config.md @@ -0,0 +1,32 @@ + +Fuzz testing harness for the osquery configuration system, designed to be run with libFuzzer to discover crashes or undefined behavior when processing arbitrary config input. + +## Key Components + +- **`LLVMFuzzerInitialize`** – One-time setup called by libFuzzer before fuzzing begins. Initializes the osquery fuzzer environment, retrieves the `options` config parser plugin from the registry, and records the start timestamp. + +- **`LLVMFuzzerTestOneInput`** – Core fuzzing entry point called repeatedly with mutated byte sequences. Feeds raw input directly into `Config::update()` as a config source named `"fuzz"`, exercising the full config parsing pipeline. + +- **Periodic reset (5-second interval)** – Prevents memory growth and state accumulation during long fuzzing sessions by resetting the config parser plugin, custom flags, and calling `malloc_trim(0)` to release freed heap memory back to the OS. + +- **`option_plugin` / `sample_start`** – Module-level globals holding the options plugin reference and the last reset timestamp, shared between the two libFuzzer callbacks. + +## Usage Example + +```cpp +// Build and run with libFuzzer (clang required) +// Compile with: -fsanitize=fuzzer,address + +// libFuzzer drives execution automatically: +// 1. Calls LLVMFuzzerInitialize once +// 2. Calls LLVMFuzzerTestOneInput repeatedly with mutated inputs + +// Example equivalent of what libFuzzer feeds in: +const uint8_t raw[] = R"({"options": {"unknown_flag": "value"}})"; +LLVMFuzzerTestOneInput(raw, sizeof(raw) - 1); + +// Run with a seed corpus directory: +// ./fuzz_config corpus/ -max_total_time=60 +``` + +> **Note:** The 5-second periodic reset is critical for sustained fuzzing — without it, repeated `Config::update()` calls accumulate custom flags and parser state, eventually exhausting memory and producing false crashes. \ No newline at end of file diff --git a/osquery/main/harnesses/.fuzz_sqlquery.md b/osquery/main/harnesses/.fuzz_sqlquery.md new file mode 100644 index 00000000000..f5d0df57834 --- /dev/null +++ b/osquery/main/harnesses/.fuzz_sqlquery.md @@ -0,0 +1,28 @@ + +Fuzzing harness for osquery's SQL query execution engine, using LibFuzzer to test arbitrary SQL query strings for crashes, hangs, or undefined behavior. + +## Key Components + +- **`LLVMFuzzerInitialize`** — LibFuzzer entry point that initializes the osquery runtime environment via `osqueryFuzzerInitialize` before fuzzing begins. +- **`LLVMFuzzerTestOneInput`** — Core fuzzing target; converts raw byte input into a `std::string` and executes it as an SQL query via `osquery::query`, discarding results into a `QueryData` sink. + +## Usage Example + +This file is not invoked directly. It is compiled into a fuzzer binary and driven by LibFuzzer: + +```bash +# Build the fuzzer target (example with clang + AddressSanitizer) +clang++ -fsanitize=fuzzer,address fuzz_sqlquery.cpp -losquery -o fuzz_sqlquery + +# Run fuzzer with a seed corpus directory +./fuzz_sqlquery corpus/ + +# Run with a specific test case for reproduction +./fuzz_sqlquery crash-abc123 +``` + +## Notes + +- Any input size or content is accepted; LibFuzzer mutates the byte sequence to maximize code coverage. +- Results from `osquery::query` are intentionally ignored — the harness only checks for crashes or hangs. +- The return value of `LLVMFuzzerTestOneInput` must always be `0`; non-zero values signal to LibFuzzer that the input should be discarded from the corpus. \ No newline at end of file diff --git a/osquery/main/harnesses/.fuzz_utils.md b/osquery/main/harnesses/.fuzz_utils.md new file mode 100644 index 00000000000..149e34bba61 --- /dev/null +++ b/osquery/main/harnesses/.fuzz_utils.md @@ -0,0 +1,23 @@ + +Provides a fuzzing utility interface for osquery, exposing an initialization function that disables core stateful features to create a clean environment for LLVM-based fuzz testing. + +## Key Components + +- **`osqueryFuzzerInitialize(int* argc, char*** argv)`** — Disables core osquery features to reduce statefulness. Intended to be called within `LLVMFuzzerInitialize` before fuzz test execution begins. + +## Usage Example + +```c +#include "fuzz_utils.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + return osquery::osqueryFuzzerInitialize(argc, argv); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Fuzz target logic here + return 0; +} +``` + +> **Note:** Always call `osqueryFuzzerInitialize` before any fuzz input is processed to ensure core subsystems (logging, eventing, etc.) are suppressed and do not introduce side effects during fuzzing runs. \ No newline at end of file diff --git a/osquery/main/posix/.main.md b/osquery/main/posix/.main.md new file mode 100644 index 00000000000..e86dce514d1 --- /dev/null +++ b/osquery/main/posix/.main.md @@ -0,0 +1,35 @@ + +Entry point for the osquery daemon on POSIX platforms (Linux/macOS). Bootstraps the osquery process by delegating directly to the core startup routine, with stub implementations for Windows-only service management functions. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `osquery::installService` | Function | No-op stub; logs that `--install` is Windows-only, returns `Status(1)` | +| `osquery::uninstallService` | Function | No-op stub; logs that `--uninstall` is Windows-only, returns `Status(1)` | +| `main` | Function | Process entry point; forwards `argc`/`argv` to `osquery::startOsquery` | + +## Usage Example + +This file is not consumed as a library — it compiles into the osquery daemon binary. The POSIX startup flow is straightforward: + +```cpp +// Effective runtime behavior: +int main(int argc, char* argv[]) { + // No service-manager handshake needed on POSIX. + // All flag parsing, plugin init, and scheduler startup + // happens inside startOsquery(). + return osquery::startOsquery(argc, argv); +} + +// Attempting Windows-only flags on POSIX logs and fails gracefully: +osquery::installService("/usr/bin/osqueryd"); +// LOG(INFO): "The --install service flag only applies to Windows platforms" +// returns Status(1) +``` + +## Notes + +- The `installService` / `uninstallService` stubs satisfy the same interface used by the Windows build, keeping the codebase cross-platform without `#ifdef` guards in shared code. +- All real initialization logic lives in `osquery::startOsquery` (`osquery/main/main.h`). +- On Windows, a separate `main.cpp` handles SCM (Service Control Manager) registration before calling `startOsquery`. \ No newline at end of file diff --git a/osquery/main/windows/.main.md b/osquery/main/windows/.main.md new file mode 100644 index 00000000000..9fb6e9d3d77 --- /dev/null +++ b/osquery/main/windows/.main.md @@ -0,0 +1,40 @@ + +Windows entry point for the osquery daemon (`osqueryd`), handling both Windows Service Controller integration and direct command-line execution. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `ServiceArgumentParser` | Class | Parses service arguments from either `ServiceMain` parameters or `GetCommandLine()`, handling both manual (`sc.exe`) and automated (`binPath`) service starts | +| `installService` | Function | Registers `osqueryd` as a Windows auto-start service with recovery behavior (one restart on failure) | +| `uninstallService` | Function | Stops and removes the `osqueryd` Windows service | +| `ServiceControlHandler` | Function | Handles `SERVICE_CONTROL_STOP` / `SERVICE_CONTROL_SHUTDOWN` signals, initiating graceful shutdown | +| `ServiceMain` | Function | Windows SCM entry point; registers the control handler, updates service status, and delegates to `startOsquery` | +| `main` | Function | Attempts SCM dispatcher registration; falls back to direct CLI execution on `ERROR_FAILED_SERVICE_CONTROLLER_CONNECT` | +| `wmain` | Function | Wide-character entry point; converts `wchar_t` arguments to `char*` and delegates to `main` | +| `DebugPrintf` / `SLOG` | Macro + Function | Debug logging to `OutputDebugStringA` (when a debugger is attached) and the osquery error log | + +## Usage Example + +```cpp +// Running as a Windows Service (registered via installService): +// SCM calls ServiceMain → ServiceArgumentParser → startOsquery() + +// Running from command line (e.g., for interactive debugging): +// main() → StartServiceCtrlDispatcherA fails with +// ERROR_FAILED_SERVICE_CONTROLLER_CONNECT +// → startOsquery(argc, argv) called directly + +// Installing the service programmatically: +osquery::Status s = osquery::installService("C:\\osquery\\osqueryd.exe"); + +// Uninstalling: +osquery::Status s = osquery::uninstallService(); +``` + +## Key Behaviors + +- **Dual startup modes**: SCM-managed service vs. interactive CLI, determined at runtime by the dispatcher error code +- **Argument parsing**: Bridges the gap between `sc.exe` (passes `argc`/`argv` to `ServiceMain`) and `binPath` auto-start (requires `GetCommandLine()` parsing) +- **Graceful shutdown**: `ServiceControlHandler` sets `SERVICE_STOP_PENDING`, calls `requestShutdown()`, then waits on the main thread handle before marking `SERVICE_STOPPED` +- **Recovery policy**: Configured for one automatic restart (5-second delay) on failure via `ChangeServiceConfig2` \ No newline at end of file diff --git a/osquery/numeric_monitoring/.numeric_monitoring.md b/osquery/numeric_monitoring/.numeric_monitoring.md new file mode 100644 index 00000000000..04024910d7d --- /dev/null +++ b/osquery/numeric_monitoring/.numeric_monitoring.md @@ -0,0 +1,53 @@ + +Defines the public interface for osquery's numeric monitoring subsystem, providing types, constants, and functions for recording time-series numeric data points to a monitoring backend. + +## Key Components + +### Structs +- **`RecordKeys`** — Field name constants for monitoring record attributes (`path`, `value`, `timestamp`, `pre_aggregation`, `sync`) +- **`HostIdentifierKeys`** — Field name constants for host identification (`name`, `scheme`) + +### Type Aliases +- **`Clock`** / **`TimePoint`** — `std::chrono::system_clock` aliases for consistent time handling +- **`ValueType`** — `long long int`, the numeric type for all monitored values + +### Enum +- **`PreAggregationType`** — Aggregation strategy applied before data is sent: `None`, `Sum`, `Min`, `Max`, `Avg`, `Stddev`, percentiles (`P10`, `P50`, `P95`, `P99`) + +### Functions +- **`record()`** — Submits a new numeric data point with optional pre-aggregation, sync mode, and timestamp +- **`flush()`** — Forces immediate flush of the pre-aggregation buffer +- **`hostIdentifierKeys()`** / **`recordKeys()`** — Accessors for canonical field name constants +- **`registryName()`** — Returns the plugin registry name for this monitoring system + +### Conversions +- **`to()`** — Serializes a `PreAggregationType` to string +- **`tryTo()`** — Parses a `PreAggregationType` from string, returning `Expected` + +## Usage Example + +```cpp +#include "numeric_monitoring.h" + +// Record a simple counter with sum pre-aggregation +osquery::monitoring::record( + "osquery.worker.queries.executed", + 42LL, + osquery::monitoring::PreAggregationType::Sum +); + +// Record a latency value, forcing immediate delivery +osquery::monitoring::record( + "osquery.worker.query.latency_ms", + 150LL, + osquery::monitoring::PreAggregationType::P95, + true /* sync */ +); + +// Convert aggregation type to/from string +auto str = osquery::to(osquery::monitoring::PreAggregationType::Sum); +// str == "Sum" + +auto type = osquery::tryTo("P99"); +// type.get() == PreAggregationType::P99 +``` \ No newline at end of file diff --git a/osquery/numeric_monitoring/.plugin_interface.md b/osquery/numeric_monitoring/.plugin_interface.md new file mode 100644 index 00000000000..411756ccf7d --- /dev/null +++ b/osquery/numeric_monitoring/.plugin_interface.md @@ -0,0 +1,39 @@ + +Defines the `NumericMonitoringPlugin` interface, an abstract base class for plugins that integrate with osquery's numeric monitoring system. + +## Key Components + +- **`NumericMonitoringPlugin`** — Inherits from `Plugin`; provides the entry point for numeric monitoring backend implementations (e.g., filesystem-based storage). + - `call(const PluginRequest&, PluginResponse&)` — Overrides the base `Plugin::call` to handle incoming monitoring requests and produce responses. + +## Usage Example + +```c +#include + +namespace osquery { + +class MyMonitoringPlugin : public NumericMonitoringPlugin { + public: + Status call(const PluginRequest& request, PluginResponse& response) override { + // Extract monitoring path and value from request + auto path = request.at("path"); + auto value = request.at("value"); + + // Persist or forward the numeric metric + storeMetric(path, value); + + return Status::success(); + } +}; + +REGISTER(MyMonitoringPlugin, "numeric_monitoring", "my_backend"); + +} // namespace osquery +``` + +## Notes + +- Concrete implementations must override `call()` and register via `REGISTER`. +- The reference implementation is `NumericMonitoringFilesystemPlugin` in `osquery/numeric_monitoring/plugins/filesystem.h`. +- Relies on `osquery::numeric_monitoring` types for point values and pre-aggregation semantics. \ No newline at end of file diff --git a/osquery/numeric_monitoring/.pre_aggregation_cache.md b/osquery/numeric_monitoring/.pre_aggregation_cache.md new file mode 100644 index 00000000000..4220542ae73 --- /dev/null +++ b/osquery/numeric_monitoring/.pre_aggregation_cache.md @@ -0,0 +1,42 @@ + +Pre-aggregation cache for the osquery numeric monitoring system. Provides in-memory buffering and aggregation of monitoring data points before they are flushed to the monitoring backend. + +## Key Components + +### `Point` +Represents a single monitoring observation unit with: +- `path_` — unique identifier/name for the metric sequence +- `value_` — the observed numeric value +- `pre_aggregation_type_` — aggregation strategy (e.g., sum, min, max) +- `time_point_` — timestamp of observation +- `tryToAggregate(const Point&)` — merges a new point into itself if `path_` and `pre_aggregation_type_` match; returns `true` on success, `false` otherwise + +### `PreAggregationCache` +Accumulates `Point` objects and handles deduplication via pre-aggregation: +- `addPoint(Point)` — inserts or aggregates a point into the internal store +- `takePoints()` — drains and returns all buffered points as a `std::vector` +- `size()` — returns current number of buffered points + +Internally uses `points_index_` (path → index map) and `points_` (ordered vector) to efficiently locate existing points for aggregation. + +## Usage Example + +```cpp +#include "pre_aggregation_cache.h" + +using namespace osquery::monitoring; + +PreAggregationCache cache; + +// Add monitoring points +cache.addPoint(Point("cpu.usage", 42.0, PreAggregationType::Sum, now())); +cache.addPoint(Point("cpu.usage", 18.0, PreAggregationType::Sum, now())); +// Same path + type: values are aggregated (e.g., stored as 60.0) + +// Flush buffered points to the monitoring backend +auto points = cache.takePoints(); +for (const auto& p : points) { + sendToBackend(p.path_, p.value_); +} +// Cache is now empty +``` \ No newline at end of file diff --git a/osquery/numeric_monitoring/tests/.numeric_monitoring.md b/osquery/numeric_monitoring/tests/.numeric_monitoring.md new file mode 100644 index 00000000000..125cb70524d --- /dev/null +++ b/osquery/numeric_monitoring/tests/.numeric_monitoring.md @@ -0,0 +1,39 @@ + +Unit test file for osquery's numeric monitoring subsystem, validating pre-aggregation type serialization and buffered/unbuffered metric recording behavior. + +## Key Components + +- **`NumericMonitoringTests`** — GTest fixture that initializes platform, registry, and database before each test +- **`NumericMonitoringInMemoryTestPlugin`** — In-memory stub plugin that captures all incoming `PluginRequest` objects into a static vector for assertion +- **`testAggrTypeToStringAndBack()`** — Helper that round-trips a `PreAggregationType` through string conversion and back, asserting lossless serialization + +## Test Cases + +| Test | What It Validates | +|---|---| +| `PreAggregationTypeToStringAndBack` | Each `PreAggregationType` enum value (`None`, `Sum`, `Min`, `Max`) serializes to and deserializes from the expected string | +| `PreAggregationTypeToStringRecall` | Every enum value below `InvalidTypeUpperLimit` has a non-empty string representation | +| `record_with_buffer` | With pre-aggregation enabled (`pre_aggregation_time=1`), buffered points are flushed and summed correctly; immediate-flush points bypass the buffer | +| `record_without_buffer` | With `pre_aggregation_time=0`, each `record()` call dispatches directly to the plugin without waiting for a flush | + +## Usage Example + +```cpp +// Typical test flow pattern used in this file +FLAGS_enable_numeric_monitoring = true; +FLAGS_numeric_monitoring_plugins = kNameForTestPlugin; +FLAGS_numeric_monitoring_pre_aggregation_time = 1; // enable buffering + +monitoring::record("some.metric.path", + monitoring::ValueType{100}, + monitoring::PreAggregationType::Sum); +monitoring::flush(); // flush buffer to plugin + +// Assert plugin received the aggregated value +EXPECT_EQ(1, NumericMonitoringInMemoryTestPlugin::points.size()); + +Dispatcher::stopServices(); +Dispatcher::joinServices(); // always clean up dispatcher +``` + +> **Note:** Tests save and restore all modified `FLAGS_*` values to avoid cross-test pollution, and always call `Dispatcher::stopServices()` / `joinServices()` to cleanly tear down background threads. \ No newline at end of file diff --git a/osquery/numeric_monitoring/tests/.pre_aggregation_cache.md b/osquery/numeric_monitoring/tests/.pre_aggregation_cache.md new file mode 100644 index 00000000000..0a455b6edaf --- /dev/null +++ b/osquery/numeric_monitoring/tests/.pre_aggregation_cache.md @@ -0,0 +1,46 @@ + +Unit test file for the `PreAggregationCache` and `PreAggregationPoint` components in osquery's numeric monitoring subsystem, validating aggregation logic across all supported aggregation types. + +## Key Components + +### Test Suites + +| Test Suite | Test Case | Purpose | +|---|---|---| +| `PreAggregationPoint` | `tryToUpdate_same_path_none` | Verifies `None` type never aggregates | +| `PreAggregationPoint` | `tryToUpdate_same_path_different_types` | Exhaustively validates aggregation eligibility across all type combinations | +| `PreAggregationPoint` | `tryToUpdate_different_path_sum` | Confirms different paths never aggregate together | +| `PreAggregationPoint` | `tryToUpdate_sum` | Validates sum aggregation and timestamp retention | +| `PreAggregationPoint` | `tryToUpdate_min` | Validates minimum value selection | +| `PreAggregationPoint` | `tryToUpdate_max` | Validates maximum value selection | +| `PreAggregationCache` | `life_cycle` | Full lifecycle test: insert, aggregate, drain, and reuse | + +### Covered Types + +- **Non-aggregatable**: `None`, `Avg`, `Stddev`, `P10`, `P50`, `P95`, `P99` +- **Aggregatable**: `Sum`, `Min`, `Max` + +## Usage Example + +```cpp +// Simulated behavior tested by this file + +// Sum aggregation: 399 + (-8) = 391, keeps latest timestamp +auto prev = monitoring::Point(path, 399, monitoring::PreAggregationType::Sum, now); +auto next = monitoring::Point(path, -8, monitoring::PreAggregationType::Sum, earlier); +prev.tryToAggregate(next); // prev.value_ == 391, prev.time_point_ == now + +// Cache lifecycle: aggregatable types collapse, None type accumulates +auto cache = monitoring::PreAggregationCache{}; +cache.addPoint(...Sum...); // size = 1 +cache.addPoint(...Sum...); // size = 1 (aggregated in place) +cache.addPoint(...None...); // size = 2 (never aggregated) +auto points = cache.takePoints(); // drains cache, returns all points +``` + +## Key Behaviors Verified + +- `tryToAggregate` returns `false` for mismatched paths, mismatched types, or non-aggregatable types +- `Sum` accumulates values; `Min`/`Max` select the extreme value +- The **most recent timestamp** is always retained after aggregation +- `takePoints()` atomically drains the cache, resetting its size to zero \ No newline at end of file diff --git a/osquery/process/.process.md b/osquery/process/.process.md new file mode 100644 index 00000000000..b47136fb39a --- /dev/null +++ b/osquery/process/.process.md @@ -0,0 +1,64 @@ + +Platform-agnostic process management abstraction layer for osquery, providing a unified interface for process lifecycle operations (launch, monitor, terminate) across Windows and POSIX systems. + +## Key Components + +### `PlatformProcess` Class +Non-copyable process wrapper with cross-platform handle management: + +| Method | Description | +|--------|-------------| +| `pid()` / `nativeHandle()` | Returns process ID or native handle (`HANDLE` on Windows, `pid_t` on POSIX) | +| `kill()` / `killGracefully()` | Hard or graceful process termination | +| `cleanup(timeout)` | Waits for process cleanup after graceful shutdown | +| `checkStatus(status)` | Returns `ProcessState` enum value | +| `launchWorker(...)` | Spawns a worker subprocess | +| `launchExtension(...)` | Spawns an osquery extension process | +| `getCurrentProcess()` / `getLauncherProcess()` | Factory methods for well-known processes | + +### `ProcessState` Enum +```text +PROCESS_ERROR = -1 +PROCESS_STILL_ALIVE = 0 +PROCESS_EXITED +PROCESS_STATE_CHANGE (POSIX only) +``` + +### `SecurityDescriptor` *(Windows only)* +RAII wrapper for `PSECURITY_DESCRIPTOR` allocated via `LocalAlloc`, automatically calls `LocalFree` on destruction. + +### Free Functions + +| Function | Description | +|----------|-------------| +| `platformGetUid()` | Current user ID (UID/RID) | +| `sleepFor(msec)` | Millisecond sleep via `std::this_thread` | +| `platformModuleOpen/GetSymbol/GetError/Close` | Cross-platform dynamic library loading | +| `isLauncherProcessDead(launcher)` | Worker-side launcher liveness check | +| `setToBackgroundPriority()` | Lowers scheduling priority | +| `platformGetPid()` / `platformGetTid()` | Current process/thread IDs | + +## Usage Example + +```cpp +#include + +// Launch a worker subprocess +auto worker = osquery::PlatformProcess::launchWorker( + "/usr/bin/osqueryd", argc, argv); + +if (worker && worker->isValid()) { + int status = 0; + auto state = worker->checkStatus(status); + + if (state == osquery::PROCESS_STILL_ALIVE) { + worker->killGracefully(); + worker->cleanup(std::chrono::milliseconds(3000)); + } +} + +// Dynamic module loading +auto handle = osquery::platformModuleOpen("/path/to/plugin.so"); +auto* sym = osquery::platformModuleGetSymbol(handle, "myFunction"); +osquery::platformModuleClose(handle); +``` \ No newline at end of file diff --git a/osquery/process/posix/.process.md b/osquery/process/posix/.process.md new file mode 100644 index 00000000000..a57b828d310 --- /dev/null +++ b/osquery/process/posix/.process.md @@ -0,0 +1,45 @@ + +POSIX process management implementation for osquery's `PlatformProcess` class, handling process lifecycle operations including launch, signal delivery, and status checking on Unix-based systems. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `PlatformProcess` | Class | Wraps a POSIX PID with move semantics and RAII-safe lifecycle | +| `kill()` | Method | Sends `SIGKILL` to the managed process | +| `killGracefully()` | Method | Sends `SIGTERM` for graceful shutdown | +| `warnResourceLimitHit()` | Method | Sends `SIGUSR1` to signal resource limit breach | +| `checkStatus()` | Method | Non-blocking `waitpid` poll returning `ProcessState` enum | +| `getCurrentProcess()` | Static | Returns a `PlatformProcess` wrapping `getpid()` | +| `getLauncherProcess()` | Static | Returns a `PlatformProcess` wrapping `getppid()` | +| `launchWorker()` | Static | Forks and `execve`s a worker, sets `OSQUERY_WORKER` env var | +| `launchExtension()` | Static | Forks an extension process with reset signal handlers and CLI args | +| `launchTestPythonScript()` | Static | Forks a Python interpreter using `OSQUERY_PYTHON_INTERPRETER_PATH` | + +## Usage Example + +```cpp +// Launch a worker process +auto worker = PlatformProcess::launchWorker("/usr/bin/osqueryd", argc, argv); +if (!worker || !worker->isValid()) { + LOG(ERROR) << "Failed to launch worker"; +} + +// Poll process state (non-blocking) +int exit_code = 0; +ProcessState state = worker->checkStatus(exit_code); +if (state == PROCESS_EXITED) { + LOG(INFO) << "Worker exited with code: " << exit_code; +} else if (state == PROCESS_STILL_ALIVE) { + // Optionally terminate + worker->killGracefully(); // SIGTERM + // or + worker->kill(); // SIGKILL +} + +// Get current and parent process handles +auto self = PlatformProcess::getCurrentProcess(); +auto parent = PlatformProcess::getLauncherProcess(); +``` + +> **Note:** `launchExtension` resets all signal handlers to `SIG_DFL` in the child before `execve`, preventing inherited handler state from the parent process. \ No newline at end of file diff --git a/osquery/process/posix/.process_ops.md b/osquery/process/posix/.process_ops.md new file mode 100644 index 00000000000..8abf3c9beb1 --- /dev/null +++ b/osquery/process/posix/.process_ops.md @@ -0,0 +1,51 @@ + +POSIX-specific implementations of cross-platform process and module management utilities used by the osquery framework on Unix-like systems. + +## Key Components + +| Function | Description | +|---|---| +| `platformGetUid()` | Returns the effective user ID of the current process via `getuid()` | +| `isLauncherProcessDead()` | Checks if the launcher process is still alive by comparing `getppid()` against the stored native handle | +| `platformModuleOpen()` | Opens a shared library using `dlopen()` with `RTLD_NOW \| RTLD_LOCAL` flags | +| `platformModuleGetSymbol()` | Resolves a symbol from an open module handle via `dlsym()` | +| `platformModuleGetError()` | Returns the last dynamic linking error string from `dlerror()` | +| `platformModuleClose()` | Closes a module handle via `dlclose()`, returns `true` on success | +| `setToBackgroundPriority()` | Lowers the process group's scheduling priority to `10` via `setpriority()` | +| `isUserAdmin()` | Returns `true` if the current process is running as root (`uid == 0`) | +| `platformGetPid()` | Returns the current process ID cast to `int` | +| `platformGetTid()` | Returns a hashed representation of the current thread ID | + +## Usage Example + +```cpp +#include + +// Check admin privileges before privileged operations +if (!isUserAdmin()) { + LOG(WARNING) << "Operation requires root privileges"; +} + +// Load a plugin module at runtime +ModuleHandle mod = platformModuleOpen("/usr/lib/osquery/plugin.so"); +if (mod == nullptr) { + LOG(ERROR) << "Module load failed: " << platformModuleGetError(); + return; +} + +// Resolve and invoke an exported symbol +auto* initFn = reinterpret_cast( + platformModuleGetSymbol(mod, "plugin_init") +); +if (initFn) { + initFn(); +} + +// Reduce scheduling impact when running background tasks +setToBackgroundPriority(); + +// Clean up +platformModuleClose(mod); +``` + +> **Note:** This file provides POSIX-only implementations. Windows equivalents are handled separately using `LoadLibrary`/`GetProcAddress` in the platform abstraction layer. \ No newline at end of file diff --git a/osquery/process/windows/.process.md b/osquery/process/windows/.process.md new file mode 100644 index 00000000000..637ed7e0d73 --- /dev/null +++ b/osquery/process/windows/.process.md @@ -0,0 +1,48 @@ + +Windows-specific implementation of the `PlatformProcess` abstraction for osquery, providing process lifecycle management, launching, and status checking using the Win32 API. + +## Key Components + +### Constructors & Operators +- `PlatformProcess(PlatformPidType id)` — duplicates an existing Windows `HANDLE` +- `PlatformProcess(pid_t pid)` — opens a process by numeric PID via `OpenProcess` +- Move constructor/assignment and equality operators based on `GetProcessId` + +### Core Methods +| Method | Description | +|---|---| +| `pid()` | Returns the integer PID from the native handle | +| `kill()` | Terminates the process via `TerminateProcess` | +| `killGracefully()` | Delegates to `kill()` (graceful shutdown not implemented on Windows) | +| `checkStatus(int&)` | Polls exit code via `GetExitCodeProcess`; returns `PROCESS_STILL_ALIVE`, `PROCESS_EXITED`, or `PROCESS_ERROR` | + +### Static Factory Methods +| Method | Description | +|---|---| +| `getCurrentProcess()` | Returns a `PlatformProcess` for the calling process | +| `getLauncherProcess()` | Reads `OSQUERY_LAUNCHER` env var (a hex-encoded `HANDLE`) to retrieve the parent launcher | +| `launchWorker(...)` | Spawns a worker child process via `CreateProcess`, injecting `OSQUERY_WORKER` and `OSQUERY_LAUNCHER` environment variables | +| `launchExtension(...)` | Launches an osquery extension; routes `.ext` files to `launchTestPythonScript` | +| `launchTestPythonScript(...)` | Spawns a Python interpreter using the `OSQUERY_PYTHON_INTERPRETER_PATH` env var | + +## Usage Example + +```cpp +// Launch a worker process +auto worker = PlatformProcess::launchWorker(exec_path, argc, argv); +if (worker && worker->isValid()) { + int status = 0; + ProcessState state = worker->checkStatus(status); + if (state == PROCESS_EXITED) { + // handle exit + } +} + +// Open an existing process by PID +PlatformProcess proc(static_cast(1234)); +if (proc.isValid()) { + proc.kill(); +} +``` + +> **Note:** `killGracefully()` and `warnResourceLimitHit()` are not fully implemented on Windows — both are no-ops or simple delegates to `kill()`. \ No newline at end of file diff --git a/osquery/process/windows/.process_ops.md b/osquery/process/windows/.process_ops.md new file mode 100644 index 00000000000..ff4b0dfb6f2 --- /dev/null +++ b/osquery/process/windows/.process_ops.md @@ -0,0 +1,48 @@ + +Windows-specific implementation of process and module management utilities for the osquery platform layer. + +## Key Components + +| Function | Description | +|---|---| +| `platformGetUid()` | Retrieves the current process user's RID (relative identifier) from the Windows token | +| `isLauncherProcessDead()` | Checks whether a launcher `PlatformProcess` has exited by inspecting its exit code | +| `platformModuleOpen()` | Loads a DLL via `LoadLibraryExW`, restricted to `SYSTEM32` search path | +| `platformModuleGetSymbol()` | Resolves a named symbol/export from a loaded module handle | +| `platformModuleGetError()` | Returns the last Win32 error as a formatted string | +| `platformModuleClose()` | Unloads a previously opened module handle | +| `setToBackgroundPriority()` | Lowers the current process to background priority class | +| `isUserAdmin()` | Checks if the current process token has elevation (admin privileges) | +| `platformGetPid()` | Returns the current process ID | +| `platformGetTid()` | Returns the current thread ID | + +## Usage Example + +```cpp +#include + +// Check current user identity and privilege level +uint32_t uid = osquery::platformGetUid(); +bool isAdmin = osquery::isUserAdmin(); + +// Load and use a system module +ModuleHandle mod = osquery::platformModuleOpen("example.dll"); +if (mod != nullptr) { + auto* fn = osquery::platformModuleGetSymbol(mod, "MyExport"); + if (fn == nullptr) { + LOG(WARNING) << osquery::platformModuleGetError(); + } + osquery::platformModuleClose(mod); +} + +// Check if a launcher process has exited +osquery::PlatformProcess launcher(existingHandle); +if (osquery::isLauncherProcessDead(launcher)) { + LOG(INFO) << "Launcher has terminated"; +} + +// Reduce scheduling impact +osquery::setToBackgroundPriority(); +``` + +> **Note:** This file is Windows-only. All Win32 APIs used (`OpenProcessToken`, `GetExitCodeProcess`, `LoadLibraryExW`, etc.) require a Windows build environment. The `LOAD_LIBRARY_SEARCH_SYSTEM32` flag in `platformModuleOpen` mitigates DLL hijacking risks. \ No newline at end of file diff --git a/osquery/profiler/.code_profiler.md b/osquery/profiler/.code_profiler.md new file mode 100644 index 00000000000..bc6e67c9a20 --- /dev/null +++ b/osquery/profiler/.code_profiler.md @@ -0,0 +1,42 @@ + +Lightweight RAII-based code profiling utility for measuring and reporting execution time of named code sections within the osquery framework. + +## Key Components + +### `CodeProfiler` (class) +A `final` RAII class that begins timing on construction and records/reports elapsed time on destruction. + +| Member | Description | +|---|---| +| `CodeProfiler(initializer_list)` | Constructor — accepts one or more label names to identify the profiled block | +| `~CodeProfiler()` | Destructor — stops timing and logs results for all associated names | +| `CodeProfilerData` | Private nested class holding the internal timing state (opaque implementation) | +| `names_` | Stored list of label strings associated with this profiling session | +| `code_profiler_data_` | `unique_ptr` to the opaque timing data (PIMPL pattern) | + +## Usage Example + +```cpp +#include "code_profiler.h" + +void someExpensiveOperation() { + // Timer starts here + osquery::CodeProfiler profiler({"my_module", "expensive_op"}); + + // ... perform work ... + + // Timer stops and results are logged when profiler goes out of scope +} + +// Single label usage +void anotherOperation() { + osquery::CodeProfiler profiler({"database_query"}); + runQuery(); +} +``` + +## Notes + +- Uses the **PIMPL idiom** (`CodeProfilerData` forward-declared, held via `unique_ptr`) to hide timing implementation details and minimize header dependencies. +- **Non-copyable/non-movable** by default due to `unique_ptr` member — intended for stack-only, scope-bound usage. +- Supports **multiple labels** per profiler instance, allowing a single timed block to be attributed to several categories simultaneously. \ No newline at end of file diff --git a/osquery/profiler/posix/.code_profiler.md b/osquery/profiler/posix/.code_profiler.md new file mode 100644 index 00000000000..c6f1d1489a3 --- /dev/null +++ b/osquery/profiler/posix/.code_profiler.md @@ -0,0 +1,43 @@ + +RAII-based code profiler that measures CPU time, memory usage, I/O load, and wall-clock duration for named code sections using `getrusage` system calls. + +## Key Components + +### `CodeProfiler` (public class) +The main profiling interface. Captures resource usage at construction and computes deltas at destruction, emitting metrics via `monitoring::record`. + +- **Constructor**: `CodeProfiler(const std::initializer_list& names)` — accepts one or more metric namespace names; snapshots initial `rusage` and wall time. +- **Destructor**: Captures end-state `rusage`, computes differences, and records all metrics automatically when the profiled scope exits. + +### `CodeProfilerData` (private inner class) +Holds the snapshot state — a `rusage` struct (wrapped in `Expected`) and a `std::chrono::steady_clock` wall-time point. + +### Metrics Recorded (per name) + +| Metric | Description | +|---|---| +| `rss.max.kb` | Peak RSS at end | +| `rss.increase.kb` | RSS growth over scope | +| `input.load` / `output.load` | Block I/O counts | +| `time.user.millis` | User-mode CPU time | +| `time.system.millis` | Kernel-mode CPU time | +| `time.total.millis` | Combined CPU time | +| `time.wall.millis` | Wall-clock duration | + +### Platform Notes +- Uses `RUSAGE_THREAD` on Linux for per-thread granularity; falls back to `RUSAGE_SELF` on other POSIX platforms. +- Each metric is recorded with both `Min` and `Sum` pre-aggregation types. + +## Usage Example + +```cpp +#include + +void runExpensiveQuery() { + // Profiling begins here; metrics emitted on scope exit + osquery::CodeProfiler profiler({"mymodule.queries", "global.metrics"}); + + // ... perform work ... + +} // <- rusage delta and wall time recorded automatically here +``` \ No newline at end of file diff --git a/osquery/profiler/windows/.code_profiler.md b/osquery/profiler/windows/.code_profiler.md new file mode 100644 index 00000000000..a5300f09c8c --- /dev/null +++ b/osquery/profiler/windows/.code_profiler.md @@ -0,0 +1,27 @@ + +Measures wall-clock execution time for named code blocks and records the elapsed duration as a monitoring metric when the profiler object goes out of scope. + +## Key Components + +- **`CodeProfiler`** — RAII-style profiler class; starts timing on construction and records elapsed milliseconds on destruction +- **`CodeProfilerData`** — Private inner class that captures `std::chrono::steady_clock::now()` at construction time +- **`record()` (anonymous namespace)** — Helper that emits a metric for each provided name via `monitoring::record()` with `PreAggregationType::None` + +## Usage Example + +```cpp +#include + +void runExpensiveQuery() { + // Timer starts here; metric is recorded when profiler goes out of scope + CodeProfiler profiler({"my_plugin", "expensive_query"}); + + // ... code being measured ... + +} // Destructor fires → records "my_plugin..time.wall.millis" + // and "expensive_query..time.wall.millis" +``` + +Multiple names can be provided; each receives its own metric entry suffixed with `.time.wall.millis`. + +> **Note:** Timing uses `std::chrono::steady_clock` (monotonic), making measurements immune to system clock adjustments. The metric key format is `..time.wall.millis` with `PreAggregationType::None`, meaning values are forwarded raw without server-side aggregation. \ No newline at end of file diff --git a/osquery/registry/.registry.md b/osquery/registry/.registry.md new file mode 100644 index 00000000000..21e0ce378ec --- /dev/null +++ b/osquery/registry/.registry.md @@ -0,0 +1,28 @@ + +Convenience header that re-exports the core osquery plugin registry infrastructure through a single include. + +## Key Components + +Transitively includes the following headers: + +| Header | Purpose | +|---|---| +| `osquery/core/plugins/plugin.h` | Base `Plugin` class and plugin lifecycle interfaces | +| `osquery/registry/registry_factory.h` | `RegistryFactory` — singleton for creating and managing plugin registries | +| `osquery/registry/registry_interface.h` | `RegistryInterface` — abstract base for all registry types | + +## Usage Example + +```c +// Instead of including three separate headers: +#include + +// Register a custom plugin via the factory +REGISTER(MyPlugin, "my_registry", "my_plugin"); + +// Access the registry factory singleton +auto& factory = RegistryFactory::get(); +auto plugin = factory.plugin("my_registry", "my_plugin"); +``` + +> **Note:** Prefer including `registry.h` over its constituent headers directly. If you only need a specific subset (e.g., just `RegistryInterface`), include the targeted header to reduce compilation overhead. \ No newline at end of file diff --git a/osquery/registry/.registry_factory.md b/osquery/registry/.registry_factory.md new file mode 100644 index 00000000000..908d83f9544 --- /dev/null +++ b/osquery/registry/.registry_factory.md @@ -0,0 +1,41 @@ + +Defines the `RegistryFactory` singleton and supporting registration infrastructure for managing osquery plugin registries, including static call dispatch, broadcast serialization, and extension route tracking. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `RegistryFactory` | Singleton class | Central manager for all plugin registries; provides add, lookup, call, alias, and broadcast operations | +| `RegistryBroadcast` | Type alias | `std::map` representing all routes from all registries | +| `Registry` | Type alias | Convenience alias for `RegistryFactory` | +| `registries::AR` | Template class | Auto-registers a registry type at startup | +| `registries::AP

    ` | Template class | Auto-registers a plugin instance into an existing registry | +| `registries::RI` | Template struct | RAII trigger for registry auto-registration | +| `registries::PI

    ` | Template struct | RAII trigger for plugin auto-registration | +| `CREATE_REGISTRY` | Macro | Registers a registry class under a given name at static init time | +| `CREATE_LAZY_REGISTRY` | Macro | Same as `CREATE_REGISTRY` but marks the registry as lazy (skips `setUp`) | +| `REGISTER` | Macro | Registers a plugin class into a named registry | +| `REGISTER_INTERNAL` | Macro | Same as `REGISTER` but marks the plugin as optional/internal | + +## Usage Example + +```cpp +// Define and register a custom registry +class MyRegistry : public RegistryPlugin { /* ... */ }; +CREATE_REGISTRY(MyRegistry, "my_registry"); + +// Register a plugin into that registry +class MyPlugin : public Plugin { /* ... */ }; +REGISTER(MyPlugin, "my_registry", "my_plugin"); + +// Call a registered plugin at runtime +PluginRequest request = {{"action", "run"}}; +PluginResponse response; +Status s = RegistryFactory::call("my_registry", "my_plugin", request, response); + +// Check existence and use the active plugin +if (RegistryFactory::get().exists("my_registry", "my_plugin")) { + RegistryFactory::get().setActive("my_registry", "my_plugin"); + RegistryFactory::call("my_registry", request, response); +} +``` \ No newline at end of file diff --git a/osquery/registry/.registry_interface.md b/osquery/registry/.registry_interface.md new file mode 100644 index 00000000000..5d52f255158 --- /dev/null +++ b/osquery/registry/.registry_interface.md @@ -0,0 +1,50 @@ + +Defines the core plugin registry abstraction layer for osquery, providing interfaces for registering, managing, and routing calls to plugins across both local and external (extension) processes. + +## Key Components + +### Type Aliases +- **`RegistryRoutes`** — Map of plugin name → `PluginResponse` for broadcasting route info +- **`RouteUUID`** — `uint64_t` identifier for extension connections +- **`AddExternalCallback`** / **`RemoveExternalCallback`** — Function signatures for extension lifecycle hooks +- **`RegistryInterfaceRef`** — `shared_ptr` + +### `RegistryInterface` +Abstract base class (non-copyable) managing plugin lifecycle. Key methods: + +| Method | Description | +|---|---| +| `add()` | Register a plugin (pure virtual) | +| `remove()` | Unregister a plugin by name | +| `call()` | Dispatch a request to a named plugin | +| `addExternal()` / `removeExternal()` | Manage plugins from remote extensions by UUID | +| `getRoutes()` | Broadcast route table for extensions | +| `setUp()` | Initialize all registered plugins | +| `setActive()` | Designate a default plugin for nameless calls | + +### `RegistryType` +Templated concrete registry that downcasts `PluginRef` to the specific `PluginType`. Provides type-safe `add()` and `plugin()` accessors, plus trampoline methods into `PluginType::addExternal` / `removeExternal`. + +### `AutoRegisterInterface` +Static registration utility for auto-loading registries and plugins at startup via `registries()` / `plugins()` collections. Consumed by `registryAndPluginInit()`. + +## Usage Example + +```cpp +// Define a typed registry for a custom plugin type +class MyRegistry : public RegistryType { + public: + explicit MyRegistry() : RegistryType("my_registry", true) {} +}; + +// Add and invoke a plugin +auto registry = std::make_shared(); +registry->add("my_plugin", std::make_shared()); + +PluginRequest request = {{"action", "query"}}; +PluginResponse response; +auto status = registry->call("my_plugin", request, response); + +// Retrieve route info for extension broadcasting +RegistryRoutes routes = registry->getRoutes(); +``` \ No newline at end of file diff --git a/osquery/registry/tests/.registry.md b/osquery/registry/tests/.registry.md new file mode 100644 index 00000000000..721315f3567 --- /dev/null +++ b/osquery/registry/tests/.registry.md @@ -0,0 +1,43 @@ + +Unit test suite for osquery's plugin registry system, validating plugin registration, lifecycle management, API routing, and exception handling using Google Test. + +## Key Components + +| Class/Symbol | Role | +|---|---| +| `TestCoreRegistry` | Isolated `RegistryFactory` subclass used in place of the production `Registry` during tests | +| `CatPlugin` / `DogPlugin` | Minimal `Plugin` stubs representing two distinct plugin types | +| `HouseCat` / `Doge` / `BadDoge` | Concrete plugin variants used to test setUp, state, and failure paths | +| `CatRegistry` | Manual `RegistryType` subclass demonstrating anonymous (non-broadcast) registry creation | +| `WidgetPlugin` / `SpecialWidget` | Plugin pair that exercises `routeInfo()`, `getRoutes()`, `getBroadcast()`, and `call()` API surface | +| `RegistryTests` | `testing::Test` fixture that bootstraps `cat` and `dog` registry types once via a static guard | + +## Test Coverage + +| Test Case | What It Validates | +|---|---| +| `test_registry` | Direct registry add, dedup, count, plugin lookup, setUp, iteration | +| `test_auto_factory` | Named registry lookup via `TestCoreRegistry::get()`, plugin identity (`cat == same_cat`) | +| `test_auto_registries` | Dog registry plugin registration and count after setUp | +| `test_persistent_registries` | Registry state persists across test cases (static singleton behaviour) | +| `test_registry_exceptions` | `BadDoge` setUp failure does not block registration; unknown registry name throws `std::runtime_error` | +| `test_registry_api` | Route info propagation, broadcast map shape, `RegistryFactory::call()` request/response round-trip | +| `test_real_registry` | Production `Registry::get()` contains at least one registered plugin | + +## Usage Example + +```cpp +// Registering a plugin type and adding a plugin instance +auto cat_registry = TestCoreRegistry::get().registry("cat"); +cat_registry->add("house", std::make_shared()); +cat_registry->setUp(); + +// Calling a plugin via the registry API +PluginRequest request; +PluginResponse response; +auto status = TestCoreRegistry::call("widgets", "special", request, response); + +// Querying broadcast state across all registries +auto broadcast = TestCoreRegistry::get().getBroadcast(); +// broadcast["widgets"]["special"][0]["name"] == "special" +``` \ No newline at end of file diff --git a/osquery/remote/.http_client.md b/osquery/remote/.http_client.md new file mode 100644 index 00000000000..54fba76226a --- /dev/null +++ b/osquery/remote/.http_client.md @@ -0,0 +1,52 @@ + +HTTP client interface providing synchronous HTTP/HTTPS request capabilities built on Boost.Beast and OpenSSL, used by osquery's remote communication subsystem. + +## Key Components + +### `Client` +The primary HTTP client class supporting `GET`, `POST`, `PUT`, `HEAD`, and `DELETE` operations over plain or TLS-encrypted connections. + +### `Client::Options` +A fluent builder for configuring client behavior. Key settings include: + +| Option | Description | +|---|---| +| `ssl_connection(bool)` | Enable TLS wrapping | +| `timeout(int)` | Request timeout in seconds | +| `always_verify_peer(bool)` | Enforce peer certificate validation | +| `follow_redirects(bool)` | Auto-follow HTTP redirects | +| `proxy_hostname(string)` | Route through a proxy | +| `openssl_certificate(string)` | Server certificate for pinning | +| `openssl_ciphers(string)` | Allowed TLS cipher suites | + +### `HTTP_Request` / `HTTP_Response` +Template wrappers extending Boost.Beast request/response types with URI parsing. `HTTP_Request` exposes `remoteHost()`, `remotePort()`, `remotePath()`, and `protocol()` helpers. + +### Type Aliases +- `Request` → `HTTP_Request` +- `Response` → `HTTP_Response` + +### Constants +- `kInstanceMetadataAuthority` — IP `169.254.169.254` for cloud metadata services (EC2, Azure, etc.) + +## Usage Example + +```cpp +#include + +osquery::http::Client::Options opts; +opts.ssl_connection(true) + .always_verify_peer(true) + .timeout(10) + .openssl_certificate("/etc/ssl/certs/ca-certificates.crt"); + +osquery::http::Client client(opts); + +osquery::http::Request req("https://example.com/api/data"); +auto response = client.get(req); + +osquery::http::Request post_req("https://example.com/api/submit"); +auto result = client.post(post_req, R"({"key":"value"})", "application/json"); +``` + +> **Note:** SSL2, SSL3, and MD5 are explicitly disabled at compile time. On Windows, Boost.ASIO thread cleanup (`set_terminate_threads`) is initialized once via `std::call_once` to prevent resource leaks. \ No newline at end of file diff --git a/osquery/remote/.requests.md b/osquery/remote/.requests.md new file mode 100644 index 00000000000..9b517e57595 --- /dev/null +++ b/osquery/remote/.requests.md @@ -0,0 +1,54 @@ + +Defines the core abstractions for osquery's remote communication layer, providing abstract base classes for transport and serialization mechanisms along with a templated `Request` class that composes them. + +## Key Components + +| Component | Type | Description | +|---|---|---| +| `compressString` | Function | GZip-compresses a string; applied post-serialization before transport | +| `Transport` | Abstract class | Base for transport implementations (HTTP, WebSockets, etc.) | +| `Serializer` | Abstract class | Base for serialization formats (JSON, XML, etc.) | +| `Request` | Template class | Composes a transport and serializer to execute remote calls | + +### `Transport` Interface + +| Method | Description | +|---|---| +| `setDestination()` | Sets the remote URI | +| `setSerializer()` | Binds a serializer instance | +| `sendRequest()` | Pure virtual — no-param request | +| `sendRequest(params, compress)` | Pure virtual — parameterized request with optional GZip | +| `getResponseStatus()` | Returns the response `Status` | +| `getResponseParams()` | Returns response body as `JSON` | +| `setOption(name, value)` | Sets transport-specific options | + +### `Serializer` Interface + +| Method | Description | +|---|---| +| `getContentType()` | Returns HTTP content-type string | +| `serialize(json, out)` | Encodes a `JSON` object to string | +| `deserialize(string, out)` | Decodes a string into a `JSON` object | + +## Usage Example + +```cpp +// Instantiate a request with concrete transport and serializer types +osquery::Request req("https://example.com/api"); + +// Set options (e.g., enable compression) +req.setOption("compress", true); + +// Send a parameterized request +osquery::JSON params; +params.add("key", "value"); +auto status = req.call(params); + +// Retrieve the response +osquery::JSON response; +if (req.getResponse(response).ok()) { + // process response.doc() +} +``` + +> **Note:** `Request` has a private constructor accepting a custom `TTransport` shared pointer, reserved for unit testing via `FRIEND_TEST` grants to `TLSTransportsTests`. \ No newline at end of file diff --git a/osquery/remote/.uri.md b/osquery/remote/.uri.md new file mode 100644 index 00000000000..176886f982c --- /dev/null +++ b/osquery/remote/.uri.md @@ -0,0 +1,47 @@ + +A URI parsing utility class (derived from folly/uri) that breaks a URI string into its component parts: scheme, authority, host, port, path, query, and fragment. + +## Key Components + +### `Uri` Class (`osquery` namespace) + +| Member | Description | +|--------|-------------| +| `Uri(const std::string& str)` | Constructor — parses a URI string; throws `std::invalid_argument` on failure | +| `scheme()` | Returns the lowercased scheme (e.g. `"http"`) | +| `host()` | Returns host, with square brackets for IPv6 (e.g. `"[::1]"`) | +| `hostname()` | Returns host without brackets — safe for `getaddrinfo()` and similar APIs | +| `port()` | Returns the port as `uint16_t` | +| `path()` | Returns the URI path component | +| `query()` | Returns the raw query string | +| `fragment()` | Returns the fragment (anchor) | +| `authority()` | Returns the combined authority string (host + port) | +| `setPort(uint16_t)` | Overrides the port and marks authority as present | +| `getQueryParams()` | Parses and returns query string as `vector>` — **not thread-safe on first call** | + +> **Note:** Component parts are **not** percent-decoded. Use `uriUnescape()` manually on authority/path, and `uriUnescape(..., UriEscapeMode::QUERY)` on query parameters. + +## Usage Example + +```cpp +#include "uri.h" + +try { + osquery::Uri uri("http://user:pass@www.example.com:8080/foo/bar?key=val#anchor"); + + uri.scheme(); // "http" + uri.hostname(); // "www.example.com" (safe for getaddrinfo) + uri.host(); // "www.example.com" + uri.port(); // 8080 + uri.path(); // "/foo/bar" + uri.query(); // "key=val" + uri.fragment(); // "anchor" + + // Parse query parameters + for (const auto& [key, value] : uri.getQueryParams()) { + // key="key", value="val" + } +} catch (const std::invalid_argument& e) { + // Handle malformed URI +} +``` \ No newline at end of file diff --git a/osquery/remote/.utility.md b/osquery/remote/.utility.md new file mode 100644 index 00000000000..a1b28dfaf28 --- /dev/null +++ b/osquery/remote/.utility.md @@ -0,0 +1,47 @@ + +Helper class providing TLS request utilities for osquery TLS plugins, including URI construction with OpenFrame mode support and retry logic for HTTP(S) requests. + +## Key Components + +### Flags Declared +| Flag | Type | Purpose | +|------|------|---------| +| `tls_hostname` | string | Base hostname for TLS requests | +| `tls_node_api` | bool | Treat clients as nodes in the API | +| `tls_secret_always` | bool | Always append enroll secret to URI | +| `tls_enroll_override` | string | Override key for enroll secret param | +| `disable_reenrollment` | bool | Prevent automatic re-enrollment | +| `openframe_mode` | bool | Enables OpenFrame URI prefix injection | +| `openframe_token` | string | Authentication token for OpenFrame | + +### `TLSRequestHelper` Class + +A non-copyable static helper class with the following methods: + +- **`makeURI(endpoint)`** — Builds a full HTTPS URI from `tls_hostname` + optional OpenFrame prefix (`/tools/agent/fleetmdm-server`) + node key + endpoint +- **`go(uri, params, output)`** — Core POST/GET request dispatcher; injects `node_key`, handles compression (`_compress`), verb override (`_verb`), and `node_invalid` rejection +- **`go(uri, output)`** — GET-only shorthand; no params required +- **`go(uri, params, output, attempts)`** — Retry variant with exponential backoff (`i²` seconds), shutdown-aware via `waitTimeoutOrShutdown` + +## Usage Example + +```cpp +// Build a URI and send a POST request with retry +auto uri = TLSRequestHelper::makeURI("/api/v1/config"); + +JSON params; +params.add("key", "value"); + +std::string output; +Status s = TLSRequestHelper::go(uri, params, output, 3); + +if (!s.ok()) { + LOG(WARNING) << "Request failed: " << s.getMessage(); +} + +// Simple GET request (no params) +JSON result; +Status s2 = TLSRequestHelper::go(uri, result); +``` + +> **OpenFrame mode**: When `--openframe_mode` is enabled, all URIs are automatically prefixed with `/tools/agent/fleetmdm-server`, routing requests through the OpenFrame platform layer. \ No newline at end of file diff --git a/osquery/remote/enroll/.enroll.md b/osquery/remote/enroll/.enroll.md new file mode 100644 index 00000000000..556a54703da --- /dev/null +++ b/osquery/remote/enroll/.enroll.md @@ -0,0 +1,42 @@ + +Header defining osquery's enrollment plugin interface and utilities for node authentication and secret management in distributed osquery deployments. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `FLAGS_disable_enrollment` | Flag | Boolean flag to disable all enrollment features | +| `kEnrollHostDetails` | `const std::set` | Table names used to populate host identification details sent during enrollment | +| `EnrollPlugin` | Abstract class | Base class for all enrollment plugins; extends `Plugin` | +| `EnrollPlugin::call()` | Method | Routes incoming `PluginRequest` actions to the appropriate handler | +| `EnrollPlugin::enroll()` | Pure virtual | Implemented by subclasses to return a node secret/key | +| `EnrollPlugin::genHostDetails()` | Protected method | Builds a JSON object from `kEnrollHostDetails` table data | +| `getNodeKey()` | Free function | Retrieves a cached node key from RocksDB or triggers enrollment | +| `clearNodeKey()` | Free function | Removes the existing node key from persistent storage | +| `getEnrollSecret()` | Free function | Reads and returns the deployment enrollment secret from disk | + +## Usage Example + +```cpp +// Implement a custom enroll plugin +class MyEnrollPlugin : public EnrollPlugin { + protected: + std::string enroll() override { + JSON host_details; + genHostDetails(host_details); // populate host identity info + // call remote endpoint with host_details... + return "my-node-secret-key"; + } +}; + +// Retrieve (or request) a node key using a named plugin +std::string node_key = getNodeKey("my_enroll_plugin"); + +// Clear stored key to force re-enrollment on next run +Status s = clearNodeKey(); + +// Read the shared enterprise enrollment secret +const std::string secret = getEnrollSecret(); +``` + +> Enrollment plugins are best used as part of a coordinated **enroll → config → logger** plugin suite. See the [osquery enrollment docs](https://osquery.readthedocs.io/en/stable/deployment/remote/) for full deployment guidance. \ No newline at end of file diff --git a/osquery/remote/enroll/tests/.enroll_tests.md b/osquery/remote/enroll/tests/.enroll_tests.md new file mode 100644 index 00000000000..d3383ff6986 --- /dev/null +++ b/osquery/remote/enroll/tests/.enroll_tests.md @@ -0,0 +1,31 @@ + +Unit tests for the osquery enrollment subsystem, verifying secret retrieval, node key fetching, and node key caching behavior via a mock `EnrollPlugin`. + +## Key Components + +| Component | Description | +|---|---| +| `EnrollTests` | GTest fixture that initializes the platform, registry, and database before each test, and clears stored node key state | +| `SimpleEnrollPlugin` | Minimal `EnrollPlugin` stub that always returns `"fetched_a_node_key"` — used to isolate enrollment logic from real network calls | +| `test_enroll_secret_retrieval` | Writes a temporary secret file and asserts `getEnrollSecret()` reads and trims it correctly | +| `test_enroll_key_retrieval` | Validates that `getNodeKey()` returns empty when enrollment is disabled, and returns a key when enabled | +| `test_enroll_key_caching` | Confirms that a second call to `getNodeKey()` uses the cached value without invoking `EnrollPlugin::enroll()` again | + +## Usage Example + +```cpp +// Run only the enroll tests via ctest or the test binary directly: +// ./enroll_tests --gtest_filter="EnrollTests.*" + +// The SimpleEnrollPlugin is registered under the "enroll" category +// with the name "test_simple", so tests invoke it via: +auto node_key = getNodeKey("test_simple"); + +// Secret retrieval is driven by the FLAGS_enroll_secret_path flag: +FLAGS_enroll_secret_path = "/tmp/secret.txt"; +writeTextFile(FLAGS_enroll_secret_path, "my_secret\n", 0600, + PF_CREATE_ALWAYS | PF_WRITE); +auto secret = getEnrollSecret(); // returns "my_secret" +``` + +> **Note:** Each test clears `nodeKey` and `nodeKeyTime` from `kPersistentSettings` in `SetUp()` to prevent cross-test cache contamination. \ No newline at end of file diff --git a/osquery/remote/serializers/.json.md b/osquery/remote/serializers/.json.md new file mode 100644 index 00000000000..fe919882cd5 --- /dev/null +++ b/osquery/remote/serializers/.json.md @@ -0,0 +1,39 @@ + +Defines `JSONSerializer`, a concrete implementation of the `Serializer` interface for serializing and deserializing JSON payloads in osquery remote requests. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `JSONSerializer` | Class | Serializes/deserializes `JSON` objects to/from `std::string` | +| `serialize()` | Method | Converts a `JSON` object into a serialized string | +| `deserialize()` | Method | Parses a serialized string back into a `JSON` object | +| `getContentType()` | Method | Returns `"application/json"` as the MIME content type | + +## Usage Example + +```c +#include + +osquery::JSONSerializer serializer; + +// Serialize +osquery::JSON json; +std::string output; +auto status = serializer.serialize(json, output); +if (!status.ok()) { + // handle error +} + +// Deserialize +osquery::JSON parsed; +auto status2 = serializer.deserialize(output, parsed); +if (!status2.ok()) { + // handle error +} + +// Content type for HTTP headers +std::string ct = serializer.getContentType(); // "application/json" +``` + +> `JSONSerializer` is a lightweight adapter — all methods delegate to the base `Serializer` contract defined in `osquery/remote/requests.h`. Wire up this class wherever a remote transport requires a `Serializer` instance with JSON encoding. \ No newline at end of file diff --git a/osquery/remote/serializers/tests/.json_serializers_tests.md b/osquery/remote/serializers/tests/.json_serializers_tests.md new file mode 100644 index 00000000000..4d911fab78a --- /dev/null +++ b/osquery/remote/serializers/tests/.json_serializers_tests.md @@ -0,0 +1,35 @@ + +Unit tests for the `JSONSerializer` class, validating JSON serialization and deserialization of key-value data using the osquery remote serializer interface. + +## Key Components + +- **`JSONSerializersTests`** — GTest fixture class scoping all JSON serializer test cases +- **`test_serialize`** — Verifies that a `JSON` object with a key-value pair serializes correctly to a compact JSON string +- **`test_deserialize`** — Verifies that a JSON string deserializes into a `JSON` document matching the expected structure + +## Usage Example + +```cpp +// Serialize a JSON object to string +auto json = JSONSerializer(); +JSON params; +params.add("foo", "bar"); + +std::string serialized; +auto s = json.serialize(params, serialized); +// s.ok() == true +// serialized == "{\"foo\":\"bar\"}" + +// Deserialize a JSON string back to a JSON object +JSON result; +std::string input = "{\"foo\":\"bar\"}"; +auto s = json.deserialize(input, result); +// s.ok() == true +// result.doc() matches the original params +``` + +## Notes + +- Tests assert both operation success via `s.ok()` and output correctness via equality checks +- Uses compact (no-whitespace) JSON formatting as the expected serialized form +- Depends on `osquery/remote/serializers/json.h` and the `JSON` utility wrapper \ No newline at end of file diff --git a/osquery/remote/tests/.requests_tests.md b/osquery/remote/tests/.requests_tests.md new file mode 100644 index 00000000000..8a3c6f7b7d4 --- /dev/null +++ b/osquery/remote/tests/.requests_tests.md @@ -0,0 +1,40 @@ + +Unit tests for osquery's remote request infrastructure, validating URI parsing, request lifecycle (call/response), and gzip compression behavior using mock and copy transport/serializer implementations. + +## Key Components + +| Component | Type | Description | +|---|---|---| +| `MockTransport` | Test double | Simulates transport with no-op `sendRequest`; injects `foo=baz` into response params when called with params | +| `MockSerializer` | Test double | No-op serializer/deserializer returning `"mock"` content type | +| `CopyTransport` | Test double | Echoes serialized params (optionally compressed) back via response status message | +| `CopySerializer` | Test double | Extracts the `"copy"` key from input JSON as the serialized payload | +| `test_url` | Test case | Validates `Uri` parsing: scheme, host, username, port, path, fragment, query params, and IPv6 addresses | +| `test_call` | Test case | Validates a no-param `Request::call()` succeeds and returns an empty response | +| `test_call_with_params` | Test case | Validates a parameterized `Request::call()` round-trip and verifies the response payload | +| `test_compression` | Test case | Validates gzip compression via `setOption("compress", true)`, checking expected gzip header bytes and compressed size reduction | + +## Usage Example + +```cpp +// Instantiate a typed Request with custom transport and serializer +Request req("https://example.com/endpoint"); + +// No-param call +Status s = req.call(); + +// Call with JSON params +JSON params; +params.add("key", "value"); +Status s2 = req.call(params); + +// Retrieve response +JSON response; +req.getResponse(response); + +// Enable compression before calling +req.setOption("compress", true); +req.call(params); +``` + +> **Note:** The `test_compression` case skips byte index 9 of the gzip output (the OS identifier byte per [RFC 1952](http://www.zlib.org/rfc-gzip.html)) when asserting compressed content equality across platforms. \ No newline at end of file diff --git a/osquery/remote/tests/.test_utils.md b/osquery/remote/tests/.test_utils.md new file mode 100644 index 00000000000..92a3174fa03 --- /dev/null +++ b/osquery/remote/tests/.test_utils.md @@ -0,0 +1,48 @@ + +Provides a singleton test utility class for managing an embedded TLS server process during osquery integration tests, enabling client TLS configuration and lifecycle control. + +## Key Components + +### `TLSServerRunner` (class) +A non-copyable singleton that spawns and manages a local TLS server subprocess for testing purposes. + +| Member | Description | +|--------|-------------| +| `instance()` | Returns the singleton `TLSServerRunner` instance | +| `start(server_cert, verify_client_cert)` | Launches the TLS server process; returns `false` on failure | +| `stop()` | Terminates the server process on exit | +| `port()` | Returns the TCP port the server is bound to | +| `setClientConfig()` | Applies osquery flags needed for TLS client tests | +| `unsetClientConfig()` | Restores osquery flags after TLS client tests | +| `getListeningPortPid()` | Resolves the PID listening on a given port (empty if not found) | +| `startAndSetScript()` | Internal helper to spawn the server subprocess | + +**Private state:** + +- `server_` — shared handle to the spawned `PlatformProcess` +- `port_` — bound TCP port string +- Saved flag values: `tls_hostname_`, `enroll_tls_endpoint_`, `tls_server_certs_`, `enroll_secret_path_` + +## Usage Example + +```cpp +#include "test_utils.h" + +// Start TLS server before tests +bool ok = osquery::TLSServerRunner::start("/path/to/server.crt", false); +ASSERT_TRUE(ok); + +// Configure the osquery client flags to point at the test server +osquery::TLSServerRunner::setClientConfig(); + +// Access the bound port +std::string port = osquery::TLSServerRunner::port(); + +// Run TLS-dependent tests... + +// Restore flags and shut down +osquery::TLSServerRunner::unsetClientConfig(); +osquery::TLSServerRunner::stop(); +``` + +> **Note:** `TLSServerRunner` is non-copyable (inherits `boost::noncopyable`). Always access it through the `instance()` singleton or the provided static methods. \ No newline at end of file diff --git a/osquery/remote/transports/.tls.md b/osquery/remote/transports/.tls.md new file mode 100644 index 00000000000..cc1cee8c5ac --- /dev/null +++ b/osquery/remote/transports/.tls.md @@ -0,0 +1,50 @@ + +HTTPS/TLS transport layer for osquery remote communications, providing secure HTTP client functionality with configurable certificate handling, cipher suite enforcement, and peer verification. + +## Key Components + +### Constants & Flags +- `kTLSCiphers` — Hardcoded restrictive cipher suite string (ECDH/DH/RSA with AES/3DES, excludes weak algorithms) +- `tls_client_key` — CLI flag: path to TLS client private key +- `tls_client_cert` — CLI flag: path to TLS client certificate (PEM) +- `tls_hostname` — CLI flag: TLS server hostname + +### Enums +- `HTTPVerb` — Defines `HTTP_POST` and `HTTP_PUT` verb selectors + +### Class: `TLSTransport` (extends `Transport`) + +| Method | Description | +|---|---| +| `sendRequest()` | Sends a parameterless HTTPS request | +| `sendRequest(params, compress)` | Sends a request with serialized parameters, optional gzip compression | +| `getInternalOptions()` | Returns strict options (limited ciphers + client certs) for osquery infrastructure | +| `getOptions()` | Returns general options for AWS or public internet endpoints | +| `decorateRequest(r)` | Applies base modifications to an HTTP request object | +| `disableVerifyPeer()` | Test-only: disables peer certificate verification | +| `setClientCertificate(cert, key)` | Configures mutual TLS client authentication | +| `setPeerCertificate(cert)` | Pins a specific server CA/certificate bundle | + +## Usage Example + +```cpp +#include + +// Basic TLS request with internal (strict) options +auto transport = std::make_shared(); + +// For osquery infrastructure — enforces restrictive cipher suite +auto options = transport->getInternalOptions(); + +// For generic/AWS endpoints — uses permissive options +auto generalOptions = transport->getOptions(); + +// Send a POST request with JSON parameters +Status status = transport->sendRequest("{\"key\":\"value\"}", /* compress */ false); +if (!status.ok()) { + // status.getCode() == 1: connectivity error + // status.getCode() == 2: TLS-specific error +} +``` + +> **Return codes:** `sendRequest` returns code `1` for general connectivity failures and code `2` for TLS-specific errors (e.g., handshake failure, certificate mismatch). \ No newline at end of file diff --git a/osquery/remote/transports/tests/.tls_transports_tests.md b/osquery/remote/transports/tests/.tls_transports_tests.md new file mode 100644 index 00000000000..039b4a3b7aa --- /dev/null +++ b/osquery/remote/transports/tests/.tls_transports_tests.md @@ -0,0 +1,50 @@ + +Unit tests for the osquery TLS transport layer, validating TLS connection behavior including peer verification, certificate pinning, client authentication, and hostname validation. + +## Key Components + +### `TLSTransportsTests` (Test Fixture) +Extends `testing::Test` with shared setup/teardown logic for spinning up a local TLS test server. + +| Member | Description | +|---|---| +| `SetUp()` | Initializes platform, registry, and test database | +| `startServer()` | Launches `TLSServerRunner` with optional cert and client-auth flag | +| `TearDown()` | Stops the test server and restores cert flags | +| `getTLSError()` | Formats a human-readable TLS error from a `Status` object | +| `nameError()` | Detects Windows-specific network name format errors | + +### Test Cases + +| Test | Description | +|---|---| +| `test_call` | Basic GET request with peer verification disabled | +| `test_call_with_params` | POST request with JSON params; validates echo response | +| `test_call_verify_peer` | Confirms TLS handshake fails against an untrusted cert | +| `test_call_server_cert_pinning` | Validates cert pinning with valid CA path and rejects a directory path | +| `test_call_client_auth` | Mutual TLS (mTLS) with client cert and key | +| `test_wrong_hostname` | Confirms rejection when server cert hostname mismatches | + +## Usage Example + +```cpp +// Run all TLS transport tests via GTest +TEST_F(TLSTransportsTests, test_call_with_params) { + startServer(); + + auto t = std::make_shared(); + t->disableVerifyPeer(); + + auto url = "https://localhost:" + port_; + Request r(url, t); + + JSON params; + params.add("foo", "bar"); + + Status status; + ASSERT_NO_THROW(status = r.call(params)); + ASSERT_TRUE(status.ok()); +} +``` + +> **Note:** Tests use `disableVerifyPeer()` to bypass certificate validation against the self-signed test CA. Production transport instances should always perform full peer verification. \ No newline at end of file diff --git a/osquery/sdk/.empty_register_foreign_tables.md b/osquery/sdk/.empty_register_foreign_tables.md new file mode 100644 index 00000000000..7f5f435b643 --- /dev/null +++ b/osquery/sdk/.empty_register_foreign_tables.md @@ -0,0 +1,21 @@ + +Provides a no-op stub implementation of `registerForeignTables()` within the `osquery` namespace, used to satisfy the linker when building the plugin SDK library without pulling in the full table registry. + +## Key Components + +- **`registerForeignTables()`** — Empty function that intentionally performs no operations, acting as a placeholder to decouple the plugin SDK from the complete osquery table registration system. + +## Usage Example + +```cpp +namespace osquery { + +// Called during plugin SDK initialization; no-op in this context +void registerForeignTables() { + // Tables are not registered in plugin_sdk builds +} + +} // namespace osquery +``` + +> **Note:** This stub exists to avoid a linker dependency on all osquery table implementations when only the plugin SDK is needed. Full builds replace this with a version that actually registers foreign tables. \ No newline at end of file diff --git a/osquery/sdk/.plugin_sdk.md b/osquery/sdk/.plugin_sdk.md new file mode 100644 index 00000000000..26acf9b656e --- /dev/null +++ b/osquery/sdk/.plugin_sdk.md @@ -0,0 +1,15 @@ + +Placeholder/stub header file for the osquery plugin SDK, containing only the standard Flamingo/osquery copyright notice and a `#pragma once` include guard with no declared symbols. + +## Key Components + +- `#pragma once` — Include guard preventing multiple inclusions; no functions, classes, or types are currently declared. + +## Usage Example + +```c +#include "plugin_sdk.h" +// No symbols exported yet; include is a no-op beyond the guard. +``` + +> **Note:** This file is currently empty beyond licensing boilerplate. It serves as a reserved entry point for future plugin SDK declarations. Watch the [OpenMSP Slack community](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) for updates on SDK expansion. \ No newline at end of file diff --git a/osquery/sdk/.sdk.md b/osquery/sdk/.sdk.md new file mode 100644 index 00000000000..706a1a9c72a --- /dev/null +++ b/osquery/sdk/.sdk.md @@ -0,0 +1,57 @@ + +Primary entry point header for building osquery extensions and modules using the osquery SDK, aggregating core includes and providing extension-specific registration macros. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `REGISTER_EXTERNAL` | Macro | Registers a plugin into the external registry, which broadcasts to the osquery extension manager | +| `CREATE_MODULE` | Macro | Declares an osquery loadable module (shared object), calling `Registry::declareModule` at construction time | +| `CREATE_MODULE_IF` | Macro | Conditional variant of `CREATE_MODULE`; only declares the module if a runtime expression evaluates to true | +| `REGISTER_MODULE` | Macro | Registers a plugin within an already-declared module context (replaces `REGISTER` inside modules) | +| `ExternalSQLPlugin` | Registration | Wires up the external SQL implementation that forwards queries to the osquery core extension manager | + +> **Note:** `REGISTER`, `REGISTER_INTERNAL`, `CREATE_REGISTRY`, and `CREATE_LAZY_REGISTRY` are intentionally undefined and replaced with error strings to prevent misuse inside SDK consumers. + +## Usage Example + +```c +// Building a simple osquery extension plugin +#include + +class MyTablePlugin : public TablePlugin { + private: + TableColumns columns() const override { + return { + std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + + QueryData generate(QueryContext& request) override { + Row r; + r["name"] = "example"; + return {r}; + } +}; + +// Register the plugin into the external (extension) registry +REGISTER_EXTERNAL(MyTablePlugin, "table", "my_table"); + +int main(int argc, char* argv[]) { + osquery::Initializer runner(argc, argv, ToolType::EXTENSION); + runner.connectExtension(); + runner.waitForShutdown(); + return 0; +} +``` + +```c +// Building a loadable module (.so/.dylib) +#include + +CREATE_MODULE("my_module", "1.0.0", "2.0.0"); + +void initModule(void) { + REGISTER_MODULE(MyTablePlugin, "table", "my_table"); +} +``` \ No newline at end of file diff --git a/osquery/sdk/tests/.registry_tests.md b/osquery/sdk/tests/.registry_tests.md new file mode 100644 index 00000000000..2424cc2aecf --- /dev/null +++ b/osquery/sdk/tests/.registry_tests.md @@ -0,0 +1,49 @@ + +Unit tests for the osquery plugin SDK registry, verifying registration integrity of plugins and mandatory registry entries. + +## Key Components + +### Test Fixture +- **`PluginSdkRegistryTests`** — GTest fixture class scoped to `osquery` namespace for all registry SDK tests. + +### Test Cases + +- **`there_is_no_registered_plugin_in_sdk`** — Asserts that every plugin registered via `AutoRegisterInterface::plugins()` is marked as `optional_`, preventing unexpected non-internal plugins from being bundled into the SDK. + +- **`whether_all_mandatory_registries_are_in_sdk`** — Validates two invariants over `AutoRegisterInterface::registries()`: + 1. No duplicate registry names exist. + 2. All entries in `mandatory_registries_` are present. + +### Mandatory Registries List +The `mandatory_registries_` vector defines the expected set of registries that must exist in the SDK: + +| Registry | Notes | +|---|---| +| `config` | Core configuration | +| `config_parser` | Configuration parsing | +| `database` | Persistence layer | +| `distributed` | Distributed queries | +| `enroll` | Node enrollment | +| `event_publisher` | Event sourcing | +| `event_subscriber` | Event consumption | +| `logger` | Logging infrastructure | +| `numeric_monitoring` | Metrics/telemetry | +| `sql` | SQL execution | +| `table` | Virtual table layer | +| `osquery_events_stream` | Experimental event streaming | + +## Usage Example + +```cpp +// Run all registry SDK tests +// From build directory: +// ./osquery_tests --gtest_filter="PluginSdkRegistryTests.*" + +// Adding a new mandatory registry to validation: +auto const mandatory_registries_ = std::vector{ + // ... existing entries ... + "my_new_registry", // add here to enforce presence in SDK +}; +``` + +> **Note:** Any plugin registered outside the SDK that is not flagged as `optional_` will cause `there_is_no_registered_plugin_in_sdk` to fail, enforcing clean SDK boundaries. \ No newline at end of file diff --git a/osquery/sql/.dynamic_table_row.md b/osquery/sql/.dynamic_table_row.md new file mode 100644 index 00000000000..cce2fa01dbc --- /dev/null +++ b/osquery/sql/.dynamic_table_row.md @@ -0,0 +1,46 @@ + +Defines the `DynamicTableRow` and `DynamicTableRowHolder` classes, providing a string-map-backed implementation of the `TableRow` interface for use in osquery's virtual table system. + +## Key Components + +### `DynamicTableRow` +Concrete `TableRow` subclass backed by a `Row` (string map). Supports: +- Construction from `Row&&`, initializer lists, or default empty state +- `get_rowid()` — retrieves the SQLite row ID +- `get_column()` — populates a SQLite column value from the internal map +- `serialize()` — serializes the row to a JSON object +- `clone()` — returns a deep copy wrapped in a `TableRowHolder` +- `operator[]` / `count()` — map-style key access + +### `DynamicTableRowHolder` +Convenience wrapper that owns a `DynamicTableRow*` alongside its `TableRowHolder`. Simplifies row construction and assignment before strong typing is fully adopted. Implicitly converts to `TableRowHolder&&`. + +### Factory Functions +- `make_table_row()` — creates an empty `DynamicTableRowHolder` +- `make_table_row({...})` — creates a pre-populated row from an initializer list + +### Free Functions +- `tableRowsFromQueryData(QueryData&&)` — converts legacy `QueryData` to `TableRows` +- `deserializeRow(const rapidjson::Value&, DynamicTableRowHolder&)` — populates a row from a JSON object + +## Usage Example + +```c +// Construct a row with known columns +auto row = make_table_row({ + {"pid", "1234"}, + {"name", "launchd"}, + {"path", "/sbin/launchd"} +}); + +// Access or modify a field +row["state"] = "S"; + +// Convert to TableRowHolder for use in query results +TableRows results; +results.push_back(std::move(row)); + +// Deserialize from JSON +DynamicTableRowHolder deserialized; +Status s = deserializeRow(jsonValue, deserialized); +``` \ No newline at end of file diff --git a/osquery/sql/.sql.md b/osquery/sql/.sql.md new file mode 100644 index 00000000000..101d5f03db5 --- /dev/null +++ b/osquery/sql/.sql.md @@ -0,0 +1,56 @@ + +Defines the core SQL query interface for osquery, providing both a high-level `SQL` class and lower-level utility functions for executing, analyzing, and inspecting SQLite-backed virtual table queries. + +## Key Components + +### `SQL` Class +The primary interface for executing osquery SQL queries. Inherits `only_movable` (no copy semantics). + +| Member | Description | +|---|---| +| `SQL(query, use_cache)` | Constructor — executes query on instantiation | +| `rows()` | Returns `QueryData` results (const and mutable overloads) | +| `columns()` | Returns `ColumnNames` for result columns | +| `ok()` | Returns `true` if query succeeded | +| `getStatus()` | Returns the underlying `Status` object | +| `getMessageString()` | Returns human-readable status message | +| `selectAllFrom(table)` | Static: executes `SELECT * FROM ` | +| `selectAllFrom(table, col, op, expr)` | Static: `SELECT *` with a single WHERE constraint | +| `selectFrom(cols, table, col, op, expr)` | Static: `SELECT [cols]` with a single WHERE constraint | + +### Free Functions + +| Function | Description | +|---|---| +| `query(q, results, use_cache)` | Lower-level query execution; prefer `SQL` class | +| `getQueryColumns(q, columns)` | Introspects result column names and types via SQLite | +| `getQueryTables(q, tables)` | Extracts virtual table names referenced in a query | + +## Usage Example + +```cpp +// High-level usage via SQL class +osquery::SQL sql("SELECT * FROM time"); +if (sql.ok()) { + for (const auto& row : sql.rows()) { + for (const auto& col : row) { + LOG(INFO) << col.first << " => " << col.second; + } + } +} else { + LOG(ERROR) << sql.getMessageString(); +} + +// Static helper with constraint +auto results = osquery::SQL::selectAllFrom( + "processes", "pid", osquery::EQUALS, "1234" +); + +// Lower-level query function +osquery::QueryData results; +auto status = osquery::query("SELECT * FROM users;", results); + +// Inspect columns before executing +osquery::TableColumns cols; +osquery::getQueryColumns("SELECT pid, name FROM processes", cols); +``` \ No newline at end of file diff --git a/osquery/sql/.sqlite_encoding.md b/osquery/sql/.sqlite_encoding.md new file mode 100644 index 00000000000..0fba4fc25d2 --- /dev/null +++ b/osquery/sql/.sqlite_encoding.md @@ -0,0 +1,46 @@ + +Registers custom Base64 encoding/decoding functions as SQLite scalar extensions within the osquery framework. + +## Key Components + +### Enum: `B64Type` +Controls the encoding behavior applied to a SQLite value: + +| Value | Behavior | +|---|---| +| `B64_ENCODE_CONDITIONAL` | Encodes only if input contains non-printable characters | +| `B64_ENCODE` | Always Base64-encodes the input | +| `B64_DECODE` | Decodes a Base64-encoded input | + +### Static Functions + +- **`b64SqliteValue`** — Core handler; reads a SQLite argument, applies the requested `B64Type` operation, and writes the result back via `sqlite3_result_text`. Handles `NULL` inputs gracefully. +- **`sqliteB64ConditionalEncFunc`** — SQLite callback wrapper for `conditional_to_base64`. +- **`sqliteB64EncFunc`** — SQLite callback wrapper for `to_base64`. +- **`sqliteB64DecFunc`** — SQLite callback wrapper for `from_base64`. + +### Public Function + +- **`registerEncodingExtensions(sqlite3* db)`** — Registers all three scalar functions into the provided SQLite database handle using `sqlite3_create_function` with `SQLITE_UTF8 | SQLITE_DETERMINISTIC` flags. + +## Usage Example + +```cpp +// Register encoding extensions on an open SQLite database +sqlite3* db; +sqlite3_open(":memory:", &db); +osquery::registerEncodingExtensions(db); + +// Now usable in any SQL query against this db handle: +// SELECT to_base64(column) -- always encode +// SELECT conditional_to_base64(column) -- encode only if non-printable +// SELECT from_base64(column) -- decode +``` + +Once registered, the three SQL scalar functions are available in all queries on that connection: + +| SQL Function | Description | +|---|---| +| `to_base64(x)` | Unconditionally Base64-encodes `x` | +| `conditional_to_base64(x)` | Encodes `x` only if it contains non-printable characters | +| `from_base64(x)` | Decodes a Base64 string `x` | \ No newline at end of file diff --git a/osquery/sql/.sqlite_filesystem.md b/osquery/sql/.sqlite_filesystem.md new file mode 100644 index 00000000000..9dfa89b52d5 --- /dev/null +++ b/osquery/sql/.sqlite_filesystem.md @@ -0,0 +1,43 @@ + +Registers custom SQLite scalar functions for filesystem path analysis, enabling osquery SQL queries to inspect and resolve executable paths from command strings. + +## Key Components + +### SQLite Functions Registered + +| Function | Signature | Description | +|---|---|---| +| `find_file_path_in_cmd` | `(cmd, allow_quoting?, escape_char?)` | Extracts the shortest existing executable path from a command string | +| `is_path_deterministic` | `(cmd, allow_quoting?, escape_char?)` | Returns `1` if the command resolves to exactly one valid executable path | +| `parent_directory` | `(path)` | Returns the parent directory portion of a file path | + +### Internal Helpers + +- **`findExistingProgramPathFromCommand`** — Tokenizes a command string (respecting escape characters and optional quoting) and validates each token against the filesystem, returning the shortest or longest matching executable path. +- **`findExistingProgramPathFromCommandSqlArgs`** — Bridges SQLite argument unpacking to `findExistingProgramPathFromCommand`, applying platform-default escape symbols (`\` on POSIX, `^` on Windows). +- **`registerFilesystemExtensions`** — Entry point that binds all three SQL functions to a `sqlite3*` database handle. + +## Usage Example + +```cpp +// Register extensions on an open SQLite connection +sqlite3* db; +sqlite3_open(":memory:", &db); +osquery::registerFilesystemExtensions(db); +``` + +```sql +-- Find the executable path inside a full launch command +SELECT find_file_path_in_cmd('/usr/bin/python3 script.py'); +-- → '/usr/bin/python3' + +-- Check if a command resolves to a single unambiguous binary +SELECT is_path_deterministic('/usr/bin/python3 script.py'); +-- → 1 + +-- Extract the parent directory from a path +SELECT parent_directory('/usr/bin/python3'); +-- → '/usr/bin' +``` + +> **Note:** All three functions are registered as `SQLITE_DETERMINISTIC`, making them safe for use in index expressions and query optimizations within osquery's virtual table engine. \ No newline at end of file diff --git a/osquery/sql/.sqlite_hashing.md b/osquery/sql/.sqlite_hashing.md new file mode 100644 index 00000000000..28970e1bcc6 --- /dev/null +++ b/osquery/sql/.sqlite_hashing.md @@ -0,0 +1,48 @@ + +Registers custom SQLite scalar functions for cryptographic hashing and network flow identification within the osquery framework. + +## Key Components + +| Function | Description | +|---|---| +| `hashSqliteValue()` | Core helper that hashes a SQLite text value using a specified `HashType` and returns the result as a SQLite text result | +| `sqliteMD5Func()` | SQLite scalar function wrapper for MD5 hashing | +| `sqliteSHA1Func()` | SQLite scalar function wrapper for SHA-1 hashing | +| `sqliteSHA256Func()` | SQLite scalar function wrapper for SHA-256 hashing | +| `sqliteCommunityIDv1()` | Implements the [Community ID v1 spec](https://github.com/corelight/community-id-spec) — a network flow hash derived from source/dest IPs, ports, and protocol, with optional seed | +| `sqliteCommunityIDv1Null()` | Community ID variant that returns `NULL` on error (logs a warning) | +| `sqliteCommunityIDv1Error()` | Community ID variant that raises a SQLite error on invalid input | +| `registerHashingExtensions()` | Entry point — registers all functions (`md5`, `sha1`, `sha256`, `community_id_v1`, `community_id_v1_strict`) into a SQLite database handle | + +## Registered SQL Functions + +| SQL Function | Args | Behavior on Error | +|---|---|---| +| `md5(text)` | 1 | Returns `NULL` | +| `sha1(text)` | 1 | Returns `NULL` | +| `sha256(text)` | 1 | Returns `NULL` | +| `community_id_v1(saddr, daddr, sport, dport, proto [, seed])` | 5–6 | Returns `NULL`, logs warning | +| `community_id_v1_strict(saddr, daddr, sport, dport, proto [, seed])` | 5–6 | Returns SQLite error | + +## Usage Example + +```cpp +// Register extensions when initializing a SQLite database handle +sqlite3* db = nullptr; +sqlite3_open(":memory:", &db); +osquery::registerHashingExtensions(db); +``` + +```sql +-- Use inside osquery SQL queries +SELECT md5('hello world'); +SELECT sha256('osquery'); + +-- Network flow Community ID (IPv4 or IPv6) +SELECT community_id_v1('192.168.1.1', '10.0.0.1', 443, 52000, 6); + +-- With optional seed and strict error handling +SELECT community_id_v1_strict('192.168.1.1', '10.0.0.1', 443, 52000, 6, 0); +``` + +> The `community_id_v1` output follows the format `1:`, canonicalizing address ordering per the spec before hashing. \ No newline at end of file diff --git a/osquery/sql/.sqlite_math.md b/osquery/sql/.sqlite_math.md new file mode 100644 index 00000000000..db42ee9d8ff --- /dev/null +++ b/osquery/sql/.sqlite_math.md @@ -0,0 +1,44 @@ + +Registers mathematical scalar functions as SQLite extensions within the osquery framework, enabling SQL queries to perform trigonometric, logarithmic, exponential, and rounding operations natively. + +## Key Components + +### Type Alias +- **`DoubleDoubleFunction`** — `std::function` alias used as a generic math function signature for cross-platform MSVC-compatible dispatch. + +### Core Dispatchers +- **`callDoubleFunc`** — Dispatches a `DoubleDoubleFunction` against a single SQLite argument, returning a `DOUBLE` result or propagating `errno`-based errors. +- **`callCastedDoubleFunc`** — Variant that casts the result to `int64_t`; passes `INTEGER` inputs through unchanged (used by `ceil`/`floor`). + +### Registered SQL Functions + +| SQL Function | Args | Description | +|---|---|---| +| `sin`, `cos`, `tan`, `cot` | 1 | Trigonometric | +| `asin`, `acos`, `atan` | 1 | Inverse trigonometric | +| `sqrt`, `exp`, `log`, `log10` | 1 | Exponential / logarithmic | +| `power` | 2 | `r1 ^ r2` via `pow()` | +| `ceil`, `floor` | 1 | Rounding (returns `INTEGER`) | +| `degrees`, `radians` | 1 | Angle unit conversion | +| `pi` | 0 | Returns `M_PI` constant | + +### Entry Point +- **`registerMathExtensions(sqlite3* db)`** — Iterates a static `FuncDef` table and registers all functions via `sqlite3_create_function` with `SQLITE_DETERMINISTIC`. + +## Usage Example + +```cpp +#include + +sqlite3* db = nullptr; +sqlite3_open(":memory:", &db); + +// Register all math extensions +osquery::registerMathExtensions(db); + +// Now usable in any SQL query against this db handle +// SELECT sin(pi() / 2); → 1.0 +// SELECT power(2, 10); → 1024.0 +// SELECT degrees(pi()); → 180.0 +// SELECT ceil(3.2); → 4 (INTEGER) +``` \ No newline at end of file diff --git a/osquery/sql/.sqlite_network.md b/osquery/sql/.sqlite_network.md new file mode 100644 index 00000000000..4a118c472c3 --- /dev/null +++ b/osquery/sql/.sqlite_network.md @@ -0,0 +1,41 @@ + +Implements a custom SQLite extension that adds IP address CIDR block membership checking to osquery's SQL query engine using Boost.Asio's networking utilities. + +## Key Components + +### `sqliteCidrBlockFunc` *(static)* +Internal SQLite scalar function handler that evaluates whether an IP address falls within a given CIDR block. Supports both IPv4 and IPv6 addresses with input validation at each parsing step. + +**Arguments (SQLite context):** +- `argv[0]` — CIDR string (e.g., `"192.168.1.0/24"`) +- `argv[1]` — IP address string (e.g., `"192.168.1.42"`) + +**Returns:** Integer `1` if the IP is within the CIDR range, `0` otherwise. Sets a SQLite error on invalid input. + +### `registerNetworkExtensions(sqlite3* db)` +Registers the `in_cidr_block` SQL function into the provided SQLite database handle with `SQLITE_DETERMINISTIC` semantics (same inputs always yield the same output). + +## Usage Example + +```cpp +// Register extensions on an open SQLite connection +sqlite3* db; +sqlite3_open(":memory:", &db); +osquery::registerNetworkExtensions(db); + +// Then use in SQL queries: +// SELECT * FROM network_interfaces +// WHERE in_cidr_block('10.0.0.0/8', address) = 1; + +// SELECT in_cidr_block('2001:db8::/32', '2001:db8::1'); -- IPv6 supported +``` + +## Error Handling + +| Condition | SQLite Error Message | +|---|---| +| CIDR argument is not a string | `"CIDR must be a string"` | +| IP argument is not a string | `"IP address must be a string"` | +| IP address fails to parse | `"IP address cannot be parsed"` | +| IPv4 CIDR fails to parse | `"CIDR for IP address v4 cannot be parsed"` | +| IPv6 CIDR fails to parse | `"CIDR for IP address v6 cannot be parsed"` | \ No newline at end of file diff --git a/osquery/sql/.sqlite_operations.md b/osquery/sql/.sqlite_operations.md new file mode 100644 index 00000000000..982e58fd01f --- /dev/null +++ b/osquery/sql/.sqlite_operations.md @@ -0,0 +1,42 @@ + +Registers custom SQLite scalar and aggregate functions that extend osquery's SQL query engine with file carving and sleep capabilities. + +## Key Components + +### Functions + +| Function | Type | Description | +|---|---|---| +| `addCarveFile` | SQLite step callback | Accumulates file paths into the global `kFunctionCarvePaths` set during aggregate execution | +| `executeCarve` | SQLite final callback | Triggers `carvePaths()` on collected paths if carving is enabled; clears the path set afterward | +| `sqlSleep` | SQLite scalar callback | Blocks the current thread for a specified number of seconds using `std::this_thread::sleep_for` | +| `registerOperationExtensions` | Public API | Registers both `carve()` and `sleep()` as SQLite functions on a given `sqlite3*` connection | + +### Globals + +- **`kFunctionCarvePaths`** — `std::set` accumulating paths staged for carving +- **`kFunctionCarveMutex`** — `Mutex` protecting concurrent access to the path set +- **`carver_disable_function`** — CLI flag (`--carver_disable_function`, default `true`) that gates carve execution + +## Usage Example + +```cpp +// Called during SQLite database initialization in osquery +sqlite3* db = nullptr; +sqlite3_open(":memory:", &db); + +// Register the custom carve() and sleep() SQL functions +registerOperationExtensions(db); + +// Now usable directly inside osquery SQL queries: +// SELECT carve('/etc/passwd'); +// SELECT carve('/var/log/syslog'); +// -- Aggregate finalizer triggers the actual carve operation + +// Pause query execution for 5 seconds: +// SELECT sleep(5); + +sqlite3_close(db); +``` + +> **Note:** The `carve()` aggregate function is disabled by default via the `--carver_disable_function` flag. Pass `--carver_disable_function=false` at startup to enable file carving via SQL queries. \ No newline at end of file diff --git a/osquery/sql/.sqlite_string.md b/osquery/sql/.sqlite_string.md new file mode 100644 index 00000000000..c5a3febe366 --- /dev/null +++ b/osquery/sql/.sqlite_string.md @@ -0,0 +1,47 @@ + +Registers custom SQLite string extension functions for use within osquery's SQL engine, enabling advanced string manipulation operations not available in standard SQLite. + +## Key Components + +| Function | SQL Name | Description | +|---|---|---| +| `tokenStringSplitFunc` | `split(str, token, idx)` | Splits a string on character tokens, returns element at index | +| `regexStringSplitFunc` | `regex_split(str, pattern, idx)` | Splits a string using a regex pattern, returns element at index | +| `regexStringMatchFunc` | `regex_match(str, pattern, idx)` | Performs regex search, returns capture group at index | +| `concatStringFunc` | `concat(...)` | Concatenates strings, ignoring NULL values | +| `concatWSStringFunc` | `concat_ws(sep, ...)` | Concatenates strings with a separator, ignoring NULLs | +| `ip4StringToDecimalFunc` | `inet_aton(ip)` | Converts an IPv4 address string to its decimal representation | +| `registerStringExtensions` | — | Entry point that registers all functions with a SQLite `db` instance | + +**Configuration flag:** `regex_max_size` (default: 256 bytes) — enforced on both `regex_split` and `regex_match` to prevent expensive regex operations. + +## Usage Example + +```cpp +// Register extensions when initializing an osquery SQLite database +sqlite3* db = nullptr; +sqlite3_open(":memory:", &db); +osquery::registerStringExtensions(db); +``` + +```sql +-- Split an IP address by "." and retrieve the second octet +SELECT split(ip_address, ".", 1) FROM addresses; +-- Result: 168 (from 192.168.0.1) + +-- Split using a regex delimiter +SELECT regex_split(ip_address, "\.", 1) FROM addresses; +-- Result: 168 + +-- Extract a regex capture group +SELECT regex_match("osquery-5.12.1", "(\d+\.\d+\.\d+)", 1); +-- Result: 5.12.1 + +-- Convert IPv4 to decimal +SELECT inet_aton("192.168.0.1"); +-- Result: 3232235521 + +-- Concatenate with separator, nulls ignored +SELECT concat_ws("-", "foo", NULL, "bar"); +-- Result: foo-bar +``` \ No newline at end of file diff --git a/osquery/sql/.sqlite_util.md b/osquery/sql/.sqlite_util.md new file mode 100644 index 00000000000..2d7de88818b --- /dev/null +++ b/osquery/sql/.sqlite_util.md @@ -0,0 +1,62 @@ + +Provides SQLite database management utilities for osquery, including RAII database instance wrappers, a singleton manager, query planning, and security authorization controls for safe SQL execution. + +## Key Components + +### `sqliteAuthorizer` +Callback function enforcing SQLite action allowlists (`kAllowedSQLiteActionCodes`, `kAllowedSQLitePragmas`). Blocks dangerous operations like `SQLITE_ATTACH`. + +### `SQLiteDBInstance` +RAII wrapper around a `sqlite3*` connection. Supports both managed (primary) and transient instances, virtual table cache control, and thread-safe attach locking. + +| Method | Description | +|---|---| +| `db()` | Access the raw `sqlite3*` pointer | +| `isPrimary()` | Check if using the shared primary DB | +| `useCache(bool)` | Toggle warm query cache | +| `addAffectedTable()` | Track virtual tables used in a query | +| `attachLock()` | Acquire recursive lock for table attachment | + +### `SQLiteDBManager` +Singleton managing SQLite resource lifecycle. Provides connection pooling — returns the primary DB when available, otherwise a transient instance. + +| Method | Description | +|---|---| +| `get()` | Return a connection (primary or transient) | +| `getUnique()` | Always return a fresh transient connection | +| `resetPrimary()` | Close and reinitialize the primary DB | +| `isDisabled(name)` | Check if a table is disabled via flags | + +### `QueryPlanner` +Lightweight planner using SQLite `EXPLAIN` output to infer column types and identify scanned tables. + +### `queryInternal` +Execute a raw SQL query against a given `SQLiteDBInstanceRef`. + +## Usage Example + +```cpp +// Get a DB connection and run a query +SQLiteDBInstanceRef db = SQLiteDBManager::get(); +QueryDataTyped results; + +Status status = queryInternal( + "SELECT pid, name FROM processes WHERE uid = 0", + results, + db +); + +if (status.ok()) { + for (const auto& row : results) { + // process each row + } +} + +// Use a unique transient connection (e.g., in tests) +SQLiteDBInstanceRef testDb = SQLiteDBManager::getUnique(); + +// Infer column types via query planner +QueryPlanner planner("SELECT * FROM users", db); +TableColumns columns; +planner.applyTypes(columns); +``` \ No newline at end of file diff --git a/osquery/sql/.sqlite_version.md b/osquery/sql/.sqlite_version.md new file mode 100644 index 00000000000..84e22f8616f --- /dev/null +++ b/osquery/sql/.sqlite_version.md @@ -0,0 +1,42 @@ + +Registers custom SQLite functions and collations for comparing software version strings, with support for Linux package versioning conventions across multiple package manager formats. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `registerVersionExtensions` | Function | Entry point — registers all version functions and collations with a SQLite database instance | +| `versionCompare` | Static Function | Core comparison logic; returns negative/zero/positive integer; accepts flags for epoch, delimiter precedence, and remainder handling | +| `versionCompareFunc` | Static Function | SQLite scalar function `version_compare(a, b [, flavor])` — wraps `versionCompare` for use in SQL queries | +| `versionCollate` | Static Function | Generic version collation (`version`) | +| `versionCollateARCH` | Static Function | Arch Linux package collation (`version_arch`) — enables epoch, remainder, and remainder precedence | +| `versionCollateDPKG` | Static Function | Debian package collation (`version_dpkg`) — enables epoch and delimiter precedence | +| `versionCollateRHEL` | Static Function | RHEL/RPM package collation (`version_rhel`) — enables epoch, delimiter precedence, and remainder | +| `compareEpoch` | Static Function | Compares epoch segment (the `epoch:` prefix) between two version strings | +| `delimiterPrecedence` | Static Function | Returns sort weight for version delimiters: `~` < `-` < `^` < `.` < `:` | +| `compareRemainder` | Static Function | Handles sort order when versions have unequal length after shared prefix | + +## Usage Example + +```cpp +// Register extensions on an open SQLite database +sqlite3* db; +sqlite3_open(":memory:", &db); +osquery::registerVersionExtensions(db); +``` + +```sql +-- Generic version comparison +SELECT version_compare('1.2.3', '1.2.4'); -- returns -1 + +-- Debian package versioning +SELECT version_compare('2:1.0-1', '1.0-2', 'DPKG'); -- returns 1 (epoch wins) + +-- RHEL/RPM versioning +SELECT version_compare('1.0^rc1', '1.0', 'RHEL'); -- returns 1 + +-- Use as a collation in ORDER BY +SELECT name, version FROM packages ORDER BY version COLLATE version_dpkg; +``` + +**Supported `version_compare` flavors:** `ARCH`, `DPKG`, `RHEL` (case-insensitive). Omitting the flavor applies basic character-by-character ASCII comparison with delimiter awareness. \ No newline at end of file diff --git a/osquery/sql/.virtual_sqlite_table.md b/osquery/sql/.virtual_sqlite_table.md new file mode 100644 index 00000000000..e4e24ba07a0 --- /dev/null +++ b/osquery/sql/.virtual_sqlite_table.md @@ -0,0 +1,40 @@ + +Provides utilities for querying external SQLite databases from within osquery's virtual table infrastructure, primarily supporting the Automatic Table Construction (ATC) feature. + +## Key Components + +### `getSystemVFS(bool respect_locking)` +Returns the appropriate SQLite VFS (Virtual File System) name based on the current platform and locking preference. Returns `"unix-none"` on POSIX or `"win32-none"` on Windows when locking is disabled; otherwise returns `nullptr` to use the default VFS. + +### `genSqliteTableRow(sqlite3_stmt*, TableRows&, const fs::path&)` +Converts a single SQLite statement result row into an osquery `TableRow`. Handles `SQLITE_TEXT`, `SQLITE_BLOB`, `SQLITE_FLOAT`, and `SQLITE_INTEGER` column types. Automatically injects the source database `path` as an implicit column unless the query already defines one. + +### `genTableRowsForSqliteTable(const fs::path&, const std::string&, TableRows&, bool)` +Opens an external SQLite database in read-only mode, sets a security authorizer, executes a provided query, and populates a `TableRows` result set by iterating over all returned rows. + +### `getSqliteJournalMode(const fs::path&)` +Queries `PRAGMA journal_mode` on a given SQLite database and returns the journal mode string (e.g., `"wal"`, `"delete"`) via the `Status` message field. Useful for determining safe access strategies before querying. + +## Usage Example + +```cpp +#include "osquery/sql/virtual_sqlite_table.h" + +TableRows results; +auto status = genTableRowsForSqliteTable( + "/path/to/external.db", + "SELECT id, name FROM records WHERE active = 1;", + results, + false /* disable locking (unix-none VFS) */ +); + +if (!status.ok()) { + LOG(ERROR) << "Query failed: " << status.getMessage(); +} + +// Check journal mode before querying a WAL-mode database +auto journal = getSqliteJournalMode("/path/to/external.db"); +// journal.getMessage() == "wal" +``` + +> **Note:** All database access is strictly read-only (`SQLITE_OPEN_READONLY`) and gated through `sqliteAuthorizer` to prevent unsafe operations on external databases. \ No newline at end of file diff --git a/osquery/sql/.virtual_table.md b/osquery/sql/.virtual_table.md new file mode 100644 index 00000000000..49405ccb9cc --- /dev/null +++ b/osquery/sql/.virtual_table.md @@ -0,0 +1,44 @@ + +Defines the SQLite virtual table interface for osquery, providing the core structures and functions needed to expose osquery table plugins as SQLite virtual tables. + +## Key Components + +### Structures + +| Name | Description | +|------|-------------| +| `BaseCursor` | Tracks SQLite cursor state per query — holds rows, position, generator, and current result | +| `VirtualTable` | Wraps a SQLite `sqlite3_vtab` with osquery metadata: table content and the active DB instance | + +### Globals + +- **`kAttachMutex`** — `RecursiveMutex` guarding concurrent table attach operations, since SQLite attach is not thread-safe + +### Functions + +| Function | Description | +|----------|-------------| +| `attachTableInternal()` | Attaches a named table plugin to a SQLite in-memory DB instance | +| `detachTableInternal()` | Drops a previously attached virtual table | +| `attachFunctionInternal()` | Registers a custom scalar function with SQLite | +| `attachVirtualTables()` | Bulk-attaches all registered table plugins to a DB instance | +| `registerForeignTables()` | Registers cross-platform schema-only table plugins from the generated amalgamation (excluded in extension builds) | + +## Usage Example + +```c +// Attach all tables to a new SQLite instance +SQLiteDBInstanceRef db = SQLiteDBManager::get(); +attachVirtualTables(db); + +// Attach a single table by name +Status s = attachTableInternal("processes", db, /*is_extension=*/false); +if (!s.ok()) { + LOG(ERROR) << "Failed to attach: " << s.getMessage(); +} + +// Detach when done +detachTableInternal("processes", db); +``` + +> **Note:** All attach operations should be performed while holding `kAttachMutex` to prevent race conditions in concurrent query environments. `BaseCursor` and `VirtualTable` are non-copyable and are only used internally by SQLite virtual table module callbacks. \ No newline at end of file diff --git a/osquery/sql/benchmarks/.sql_benchmarks.md b/osquery/sql/benchmarks/.sql_benchmarks.md new file mode 100644 index 00000000000..f2588f29a27 --- /dev/null +++ b/osquery/sql/benchmarks/.sql_benchmarks.md @@ -0,0 +1,45 @@ + +Performance benchmarks for osquery's SQL virtual table subsystem, measuring query execution overhead across different table configurations, database connection strategies, and row-generation approaches. + +## Key Components + +### Benchmark Table Plugins + +| Class | Description | +|---|---| +| `BenchmarkTablePlugin` | Minimal 2-column table returning 2 rows via `generate()` | +| `BenchmarkTableYieldPlugin` | Same schema/data as above but uses coroutine-style `generator()` with `RowYield` | +| `BenchmarkLongTablePlugin` | 2-column table returning 1,000 rows for high-volume read benchmarks | +| `BenchmarkWideTablePlugin` | 20-column table with configurable row count (`kWideCount`) via `generate()` | +| `BenchmarkWideTableYieldPlugin` | Wide table variant using `generator()` / `RowYield` pattern | + +### Registered Benchmarks + +| Benchmark | What It Measures | +|---|---| +| `SQL_virtual_table_registry` | Raw plugin dispatch via `Registry::call` (no SQL layer) | +| `SQL_virtual_table_internal` | Full `queryInternal` on a unique SQLite connection | +| `SQL_virtual_table_internal_yield` | Same as above using the yield-based generator | +| `SQL_virtual_table_internal_global` | Query cost using the shared/persistent `SQLiteDBManager::get()` | +| `SQL_virtual_table_internal_unique` | Query cost using a fresh `SQLiteDBManager::getUnique()` per iteration | +| `SQL_virtual_table_internal_long` | 1,000-row result set on a unique connection | +| `SQL_virtual_table_internal_wide` | 20-column table at 1/10/100/1,000 rows (ArgPair sweep) | +| `SQL_virtual_table_internal_wide_yield` | Wide table yield variant with same row-count sweep | +| `SQL_select_metadata` | Baseline: `SELECT count(*)` against `sqlite_temp_master` | +| `SQL_select_basic` | Full `SQLInternal` path querying an already-attached table | + +## Usage Example + +```cpp +// Run all SQL benchmarks (Google Benchmark CLI) +// Build with CMake, then execute: +``` + +```bash +./sql_benchmarks --benchmark_filter="SQL_virtual_table_internal_wide" +# Example output: +# SQL_virtual_table_internal_wide/0/1 3421 ns +# SQL_virtual_table_internal_wide/0/10 18742 ns +# SQL_virtual_table_internal_wide/0/100 174331 ns +# SQL_virtual_table_internal_wide/0/1000 1823442 ns +``` \ No newline at end of file diff --git a/osquery/sql/tests/.sql.md b/osquery/sql/tests/.sql.md new file mode 100644 index 00000000000..008a6a68bdb --- /dev/null +++ b/osquery/sql/tests/.sql.md @@ -0,0 +1,36 @@ + +Comprehensive unit test suite for osquery's SQL layer, validating core SQL functionality including table access, string escaping, custom SQL functions, and regex/split operations. + +## Key Components + +- **`SQLTests`** — GTest fixture that initializes the platform and plugin registry before each test +- **`TestTablePlugin`** — A mock `TablePlugin` used to verify constraint-aware query context behavior +- **Test Groups:** + - `test_raw_access` / `test_raw_access_context` — Direct table access via `SQL::selectAllFrom()` with and without column constraints + - `test_sql_escape` — Validates `escapeNonPrintableBytesEx()` for Unicode (Japanese, Russian) and ASCII input + - `test_sql_base64_*` — Covers `to_base64`, `from_base64`, and `conditional_to_base64` SQL functions + - `test_sql_md5/sha1/sha256` — Hash function SQL extensions + - `test_regex_match_*` — Tests the `regex_match()` SQL function for capture groups, no-match, invalid patterns, and oversized regex + - `test_split_*` — Tests the `split()` SQL function with single/multi-char delimiters and empty delimiter edge cases + - `test_regex_split_*` — Tests the `regex_split()` SQL function with regex patterns, alternation, and error cases + - `test_concat` — Tests the `concat()` SQL function with various argument types and nulls + +## Usage Example + +```cpp +// Running a specific test case via GTest filter +// ./sql_tests --gtest_filter=SQLTests.test_regex_match_multiple + +// Example of the pattern used throughout the test file +TEST_F(SQLTests, test_sql_sha256) { + QueryData d; + query("select sha256('test') as test;", d); + ASSERT_EQ(d.size(), 1U); + EXPECT_EQ(d[0]["test"], + "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); +} + +// Constraint-aware table access +auto results = SQL::selectAllFrom("test", "test_int", EQUALS, "1"); +EXPECT_EQ(results.size(), 2U); +``` \ No newline at end of file diff --git a/osquery/sql/tests/.sql_test_utils.md b/osquery/sql/tests/.sql_test_utils.md new file mode 100644 index 00000000000..7aea9eda6d7 --- /dev/null +++ b/osquery/sql/tests/.sql_test_utils.md @@ -0,0 +1,38 @@ + +Utility header providing test fixtures and serialization helpers for osquery SQL query testing, including pre-built query results, diff results, and query log item data in both structured and JSON formats. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kTestQuery` | `const std::string` | Standard test query: `SELECT * FROM test_table` | +| `getTestDBResultStream()` | Function | Returns query/result pairs representing incremental mutations on the test database | +| `getTestDBExpectedResults()` | Function | Returns the baseline expected results for `kTestQuery` | +| `getSerializedRowColumnNames()` | Function | Returns test column names, optionally unordered and with duplicates | +| `getSerializedRow()` | Function | Returns a `(JSON, RowTyped)` pair for serialization round-trip testing | +| `getSerializedQueryData()` | Function | Returns a `(JSON, QueryDataTyped)` pair for standard query data | +| `getSerializedQueryDataWithColumnOrder()` | Function | Returns query data with repeated/non-alphabetical columns | +| `getSerializedQueryDataJSON()` | Function | Returns a `(string, QueryDataTyped)` pair in raw JSON string form | +| `getSerializedDiffResults()` | Function | Returns a `(JSON, DiffResults)` pair for diff result testing | +| `getSerializedQueryLogItem()` | Function | Returns a `(JSON, QueryLogItem)` pair for log item testing | + +## Usage Example + +```cpp +#include "sql_test_utils.h" + +// Verify expected query results match baseline +QueryDataTyped expected = osquery::getTestDBExpectedResults(); + +// Round-trip serialization test for a single row +auto [json, row] = osquery::getSerializedRow(); +// Serialize row → compare to json, deserialize json → compare to row + +// Test diff result serialization +auto [diffJson, diffResult] = osquery::getSerializedDiffResults(); + +// Iterate over incremental DB mutation stream +for (auto& [query, result] : osquery::getTestDBResultStream()) { + // Apply query, assert result matches expected +} +``` \ No newline at end of file diff --git a/osquery/sql/tests/.sqlite_hashing_tests.md b/osquery/sql/tests/.sqlite_hashing_tests.md new file mode 100644 index 00000000000..fbd96633174 --- /dev/null +++ b/osquery/sql/tests/.sqlite_hashing_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the `community_id_v1` and `community_id_v1_strict` SQLite scalar functions in osquery, validating Community ID v1 hash generation for network flow identification across TCP, SCTP, and UDP protocols. + +## Key Components + +- **`SQLiteHashingTests`** — Google Test fixture that initializes the osquery platform and plugin registry via `SetUp()` +- **`test_community_id_v1_tcp`** — Verifies hash symmetry (src/dst order independence), seed variation, and mixed IPv4/IPv6 inputs for TCP (protocol `6`) +- **`test_community_id_v1_sctp`** — Validates hash output for SCTP (protocol `132`) flows with both seed `0` and seed `1` +- **`test_community_id_v1_udp`** — Validates hash output for UDP (protocol `17`) flows with both seed `0` and seed `1` +- **`test_community_id_v1_strict`** — Confirms that `community_id_v1_strict` returns an error (not a row) on invalid inputs such as a malformed IP or an out-of-range seed +- **`test_community_id_v1_nulls`** — Confirms that the non-strict `community_id_v1` silently returns an empty string for the same invalid inputs instead of failing + +## Usage Example + +```cpp +// Invoke community_id_v1 via osquery SQL — returns a base64 Community ID hash +// Signature: community_id_v1(src_ip, dst_ip, src_port, dst_port, proto [, seed]) + +SQL sql = SQL( + "SELECT community_id_v1('66.35.250.204', '128.232.110.120', 80, 34855, 6, 0) AS hash" +); + +if (sql.ok() && sql.rows().size() == 1U) { + std::string hash = sql.rows()[0].at("hash"); + // hash == "1:LQU9qZlK+B5F3KDmev6m5PMibrg=" +} + +// Strict variant: returns an error instead of empty string on bad input +SQL strict_sql = SQL( + "SELECT community_id_v1_strict('192.168.1.52', 'invalid', 10, 53, 17, 0) AS hash" +); +// strict_sql.ok() == false +``` + +## Notes + +- Hash output is **order-independent**: swapping src/dst IP and port pairs produces the same hash +- The optional `seed` parameter (default `0`) changes the hash output deterministically +- The `_strict` variant propagates errors as SQL failures; the non-strict variant returns an empty string on invalid input \ No newline at end of file diff --git a/osquery/sql/tests/.sqlite_network_tests.md b/osquery/sql/tests/.sqlite_network_tests.md new file mode 100644 index 00000000000..2e0e790d684 --- /dev/null +++ b/osquery/sql/tests/.sqlite_network_tests.md @@ -0,0 +1,30 @@ + +Unit tests for the SQLite `in_cidr_block` network function, verifying CIDR range membership for both IPv4 and IPv6 addresses using osquery's SQL interface. + +## Key Components + +- **`SQLiteNetworkTests`** — GTest fixture that initializes the osquery platform and plugin registry before each test +- **`test_in_cidr_block`** — Test case validating the `in_cidr_block()` SQL scalar function across four scenarios: + - IPv4 address within a `/26` subnet + - IPv4 address within a `/24` subnet (host address matching its own CIDR) + - IPv6 address within a `/64` subnet + - IPv6 address within a `/48` subnet (boundary address) + +## Usage Example + +```cpp +// Run a specific test from the build directory +./osquery_sql_tests --gtest_filter=SQLiteNetworkTests.test_in_cidr_block + +// The tested SQL function can also be used directly in osquery shell: +// SELECT in_cidr_block('10.0.0.0/26', '10.0.0.24') AS result; +// → result: 1 (true) + +// SELECT in_cidr_block('10.0.0.0/26', '10.0.0.65') AS result; +// → result: 0 (false) +``` + +## Notes + +- All test assertions expect `result = "1"` (true), meaning the tested IPs are expected to fall **within** the given CIDR blocks +- `SetUp()` calls `platformSetup()` and `registryAndPluginInit()` to bootstrap the full osquery runtime, which is required for SQL extensions and plugin-backed functions to resolve correctly \ No newline at end of file diff --git a/osquery/sql/tests/.sqlite_util_tests.md b/osquery/sql/tests/.sqlite_util_tests.md new file mode 100644 index 00000000000..2f5765e3164 --- /dev/null +++ b/osquery/sql/tests/.sqlite_util_tests.md @@ -0,0 +1,43 @@ + +Unit test suite for osquery's SQLite utility layer, validating query execution, database instance management, type precision, query planning, and table metadata operations. + +## Key Components + +### Test Fixture: `SQLiteUtilTests` +Inherits from `testing::Test`. `SetUp()` initializes the platform, registry/plugins, and configures allowed/disabled tables via `Flag::updateValue`. + +### Helper Functions +- **`getTestDBC()`** — Creates a unique `SQLiteDBInstance` populated with an in-memory `test_table` containing two sample rows (`mike/23`, `matt/24`) +- **`getTypes(const TableColumns&)`** — Extracts a `vector` from a `TableColumns` result for concise type assertions + +### Test Cases + +| Test | What It Validates | +|---|---| +| `test_zero_as_float_doesnt_convert_to_int` | `0.0` is preserved as float, not coerced to `0` | +| `test_precision_is_maintained` | High/low precision decimals survive round-trip | +| `test_simple_query_execution` | Basic `SELECT * FROM time` returns one row | +| `test_sqlite_instance_manager` | `get()` returns distinct DB handles per call | +| `test_sqlite_instance` | Primary DB handle is stable when no instances are in scope | +| `test_reset` | `resetPrimary()` destroys views created on the old instance | +| `test_direct_query_execution` | `queryInternal` results match expected fixture data | +| `test_no_results_query` | Zero-row result returns `ok()` status | +| `test_whitespace_query` | Whitespace-only SQL is handled gracefully | +| `test_affected_tables` | `affected_tables_` tracks and clears correctly | +| `test_table_attributes_event_based` | `eventBased()` is `true` for `process_events`, `false` for `time` | +| `test_get_query_columns` | Column names and types resolved via `getQueryColumnsInternal` | +| `test_get_query_tables` | `getQueryTables` extracts table names from complex queries including subqueries | +| `test_query_planner` | Column type inference for expressions, `CAST`, aggregates, joins, and literals | + +## Usage Example + +```cpp +// Run all SQLiteUtil tests +TEST_F(SQLiteUtilTests, test_direct_query_execution) { + auto dbc = getTestDBC(); + QueryDataTyped results; + auto status = queryInternal(kTestQuery, results, dbc); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(results, getTestDBExpectedResults()); +} +``` \ No newline at end of file diff --git a/osquery/sql/tests/.virtual_table.md b/osquery/sql/tests/.virtual_table.md new file mode 100644 index 00000000000..b3bfa9d3677 --- /dev/null +++ b/osquery/sql/tests/.virtual_table.md @@ -0,0 +1,45 @@ + +Unit test suite for osquery's virtual table implementation, validating `TablePlugin` column definitions, constraint handling, SQLite attachment, and query join behavior. + +## Key Components + +- **`VirtualTableTests`** — GTest fixture with setup for platform, registry, and database initialization +- **`sampleTablePlugin`** — Minimal two-column (`foo` INTEGER, `bar` TEXT) plugin used in basic attachment and column definition tests +- **`optionsTablePlugin`** / **`moreOptionsTablePlugin`** / **`additionalOnlyTablePlugin`** — Plugins exercising `INDEX`, `REQUIRED`, `OPTIMIZED`, and `ADDITIONAL` column options and their effect on `PRIMARY KEY` / `WITHOUT ROWID` DDL generation +- **`aliasesTablePlugin`** — Validates table aliases, column aliases, and `HIDDEN` column generation in schema statements +- **`pTablePlugin`** / **`kTablePlugin`** — Two-column integer plugins with static `generate()` output used for constraint stacking and join tests +- **`makeResult()`** — Helper that builds `QueryData` from a column name and value list for result comparison + +## Usage Example + +```cpp +// Attach a plugin to a SQLite connection and verify schema +auto table = std::make_shared(); +table->setName("sample"); + +auto tables = RegistryFactory::get().registry("table"); +tables->add("sample", std::make_shared()); + +auto dbc = SQLiteDBManager::get(); +auto status = attachTableInternal("sample", dbc, false); +EXPECT_EQ(status.getCode(), SQLITE_OK); + +// Verify generated DDL for a plugin with INDEX + REQUIRED options +std::string expected = + "(`id` INTEGER, `username` TEXT, `name` TEXT, PRIMARY KEY (`id`)) " + "WITHOUT ROWID"; +EXPECT_EQ(expected, columnDefinition(response, true, false)); +``` + +## Test Coverage + +| Test | What it validates | +|---|---| +| `test_tableplugin_columndefinition` | Basic `columnDefinition()` output | +| `test_tableplugin_options` | `INDEX` + `REQUIRED` flags, `PRIMARY KEY`, `WITHOUT ROWID` | +| `test_tableplugin_moreoptions` | `INDEX` + `ADDITIONAL` compound primary key | +| `test_tableplugin_additionalonly` | All-column primary key fallback | +| `test_tableplugin_aliases` | Table/column alias schema and `HIDDEN` columns | +| `test_sqlite3_attach_vtable` | Full attach lifecycle, failure on unknown table | +| `test_sqlite3_table_joins` | Cross-table join via `osquery_info` and `processes` | +| `test_constraints_stacking` | Subquery constraint propagation across multiple virtual tables | \ No newline at end of file diff --git a/osquery/system/network/.hostname.md b/osquery/system/network/.hostname.md new file mode 100644 index 00000000000..37e6826819d --- /dev/null +++ b/osquery/system/network/.hostname.md @@ -0,0 +1,35 @@ + +Defines the `HostIdentity` structure used to represent a host machine's identity within the osquery framework, capturing its fully qualified domain name and UUID. + +## Key Components + +### `HostIdentity` (class) +A final class encapsulating host identification data within the `osquery` namespace. + +| Member | Type | Description | +|--------|------|-------------| +| `fqdn` | `std::string` | Fully qualified domain name of the host | +| `uuid` | `std::string` | Unique identifier of the host | + +### `localhost()` (static method) +Factory method that constructs and returns a `HostIdentity` instance populated with the local machine's identity information. + +### `discloseSchema()` (static template method) +Integrates with the `schemer` serialization framework to expose the `fqdn` and `uuid` fields for schema-driven serialization/deserialization (e.g., JSON, config persistence). + +## Usage Example + +```cpp +#include +#include "hostname.h" + +// Retrieve identity of the local host +osquery::HostIdentity host = osquery::HostIdentity::localhost(); + +// Access identity fields +std::string fqdn = host.fqdn; // e.g. "my-machine.example.com" +std::string uuid = host.uuid; // e.g. "550e8400-e29b-41d4-a716-446655440000" + +// Schema disclosure is handled automatically by the schemer framework +// when serializing HostIdentity instances +``` \ No newline at end of file diff --git a/osquery/system/network/tests/.host_identity.md b/osquery/system/network/tests/.host_identity.md new file mode 100644 index 00000000000..90f8fd356e5 --- /dev/null +++ b/osquery/system/network/tests/.host_identity.md @@ -0,0 +1,35 @@ + +Defines unit tests for the `HostIdentity` localhost resolution functionality within the osquery framework. + +## Key Components + +- **`HostIdentityTests`** — Test fixture class inheriting from `testing::Test`, initializing the platform, registry/plugin system, and database plugin before each test. +- **`create_localhost`** — Test case validating that `HostIdentity::localhost()` returns a consistent, well-formed identity. + +## Usage Example + +```cpp +// The test verifies that HostIdentity::localhost() behaves correctly: + +auto const v1 = HostIdentity::localhost(); +auto const v2 = HostIdentity::localhost(); + +// FQDN must be non-empty +EXPECT_FALSE(v1.fqdn.empty()); + +// UUID must be a valid UUID string (throws if invalid) +boost::uuids::string_generator()(v1.uuid); + +// Repeated calls must return identical values (deterministic) +EXPECT_EQ(v1.fqdn, v2.fqdn); +EXPECT_EQ(v1.uuid, v2.uuid); +``` + +## Test Assertions + +| Assertion | Purpose | +|---|---| +| `v1.fqdn` is non-empty | Ensures the hostname/FQDN is resolved | +| `string_generator()(v1.uuid)` | Validates UUID format (throws on malformed input) | +| `v1.fqdn == v2.fqdn` | Confirms deterministic FQDN resolution across calls | +| `v1.uuid == v2.uuid` | Confirms stable UUID generation across calls | \ No newline at end of file diff --git a/osquery/system/usersgroups/windows/.groups_service.md b/osquery/system/usersgroups/windows/.groups_service.md new file mode 100644 index 00000000000..b38d06ecef0 --- /dev/null +++ b/osquery/system/usersgroups/windows/.groups_service.md @@ -0,0 +1,42 @@ + +Background service that enumerates and caches local Windows groups by extending the `InternalRunnable` dispatcher interface. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `GroupsService` | Class | Background runnable service that populates and maintains the `GroupsCache` for local Windows groups | +| `GroupsService()` | Constructor | Accepts a `std::promise` (signals cache readiness) and a shared pointer to the `GroupsCache` instance | +| `start()` | Protected method | Entry point called by the dispatcher when the service thread begins execution | +| `processLocalGroups()` | Private method | Iterates over local groups and invokes the provided callback to insert each `Group` into the cache | +| `UpdateGroupFunc` | Type alias | Defines the signature `void(Group)` used for the per-group update callback | + +## Usage Example + +```cpp +#include + +// Create the shared cache and a promise to signal completion +auto groups_cache = std::make_shared(); +std::promise cache_ready_promise; + +// Retrieve the future before moving the promise +auto cache_ready = cache_ready_promise.get_future(); + +// Instantiate and dispatch the service +auto groups_svc = std::make_shared( + std::move(cache_ready_promise), groups_cache); + +Dispatcher::addService(groups_svc); + +// Block until the initial cache population is complete +cache_ready.wait(); + +// GroupsCache is now populated and safe to query +``` + +## Notes + +- Windows-only — lives under the `windows/` users/groups subsystem path +- The `std::promise` pattern ensures consumers can block until the first full enumeration completes before querying the cache +- Follows the osquery `InternalRunnable` pattern; do not call `start()` directly — use `Dispatcher::addService()` \ No newline at end of file diff --git a/osquery/system/usersgroups/windows/.users_groups_cache.md b/osquery/system/usersgroups/windows/.users_groups_cache.md new file mode 100644 index 00000000000..429efb17b40 --- /dev/null +++ b/osquery/system/usersgroups/windows/.users_groups_cache.md @@ -0,0 +1,63 @@ + +Thread-safe Windows user and group caching layer for osquery, providing indexed lookup of system accounts with generation-based cache invalidation. + +## Key Components + +### Structs + +| Struct | Fields | Description | +|--------|--------|-------------| +| `User` | `uid`, `gid`, `sid`, `username`, `description`, `type`, `directory`, `generation` | Represents a Windows user account | +| `Group` | `gid`, `sid`, `groupname`, `comment`, `generation` | Represents a Windows group | + +Both structs implement `operator==` for value comparison (excluding `generation`). + +### Cache Index Types + +```c +using UidCacheIndex = std::unordered_multimap; +using GidCacheIndex = std::unordered_multimap; +using SidCacheIndex = std::unordered_map; +using GroupnameCacheIndex = std::unordered_map; +``` + +Indexes map lookup keys to positions in the backing `std::vector`. + +### `UsersCache` + +| Method | Description | +|--------|-------------| +| `initializeCache(users)` | Bulk-loads initial user set | +| `updateUser(user)` | Upserts by SID, bumps generation | +| `increaseGeneration()` | Advances cache generation counter | +| `cleanupExpiredUsers()` | Evicts users below current generation | +| `getUsersByUid(uid)` | Returns all users matching a UID | +| `getUserBySid(sid)` | Returns a single user by SID | +| `getAllUsers()` | Returns full user list | +| `clear()` | Resets cache state (testing) | + +### `GroupsCache` + +Mirrors `UsersCache` with additional `getGroupByName(name)` lookup. Internally maintains three indexes: GID, SID, and group name. + +## Usage Example + +```cpp +osquery::UsersCache cache; + +// Bulk initialize +cache.initializeCache(fetchAllSystemUsers()); + +// Incremental update cycle +for (auto& user : getUpdatedUsers()) { + cache.updateUser(user); +} +cache.increaseGeneration(); +cache.cleanupExpiredUsers(); // evicts stale entries + +// Lookups +auto user = cache.getUserBySid("S-1-5-21-..."); +auto users = cache.getUsersByUid(1001); +``` + +> **Generation mechanism:** `updateUser` / `updateGroup` stamps entries with the next generation. After a full refresh cycle, call `increaseGeneration()` then `cleanup*()` to evict any accounts removed from the system since the last scan. \ No newline at end of file diff --git a/osquery/system/usersgroups/windows/.users_service.md b/osquery/system/usersgroups/windows/.users_service.md new file mode 100644 index 00000000000..395cbaeb1d1 --- /dev/null +++ b/osquery/system/usersgroups/windows/.users_service.md @@ -0,0 +1,36 @@ + +Declares the `UsersService` class, a background service that populates and maintains a Windows user cache by enumerating local accounts and roaming profiles. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `UsersService` | Class | Background `InternalRunnable` that builds the `UsersCache` on Windows | +| `UsersService()` | Constructor | Accepts a `std::promise` (signals cache readiness) and a shared `UsersCache` pointer | +| `start()` | Protected method | Entry point called by the dispatcher to begin user enumeration | +| `processLocalAccounts()` | Private method | Enumerates local Windows accounts and invokes a callback per user; tracks processed SIDs | +| `processRoamingProfiles()` | Private method | Enumerates roaming profile accounts not already covered by local account processing | +| `UpdateUserFunc` | Type alias | `void(User user)` — callback signature used to update the cache | + +## Usage Example + +```cpp +#include +#include + +// Create the shared cache and a promise to signal when it's ready +auto users_cache = std::make_shared(); +std::promise cache_ready_promise; +auto cache_ready_future = cache_ready_promise.get_future(); + +// Instantiate and dispatch the service +auto service = std::make_shared( + std::move(cache_ready_promise), users_cache); + +osquery::Dispatcher::addService(service); + +// Block until the initial cache population is complete +cache_ready_future.wait(); +``` + +> **Note:** This header is Windows-specific. The `std::promise` pattern ensures that consumers can synchronize against the initial cache population before querying user data. \ No newline at end of file diff --git a/osquery/system/usersgroups/windows/tests/.users_groups_cache.md b/osquery/system/usersgroups/windows/tests/.users_groups_cache.md new file mode 100644 index 00000000000..aa00c407124 --- /dev/null +++ b/osquery/system/usersgroups/windows/tests/.users_groups_cache.md @@ -0,0 +1,45 @@ + +Unit tests for the `UsersCache` and `GroupsCache` classes on Windows, validating cache initialization, insertion, lookup, update, and expiration/cleanup behavior. + +## Key Components + +### Test Fixture +- **`UsersGroupsCacheTests`** — GTest fixture class wrapping all cache test cases. + +### Test Data +- **`kDefaultGroups`** — Three predefined `Group` entries (gid, SID, groupname, comment) used across multiple tests. +- **`kDefaultUsers`** — Three predefined `User` entries (uid, gid, SID, username, description, type, directory) used across multiple tests. + +### Test Cases + +| Test | Description | +|---|---| +| `test_empty_cache` | Verifies freshly constructed caches return empty results for all query methods | +| `test_cache_initialization` | Confirms `initializeCache()` correctly populates both `GroupsCache` and `UsersCache` | +| `test_groups_cache_update` | Validates `updateGroup()` inserts new entries and updates existing ones keyed by SID | +| `test_users_cache_update` | Validates `updateUser()` inserts new entries and updates existing ones keyed by SID | +| `test_groups_cache_search` | Tests `getGroupsByGid()` (returns multiple matches on duplicate GID) and `getGroupBySid()` | +| `test_users_cache_search` | Tests `getUsersByUid()` (returns multiple matches on duplicate UID) and `getUserBySid()` | +| `test_group_cache_cleaning` | Verifies `increaseGeneration()` + `cleanupExpiredGroups()` evicts stale entries while retaining updated ones | +| `test_user_cache_cleaning` | Verifies `increaseGeneration()` + `cleanupExpiredUsers()` evicts stale entries while retaining updated ones | + +## Usage Example + +```cpp +// Initialize and query a GroupsCache +GroupsCache groups_cache; +groups_cache.initializeCache(kDefaultGroups); + +// Lookup by GID (may return multiple if SIDs differ) +auto groups = groups_cache.getGroupsByGid(457); + +// Lookup by SID (unique, returns optional) +auto opt_group = groups_cache.getGroupBySid("S-123-457"); +if (opt_group.has_value()) { + // use *opt_group +} + +// Generational cleanup — evicts entries not seen in current generation +groups_cache.increaseGeneration(); +groups_cache.cleanupExpiredGroups(); +``` \ No newline at end of file diff --git a/osquery/tables/applications/.browser_firefox.md b/osquery/tables/applications/.browser_firefox.md new file mode 100644 index 00000000000..81874a91568 --- /dev/null +++ b/osquery/tables/applications/.browser_firefox.md @@ -0,0 +1,40 @@ + +Parses Firefox browser extension data from `extensions.json` files across user profiles, generating queryable addon records for the osquery `firefox_addons` table. + +## Key Components + +- **`kFirefoxPaths`** — Platform-specific profile directory paths (macOS, Linux, Windows) +- **`kFirefoxAddonKeys`** — Mapping of JSON field names to osquery column names for addon metadata +- **`findNestedMember()`** — Traverses dot-notation nested JSON keys (e.g., `"defaultLocale.name"`) within a `rapidjson::Value` +- **`isMemberTrue()`** — Checks boolean JSON members safely, returning `false` if the member is absent +- **`genFirefoxAddonsFromExtensions()`** — Reads and parses a single profile's `extensions.json`, extracting addon rows with fields like `name`, `version`, `creator`, `disabled`, and `autoupdate` +- **`genFirefoxAddons()`** — Entry point; iterates over all system users and their Firefox profiles, skipping `Crash Reports` and `Pending Pings` directories, then calls `genFirefoxAddonsFromExtensions()` per profile + +## Usage Example + +```cpp +// Called internally by osquery when the firefox_addons table is queried: +// SELECT * FROM firefox_addons; + +// Directly invoking the profile parser for a specific user/profile: +QueryData results; +genFirefoxAddonsFromExtensions( + "1001", // uid + "/home/user/.mozilla/firefox/abc123.default", // profile path + results +); + +for (const auto& row : results) { + std::cout << row.at("name") << " v" << row.at("version") << "\n"; +} +``` + +## Disabled State Logic + +An addon is marked `disabled = 1` if **any** of these JSON flags are `true`: + +| Flag | Meaning | +|---|---| +| `softDisable` | Temporarily disabled | +| `appDisabled` | Disabled by the application | +| `userDisabled` | Manually disabled by the user | \ No newline at end of file diff --git a/osquery/tables/applications/.jetbrains_plugins.md b/osquery/tables/applications/.jetbrains_plugins.md new file mode 100644 index 00000000000..0b2d0d00fb7 --- /dev/null +++ b/osquery/tables/applications/.jetbrains_plugins.md @@ -0,0 +1,35 @@ + +Defines constants and utilities for locating and identifying JetBrains IDE plugin directories across Windows, macOS, and Linux within the osquery tables framework. + +## Key Components + +- **`JetBrainsProductType`** — Enum class enumerating all supported JetBrains IDEs: CLion, DataGrip, GoLand, IntelliJ IDEA (Ultimate + Community), PhpStorm, PyCharm (Ultimate + Community), ReSharper, Rider, RubyMine, RustRover, and WebStorm. +- **`ProductPathMap`** — Type alias for `std::vector>` mapping product types to their relative plugin directory paths. +- **`kWindowsPathList`** — Platform path map for Windows (`AppData\Roaming\JetBrains\%\plugins`). +- **`kMacOsPathList`** — Platform path map for macOS (`Library/Application Support/JetBrains/%/plugins`). +- **`kLinuxPathList`** — Platform path map for Linux (`.local/share/JetBrains/%`). Note: Linux paths omit the trailing `/plugins` segment. +- **`kProductTypeToString`** — Lookup map converting `JetBrainsProductType` enum values to lowercase string identifiers (e.g., `"intellij_idea"`). +- **`getProductName()`** — Returns the string name for a given `JetBrainsProductType`. +- **`putMoreLikelyPluginJarsFirst()`** — Reorders a list of files in a plugin's `lib` directory, prioritizing the most likely primary JAR. +- **`fileNameIsLikeVersionedLibraryName()`** — Predicate that checks whether a filename matches the pattern of a versioned library JAR. + +## Usage Example + +```c +// Iterate all known Windows JetBrains plugin paths +for (const auto& [product, path] : osquery::tables::kWindowsPathList) { + std::string name = osquery::tables::getProductName(product); + // e.g., name = "clion", path = "AppData\\Roaming\\JetBrains\\CLion%\\plugins" + std::cout << name << " -> " << path << "\n"; +} + +// Sort plugin JARs for a plugin directory +std::vector jars = {"plugin-1.0.jar", "plugin.jar", "util.jar"}; +osquery::tables::putMoreLikelyPluginJarsFirst("my-plugin", jars); + +// Check if a filename looks like a versioned library +bool versioned = osquery::tables::fileNameIsLikeVersionedLibraryName("log4j-2.17.jar"); +// versioned == true +``` + +> **Note:** The `%` wildcard in path strings represents a version suffix glob, allowing matching across multiple installed IDE versions. \ No newline at end of file diff --git a/osquery/tables/applications/.vscode_extensions.md b/osquery/tables/applications/.vscode_extensions.md new file mode 100644 index 00000000000..be45d4471ec --- /dev/null +++ b/osquery/tables/applications/.vscode_extensions.md @@ -0,0 +1,41 @@ + +Implements an osquery table that enumerates installed VS Code extensions for all users by parsing the `extensions.json` manifest files found in user home directories. + +## Key Components + +### `genReadJSONAndAddExtensionRows` +Reads and parses a VS Code `extensions.json` file at the given path, extracting per-extension metadata and appending rows to the query results. Fields extracted per extension: + +| Column | Source JSON Key | +|---|---| +| `name` | `identifier.id` | +| `uuid` | `identifier.uuid` | +| `version` | `version` | +| `path` | `location.path` | +| `publisher` | `metadata.publisherDisplayName` | +| `publisher_id` | `metadata.publisherId` | +| `installed_at` | `metadata.installedTimestamp` | +| `prerelease` | `metadata.isPreReleaseVersion` | + +### `ConfDir` (struct) +A sortable structure holding a user's `uid`, home directory path, and VS Code edition label (`vscode` or `vscode_insiders`). Used to deduplicate config directory candidates via `std::set`. + +### `genVSCodeExtensions` +The osquery table entry point. Iterates over users from the query context, builds candidate config directory paths (covering `.vscode`, `.vscode-server`, `.vscode-insiders`, `.vscode-server-insiders`), and delegates to `genReadJSONAndAddExtensionRows` for each. + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via osqueryi: +// SELECT name, version, publisher, installed_at, vscode_edition +// FROM vscode_extensions +// WHERE uid = '1000'; + +QueryContext context; +QueryData results = genVSCodeExtensions(context); +for (const auto& row : results) { + std::cout << row.at("name") << " v" << row.at("version") << "\n"; +} +``` + +> **Note:** Covers both local (`.vscode`) and remote SSH server (`.vscode-server`) extension directories, supporting standard and Insiders editions. \ No newline at end of file diff --git a/osquery/tables/applications/chrome/.chrome_extension_content_scripts.md b/osquery/tables/applications/chrome/.chrome_extension_content_scripts.md new file mode 100644 index 00000000000..5cf984146e2 --- /dev/null +++ b/osquery/tables/applications/chrome/.chrome_extension_content_scripts.md @@ -0,0 +1,38 @@ + +Generates osquery table rows for Chrome extension content scripts by iterating over all detected Chrome profiles and their installed extensions. + +## Key Components + +### `genChromeExtensionContentScripts(QueryContext& context)` +The sole exported table generator function. It: +- Retrieves all Chrome-family browser profiles via `getChromeProfiles()` +- Iterates each profile's extension list, extracting metadata (version, path, identifier, referenced status) +- Flattens each extension's `content_scripts_matches` entries into individual rows (one row per `match`/`script` pair) +- Logs an error if the `version` property is missing from an extension's properties map + +### Output Columns per Row + +| Column | Type | Description | +|---|---|---| +| `browser_type` | `TEXT` | Chrome variant name (e.g. Chrome, Brave) | +| `uid` | `BIGINT` | OS user ID owning the profile | +| `profile` | `TEXT` | Profile display name | +| `profile_path` | `TEXT` | Filesystem path to the profile | +| `version` | `TEXT` | Extension version string | +| `path` | `TEXT` | Extension directory path | +| `referenced` | `BIGINT` | `1` if referenced in profile settings | +| `identifier` | `TEXT` | Extension ID from profile settings | +| `match` | `TEXT` | URL match pattern for the content script | +| `script` | `TEXT` | Script file path within the extension | + +## Usage Example + +```cpp +// Called internally by the osquery table infrastructure +QueryData rows = genChromeExtensionContentScripts(context); + +// Each row represents one content script match entry, e.g.: +// { browser_type: "chrome", uid: 1000, profile: "Default", +// identifier: "abc123", match: "*://*.example.com/*", +// script: "content.js", referenced: 1 } +``` \ No newline at end of file diff --git a/osquery/tables/applications/chrome/.chrome_extensions.md b/osquery/tables/applications/chrome/.chrome_extensions.md new file mode 100644 index 00000000000..037096e749b --- /dev/null +++ b/osquery/tables/applications/chrome/.chrome_extensions.md @@ -0,0 +1,44 @@ + +Enumerates installed Chrome (and Chromium-based browser) extensions across all user profiles by reading extension manifests and profile settings, returning structured rows for each discovered extension. + +## Key Components + +### `genChromeExtensions(QueryContext& context)` +The primary table generator function registered with osquery's virtual table system. Iterates over all detected Chrome profiles and their associated extensions, building a result row per extension with the following data: + +| Column | Source | +|---|---| +| `browser_type`, `uid`, `profile`, `profile_path` | Profile metadata via `getChromeProfiles()` | +| `name`, `version`, `author`, `description` | Manifest JSON via `getExtensionProperty()` | +| `permissions`, `optional_permissions` | Manifest JSON (with `_json` variants) | +| `state`, `from_webstore`, `install_time` | Profile settings via `getExtensionProfileSettingsValue()` | +| `install_timestamp` | Converted from WebKit timestamp via `webkitTimeToUnixTimestamp()` | +| `identifier` | Computed identifier (falls back to empty string) | +| `locale` *(deprecated/hidden)* | Alias of `default_locale` | + +## Usage Example + +```cpp +// Query via osquery SQL interface +SELECT + browser_type, + name, + version, + identifier, + permissions, + from_webstore, + install_timestamp +FROM chrome_extensions +WHERE uid = 1000; +``` + +```bash +# Run directly via osqueryi +osqueryi "SELECT name, version, identifier, state FROM chrome_extensions;" +``` + +## Notes + +- `install_timestamp` always defaults to `0` if WebKit timestamp conversion fails, ensuring consistent schema validation. +- `persistent` is normalized to a `BIGINT` (`1`/`0`) from the string `"true"`/`"false"` using case-insensitive comparison. +- The `locale` column is **deprecated and hidden**; use `default_locale` instead. \ No newline at end of file diff --git a/osquery/tables/applications/chrome/.utils.md b/osquery/tables/applications/chrome/.utils.md new file mode 100644 index 00000000000..6bfd0fe53d0 --- /dev/null +++ b/osquery/tables/applications/chrome/.utils.md @@ -0,0 +1,54 @@ + +Utility header for Chrome-based browser extension and profile introspection within the osquery tables subsystem. Defines data structures and helper functions for enumerating browser profiles, parsing extension manifests, and extracting metadata across multiple Chromium-derived browsers. + +## Key Components + +### Enums +- **`ChromeBrowserType`** — Identifies supported Chromium-based browsers (Chrome, Brave, Edge, Opera, Vivaldi, Arc, etc.) +- **`ExtensionKeyError`** — Error codes for extension identifier computation failures + +### Structs +- **`ChromeProfileSnapshot`** — Raw file contents captured from a browser profile directory, including `Preferences`, `Secure Preferences`, and extension manifests +- **`ChromeProfile`** — Parsed, structured representation of a profile with its associated `Extension` list and metadata +- **`ContentScriptsEntry`** — A `script`/`match` pair parsed from a manifest's `content_scripts` array + +### Key Functions + +| Function | Purpose | +|---|---| +| `getChromeProfiles()` | Entry point — returns all profiles for all supported browsers | +| `getChromeProfilesFromSnapshotList()` | Converts raw snapshots into structured `ChromeProfile` objects | +| `getExtensionFromSnapshot()` | Parses a single extension snapshot into a `ChromeProfile::Extension` | +| `computeExtensionIdentifier()` | Derives extension ID from the manifest `key` property | +| `webkitTimeToUnixTimestamp()` | Converts WebKit epoch timestamps to Unix format | +| `getStringLocalization()` | Resolves `__MSG_*` style localized strings from a parsed locale tree | +| `getExtensionProperty()` | Safe accessor for extension manifest properties with optional defaults | + +## Usage Example + +```cpp +#include "utils.h" + +// Retrieve all Chrome-based browser profiles for a query +osquery::QueryContext ctx; +auto profiles = osquery::tables::getChromeProfiles(ctx); + +for (const auto& profile : profiles) { + // Print browser name and profile path + std::cout << getChromeBrowserName(profile.type) + << " - " << profile.path << "\n"; + + for (const auto& ext : profile.extension_list) { + // Resolve computed extension identifier + auto id_result = osquery::tables::computeExtensionIdentifier(ext); + if (id_result) { + std::cout << " Extension ID: " << id_result.get() << "\n"; + } + + // Access a manifest property with a fallback default + std::string version = osquery::tables::getExtensionProperty( + ext, "version", /*optional=*/true, "unknown"); + std::cout << " Version: " << version << "\n"; + } +} +``` \ No newline at end of file diff --git a/osquery/tables/applications/chrome/tests/.utils.md b/osquery/tables/applications/chrome/tests/.utils.md new file mode 100644 index 00000000000..1dc4e8f5b81 --- /dev/null +++ b/osquery/tables/applications/chrome/tests/.utils.md @@ -0,0 +1,36 @@ + +Unit test file for Chrome browser extension utility functions in the osquery tables framework, validating parsing of extension manifests, profile preferences, localization files, and profile settings across Windows and Linux platforms. + +## Key Components + +- **`ChromeUtilsTests`** — GTest fixture class housing all Chrome utility unit tests +- **Test constants** — Platform-specific (`WIN32` / POSIX) path fixtures and JSON blobs for profile preferences, extension manifests, and localization files +- **`trim_trailing_newline()`** — Helper to normalize property values before assertion + +## Test Cases + +| Test | Validates | +|---|---| +| `getChromeBrowserName` | Browser type enum → name string mapping (Chrome, Brave, Chromium, Yandex, Opera) | +| `getExtensionContentScriptsMatches` | Parses `content_scripts` JS/match pairs from manifest | +| `getExtensionProperties` | Extracts all manifest fields (name, key, permissions, locale, etc.) | +| `getProfileNameFromPreferences` | Reads profile name from `Preferences` JSON | +| `getStringLocalization` | Resolves `__MSG_*__` tokens against localization file; case-insensitive | +| `getExtensionProfileSettings` | Matches extension path (relative and absolute) to preferences settings block | +| `getExtensionFromSnapshot` | Constructs a `ChromeProfile::Extension` from a snapshot, verifying SHA-256 manifest hash | + +## Usage Example + +```cpp +// Typical pattern used throughout these tests: +pt::iptree parsed_manifest; +std::stringstream json_stream; +json_stream << kTestExtensionManifest; +pt::read_json(json_stream, parsed_manifest); + +ChromeProfile::Extension::Properties properties; +auto status = getExtensionProperties(properties, parsed_manifest); +ASSERT_TRUE(status.ok()); +EXPECT_EQ(properties["name"], "Test extension"); +EXPECT_EQ(properties["version"], "1.00.0"); +``` \ No newline at end of file diff --git a/osquery/tables/applications/darwin/.browser_plugins.md b/osquery/tables/applications/darwin/.browser_plugins.md new file mode 100644 index 00000000000..94850dca536 --- /dev/null +++ b/osquery/tables/applications/darwin/.browser_plugins.md @@ -0,0 +1,38 @@ + +Implements osquery table generation for macOS browser plugins and Safari extensions, querying both system-wide and per-user plugin/extension data from the filesystem. + +## Key Components + +### Functions + +| Function | Description | +|---|---| +| `genBrowserPlugin()` | Parses a single browser plugin's `Info.plist` and appends a result row including name, version, identifier, and disabled status | +| `genBrowserPlugins()` | Entry point for the `browser_plugins` table; enumerates system and user-level plugins under `/Library/Internet Plug-Ins/` | +| `getSandboxedExtensionData()` | Extracts metadata from a Safari App/Web Extension's `Info.plist`, validating the Safari extension point identifier | +| `genSafariSandboxedExtensions()` | Traverses `/Applications/` for installed Safari extensions, then cross-references per-user extension plists to determine installation ownership | +| `genSafariExtensions()` | Entry point for the `safari_extensions` table; delegates to `genSafariSandboxedExtensions()` | +| `getPtreeFromPlist()` | Helper that reads and parses a plist file into a Boost property tree | +| `isUserExtension()` | Checks user-scoped App/Web extension plists to determine if a given extension is installed for a specific user | +| `isExtensionAppExcluded()` | Filters out apps like Xcode and Safari itself from extension discovery | +| `jsonBoolAsInt()` | Normalizes plist boolean string values to `"1"` or `"0"` | + +### Key Constants + +- `kBrowserPluginsPath` — `/Library/Internet Plug-Ins/` +- `kSafariAppExtensionsPath` — `/Applications/` +- `kBrowserPluginKeys` — plist key → column name mapping for browser plugins + +## Usage Example + +```cpp +// Querying browser plugins via osquery SQL: +// SELECT * FROM browser_plugins; +// SELECT * FROM browser_plugins WHERE uid = '501'; + +// Querying Safari extensions: +// SELECT * FROM safari_extensions; +// SELECT name, identifier, version, path FROM safari_extensions WHERE uid = '501'; +``` + +> **Note:** `genSafariExtensions()` requires **Full Disk Access (FDA)** permissions on macOS to read sandboxed Safari extension metadata. \ No newline at end of file diff --git a/osquery/tables/applications/linux/.lxd.md b/osquery/tables/applications/linux/.lxd.md new file mode 100644 index 00000000000..0ef7bcb038b --- /dev/null +++ b/osquery/tables/applications/linux/.lxd.md @@ -0,0 +1,58 @@ + +Implements osquery virtual tables for querying LXD container runtime data by communicating with the LXD daemon over its UNIX domain socket using raw HTTP/1.0 requests and parsing JSON responses via Boost.PropertyTree. + +## Key Components + +| Function | Description | +|---|---| +| `lxdApi()` | Core HTTP GET client that communicates with the LXD UNIX socket, validates the response, and parses JSON into a `pt::ptree` | +| `genLxdInstances()` | Table entry point for `lxd_instances`; supports filtering by `name` constraint | +| `getLxdInstance()` | Fetches name, status, stateful/ephemeral flags, creation time, and base image for a single container | +| `getLxdInstanceState()` | Fetches PID and process count from `/state` endpoint | +| `getLxdInstanceMetadata()` | Fetches OS, architecture, and description from `/metadata` endpoint | +| `genLxdInstanceConfig()` | Table entry point for `lxd_instance_config`; enumerates key/value config pairs | +| `genLxdInstanceDevices()` | Table entry point for `lxd_instance_devices`; enumerates expanded device properties | +| `genLxdImages()` | Table entry point for `lxd_images`; supports filtering by `id` constraint | +| `getLxdCert()` | Fetches certificate metadata (name, type, fingerprint) from the LXD API | + +## Configuration + +A runtime flag controls the socket path: + +```bash +# Default path +/var/lib/lxd/unix.socket + +# Override via osquery flag +--lxd_socket=/var/snap/lxd/common/lxd/unix.socket +``` + +## Usage Example + +Query LXD containers from osquery: + +```cpp +// Internally called when osquery executes: +// SELECT name, status, pid, os FROM lxd_instances; +QueryData genLxdInstances(QueryContext& context) { + QueryData results; + // Filtered query by name + if (context.constraints["name"].exists(EQUALS)) { + for (const auto& name : context.constraints["name"].getAll(EQUALS)) { + getLxdInstance("/1.0/containers/" + name, results); + } + } else { + getLxdInstances(results); // Enumerate all containers + } + return results; +} +``` + +Equivalent osquery SQL: + +```sql +SELECT name, status, pid, os, architecture FROM lxd_instances; +SELECT key, value FROM lxd_instance_config WHERE name = 'my-container'; +SELECT device, device_type, key, value FROM lxd_instance_devices WHERE name = 'my-container'; +SELECT id, os, release, size, aliases FROM lxd_images; +``` \ No newline at end of file diff --git a/osquery/tables/applications/posix/.browser_opera.md b/osquery/tables/applications/posix/.browser_opera.md new file mode 100644 index 00000000000..9e4f3ee5357 --- /dev/null +++ b/osquery/tables/applications/posix/.browser_opera.md @@ -0,0 +1,30 @@ + +Generates osquery table data for installed Opera browser extensions by locating and parsing extension manifests from Opera's platform-specific profile directory. + +## Key Components + +- **`kOperaPath`** — Platform-conditional macro defining Opera's profile directory (`/Library/Application Support/com.operasoftware.Opera/` on macOS, `/.config/opera/` on Linux) +- **`kOperaExtensionsPath`** — Macro defining the extensions subdirectory (`Extensions/`) +- **`genOperaExtensions(QueryContext&)`** — Table generator function that delegates to the shared `genChromeBasedExtensions` utility, passing Opera's resolved extensions path + +## Usage Example + +This file is compiled as part of the osquery `browser_extensions` virtual table infrastructure. The function is registered as a table generator and invoked automatically by the osquery runtime: + +```cpp +// Called internally by osquery when querying: +// SELECT * FROM opera_extensions; +QueryData genOperaExtensions(QueryContext& context) { + return genChromeBasedExtensions(context, {(kOperaPath kOperaExtensionsPath)}); + // Resolves to per-user path, e.g.: + // ~/.config/opera/Extensions/ (Linux) + // ~/Library/Application Support/ + // com.operasoftware.Opera/Extensions/ (macOS) +} +``` + +## Notes + +- Reuses `genChromeBasedExtensions` from `browser_utils.h` since Opera is Chromium-based and shares the same extension manifest format +- Paths are relative to each discovered home directory — the utility handles home directory enumeration +- Windows is not currently handled (no `kOperaPath` defined for `_WIN32`) \ No newline at end of file diff --git a/osquery/tables/applications/posix/.carbon_black.md b/osquery/tables/applications/posix/.carbon_black.md new file mode 100644 index 00000000000..fa080d16613 --- /dev/null +++ b/osquery/tables/applications/posix/.carbon_black.md @@ -0,0 +1,39 @@ + +Implements the `carbon_black_info` osquery virtual table by reading Carbon Black EDR sensor metadata, configuration settings, and queue sizes from the local filesystem on Linux endpoints. + +## Key Components + +### Constants +| Constant | Path | +|---|---| +| `kCbSensorIdFile` | `/var/lib/cb/sensor.id` | +| `kCbSensorSettingsFile` | `/var/lib/cb/sensorsettings.ini` | +| `kCbDir` | `/var/lib/cb/` | + +### Functions + +- **`getSensorId(Row& r)`** — Reads the 16-byte sensor ID file, extracts a hex substring (bytes 11–16), converts it to an unsigned integer, and populates `sensor_id`. +- **`getSensorSettings(Row& r)`** — Parses `sensorsettings.ini` using Boost's INI parser and maps `CB.*` keys (collection flags, server URL, config name) into the result row. URL-encoded characters (`%20`, `%3A`) are decoded inline. +- **`getQueue(Row& r)`** — Recursively lists `/var/lib/cb/`, accumulates sizes of `filedata`/`metadata` files into `binary_queue` and `events.*` files into `event_queue`. +- **`genCarbonBlackInfo(QueryContext& context)`** — Entry point; composes all three helpers into a single `QueryData` result row. + +## Usage Example + +```cpp +// Called automatically by osquery when the table is queried: +// SELECT * FROM carbon_black_info; + +QueryData genCarbonBlackInfo(QueryContext& context) { + Row r; + QueryData results; + + getSensorId(r); // r["sensor_id"] = 12345 + getSensorSettings(r); // r["config_name"], r["collect_processes"], etc. + getQueue(r); // r["binary_queue"], r["event_queue"] + results.push_back(r); + + return results; +} +``` + +> **Note:** This table is Linux-only (paths are hardcoded under `/var/lib/cb/`). If the sensor files are absent or malformed, affected fields are silently omitted or set to `-1`/`0` defaults rather than failing the query. \ No newline at end of file diff --git a/osquery/tables/applications/posix/.docker.md b/osquery/tables/applications/posix/.docker.md new file mode 100644 index 00000000000..ece0ce5dec1 --- /dev/null +++ b/osquery/tables/applications/posix/.docker.md @@ -0,0 +1,35 @@ + +Implements osquery table generation for Docker daemon inspection by communicating with the Docker UNIX domain socket via HTTP/1.0 requests, parsing JSON responses into queryable osquery tables. + +## Key Components + +- **`dockerApi(uri, tree)`** — Core HTTP client that connects to the Docker UNIX socket (`/var/run/docker.sock` by default), sends a GET request, validates the HTTP 200 response, and parses the JSON body into a Boost property tree. +- **`genVersion()`** — Table generator for `docker_version`; returns Docker daemon version metadata (API version, Go version, kernel, architecture, etc.). +- **`genInfo()`** — Table generator for `docker_info`; returns daemon-level info including container counts, resource limits, cgroup/logging drivers, and proxy settings. +- **`genContainers()`** — Table generator for `docker_containers`; supports prefix-based ID filtering via SQL `WHERE` constraints. +- **`getLabels()`** — Shared utility for fetching key/value labels from containers, volumes, and networks. +- **`getQuery()`** — Translates osquery SQL `EQUALS` constraints into Docker API filter query strings (URL-encoded JSON). +- **`getValue()`** — Resolves property tree values with prefix-matching support and strips `sha256:` prefixes from image digests. +- **`checkConstraintValue()`** — Validates that constraint values are valid SHA-256 hex strings (max 64 chars). +- **`FLAG(docker_socket)`** — Runtime-configurable path to the Docker UNIX socket. + +## Usage Example + +```cpp +// Query the docker_version table via osquery SQL +// SELECT version, api_version, go_version FROM docker_version; + +// Override socket path at runtime: +// --docker_socket=/run/docker.sock + +// Internal API call pattern: +pt::ptree tree; +Status s = dockerApi("/containers/json?all=1", tree); +if (s.ok()) { + for (const auto& entry : tree) { + std::string id = entry.second.get("Id", ""); + } +} +``` + +> **Note:** Requires a running Docker daemon and read access to the UNIX socket. On Linux, extended columns for user namespace support are conditionally compiled via `__linux__`. \ No newline at end of file diff --git a/osquery/tables/applications/posix/.prometheus_metrics.md b/osquery/tables/applications/posix/.prometheus_metrics.md new file mode 100644 index 00000000000..d7ea278e74e --- /dev/null +++ b/osquery/tables/applications/posix/.prometheus_metrics.md @@ -0,0 +1,45 @@ + +Declares the data structures and core functions for the `prometheus_metrics` osquery table, which scrapes and parses Prometheus-format metrics from HTTP endpoints. + +## Key Components + +### Constants + +| Constant | Value | Description | +|---|---|---| +| `kColTargetName` | `"target_name"` | Column name for the scraped target URL | +| `kColMetric` | `"metric_name"` | Column name for the metric identifier | +| `kColValue` | `"metric_value"` | Column name for the metric value | +| `kColTimeStamp` | `"timestamp_ms"` | Column name for the scrape timestamp | + +### Struct + +**`PrometheusResponseData`** — Holds the raw HTTP response body (`content`) and the millisecond-precision timestamp (`timestampMS`) recorded at scrape time. + +### Functions + +- **`scrapeTargets(scrapeResults, timeoutS)`** — Performs HTTP GET requests against all target URLs provided as keys in `scrapeResults`, writing the response body and timestamp into each corresponding `PrometheusResponseData` value. Default timeout is `1` second. +- **`parseScrapeResults(scrapeResults, rows)`** — Converts the raw Prometheus text-format payloads from `scrapeResults` into osquery `QueryData` rows, mapping each metric to the four defined columns. + +## Usage Example + +```cpp +#include "prometheus_metrics.h" + +using namespace osquery::tables; + +// Define targets to scrape +std::map scrapeResults = { + {"http://localhost:9090/metrics", {}}, + {"http://localhost:9100/metrics", {}}, +}; + +// Scrape all targets with a 2-second timeout +scrapeTargets(scrapeResults, 2); + +// Parse results into osquery rows +QueryData rows; +parseScrapeResults(scrapeResults, rows); + +// rows now contains columns: target_name, metric_name, metric_value, timestamp_ms +``` \ No newline at end of file diff --git a/osquery/tables/applications/posix/tests/.prometheus_metrics_tests.md b/osquery/tables/applications/posix/tests/.prometheus_metrics_tests.md new file mode 100644 index 00000000000..a62845b3109 --- /dev/null +++ b/osquery/tables/applications/posix/tests/.prometheus_metrics_tests.md @@ -0,0 +1,41 @@ + +Unit tests for the `parseScrapeResults` function in osquery's Prometheus metrics table, validating correct parsing of Prometheus exposition format data into query rows across various scenarios. + +## Key Components + +- **`comparePMRow`** — Inline comparator for sorting `Row` objects by concatenated target name and metric name fields +- **`validate`** — Helper that calls `parseScrapeResults`, sorts both actual and expected results, then asserts equality across all four columns (`target_name`, `metric`, `value`, `timestamp`) +- **`PrometheusMetricsTest`** — GTest fixture class hosting all test cases + +### Test Cases + +| Test | Scenario | +|---|---| +| `no_targets` | Empty scrape map produces no rows | +| `happy_path_0_metrics` | Target with empty response body produces no rows | +| `happy_path_1_metric` | Single metric with a comment line is parsed correctly | +| `happy_path_extra_spaces_lines` | Extra whitespace and blank lines are stripped/ignored | +| `happy_path_10_metrics_1_target` | Full Prometheus exposition block with 10 metrics, 1 target | +| `happy_path_10_metrics_2_targets` | 10 metrics each across 2 distinct targets (20 rows total) | + +## Usage Example + +```cpp +// Running the tests via ctest or gtest directly: +// ctest -R PrometheusMetricsTest +// or directly: +// ./prometheus_metrics_tests --gtest_filter=PrometheusMetricsTest.* + +// Example of what validate() asserts internally: +std::map sr = { + {"example1.com", + PrometheusResponseData{"process_open_fds 7", now}}}; + +QueryData expected = { + {{kColTargetName, "example1.com"}, + {kColMetric, "process_open_fds"}, + {kColValue, "7"}, + {kColTimeStamp, std::to_string(now.count())}}}; + +validate(sr, expected); // asserts parseScrapeResults output matches expected +``` \ No newline at end of file diff --git a/osquery/tables/applications/tests/.jetbrains_plugins_tests.md b/osquery/tables/applications/tests/.jetbrains_plugins_tests.md new file mode 100644 index 00000000000..e9fc296228c --- /dev/null +++ b/osquery/tables/applications/tests/.jetbrains_plugins_tests.md @@ -0,0 +1,46 @@ + +Unit tests for the `jetbrains_plugins` osquery table, validating JAR file classification and ordering logic used to identify JetBrains IDE plugin metadata. + +## Key Components + +### Test Fixture +- **`JetbrainsPluginsTest`** — GTest fixture class wrapping all JetBrains plugin table tests + +### Test Cases + +| Test | Description | +|---|---| +| `test_fileNameIsLikeVersionedLibraryName` | Validates detection of versioned JAR filenames (e.g., `kotlin-stdlib-1.5.0.jar`) vs. non-versioned ones | +| `test_putMoreLikelyPluginJarsFirst` | Validates the prioritization/sorting logic for selecting the most likely plugin-defining JAR | + +## Tested Functions + +- **`fileNameIsLikeVersionedLibraryName(filename)`** — Returns `true` if a JAR filename matches a versioned library pattern (numeric or milestone suffix like `-1.5.0` or `-m5`) +- **`putMoreLikelyPluginJarsFirst(pluginName, files)`** — Reorders a list of JAR paths so the most likely plugin descriptor JAR appears first + +## Ordering Priority (Most → Least Likely) + +```text +1. Plugin name match (e.g., "banana-stdlib.jar" for plugin "banana") +2. Shorter filenames +3. Milestone-versioned jars (e.g., -m5.jar) +4. Generic plugin jars (e.g., "plugin.jar") +5. Versioned numeric jars (e.g., json-2.8.0.jar) +6. resources.jar / -idea.jar / database-plugin.jar suffixes (last) +``` + +## Usage Example + +```cpp +// Check if a JAR filename appears versioned +bool versioned = fileNameIsLikeVersionedLibraryName("kotlin-stdlib-1.5.0.jar"); +// versioned == true + +// Sort JARs so the most plugin-representative one comes first +std::vector jars = { + "/plugin/lib/resources.jar", + "/plugin/lib/banana-stdlib.jar" +}; +putMoreLikelyPluginJarsFirst("banana", jars); +// jars[0] == "/plugin/lib/banana-stdlib.jar" +``` \ No newline at end of file diff --git a/osquery/tables/applications/windows/.carbon_black.md b/osquery/tables/applications/windows/.carbon_black.md new file mode 100644 index 00000000000..63a27ba2c82 --- /dev/null +++ b/osquery/tables/applications/windows/.carbon_black.md @@ -0,0 +1,26 @@ + +Implements the osquery `carbon_black_info` table for Windows, exposing Carbon Black EDR sensor configuration and queue metrics by reading from the Windows Registry and the local `CarbonBlack` directory. + +## Key Components + +- **`kCbRegLoc`** — Registry path constant pointing to `SOFTWARE\CarbonBlack\config` under `HKEY_LOCAL_MACHINE`. +- **`getQueue(Row& r)`** — Scans the `%SystemRoot%\CarbonBlack` directory to calculate cumulative disk usage for two queue types: + - `binary_queue` — size of `data` and `info.txt` files. + - `event_queue` — size of `active-event.log` and files prefixed with `eventlog_`. +- **`getSettings(Row& r)`** — Queries the Carbon Black registry key and maps named values to row fields, including sensor ID, backend server, IP address, collection flags, quota settings, and config name. Handles URL-encoded characters (`%20` → space, `%3A` → `:`) and applies a hex conversion pipeline to derive the human-readable `sensor_id`. +- **`genCarbonBlackInfo(QueryContext&)`** — Entry point registered with osquery; composes a single result row by calling `getSettings` and `getQueue`. + +## Usage Example + +```cpp +// Called internally by osquery when the virtual table is queried: +QueryData results = genCarbonBlackInfo(context); + +// Equivalent SQL query from the osquery shell: +// SELECT sensor_id, sensor_backend_server, sensor_ip_addr, +// binary_queue, event_queue, protection_disabled, +// collect_processes, collect_net_conns +// FROM carbon_black_info; +``` + +> **Note:** This table is Windows-only. It requires Carbon Black EDR (formerly Cb Response) to be installed. Registry access runs under the osquery service account and must have read permissions on `HKEY_LOCAL_MACHINE\SOFTWARE\CarbonBlack\config`. \ No newline at end of file diff --git a/osquery/tables/applications/windows/.office_mru.md b/osquery/tables/applications/windows/.office_mru.md new file mode 100644 index 00000000000..2e369b6b117 --- /dev/null +++ b/osquery/tables/applications/windows/.office_mru.md @@ -0,0 +1,48 @@ + +Implements the `office_mru` osquery table for Windows, which queries the Windows Registry to enumerate Microsoft Office and Office 365 Most Recently Used (MRU) file entries across all user profiles. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kOfficePath` | `constexpr` | Registry glob pattern for classic Office File MRU entries | +| `kOffice365Path` | `constexpr` | Registry glob pattern for Office 365 User MRU entries | +| `parseOfficeData` | `function` | Parses a single MRU registry entry into a result row, extracting path, version, application name, SID, and last-opened timestamp | +| `officeData` | `function` | Collects MRU entries for classic Office installations via the `File MRU` registry key | +| `office365Data` | `function` | Collects MRU entries for Office 365 installations, iterating user-scoped `User MRU` subkeys | +| `genOfficeMru` | `function` | Table generator entry point; iterates all `HKEY_USERS` profiles and dispatches to `officeData` / `office365Data` | + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM office_mru; +// +// Internally, genOfficeMru iterates HKEY_USERS and expands registry globs: + +QueryData results; +QueryData users; +queryKey("HKEY_USERS", users); + +for (const auto& uKey : users) { + std::string office_path = keyPath->second + kOfficePath; + std::set office_results; + expandRegistryGlobs(office_path, office_results); + for (const auto& office_data : office_results) { + officeData(results, office_data, sid); + } +} +// Each result row contains: path, version, application, last_opened_time, sid +``` + +## Output Schema + +| Column | Description | +|---|---| +| `path` | Full path of the recently opened file | +| `version` | Office version number (e.g., `16.0`) | +| `application` | Office app name (e.g., `Word`, `Excel`) | +| `last_opened_time` | Unix timestamp of last file access, converted from Windows FILETIME | +| `sid` | User Security Identifier (SID) | + +> **Note:** Last-opened timestamps are stored as big-endian Windows FILETIME hex strings prefixed with `T` inside the registry value data and are converted to Unix time via `filetimeToUnixtime`. \ No newline at end of file diff --git a/osquery/tables/cloud/aws/.ec2_instance_metadata.md b/osquery/tables/cloud/aws/.ec2_instance_metadata.md new file mode 100644 index 00000000000..e6ecb42d394 --- /dev/null +++ b/osquery/tables/cloud/aws/.ec2_instance_metadata.md @@ -0,0 +1,41 @@ + +Implements the `ec2_instance_metadata` osquery virtual table, which queries the AWS EC2 Instance Metadata Service (IMDS) to expose instance identity and configuration data as a SQL-queryable table. + +## Key Components + +### Classes + +| Class | Description | +|---|---| +| `Ec2MetaData` | Abstract base class; handles HTTP GET via `doGet()` with IMDSv2 token support and optional IMDSv1 fallback | +| `SimpleEc2MetaData` | Concrete subclass for plain-text metadata fields (e.g., MAC address, hostname) | +| `JSONEc2MetaData` | Concrete subclass for JSON-structured metadata fields (e.g., instance identity document, IAM info) | + +### Key Functions + +- **`Ec2MetaData::doGet()`** — Performs the HTTP request to the IMDS endpoint; attaches IMDSv2 token header when available, respects `--aws_disable_imdsv1_fallback` flag, and handles 404/non-200 responses gracefully with a 3-second timeout. +- **`setRowField()`** — Normalizes and assigns a metadata value into an osquery `Row`, handling `TEXT_TYPE` (strips/joins newlines) and `INTEGER_TYPE`. +- **`genEc2Metadata()`** — Entry point for the osquery table; builds a static list of metadata accessors and populates a single result row. + +### Metadata Fields Collected + +| Column | Source Endpoint | +|---|---| +| `instance_id`, `instance_type`, `region`, `account_id`, `ami_id`, `architecture`, `local_ipv4`, `availability_zone` | `dynamic/instance-identity/document` | +| `iam_arn` | `meta-data/iam/info` | +| `mac`, `local_hostname`, `ssh_public_key`, `reservation_id`, `security_groups` | `meta-data/*` | + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT * FROM ec2_instance_metadata; + +// Internally, genEc2Metadata is called by the osquery table plugin: +QueryData genEc2Metadata(QueryContext& context) { + // Iterates static field accessors, populates a single Row, + // and returns it wrapped in QueryData +} +``` + +> **Note:** IMDSv2 is preferred. If token retrieval fails and `--aws_disable_imdsv1_fallback` is set, all fields return empty. Only functional on AWS EC2 instances with IMDS accessible at the standard link-local address. \ No newline at end of file diff --git a/osquery/tables/cloud/aws/.ec2_instance_tags.md b/osquery/tables/cloud/aws/.ec2_instance_tags.md new file mode 100644 index 00000000000..0ce8e1af759 --- /dev/null +++ b/osquery/tables/cloud/aws/.ec2_instance_tags.md @@ -0,0 +1,38 @@ + +Retrieves EC2 instance tags for the current host instance using the AWS EC2 API, returning them as osquery table rows. + +## Key Components + +- **`genEc2InstanceTags(QueryContext& context)`** — Main table generator function that: + 1. Resolves the current instance ID and AWS region via `getInstanceIDAndRegion()` + 2. Validates and constructs an `AWSRegion` object + 3. Initializes an `EC2Client` scoped to that region + 4. Calls `DescribeTags` with a `resource-id` filter targeting the current instance + 5. Returns rows with `instance_id`, `key`, and `value` columns + +## Usage Example + +This function backs the `ec2_instance_tags` osquery virtual table. Query it directly via the osquery shell or daemon: + +```sql +SELECT instance_id, key, value +FROM ec2_instance_tags; +``` + +Example output: + +```text ++---------------------+-------------+------------+ +| instance_id | key | value | ++---------------------+-------------+------------+ +| i-0abc123def456789 | Environment | production | +| i-0abc123def456789 | Name | web-server | ++---------------------+-------------+------------+ +``` + +## Notes + +- Requires valid AWS credentials and IAM permissions for `ec2:DescribeTags` +- Only runs meaningfully on an EC2 instance where instance metadata is available +- Capped at **50 tags per request** (`SetMaxResults(50)`), matching the AWS-enforced maximum tags per EC2 resource +- Failures at any stage (metadata fetch, region validation, client creation, API call) are logged and return an empty result set rather than throwing \ No newline at end of file diff --git a/osquery/tables/cloud/azure/.azure_instance_metadata.md b/osquery/tables/cloud/azure/.azure_instance_metadata.md new file mode 100644 index 00000000000..0d5911e3cf0 --- /dev/null +++ b/osquery/tables/cloud/azure/.azure_instance_metadata.md @@ -0,0 +1,42 @@ + +Implements the `azure_instance_metadata` osquery table, which fetches and exposes Azure VM instance metadata by querying the Azure Instance Metadata Service (IMDS). + +## Key Components + +### `genAzureMetadata(QueryContext& context)` +The sole table generator function registered with the osquery table infrastructure. It: +1. Calls `fetchAzureMetadata()` (from `azure_util`) to retrieve raw JSON from the Azure IMDS endpoint +2. Extracts individual fields using `getAzureKey()` into a single result row +3. Returns an empty result set if the metadata fetch fails + +### Populated Fields + +| Column | Azure JSON Key | +|---|---| +| `vm_id` | `vmId` | +| `location` | `location` | +| `name` | `name` | +| `offer` / `publisher` / `sku` / `version` | Image metadata | +| `os_type` | `osType` | +| `platform_update_domain` / `platform_fault_domain` | Fault isolation info | +| `vm_size` | `vmSize` | +| `subscription_id` | `subscriptionId` | +| `resource_group_name` | `resourceGroupName` | +| `placement_group_id` / `vm_scale_set_name` | Scale set info | +| `zone` | `zone` | + +## Usage Example + +```cpp +// Query from osquery shell or scheduled query: +// SELECT * FROM azure_instance_metadata; + +// Internal call path: +QueryContext ctx; +QueryData rows = genAzureMetadata(ctx); +// rows[0]["vm_id"] -> e.g. "a1b2c3d4-..." +// rows[0]["vm_size"] -> e.g. "Standard_D2s_v3" +// rows[0]["location"]-> e.g. "eastus" +``` + +> **Note:** Returns an empty result set when executed outside an Azure VM environment or when the IMDS endpoint (`169.254.169.254`) is unreachable. \ No newline at end of file diff --git a/osquery/tables/cloud/azure/.azure_instance_tags.md b/osquery/tables/cloud/azure/.azure_instance_tags.md new file mode 100644 index 00000000000..5bd2055e4ca --- /dev/null +++ b/osquery/tables/cloud/azure/.azure_instance_tags.md @@ -0,0 +1,38 @@ + +Parses Azure instance metadata to extract VM tags and expose them as a structured osquery table with key-value pairs. + +## Key Components + +- **`genAzureTags(QueryContext& context)`** — Main table generation function that fetches Azure instance metadata, extracts the `tags` and `vmId` fields, splits the semicolon-delimited tag string, and returns rows with `vm_id`, `key`, and `value` columns. + +### Dependencies +- `fetchAzureMetadata(doc)` — Retrieves raw Azure IMDS metadata as a JSON document. +- `getAzureKey(doc, key)` — Extracts a specific field from the metadata JSON. +- `boost::split` — Splits the semicolon-delimited tag string (e.g., `"env:prod;team:ops"`) into individual tag entries. + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM azure_instance_tags; +// +// Expected output: +// +--------------------------------------+-------+-------+ +// | vm_id | key | value | +// +--------------------------------------+-------+-------+ +// | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | env | prod | +// | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | team | ops | +// +--------------------------------------+-------+-------+ + +QueryData genAzureTags(QueryContext& context) { + // Fetches metadata from Azure IMDS endpoint, + // parses "tags" field formatted as "key:value;key:value" + // and returns one row per tag. +} +``` + +## Notes + +- Azure tags are returned in `key:value` format, delimited by `;`. +- Tags missing a `:` separator are silently skipped to avoid malformed rows. +- Metadata fetch failures are logged via `TLOG` and return an empty result set. \ No newline at end of file diff --git a/osquery/tables/cloud/ycloud/.ycloud_instance_metadata.md b/osquery/tables/cloud/ycloud/.ycloud_instance_metadata.md new file mode 100644 index 00000000000..1ab77e862fd --- /dev/null +++ b/osquery/tables/cloud/ycloud/.ycloud_instance_metadata.md @@ -0,0 +1,40 @@ + +Implements the `ycloud_instance_metadata` osquery virtual table, which fetches and exposes Yandex Cloud (YCloud) instance metadata from the instance metadata service endpoint. + +## Key Components + +### `genYCloudMetadata(QueryContext& context)` +The primary table generation function that: +- Resolves the metadata endpoint from query constraints (`metadata_endpoint` column) or falls back to the default HTTP endpoint via `kInstanceMetadataAuthority` +- Supports querying multiple endpoints in a single query via `WHERE metadata_endpoint IN (...)` constraints +- Calls `fetchYCloudMetadata()` to retrieve raw JSON from the metadata service +- Maps JSON fields to table columns using YCloud utility helpers + +### Columns Populated + +| Column | Source Helper | +|---|---| +| `instance_id` | `getYCloudKey(doc, "id")` | +| `folder_id` | `getVendorKey(doc, "folderId")` | +| `cloud_id` | `getVendorKey(doc, "cloudId")` | +| `zone` | `getZoneId(getYCloudKey(doc, "zone"))` | +| `name` | `getYCloudKey(doc, "name")` | +| `hostname` | `getYCloudKey(doc, "hostname")` | +| `ssh_public_key` | `getYCloudSshKey(doc)` | +| `serial_port_enabled` | `getSerialPortEnabled(doc)` | +| `metadata_endpoint` | Raw endpoint string | + +## Usage Example + +```cpp +// Default query using the built-in metadata endpoint +SELECT instance_id, folder_id, cloud_id, zone, hostname +FROM ycloud_instance_metadata; + +// Query against a custom or mock metadata endpoint +SELECT instance_id, name +FROM ycloud_instance_metadata +WHERE metadata_endpoint = 'http://custom-metadata-host'; +``` + +Failed endpoint fetches are logged via `TLOG` and skipped, allowing partial results when multiple endpoints are queried. \ No newline at end of file diff --git a/osquery/tables/events/.event_utils.md b/osquery/tables/events/.event_utils.md new file mode 100644 index 00000000000..c35eed836e3 --- /dev/null +++ b/osquery/tables/events/.event_utils.md @@ -0,0 +1,25 @@ + +Utility header providing shared helpers for cross-platform file event handling in osquery. + +## Key Components + +- **`kCommonFileColumns`** — Extern constant set of column names shared across all platform file event implementations. +- **`decorateFileEvent()`** — Decorator function that populates a `Row` with common file metadata and optional hash data from the `file` table. + +## Usage Example + +```c +#include "event_utils.h" + +// Populate a row with file event metadata +osquery::Row r; +osquery::decorateFileEvent("/etc/passwd", /* hash= */ true, r); + +// r now contains common file columns (e.g., size, inode, hash) +``` + +## Notes + +- Designed to unify file event columns across Linux (inotify), macOS (FSEvents), and Windows platform implementations. +- When `hash` is `true`, the target file is read and its hash is computed and stored in the row. +- Consumers should check `kCommonFileColumns` to determine which columns are guaranteed present after decoration. \ No newline at end of file diff --git a/osquery/tables/events/darwin/.disk_events.md b/osquery/tables/events/darwin/.disk_events.md new file mode 100644 index 00000000000..12a08d2767e --- /dev/null +++ b/osquery/tables/events/darwin/.disk_events.md @@ -0,0 +1,41 @@ + +Implements an osquery event subscriber that monitors disk mount/unmount events on macOS using the Disk Arbitration framework, capturing metadata for non-physical (virtual/removable) disk events. + +## Key Components + +- **`DiskEventSubscriber`** — Event subscriber class inheriting from `EventSubscriber` that registers and handles disk arbitration events. +- **`init()`** — Initializes the subscription with `physical_disks = false`, filtering out physical disk events and subscribing only to virtual/removable media changes. +- **`Callback()`** — Processes each disk event and populates a result row with disk metadata. + +## Captured Fields + +| Field | Description | +|---|---| +| `action` | Event type (`add` / `remove`) | +| `path` | Mount path | +| `name` | Disk name | +| `device` | Device node | +| `uuid` | Volume UUID | +| `size` | Disk size | +| `ejectable` | Whether disk is ejectable | +| `mountable` | Whether disk is mountable | +| `writable` | Whether disk is writable | +| `content` | Content hint | +| `media_name` | Media name | +| `vendor` | Vendor identifier | +| `filesystem` | Filesystem type | +| `checksum` | Disk checksum | + +## Usage Example + +```cpp +// Registered automatically via the REGISTER macro: +// REGISTER(DiskEventSubscriber, "event_subscriber", "disk_events"); + +// Query via osquery SQL interface: +// SELECT action, path, name, uuid, filesystem, size +// FROM disk_events +// WHERE action = 'add'; +``` + +> **Note:** On `add` events, the subscriber attempts to use `disk_appearance_time` from the Disk Arbitration event context as the event timestamp, falling back to the raw event time if conversion fails. \ No newline at end of file diff --git a/osquery/tables/events/darwin/.es_process_events.md b/osquery/tables/events/darwin/.es_process_events.md new file mode 100644 index 00000000000..a95eea2a061 --- /dev/null +++ b/osquery/tables/events/darwin/.es_process_events.md @@ -0,0 +1,42 @@ + +Implements the `es_process_events` osquery event subscriber, which captures macOS process lifecycle events (exec, fork, exit) using the Endpoint Security framework. Requires macOS 10.15 (Catalina) or later. + +## Key Components + +### `ESProcessEventSubscriber` +Registered as `"es_process_events"` via `REGISTER` macro. Subscribes to three ES event types and populates rows for the osquery virtual table. + +**`init()`** +Subscribes to the following Endpoint Security event types: +- `ES_EVENT_TYPE_NOTIFY_EXEC` — process execution +- `ES_EVENT_TYPE_NOTIFY_FORK` — process fork +- `ES_EVENT_TYPE_NOTIFY_EXIT` — process exit + +Returns failure on macOS versions below 10.15. + +**`Callback(ec, sc)`** +Handles incoming ES events and maps context fields to a table row. Conditionally includes event-specific fields: +- `child_pid` — populated only for `fork` events +- `exit_code` — populated only for `exit` events + +## Captured Fields + +| Field | Description | +|---|---| +| `pid`, `parent`, `original_parent` | Process hierarchy IDs | +| `signing_id`, `team_id`, `cdhash` | Code signing metadata | +| `cmdline`, `env` | Arguments and environment variables | +| `uid`, `euid`, `gid`, `egid` | User/group identity | +| `platform_binary` | Whether the binary is Apple-signed | + +## Usage Example + +```cpp +// Subscriber is auto-registered at startup via: +REGISTER(ESProcessEventSubscriber, "event_subscriber", "es_process_events"); + +// Query via osquery SQL interface: +// SELECT pid, path, cmdline, signing_id, event_type +// FROM es_process_events +// WHERE event_type = 'exec'; +``` \ No newline at end of file diff --git a/osquery/tables/events/darwin/.es_process_file_events.md b/osquery/tables/events/darwin/.es_process_file_events.md new file mode 100644 index 00000000000..ddea9d71804 --- /dev/null +++ b/osquery/tables/events/darwin/.es_process_file_events.md @@ -0,0 +1,35 @@ + +Implements the `ESProcessFileEventSubscriber`, an osquery event subscriber that captures macOS Endpoint Security file system events (create, rename, write, truncate, and optionally open) and exposes them as queryable rows in the `es_process_file_events` table. + +## Key Components + +- **`ESProcessFileEventSubscriber`** — Registered event subscriber under the `"es_process_file_events"` table name. +- **`init()`** — Initializes the subscriber on macOS 10.15+, subscribing to `ES_EVENT_TYPE_NOTIFY_CREATE`, `RENAME`, `WRITE`, and `TRUNCATE`. Conditionally adds `ES_EVENT_TYPE_NOTIFY_OPEN` on macOS 13.0+ when the `es_fim_enable_open_events` flag is set. +- **`Callback()`** — Processes incoming `EndpointSecurityFileEventContext` events, populating rows with process and file metadata before batching them for osquery ingestion. + +## Subscribed Events + +| ES Event Type | macOS Requirement | +|---|---| +| `ES_EVENT_TYPE_NOTIFY_CREATE` | 10.15+ | +| `ES_EVENT_TYPE_NOTIFY_RENAME` | 10.15+ | +| `ES_EVENT_TYPE_NOTIFY_WRITE` | 10.15+ | +| `ES_EVENT_TYPE_NOTIFY_TRUNCATE` | 10.15+ | +| `ES_EVENT_TYPE_NOTIFY_OPEN` | 13.0+ (flag-gated) | + +## Usage Example + +```cpp +// Rows emitted to the es_process_file_events table contain: +// version, seq_num, global_seq_num — event sequencing metadata +// event_type — e.g., "create", "rename" +// pid, parent — process and parent process IDs +// path — directory path of the affected file +// filename — affected file name +// dest_filename — rename destination (if applicable) + +// Enable open events via osquery flag: +// --es_fim_enable_open_events=true (requires macOS 13.0+) +``` + +> **Platform requirement:** This subscriber is only active on macOS 10.15 (Catalina) and later. On earlier versions, `init()` returns a failure status and no events are collected. \ No newline at end of file diff --git a/osquery/tables/events/darwin/.file_events.md b/osquery/tables/events/darwin/.file_events.md new file mode 100644 index 00000000000..196a4e68c2f --- /dev/null +++ b/osquery/tables/events/darwin/.file_events.md @@ -0,0 +1,39 @@ + +Implements a macOS FSEvents-based file system event subscriber for osquery, tracking file creation, modification, and other changes across configured paths. + +## Key Components + +### `FileEventSubscriber` +An `EventSubscriber` subclass registered under the `"file_events"` plugin name. Listens to `FSEventsEventPublisher` events and records file activity into osquery's event table. + +| Method | Description | +|---|---| +| `init()` | Minimal initialization; returns success immediately | +| `configure()` | Clears existing subscriptions and re-registers paths from the osquery config | +| `Callback(ec, sc)` | Handles incoming FSEvents, builds result rows, and adds them to the event table | + +### Registration +```cpp +REGISTER(FileEventSubscriber, "event_subscriber", "file_events"); +``` +Registers the subscriber into the osquery plugin registry so it is auto-initialized at startup. + +## Key Behaviors + +- **Dynamic reconfiguration** — `configure()` calls `removeSubscriptions()` then iterates `Config::get().files()` to create fresh subscriptions per path/category. +- **Mount point handling** — When an `kFSEventStreamEventFlagMount` flag is detected, a new subscription for `/*` is spawned asynchronously via `std::packaged_task` on a detached thread. +- **Row decoration** — `decorateFileEvent()` enriches each row with file hash and stat metadata when the action is `CREATED` or `UPDATED`. + +## Usage Example + +```cpp +// Rows emitted to the file_events table contain: +Row r; +r["action"] = ec->action; // e.g., "CREATED", "UPDATED" +r["target_path"] = ec->path; // Absolute path of the changed file +r["category"] = sc->category; // Config category label +r["transaction_id"] = INTEGER(ec->transaction_id); // FSEvents transaction ID +// + hash/stat fields added by decorateFileEvent() +``` + +Configure monitored paths via the osquery `file_paths` config key; the subscriber picks them up automatically on each `configure()` call. \ No newline at end of file diff --git a/osquery/tables/events/darwin/.hardware_events.md b/osquery/tables/events/darwin/.hardware_events.md new file mode 100644 index 00000000000..c84f4af125a --- /dev/null +++ b/osquery/tables/events/darwin/.hardware_events.md @@ -0,0 +1,39 @@ + +Subscribes to macOS IOKit HID (Human Interface Device) events and records hardware attach/detach actions as osquery table rows. + +## Key Components + +| Component | Type | Description | +|---|---|---| +| `HardwareEventSubscriber` | Class | Event subscriber that listens for IOKit hardware events via `IOKitEventPublisher` | +| `init()` | Method | Registers the callback with a default subscription context | +| `Callback()` | Method | Handles incoming IOKit events and maps device metadata to table rows | +| `REGISTER` macro | Registration | Exposes the subscriber as `hardware_events` under the `event_subscriber` registry | + +## Usage Example + +The subscriber is automatically registered and activated by the osquery registry — no manual instantiation is needed. When a USB or HID device is connected or disconnected, the `Callback` populates a row with the following fields: + +```cpp +// Fields written to the hardware_events table on each event +Row r; +r["action"] = "attach" | "detach"; // IOKit action type +r["path"] = ec->path; // Device path (e.g., IOService path) +r["type"] = ec->type; // Device class/type +r["driver"] = ec->driver; // Matched kernel driver name +r["model_id"] = ec->model_id; // USB model/product ID +r["model"] = ec->model; // Human-readable model name +r["vendor_id"] = ec->vendor_id; // USB vendor ID +r["vendor"] = ec->vendor; // Human-readable vendor name +r["serial"] = ec->serial; // Device serial number +r["revision"] = ec->version; // Firmware/hardware revision +``` + +Query results are available in osquery SQL: + +```sql +SELECT action, vendor, model, serial, driver +FROM hardware_events; +``` + +> **Platform note:** This subscriber is macOS-only, relying on the `darwin/iokit.h` event publisher. It will not compile or run on Linux or Windows targets. \ No newline at end of file diff --git a/osquery/tables/events/darwin/.openbsm_events.md b/osquery/tables/events/darwin/.openbsm_events.md new file mode 100644 index 00000000000..ba3176fa3ba --- /dev/null +++ b/osquery/tables/events/darwin/.openbsm_events.md @@ -0,0 +1,44 @@ + +Implements osquery event subscribers for Darwin (macOS) that consume OpenBSM audit events to populate the `process_events` and `user_events` tables. + +## Key Components + +### Helper Function +- **`OpenBSM_AUT_SUBJECT32_EX`** — Extracts subject fields (auid, pid, uid, gid, euid, egid, IPv4/IPv6 address) from an `AUT_SUBJECT32_EX` token into a result row. + +### Event Subscribers + +| Class | Registered As | Handles | +|---|---|---| +| `OpenBSMProcEvSubscriber` | `process_events` | exec, spawn, fork, vfork, exit | +| `OpenBSMSSHLoginSubscriber` | `user_events` | SSH login audit events (event ID 32800) | + +### `OpenBSMProcEvSubscriber` Methods + +- **`configure()`** — Subscribes to `AUE_EXECVE`, `AUE_POSIX_SPAWN`, `AUE_FORK`, `AUE_VFORK`, `AUE_FORK1`, `AUE_DARWIN_RFORK`, `AUE_RFORK`, `AUE_EXIT`. +- **`handleExec()`** — Parses exec/spawn audit tokens into a row with path, cmdline, env, uid/gid, status, and uptime. Resolves parent PID via internal `ppid_map`. +- **`handleFork()`** — Extracts parent and child PIDs from fork events and maintains the `ppid_map` mapping child→parent. +- **`handleExit()`** — Removes the exiting process's entry from `ppid_map` on process exit. + +### `OpenBSMSSHLoginSubscriber` Methods + +- **`configure()`** — Subscribes to BSM event ID `32800` (OpenSSH login). +- **`Callback()`** — Emits a row with timestamp, auid, address, and a prefixed message string. + +## Usage Example + +```cpp +// Subscribers are auto-registered via macros; no manual instantiation needed. +// osquery wires them up at startup: +REGISTER(OpenBSMProcEvSubscriber, "event_subscriber", "process_events"); +REGISTER(OpenBSMSSHLoginSubscriber, "event_subscriber", "user_events"); + +// Query process_events in osquery SQL: +// SELECT pid, parent, path, cmdline, uid, euid, auid, time +// FROM process_events; + +// Query SSH login events: +// SELECT auid, address, message, time FROM user_events; +``` + +> **Note:** `ppid_map` is an in-memory `unordered_map` that tracks child→parent PID relationships across fork/exec/exit events. If a fork event was not captured, `parent` is reported as `-1`. \ No newline at end of file diff --git a/osquery/tables/events/darwin/.socket_events.md b/osquery/tables/events/darwin/.socket_events.md new file mode 100644 index 00000000000..792b76601a5 --- /dev/null +++ b/osquery/tables/events/darwin/.socket_events.md @@ -0,0 +1,43 @@ + +Implements the `socket_events` osquery event subscriber for macOS, capturing network socket operations (connect, bind, accept) by subscribing to OpenBSM audit events and populating query-able rows with process and address metadata. + +## Key Components + +### Static Helpers + +- **`getIpFromToken(const tokenstr_t& tok)`** — Converts a BSM socket token's raw address bytes into a human-readable IPv4 or IPv6 string using `inet_ntop`. +- **`getPathFromPid(int pid)`** — Resolves a PID to its executable path via `proc_pidpath`; returns an empty string on failure. + +### `OpenBSMNetEvSubscriber` + +An `EventSubscriber` that hooks into `OpenBSMEventPublisher` and is registered under the table name `socket_events`. + +| Method | Responsibility | +|---|---| +| `init()` | Guards activation behind `--audit_allow_sockets` and `--disable_audit` flags | +| `configure()` | Subscribes to `AUE_CONNECT`, `AUE_BIND`, and `AUE_ACCEPT` audit event IDs | +| `Callback(ec, sc)` | Iterates BSM tokens to build a result row; skips Unix-domain sockets (`AUT_SOCKUNIX`) | + +### Row Fields Emitted + +`time`, `action`, `pid`, `auid`, `fd`, `success`, `local_address`, `local_port`, `remote_address`, `remote_port`, `family`, `path`, `uptime` + +> A row is only committed via `add(r)` when a `remote_address` token was parsed, filtering out incomplete or Unix-socket events. + +## Usage Example + +```cpp +// Querying from osqueryi (SQL interface) +SELECT pid, path, action, remote_address, remote_port, family, success +FROM socket_events +WHERE action = 'connect'; + +// Enabling the subscriber at daemon startup +// --audit_allow_sockets=true +// --disable_audit=false +``` + +```bash +# Launch osqueryd with socket event collection enabled +osqueryd --audit_allow_sockets=true --disable_audit=false +``` \ No newline at end of file diff --git a/osquery/tables/events/darwin/.user_interaction_events.md b/osquery/tables/events/darwin/.user_interaction_events.md new file mode 100644 index 00000000000..90afa524dbf --- /dev/null +++ b/osquery/tables/events/darwin/.user_interaction_events.md @@ -0,0 +1,30 @@ + +Implements an osquery event subscriber that listens for macOS user interaction events via the Event Tapping API, capturing input activity and emitting rows to the `user_interaction_events` virtual table. + +## Key Components + +- **`UserInteractionSubscriber`** — Event subscriber class derived from `EventSubscriber` that hooks into the macOS CGEvent tap pipeline. + - **`init()`** — Performs subscriber initialization; returns `Status(0)` (success) unconditionally. + - **`configure()`** — Creates a subscription context and registers the `Callback` handler with the event publisher. + - **`Callback()`** — Invoked on each tapped user interaction event; constructs a `Row` and appends it to the results table via `add(r)`. + +- **`REGISTER` macro** — Registers `UserInteractionSubscriber` under the `"event_subscriber"` category with the table name `"user_interaction_events"` in the osquery registry. + +## Usage Example + +This subscriber is auto-registered and activated by the osquery daemon at startup. Query it via the osquery SQL interface: + +```sql +SELECT * FROM user_interaction_events; +``` + +Programmatic subscription wiring (internal, handled by `configure()`): + +```cpp +void UserInteractionSubscriber::configure() { + auto sc = createSubscriptionContext(); + subscribe(&UserInteractionSubscriber::Callback, sc); +} +``` + +> **Note:** This file is Darwin (macOS) specific and depends on `event_taps.h`. The `Callback` currently captures events and adds empty rows — column population is expected to be extended via the `EventTappingEventContextRef` fields. \ No newline at end of file diff --git a/osquery/tables/events/linux/.apparmor_events.md b/osquery/tables/events/linux/.apparmor_events.md new file mode 100644 index 00000000000..e183097e9d6 --- /dev/null +++ b/osquery/tables/events/linux/.apparmor_events.md @@ -0,0 +1,46 @@ + +Brief description of the file's purpose: Declares the `AppArmorEventSubscriber` class, which subscribes to Linux audit events related to AppArmor policy enforcement and processes them into queryable osquery rows. + +## Key Components + +### `AppArmorEventSubscriber` +A final class inheriting from `EventSubscriber` with the following members: + +| Member | Type | Description | +|--------|------|-------------| +| `init()` | `Status` | Registers the audit event type subscription on startup | +| `callback()` | `Status` | Fires when a matching kernel audit event is received | +| `processEvents()` | `static Status` | Parses a batch of `AuditEvent` objects into `QueryData` rows | +| `getEventSet()` | `static const std::set&` | Returns the set of audit event type IDs this subscriber handles | + +## Usage Example + +```c +// Typical osquery subscriber lifecycle (handled by the framework): + +// 1. Framework calls init() to register subscriptions +AppArmorEventSubscriber subscriber; +subscriber.init(); + +// 2. Kernel audit events fire callback() automatically +// subscriber.callback(ec, sc) is invoked by AuditEventPublisher + +// 3. Manually process a batch of audit events (e.g., in tests) +QueryData results; +std::vector events = getAuditEvents(); + +auto status = AppArmorEventSubscriber::processEvents(results, events); +if (status.ok()) { + for (const auto& row : results) { + // Each row contains AppArmor enforcement data + } +} + +// 4. Inspect handled event type IDs +const auto& eventSet = AppArmorEventSubscriber::getEventSet(); +for (int eventType : eventSet) { + // e.g., AUDIT_APPARMOR_ALLOWED, AUDIT_APPARMOR_DENIED, etc. +} +``` + +> **Note:** This subscriber is Linux-only and depends on the kernel audit subsystem (`linux/audit.h`). It integrates with osquery's `AuditEventPublisher` to surface AppArmor allow/deny decisions as structured table data. \ No newline at end of file diff --git a/osquery/tables/events/linux/.bpf_process_events.md b/osquery/tables/events/linux/.bpf_process_events.md new file mode 100644 index 00000000000..19e0a9c955f --- /dev/null +++ b/osquery/tables/events/linux/.bpf_process_events.md @@ -0,0 +1,45 @@ + +Header file defining the `BPFProcessEventSubscriber` class, which subscribes to BPF-based process events on Linux and converts them into osquery table rows. + +## Key Components + +### `BPFProcessEventSubscriber` +An `EventSubscriber` specialized for `BPFEventPublisher` that handles process lifecycle events captured via eBPF. + +| Method | Description | +|---|---| +| `init()` | Initializes the subscriber and registers subscriptions | +| `eventCallback(...)` | Handles incoming BPF events from the publisher | +| `generateRow(...)` | Converts a single `ISystemStateTracker::Event` into a table `Row` | +| `generateRowList(...)` | Batch-converts an `EventList` into a `vector` | +| `generateCmdlineColumn(...)` | Formats argv into a space-separated command line string | +| `generateJsonCmdlineColumn(...)` | Formats argv into a JSON-encoded command line string | + +## Usage Example + +```c +// Typical osquery subscriber pattern - registered automatically via macro +// Implementation side (bpf_process_events.cpp): + +Status BPFProcessEventSubscriber::init() { + auto subscription = createSubscriptionContext(); + subscribe(&BPFProcessEventSubscriber::eventCallback, subscription); + return Status::success(); +} + +// Generating a row from a captured process event: +ISystemStateTracker::Event event = /* from BPF publisher */; +Row row; +if (BPFProcessEventSubscriber::generateRow(row, event)) { + // row is populated with pid, path, cmdline, etc. +} + +// Formatting command-line arguments: +std::vector argv = {"/usr/bin/ssh", "-l", "user", "host"}; +auto cmdline = BPFProcessEventSubscriber::generateCmdlineColumn(argv); +// → "/usr/bin/ssh -l user host" +auto json_cmdline = BPFProcessEventSubscriber::generateJsonCmdlineColumn(argv); +// → '["/usr/bin/ssh","-l","user","host"]' +``` + +> **Note:** This subscriber is Linux-only and requires eBPF support in the kernel. It feeds the `bpf_process_events` virtual table in osquery. \ No newline at end of file diff --git a/osquery/tables/events/linux/.bpf_socket_events.md b/osquery/tables/events/linux/.bpf_socket_events.md new file mode 100644 index 00000000000..499edd568be --- /dev/null +++ b/osquery/tables/events/linux/.bpf_socket_events.md @@ -0,0 +1,37 @@ + +BPF-based socket event subscriber that listens for socket-related system events via the eBPF event publisher and converts them into osquery table rows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `BPFSocketEventSubscriber` | Class | Final subscriber class inheriting from `EventSubscriber` | +| `init()` | Method | Initializes the subscriber and registers subscriptions | +| `eventCallback()` | Method | Handles incoming BPF socket events from the publisher | +| `generateRow()` | Static Method | Converts a single `ISystemStateTracker::Event` into an osquery `Row` | +| `generateRowList()` | Static Method | Batch-converts an `EventList` into a `std::vector` | + +## Usage Example + +```c +// Subscriber is registered automatically via osquery's event framework. +// Static helpers can be used independently to convert events to rows: + +ISystemStateTracker::Event socket_event = /* ... */; +Row row; + +if (BPFSocketEventSubscriber::generateRow(row, socket_event)) { + // row is now populated with socket event data +} + +// Batch conversion from an event list +ISystemStateTracker::EventList event_list = /* ... */; +std::vector rows = + BPFSocketEventSubscriber::generateRowList(event_list); +``` + +## Notes + +- Operates on Linux only via the `BPFEventPublisher` pipeline +- The `generateRow` / `generateRowList` static methods are useful for testing row generation independently of the event loop +- Follows osquery's standard subscriber pattern: `init()` → `eventCallback()` → row emission \ No newline at end of file diff --git a/osquery/tables/events/linux/.file_events.md b/osquery/tables/events/linux/.file_events.md new file mode 100644 index 00000000000..3a58ad215b7 --- /dev/null +++ b/osquery/tables/events/linux/.file_events.md @@ -0,0 +1,34 @@ + +Implements the `file_events` osquery event subscriber, which monitors filesystem changes using Linux inotify and records file create, update, delete, and access events to the osquery event table. + +## Key Components + +- **`FileEventSubscriber`** — EventSubscriber subclass backed by `INotifyEventPublisher`; registered under the plugin name `"file_events"`. + - **`init()`** — Minimal initialization; returns success immediately. + - **`configure()`** — Reads `file_paths` and `file_accesses` from the osquery config, clears existing inotify subscriptions, then re-subscribes to each configured path with the appropriate event mask (`kFileDefaultMasks` or `kFileAccessMasks` for access-audited categories). + - **`Callback(ec, sc)`** — Handles incoming inotify events; builds a result row containing `action`, `target_path`, `category`, and `transaction_id`, then calls `decorateFileEvent()` to attach file stat and hash data before storing with `add()`. + +- **`REGISTER` macro** — Registers `FileEventSubscriber` into the osquery EventSubscriber pseudo-plugin registry. + +## Usage Example + +Configure monitored paths in your osquery pack/config: + +```json +{ + "file_paths": { + "homes": ["/root/.ssh/%%", "/home/%/.ssh/%%"], + "etc": ["/etc/%%"] + }, + "file_accesses": ["homes"] +} +``` + +Querying the resulting table: + +```sql +SELECT action, target_path, category, md5, sha256, transaction_id +FROM file_events; +``` + +> **Note:** Categories listed in `file_accesses` enable `kFileAccessMasks` (read-access tracking). For those categories, file hashing is intentionally skipped to avoid triggering additional inotify access events in a feedback loop. All other categories receive full stat and hash decoration via `decorateFileEvent()`. \ No newline at end of file diff --git a/osquery/tables/events/linux/.hardware_events.md b/osquery/tables/events/linux/.hardware_events.md new file mode 100644 index 00000000000..83a938b67e1 --- /dev/null +++ b/osquery/tables/events/linux/.hardware_events.md @@ -0,0 +1,61 @@ + +Implements an osquery event subscriber that tracks Linux hardware events via the udev subsystem, capturing device attach/detach/change actions and emitting structured rows to the `hardware_events` table. + +## Key Components + +### `HardwareEventSubscriber` +An `EventSubscriber` subclass registered under `"hardware_events"` that listens to `UdevEventPublisher` events. + +| Method | Description | +|---|---| +| `init()` | Subscribes to all udev actions (`UDEV_EVENT_ACTION_ALL`) | +| `Callback(ec, sc)` | Filters and maps udev event context to a table row | + +### Flag: `hardware_disabled_types` +```text +--hardware_disabled_types="partition" +``` +Comma-style string of device types to suppress from output. Defaults to `"partition"`. + +### Emitted Row Fields + +| Column | Source | +|---|---| +| `type` | `ec->devtype` | +| `action` | `ec->action_string` | +| `path` | `ec->devnode` | +| `driver` | `ec->driver` | +| `model` | `ID_MODEL_FROM_DATABASE` | +| `model_id` | `ID_MODEL_ID` | +| `vendor` | `ID_VENDOR_FROM_DATABASE` | +| `vendor_id` | `ID_VENDOR_ID` | +| `serial` | `ID_SERIAL_SHORT` | +| `revision` | `ID_REVISION` | + +## Usage Example + +Query hardware events from the osquery shell: + +```cpp +// Callback filtering logic (simplified) +if (ec->devtype.empty()) return Status::success(); // skip +if (ec->devnode.empty() + && ec->driver.empty()) return Status::success(); // skip +if (disabled_types.find(devtype)) return Status::success(); // suppressed +// Otherwise populate and emit row via add(r) +``` + +```sql +-- osquery SQL: monitor non-partition hardware changes +SELECT action, type, path, driver, model, vendor +FROM hardware_events +WHERE action = 'add'; +``` + +## Filtering Logic + +Events are silently dropped when: +- `devtype` is empty (superfluous events) +- Both `devnode` and `driver` are empty +- `type` matches any value in `--hardware_disabled_types` +- Both `path` and `model` are empty after property lookup \ No newline at end of file diff --git a/osquery/tables/events/linux/.process_events.md b/osquery/tables/events/linux/.process_events.md new file mode 100644 index 00000000000..fe10862489c --- /dev/null +++ b/osquery/tables/events/linux/.process_events.md @@ -0,0 +1,38 @@ + +Defines the `AuditProcessEventSubscriber` class, an osquery event subscriber that listens to Linux audit events for process-related system calls (e.g., `execve`, `clone`). + +## Key Components + +| Member | Description | +|---|---| +| `init()` | Registers the subscriber's audit event type subscription | +| `Callback()` | Invoked when kernel audit events matching the subscription fire | +| `ProcessEvents()` | Batch-processes a list of `AuditEvent` objects into `Row` output | +| `ProcessExecveEventData()` | Extracts fields specific to `execve`/`execveat` syscall events | +| `GetProcessIDs()` | Parses `pid` and `ppid` from a syscall audit record | +| `IsThreadClone()` | Checks whether a `clone()` syscall includes the `CLONE_THREAD` flag | +| `GetSyscallName()` | Resolves a syscall number to its string name | +| `GetSyscallNameMap()` | Returns the static syscall number-to-name lookup table | + +## Usage Example + +```cpp +// Typical subscriber wiring (handled by osquery internals) +AuditProcessEventSubscriber subscriber; +subscriber.init(); // registers audit event subscription + +// Static processing used in unit tests or direct invocation +std::vector rows; +std::vector events = getAuditEvents(); + +auto status = AuditProcessEventSubscriber::ProcessEvents(rows, events); +if (!status.ok()) { + LOG(ERROR) << "Failed to process audit events: " << status.getMessage(); +} + +// Check if a clone() call spawned a thread vs. a process +bool is_thread = false; +AuditProcessEventSubscriber::IsThreadClone(is_thread, syscall_nr, record); +``` + +> **Note:** All processing methods are declared `static` and `noexcept`, making them independently testable without a live subscriber instance. This class is Linux-only, depending on `AuditEventPublisher` from `osquery/events/linux/`. \ No newline at end of file diff --git a/osquery/tables/events/linux/.process_file_events.md b/osquery/tables/events/linux/.process_file_events.md new file mode 100644 index 00000000000..0d06d5a5937 --- /dev/null +++ b/osquery/tables/events/linux/.process_file_events.md @@ -0,0 +1,48 @@ + +Header file defining the Linux audit-based File Integrity Monitoring (FIM) subsystem for osquery, tracking file system events via syscall interception through the Linux audit framework. + +## Key Components + +### Data Structures + +| Struct/Class | Purpose | +|---|---| +| `AuditdFimInodeDescriptor` | Describes a file/folder inode with path and type | +| `AuditdFimFdDescriptor` | Tracks an open file descriptor and its last operation | +| `AuditdFimPathRecordItem` | Aggregates `AUDIT_PATH` records per syscall | +| `AuditdFimSrcDestData` | Holds source/destination for rename/link operations | +| `AuditdFimIOData` | Holds target and type for open/read/write/close/unlink operations | +| `AuditdFimSyscallContext` | Full context for a captured syscall event (PIDs, UIDs, paths, return values) | +| `AuditdFimContext` | Top-level FIM state: included paths, process map, inode map | + +### Core Classes + +- **`AuditdFimInodeMap`** — Global inode-to-path registry with get/save/remove operations +- **`AuditdFimFdMap`** — Per-process file descriptor map supporting open, dup, and close tracking +- **`AuditdFimProcessMap`** — Process-level manager over `AuditdFimFdMap` instances; handles `fork`/`clone` via `clone()` and `dup`/`dup2` via `duplicate()` +- **`ProcessFileEventSubscriber`** — osquery `EventSubscriber` that consumes `AuditEventPublisher` events and emits rows on read/write activity + +### Supported Syscall Types + +```text +Link · Symlink · Unlink · Rename · Open · OpenTruncate +Close · Dup · Read · Write · Truncate · Mmap · NameToHandleAt · CloneOrFork +``` + +## Usage Example + +```cpp +// Static entry points on the subscriber +std::vector rows; +AuditdFimContext ctx; + +// Provide audit events from the publisher +Status s = ProcessFileEventSubscriber::ProcessEvents( + rows, ctx, event_list); + +// Query which syscall numbers this subscriber handles +const std::set& syscalls = + ProcessFileEventSubscriber::GetSyscallSet(); +``` + +> **Note:** This header is Linux-only (depends on `` and the Linux audit framework). The subscriber integrates with osquery's event pipeline via `AuditEventPublisher`. \ No newline at end of file diff --git a/osquery/tables/events/linux/.seccomp_events.md b/osquery/tables/events/linux/.seccomp_events.md new file mode 100644 index 00000000000..0db6a3818e6 --- /dev/null +++ b/osquery/tables/events/linux/.seccomp_events.md @@ -0,0 +1,51 @@ + +Defines the `SeccompEventSubscriber` class for capturing and parsing Linux seccomp audit events within the osquery event framework. + +## Key Components + +### Preprocessor Definitions +Backward-compatibility constants for kernels older than 4.14: + +| Constant | Value | Description | +|---|---|---| +| `SECCOMP_RET_KILL_PROCESS` | `0x80000000U` | Kill entire process | +| `SECCOMP_RET_KILL_THREAD` | `SECCOMP_RET_KILL` | Kill calling thread | +| `SECCOMP_RET_LOG` | `0x7ffc0000U` | Allow after logging | + +### `SeccompEventSubscriber` +Subscribes to `AuditEventPublisher` events and translates raw audit records into structured query rows. + +**Static Lookup Tables** +- `seccomp_actions_map` — Maps seccomp return codes to human-readable action names +- `arch_codes_map` — Maps architecture codes (from `audit.h`) to architecture name strings +- `syscall_x86_64_map` — Maps x86_64 syscall numbers to syscall name strings + +**Methods** + +| Method | Description | +|---|---| +| `init()` | Registers the audit event type subscription | +| `Callback(ec, sc)` | Fires when a matching kernel seccomp event is received | +| `processEvents(...)` | Batch-processes a list of `AuditEvent` objects into `QueryData` rows | +| `parseEvent(...)` | Internal parser mapping raw `AuditEvent` fields to a `Row` | + +## Usage Example + +```c +// Subscriber is auto-registered via the osquery event framework. +// Emitted rows can be queried via osquery SQL: +// +// SELECT * FROM seccomp_events; +// +// processEvents can be called directly in tests: + +QueryData results; +std::vector events = { /* populated by publisher */ }; + +Status s = SeccompEventSubscriber::processEvents(results, events); +if (s.ok()) { + for (const auto& row : results) { + // row["syscall"], row["action"], row["arch"], etc. + } +} +``` \ No newline at end of file diff --git a/osquery/tables/events/linux/.selinux_events.md b/osquery/tables/events/linux/.selinux_events.md new file mode 100644 index 00000000000..b60e6324b86 --- /dev/null +++ b/osquery/tables/events/linux/.selinux_events.md @@ -0,0 +1,43 @@ + +Defines the `SELinuxEventSubscriber` class, an osquery event subscriber that listens for SELinux-related audit events on Linux via the `AuditEventPublisher`. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `SELinuxEventSubscriber` | Class | Event subscriber for SELinux audit events | +| `init()` | Method | Registers the audit event type subscription on startup | +| `Callback()` | Method | Invoked when kernel audit events matching the subscribed type fire | +| `ProcessEvents()` | Static Method | Parses and transforms a batch of `AuditEvent` objects into osquery `Row` results | +| `GetEventSet()` | Static Method | Returns the set of audit event type IDs this subscriber handles | + +## Usage Example + +```cpp +// ProcessEvents can be called directly for unit testing +// without requiring a live audit publisher + +std::vector rows; +std::vector events = GetMockSELinuxEvents(); + +Status status = SELinuxEventSubscriber::ProcessEvents(rows, events); + +if (status.ok()) { + for (const auto& row : rows) { + // Each row contains SELinux event fields + // e.g., row["avc_decision"], row["pid"], row["scontext"] + } +} + +// Inspect which audit event codes this subscriber handles +const std::set& handled = SELinuxEventSubscriber::GetEventSet(); +for (int code : handled) { + LOG(INFO) << "Handles audit event code: " << code; +} +``` + +## Notes + +- Inherits from `EventSubscriber`, binding it to Linux's audit subsystem +- `ProcessEvents` and `GetEventSet` are `static` and `noexcept`, making them safely testable in isolation +- Lives inside the `osquery` namespace alongside other platform event subscribers \ No newline at end of file diff --git a/osquery/tables/events/linux/.socket_events.md b/osquery/tables/events/linux/.socket_events.md new file mode 100644 index 00000000000..bc3b6b8956a --- /dev/null +++ b/osquery/tables/events/linux/.socket_events.md @@ -0,0 +1,46 @@ + +Defines the `SocketEventSubscriber` class for capturing and processing Linux socket-related audit events via the osquery audit event framework. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `SocketEventSubscriber` | Class | EventSubscriber for `AuditEventPublisher`; handles socket syscall audit events | +| `init()` | Method | Registers audit event type subscriptions on startup | +| `Callback()` | Method | Fires when kernel socket events match the subscribed type | +| `ProcessEvents()` | Static Method | Transforms raw `AuditEvent` list into osquery `Row` results with configurable filtering | +| `GetSyscallSet()` | Static Method | Returns the set of syscall IDs this subscriber handles | +| `parseSockAddr()` | Static Method | Decodes the `saddr` field from `AUDIT_SOCKADDR` records into row columns | + +## Usage Example + +```cpp +#include + +// ProcessEvents is the primary testable interface +std::vector rows; +std::vector events = getAuditEvents(); + +Status status = SocketEventSubscriber::ProcessEvents( + rows, + events, + /*allow_failed_socket_events=*/false, + /*allow_unix_socket_events=*/true, + /*allow_null_accept_events=*/false, + /*allow_null_accept_socket_events=*/false +); + +// Inspect handled syscalls +const auto& syscalls = SocketEventSubscriber::GetSyscallSet(); + +// Parse a raw saddr field +Row row; +bool is_unix = false; +bool ok = SocketEventSubscriber::parseSockAddr("0200...", row, is_unix); +``` + +## Notes + +- Filtering flags in `ProcessEvents` allow fine-grained control over which socket events are emitted (failed, UNIX domain, null-accept variants) +- `parseSockAddr` sets `unix_socket` to `true` when the address family is `AF_UNIX` +- Part of the osquery Linux audit subsystem; requires `AuditEventPublisher` to be active \ No newline at end of file diff --git a/osquery/tables/events/linux/.syslog_events.md b/osquery/tables/events/linux/.syslog_events.md new file mode 100644 index 00000000000..022d5fe5c09 --- /dev/null +++ b/osquery/tables/events/linux/.syslog_events.md @@ -0,0 +1,36 @@ + +Implements an osquery event subscriber that captures and buffers Linux syslog events, persisting them as queryable rows via the `syslog_events` virtual table. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `syslog_events_expiry` | Flag (`uint64`) | Retention period for buffered events; defaults to 30 days | +| `syslog_events_max` | Flag (`uint64`) | Maximum buffered events per type; defaults to 100,000 | +| `SyslogEventSubscriber` | Class | Event subscriber bound to `SyslogEventPublisher`; registers as `syslog_events` | +| `init()` | Method | Creates a subscription context and registers the callback | +| `Callback()` | Method | Receives each syslog event context and appends its fields as a table row | +| `getEventsExpiry()` | Method | Returns the configured expiry flag value | +| `getEventBatchesMax()` | Method | Returns the configured max events flag value | + +## Usage Example + +```cpp +// The subscriber is auto-registered via the REGISTER macro. +// No manual instantiation is needed. +// Query buffered syslog events via osquery SQL: + +// SELECT * FROM syslog_events; +// SELECT facility, severity, message, time +// FROM syslog_events +// WHERE severity <= 3 +// AND time > (strftime('%s','now') - 3600); +``` + +Override expiry or buffer limits at runtime using osquery flags: + +```bash +osqueryd --syslog_events_expiry=86400 --syslog_events_max=50000 +``` + +> **Note:** This subscriber requires the `SyslogEventPublisher` to be active (Linux only). Event rows mirror the fields provided by the publisher's event context (`ec->fields`) without transformation. \ No newline at end of file diff --git a/osquery/tables/events/linux/.user_events.md b/osquery/tables/events/linux/.user_events.md new file mode 100644 index 00000000000..6ae0a2fcd5b --- /dev/null +++ b/osquery/tables/events/linux/.user_events.md @@ -0,0 +1,42 @@ + +Implements the `user_events` osquery event subscriber that captures Linux user-space audit events via the `AuditEventPublisher`, populating the `user_events` virtual table. + +## Key Components + +- **`UserEventSubscriber`** — Final class extending `EventSubscriber`; registers under the name `"user_events"`. +- **`init()`** — Checks the `audit_allow_user_events` flag before subscribing; returns a disabled status if the flag is not set. +- **`Callback()`** — Receives batched audit event contexts, delegates to `ProcessEvents`, and commits rows via `addBatch`. +- **`ProcessEvents()`** — Static method that filters raw `AuditEvent` entries for `AuditEvent::Type::UserEvent`, extracts fields (`uid`, `pid`, `auid`, `terminal`, `addr`, `exe`, `msg`), decodes paths, and builds output rows. + +## Usage Example + +```cpp +// Enabled via CLI/config flag (disabled by default): +// --audit_allow_user_events=true + +// ProcessEvents can be exercised directly in tests: +std::vector events = GetTestAuditEvents(); +std::vector rows; + +auto status = UserEventSubscriber::ProcessEvents(rows, events); +if (status.ok()) { + for (const auto& row : rows) { + // Access: row["uid"], row["pid"], row["path"], + // row["message"], row["address"], row["uptime"] + } +} +``` + +## Row Fields + +| Field | Source Audit Key | Notes | +|---|---|---| +| `uid` | `uid` | Copied as-is | +| `pid` | `pid` | Copied as-is | +| `auid` | `auid` | Audit login UID | +| `terminal` | `terminal` | TTY/terminal name | +| `address` | `addr` | Remote address | +| `path` | `exe` | Decoded via `DecodeAuditPathValues` | +| `message` | `msg` | Leading character stripped | +| `uptime` | — | System uptime at event time | +| `type` | record type | Numeric audit record type | \ No newline at end of file diff --git a/osquery/tables/events/tests/.file_events_tests.md b/osquery/tables/events/tests/.file_events_tests.md new file mode 100644 index 00000000000..4ac6ccac811 --- /dev/null +++ b/osquery/tables/events/tests/.file_events_tests.md @@ -0,0 +1,37 @@ + +Unit tests for the `file_events` osquery table, verifying subscriber registration, empty table state, and dynamic subscription configuration via config plugins. + +## Key Components + +| Component | Type | Description | +|---|---|---| +| `FileEventsTableTests` | Test Fixture | Base fixture handling setup/teardown of registry, database, and config state | +| `FileEventsTestsConfigPlugin` | Config Plugin | Stub config plugin providing an unrestricted file pack for subscription testing | +| `test_subscriber_exists` | Test (non-Windows) | Asserts the `file_events` event subscriber is registered in the plugin registry | +| `test_table_empty` | Test | Verifies `file_events` SQL table returns zero rows on a fresh attach | +| `test_configure_subscriptions` | Test (non-Windows) | Validates that loading a config creates subscriptions and that a config update removes them | + +## Usage Example + +```cpp +// Run all FileEventsTableTests with gtest +// From the osquery build directory: +// ./osquery_tests --gtest_filter="FileEventsTableTests.*" + +// Example: querying subscription count after config load +std::string q = "select * from osquery_events where name = 'file_events'"; +SQL results(q); +auto& row = results.rows()[0]; +// Expect "2" subscriptions from the unrestricted pack +EXPECT_EQ(row.at("subscriptions"), "2"); + +// After clearing config, subscriptions drop to zero +Config::get().update({{"data", "{}"}}); +SQL results2(q); +EXPECT_EQ(results2.rows()[0].at("subscriptions"), "0"); +``` + +## Notes + +- Tests guarded by `#ifndef WIN32` are skipped on Windows as `FileEventSubscriber` is not available on that platform. +- `FLAGS_ignore_registry_exceptions` is forced to `false` during tests to surface plugin lookup errors explicitly, and is restored in `TearDown`. \ No newline at end of file diff --git a/osquery/tables/events/tests/linux/.bpf_process_events_tests.md b/osquery/tables/events/tests/linux/.bpf_process_events_tests.md new file mode 100644 index 00000000000..09a25603e36 --- /dev/null +++ b/osquery/tables/events/tests/linux/.bpf_process_events_tests.md @@ -0,0 +1,45 @@ + +Unit tests for `BPFProcessEventSubscriber`, validating BPF-based process event row generation and command-line column formatting on Linux. + +## Key Components + +- **`BPFProcessEventsTests`** — GTest fixture class housing all test cases for the BPF process events subscriber. +- **`kExpectedRowList`** — Compile-time constant defining the 14 required column names a generated row must contain: `ntime`, `tid`, `pid`, `uid`, `gid`, `cid`, `exit_code`, `probe_error`, `syscall`, `parent`, `path`, `cwd`, `cmdline`, `json_cmdline`. +- **`kBaseBPFEventHeader`** — Shared BPF event header fixture with predefined values (timestamp, thread/process/user/group IDs, cgroup ID, exit code, probe error flag). + +## Test Cases + +| Test | Description | +|---|---| +| `generateRow` | Validates `BPFProcessEventSubscriber::generateRow()` produces correctly typed and valued row fields for exec events, both with and without `argv` data | +| `generateCmdlineColumn` | Validates shell-safe command-line string generation, including single-quoting of arguments containing spaces | + +## Usage Example + +```cpp +// Constructing a minimal exec event and generating a row +ISystemStateTracker::Event event{}; +event.type = ISystemStateTracker::Event::Type::Exec; +event.bpf_header = kBaseBPFEventHeader; +event.parent_process_id = 2; +event.binary_path = "/usr/bin/sudo"; +event.cwd = "/home/user"; + +Row row; +bool succeeded = BPFProcessEventSubscriber::generateRow(row, event); +// row["syscall"] == "exec" +// row["json_cmdline"] == "[]" (no argv set) + +// With argv populated: +ISystemStateTracker::Event::ExecData data; +data.argv = {"sudo", "-H", "-i"}; +event.data = std::move(data); +BPFProcessEventSubscriber::generateRow(row, event); +// row["cmdline"] == "sudo -H -i" +// row["json_cmdline"] == "[\"sudo\",\"-H\",\"-i\"]" + +// Shell-safe cmdline formatting: +auto cmdline = BPFProcessEventSubscriber::generateCmdlineColumn( + {"cat", "/test folder"}); +// cmdline == "cat '/test folder'" +``` \ No newline at end of file diff --git a/osquery/tables/events/tests/linux/.bpf_socket_events_tests.md b/osquery/tables/events/tests/linux/.bpf_socket_events_tests.md new file mode 100644 index 00000000000..f0ab3de844b --- /dev/null +++ b/osquery/tables/events/tests/linux/.bpf_socket_events_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the `BPFSocketEventSubscriber::generateRow()` function, validating row generation from BPF socket events (connect, bind, listen, accept) on Linux. + +## Key Components + +### Test Fixture +- **`BPFSocketEventsTests`** — GTest fixture class for all BPF socket event row generation tests. + +### Constants +- **`kEventTypeToLabel`** — Maps `ISystemStateTracker::Event::Type` values (`Connect`, `Bind`, `Listen`, `Accept`) to their expected syscall label strings. +- **`kExpectedRowList`** — Canonical list of 19 expected column names in a generated row (`syscall`, `ntime`, `tid`, `pid`, `uid`, `gid`, `cid`, `exit_code`, `probe_error`, `parent`, `path`, `fd`, `family`, `type`, `protocol`, `local_address`, `remote_address`, `local_port`, `remote_port`). + +### Test Cases +| Test | Description | +|---|---| +| `event_type` | Verifies each event type maps to its correct `syscall` label and produces a row with all 19 expected columns. | +| `default_values` | Verifies that an event with no socket data produces sensible defaults (`-1` for family/type/protocol, `0` for ports, empty strings for addresses/fd). | +| `socket_data` | Verifies that a fully populated `SocketData` struct (AF_INET, SOCK_STREAM, fd=10, addresses, ports) is correctly serialized into row fields. | + +## Usage Example + +```cpp +// Typical pattern exercised by these tests: +ISystemStateTracker::Event event{}; +event.type = ISystemStateTracker::Event::Type::Connect; + +ISystemStateTracker::Event::SocketData socket_data; +socket_data.domain = AF_INET; +socket_data.type = SOCK_STREAM; +socket_data.fd = 10; +socket_data.local_address = "127.0.0.1"; +socket_data.local_port = 5000; +socket_data.remote_address = "192.168.1.2"; +socket_data.remote_port = 8080; +event.data = std::move(socket_data); + +Row row; +bool ok = BPFSocketEventSubscriber::generateRow(row, event); +// row["fd"] == "10", row["family"] == "2", row["local_port"] == "5000" +``` \ No newline at end of file diff --git a/osquery/tables/events/tests/linux/.process_events_tests.md b/osquery/tables/events/tests/linux/.process_events_tests.md new file mode 100644 index 00000000000..318143d68c3 --- /dev/null +++ b/osquery/tables/events/tests/linux/.process_events_tests.md @@ -0,0 +1,45 @@ + +Unit tests for Linux process events (`execve`, `kill`, `clone`/fork syscalls) in osquery's audit event processing pipeline, validating parsing, field extraction, and edge case handling. + +## Key Components + +| Component | Description | +|---|---| +| `GenerateAuditEventRecord` | Converts raw type/content pairs into `AuditEventRecord` via `AuditdNetlinkParser::ParseAuditReply` | +| `GenerateAuditEvent` | Builds a list of `AuditEventRecord`s from a `RawAuditEvent` descriptor | +| `GenerateEventContext` | Invokes `AuditEventPublisher::ProcessEvents` to produce an `AuditEventContext` | +| `GenerateEventRow` | Calls `AuditProcessEventSubscriber::ProcessEvents` to produce a final `Row` for assertion | +| `ProcessEventsTests` | GTest fixture housing all test cases | + +### Test Cases + +| Test | What It Validates | +|---|---| +| `syscall_name_label` | Syscall map completeness for exec, fork, and kill syscall sets | +| `exec_event_processing` | Full `execve` event parsing — PID, UIDs/GIDs, path, cmdline, cwd, mode | +| `kill_syscall_event_processing` | `kill` syscall field extraction including target process metadata (`ocomm`, `oauid`, `oses`) | +| `kill_syscall_without_obj_pid_record` | Graceful handling when `AUDIT_OBJ_PID` record is absent (no crash) | +| `thread_detection` | `IsThreadClone` distinguishes thread vs process creation via `clone` flags; validates wrong record type returns error | +| `process_id_acquisition` | PID/PPID extraction from normal and same-PPID process creation events | + +## Usage Example + +```cpp +// Simulate a raw audit execve event and extract the parsed row +RawAuditEvent kSampleExecveEvent = { + { 1300, "audit(...): arch=c000003e syscall=59 success=yes ..." }, + { 1309, "audit(...): argc=1 a0=\"sh\"" }, + { 1307, "audit(...): cwd=\"/home/user\"" }, + { 1320, "audit(...): " } +}; + +Row event_row; +GenerateEventRow(event_row, kSampleExecveEvent); + +// Assert expected field values +EXPECT_EQ(event_row["syscall"], "execve"); +EXPECT_EQ(event_row["pid"], "7841"); +EXPECT_EQ(event_row["cmdline"], "sh"); +``` + +> **Note:** Tests are architecture-aware — syscall numbers differ between `x86_64` (e.g., `execve=59`, `kill=62`, `clone=56`) and `aarch64` (e.g., `execve=221`, `kill=129`). Unsupported architectures produce a compile-time `#error`. \ No newline at end of file diff --git a/osquery/tables/events/tests/linux/.seccomp_events_tests.md b/osquery/tables/events/tests/linux/.seccomp_events_tests.md new file mode 100644 index 00000000000..c6db2d0045e --- /dev/null +++ b/osquery/tables/events/tests/linux/.seccomp_events_tests.md @@ -0,0 +1,34 @@ + +Unit test suite for validating seccomp audit event processing in osquery's Linux event subscriber, covering field parsing, syscall/architecture decoding, and seccomp action resolution. + +## Key Components + +### `SeccompEventsTests` (Test Fixture) +Extends `testing::Test` with shared state and helpers for all test cases: + +- **`ProcessEventsWithParams(syscall_num, arch, seccomp_action, result)`** — Populates event fields, invokes `SeccompEventSubscriber::processEvents()`, and returns the resulting `Row` +- **`SetUp()`** — Initializes a baseline `SeccompAuditEventData` fixture simulating a QEMU process audit event on IA64 + +### Test Cases + +| Test | Description | +|---|---| +| `test_basic_fields` | Validates core audit fields (`auid`, `uid`, `gid`, `pid`, `comm`, `exe`, `ip`, etc.) are correctly parsed and stringified | +| `test_seccomp_value_decode_x86_64_1` | Verifies syscall `0` decodes to `"read"`, arch to `"X86_64"`, action to `"LOG"` | +| `test_seccomp_value_decode_x86_64_2` | Verifies syscall `88` decodes to `"symlink"` with `"KILL_PROCESS"` action | +| `test_seccomp_value_decode_x86_64_3` | Verifies unknown syscall `1337` emits `"1337(unknown)"` with `"KILL_THREAD"` action | +| `test_seccomp_value_decode_mips` | Verifies MIPS64 syscall `13` falls back to numeric representation (no symbol table) with `"ERRNO"` action | + +## Usage Example + +```cpp +// Running a specific test from the command line: +// ./seccomp_events_tests --gtest_filter=SeccompEventsTests.test_seccomp_value_decode_x86_64_2 + +// The fixture pattern used internally: +Row result; +ProcessEventsWithParams(88, AUDIT_ARCH_X86_64, SECCOMP_RET_KILL_PROCESS, result); +EXPECT_EQ(result["syscall"], "symlink"); +EXPECT_EQ(result["arch"], "X86_64"); +EXPECT_EQ(result["code"], "KILL_PROCESS"); +``` \ No newline at end of file diff --git a/osquery/tables/events/tests/linux/.selinux_events_tests.md b/osquery/tables/events/tests/linux/.selinux_events_tests.md new file mode 100644 index 00000000000..a131b300540 --- /dev/null +++ b/osquery/tables/events/tests/linux/.selinux_events_tests.md @@ -0,0 +1,26 @@ + +Unit test file validating the consistency between SELinux event type definitions and their corresponding record labels in the osquery SELinux events subsystem. + +## Key Components + +- **`SELinuxEventsTests`** — Google Test fixture class for SELinux event unit tests +- **`record_type_labels`** — Test case that enforces two invariants: + - `kSELinuxRecordLabels` and `kSELinuxEventList` must have the same number of entries + - Every event type in `kSELinuxEventList` must have a corresponding entry in `kSELinuxRecordLabels` + +## Usage Example + +```cpp +// Run only SELinux event tests +./osquery_tests --gtest_filter="SELinuxEventsTests.*" +``` + +The test guards against mismatches where a new SELinux event type is added to `kSELinuxEventList` without a corresponding label being registered in `kSELinuxRecordLabels` (or vice versa), which would cause runtime lookup failures when processing audit events. + +## Dependencies + +| Header | Purpose | +|---|---| +| `osquery/events/linux/selinux_events.h` | Provides `kSELinuxEventList` | +| `osquery/tables/events/linux/selinux_events.h` | Provides `kSELinuxRecordLabels` | +| `gtest/gtest.h` | Google Test framework | \ No newline at end of file diff --git a/osquery/tables/events/tests/windows/.etw_process_events_tests.md b/osquery/tables/events/tests/windows/.etw_process_events_tests.md new file mode 100644 index 00000000000..99eac584786 --- /dev/null +++ b/osquery/tables/events/tests/windows/.etw_process_events_tests.md @@ -0,0 +1,43 @@ + +Unit tests for the ETW (Event Tracing for Windows) process events subsystem in osquery, validating publisher/subscriber registration, end-to-end process event capture, and `ConcurrentQueue` thread safety. + +## Key Components + +### Test Fixture: `ETWProcessEventsTests` +Initializes the osquery runtime environment (tool type, platform, registry, database) and enables ETW process events via `FLAGS_enable_process_etw_events`. + +### Test Cases + +| Test | Description | +|---|---| +| `test_subscriber_exists` | Verifies `process_etw_events` subscriber is registered in the event registry | +| `test_publisher_exists` | Verifies the ETW process publisher is registered and reachable | +| `test_process_event_sanity_check` | Launches `logman.exe`, waits for ETW capture, then queries `process_etw_events` table and validates fields (`cmdline`, `path`, `type`, `username`, `pid`, `ppid`, etc.) | +| `test_concurrent_queue_blockwait` | Stress-tests `ConcurrentQueue` with random producer threads using blocking `pop()` | +| `test_concurrent_queue_timeout` | Stress-tests `ConcurrentQueue` with random producer threads using `popWait()` with timeout semantics | + +## Usage Example + +```bash +# Run all ETW process event tests +osquery_tests --gtest_filter="ETWProcessEventsTests.*" + +# Run a specific test +osquery_tests --gtest_filter="ETWProcessEventsTests.test_process_event_sanity_check" +``` + +```cpp +// ConcurrentQueue behavior under test +ConcurrentQueue queue; + +// Blocking pop — waits until an item is available +unsigned int val = queue.pop(); + +// Timed pop — returns false on timeout +unsigned int val = 0; +if (queue.popWait(val)) { + // item retrieved +} +``` + +> **Note:** `test_process_event_sanity_check` requires Windows with ETW privileges and introduces a 4-second sleep to allow event propagation. It also calls `Dispatcher` teardown — ensure it runs in an isolated test environment. \ No newline at end of file diff --git a/osquery/tables/events/tests/windows/.powershell_events_tests.md b/osquery/tables/events/tests/windows/.powershell_events_tests.md new file mode 100644 index 00000000000..8db20e8b0ef --- /dev/null +++ b/osquery/tables/events/tests/windows/.powershell_events_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the `PowershellEventSubscriber` class, validating parsing, processing, row emission, event expiration, and multi-chunk script reassembly of Windows PowerShell Event Log (Event ID 4104) entries. + +## Key Components + +- **`PowershellEventsTests`** — GTest fixture class containing all test cases for the PowerShell event subscriber +- **`initializePowershellEventsContext()`** — Helper that parses a list of raw XML event strings and feeds them through `PowershellEventSubscriber::processEventObject()` into a shared `Context` +- **Test XML fixtures** — Three inline XML strings simulating real Windows Event Log records: + - `kSingleScriptBlock` — A single-chunk script block event + - `kMultiChunkScript01/02/03` — A three-part split script block event + +## Test Cases + +| Test | What It Validates | +|---|---| +| `parse_simple_event` | Correct field extraction from a single XML event | +| `process_broken_event` | Error returned on empty event object | +| `parse_broken_event` | Graceful failure with empty `ptree` | +| `event_expiration` | Stale entries purged, active entries retained | +| `simple_event_row_emission` | All 8 columns populated correctly in emitted row | +| `trace_simple_event` | Context state clean after single event | +| `trace_ordered_complete_split_event` | 3-chunk script reassembled in order | +| `trace_unordered_complete_split_event` | 3-chunk script reassembled out of order | +| `trace_ordered/unordered_incomplete_split_event` | Partial chunks held in `script_state_map`, no row emitted | +| `trace_invalid_event` | Invalid XML increments `invalid_event_count` | + +## Usage Example + +```cpp +// Parse a raw PowerShell event XML and inspect the resulting row +PowershellEventSubscriber::Context context; +auto status = initializePowershellEventsContext( + context, {std::ref(kSingleScriptBlock)}); + +if (status.ok() && !context.row_list.empty()) { + const auto& row = context.row_list.at(0); + std::cout << row.at("script_text"); // e.g. write-host "1" + std::cout << row.at("script_name"); // e.g. script.ps1 +} +``` \ No newline at end of file diff --git a/osquery/tables/events/tests/windows/.windows_events_tests.md b/osquery/tables/events/tests/windows/.windows_events_tests.md new file mode 100644 index 00000000000..7dfd30ee20f --- /dev/null +++ b/osquery/tables/events/tests/windows/.windows_events_tests.md @@ -0,0 +1,44 @@ + +Unit test suite for the Windows Event Log parsing and processing pipeline in osquery, validating XML event ingestion, structured field extraction, and row generation for the `windows_events` table. + +## Key Components + +- **`WindowsEventsTests`** — GTest fixture class housing all Windows Event Log test cases +- **`kExpectedOutputFieldList`** — Constant list of required output fields validated against test fixture JSON files +- **`test_recorded_events`** — Data-driven test that iterates over XML event samples from `TEST_DATA_PATH/input`, parses them through the full pipeline, and compares results against expected JSON in `TEST_DATA_PATH/output` +- **`invalid_event_parsing`** — Negative test verifying that `parseWindowsEventLogPTree` fails gracefully on an empty property tree +- **`row_generation`** — Unit test for `WindowsEventSubscriber::generateRow`, confirming correct mapping of `WELEvent` fields to an osquery `Row` with 11 columns + +## Usage Example + +```bash +# Run the Windows Events test suite +./osquery_tests --gtest_filter="WindowsEventsTests.*" +``` + +```cpp +// Core parsing pipeline exercised by these tests +std::wstring wide_xml = stringToWstring(raw_xml_string); + +boost::property_tree::ptree event_object; +auto status = parseWindowsEventLogXML(event_object, wide_xml); + +WELEvent windows_event; +status = parseWindowsEventLogPTree(windows_event, event_object); + +Row row; +WindowsEventSubscriber::generateRow(row, windows_event); +// row["eventid"], row["source"], row["data"], etc. +``` + +## Test Data Layout + +```text +TEST_DATA_PATH/ +├── input/ +│ └── / +│ └── .xml ← Raw Event Viewer XML exports +└── output/ + └── / + └── .json ← Expected parsed field values +``` \ No newline at end of file diff --git a/osquery/tables/events/windows/.dns_lookup_events.md b/osquery/tables/events/windows/.dns_lookup_events.md new file mode 100644 index 00000000000..d14854b274b --- /dev/null +++ b/osquery/tables/events/windows/.dns_lookup_events.md @@ -0,0 +1,40 @@ + +Declares the `EtwDNSEventSubscriber` class, an ETW-based Windows event subscriber that captures and processes DNS lookup events via the osquery event system. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `EtwDNSEventSubscriber` | Class | Final subscriber class templated on `EtwPublisherDNS` | +| `init()` | Method | Initializes the subscriber and registers subscriptions | +| `eventCallback()` | Method | Handles incoming DNS ETW events with event and subscription context | + +## Usage Example + +```c +// Registered automatically via osquery's event framework. +// The subscriber hooks into EtwPublisherDNS at init time. + +class EtwDNSEventSubscriber final : public EventSubscriber { + public: + Status init() override { + // Subscribe to DNS ETW events + auto sc = createSubscriptionContext(); + subscribe(&EtwDNSEventSubscriber::eventCallback, sc); + return Status::success(); + } + + Status eventCallback(const ECRef& event_context, + const SCRef& subscription_context) { + // Process DNS lookup event data from ETW + // e.g., extract queried hostname, response IPs, PID + return Status::success(); + } +}; +``` + +## Notes + +- **Windows-only**: Depends on ETW (Event Tracing for Windows) infrastructure via `etw_publisher_dns.h` +- Follows osquery's pub/sub event model — the publisher (`EtwPublisherDNS`) emits raw ETW DNS events; this subscriber consumes and tables them +- Registered into the osquery event framework automatically via `REGISTER` macros (typically in the corresponding `.cpp` file) \ No newline at end of file diff --git a/osquery/tables/events/windows/.etw_process_events.md b/osquery/tables/events/windows/.etw_process_events.md new file mode 100644 index 00000000000..4beed37bb1e --- /dev/null +++ b/osquery/tables/events/windows/.etw_process_events.md @@ -0,0 +1,35 @@ + +Defines the `EtwProcessEventSubscriber` class, an ETW (Event Tracing for Windows) event subscriber that listens for process-related events published by `EtwPublisherProcesses`. + +## Key Components + +- **`EtwProcessEventSubscriber`** — Final class inheriting from `EventSubscriber`. Subscribes to Windows ETW process events within the osquery event framework. + - **`init()`** — Initializes the subscriber and registers its event subscriptions. + - **`eventCallback()`** — Called by the event framework when a matching process event fires; receives both the event context (`ECRef`) and subscription context (`SCRef`) for processing. + +## Usage Example + +```c +// Subscriber is registered automatically via osquery's plugin/event system. +// Typical initialization flow: + +namespace osquery { + +Status EtwProcessEventSubscriber::init() { + auto subscription = createSubscriptionContext(); + subscribe(&EtwProcessEventSubscriber::eventCallback, subscription); + return Status::success(); +} + +Status EtwProcessEventSubscriber::eventCallback( + const ECRef& event_context, + const SCRef& subscription_context) { + // Handle incoming ETW process event data + // e.g., process creation, termination events + return Status::success(); +} + +} // namespace osquery +``` + +> **Note:** This subscriber is Windows-only. It integrates with osquery's `EventSubscriber` framework, meaning registration and dispatch are handled by the osquery event publisher/subscriber lifecycle — no manual instantiation is required. \ No newline at end of file diff --git a/osquery/tables/events/windows/.ntfs_journal_events.md b/osquery/tables/events/windows/.ntfs_journal_events.md new file mode 100644 index 00000000000..b7185618434 --- /dev/null +++ b/osquery/tables/events/windows/.ntfs_journal_events.md @@ -0,0 +1,45 @@ + +Declares the `NTFSEventSubscriber` class and supporting utilities for subscribing to and processing NTFS file system change events on Windows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `NTFSEventSubscriber` | Class | Event subscriber that receives NTFS file change events from `NTFSEventPublisher` | +| `shouldEmit` | Method | Filters events based on subscription context before emission | +| `generateRowFromEvent` | Method | Converts an `NTFSEventRecord` into an osquery `Row` for table output | +| `init` | Method | Initialization routine called once at startup | +| `configure` | Method | Configuration callback; may be invoked multiple times on config changes | +| `Callback` | Method | Primary entry point for receiving events from the publisher | +| `StringList` | Type alias | Convenience alias for `std::vector` | +| `processConfiguration` | Function | Parses subscriber configuration to populate include/exclude path lists and access categories | + +## Usage Example + +```c +// Typical lifecycle managed by the osquery event framework: + +NTFSEventSubscriber subscriber; + +// Called once at startup +subscriber.init(); + +// Called on config load or reload +subscriber.configure(); + +// Populate include/exclude paths from config +StringList access_categories = {"write", "delete"}; +StringList include_paths, exclude_paths; + +processConfiguration( + subscriptionContext, + access_categories, + include_paths, + exclude_paths +); + +// Events arrive automatically via: +// subscriber.Callback(eventContext, subscriptionContext); +``` + +> **Note:** This header is Windows-only and depends on `ntfs_event_publisher.h`. The subscriber integrates with the osquery event framework — direct instantiation is typically handled by the framework, not application code. \ No newline at end of file diff --git a/osquery/tables/events/windows/.powershell_events.md b/osquery/tables/events/windows/.powershell_events.md new file mode 100644 index 00000000000..d53bf51e52f --- /dev/null +++ b/osquery/tables/events/windows/.powershell_events.md @@ -0,0 +1,53 @@ + +Header file defining the `PowershellEventSubscriber` class, which subscribes to Windows Event Log events to capture and process PowerShell script block logging activity within the osquery framework. + +## Key Components + +### `PowershellEventSubscriber` +Extends `EventSubscriber` to collect PowerShell execution telemetry from Windows Event Log. + +- **`init()`** — Registers subscriptions with the Windows Event Log publisher +- **`Callback()`** — Handles incoming PowerShell event log entries + +### `Context` (nested struct) +Holds stateful tracking data across multi-part script block messages: + +| Field | Purpose | +|---|---| +| `script_state_map` | Maps script block IDs to their partial message fragments | +| `character_frequency_map` | Frequency analysis vector (entropy/obfuscation detection) | +| `row_list` | Accumulated rows ready for osquery table emission | +| `last_event_expiration_time` | Tracks stale event cleanup threshold | +| `invalid_event_count` / `expired_event_count` | Diagnostic counters | + +### Static Processing Methods + +| Method | Purpose | +|---|---| +| `parseScriptMessageEvent()` | Extracts a `ScriptMessage` from a raw ptree event | +| `processEventObject()` | Advances context state with a new event | +| `processEventExpiration()` | Evicts incomplete/stale script block entries | +| `generateRow()` | Assembles a final osquery `Row` from completed message fragments | + +## Usage Example + +```cpp +// Subscriber is auto-registered via osquery plugin system. +// Manual interaction with static helpers: + +boost::optional msg; +PowershellEventSubscriber::parseScriptMessageEvent(msg, event_ptree); + +if (msg) { + PowershellEventSubscriber::Context ctx; + PowershellEventSubscriber::processEventObject(ctx, event_ptree); + PowershellEventSubscriber::processEventExpiration(ctx); + + Row row; + PowershellEventSubscriber::generateRow( + row, ctx.script_state_map[msg->script_block_id], ctx.character_frequency_map + ); +} +``` + +> **Note:** PowerShell script block logging must be enabled via Windows Group Policy (`ScriptBlockLogging`) for events to be captured. \ No newline at end of file diff --git a/osquery/tables/events/windows/.windows_events.md b/osquery/tables/events/windows/.windows_events.md new file mode 100644 index 00000000000..7881c43a704 --- /dev/null +++ b/osquery/tables/events/windows/.windows_events.md @@ -0,0 +1,41 @@ + +Brief header defining the `WindowsEventSubscriber` class, which subscribes to Windows Event Log events and converts them into osquery table rows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `WindowsEventSubscriber` | Class | Event subscriber for the Windows Event Log publisher | +| `init()` | Method | Initializes the subscriber and registers subscriptions | +| `Callback()` | Method | Handles incoming Windows Event Log events | +| `generateRow()` | Static Method | Converts a `WELEvent` into an osquery `Row` for table output | + +## Usage Example + +```c +// Subscriber is auto-registered via osquery's event framework. +// Implement callback to process incoming Windows events: + +Status WindowsEventSubscriber::Callback(const ECRef& event, + const SCRef& subscription) { + Row row; + WindowsEventSubscriber::generateRow(row, event->windows_event); + add(row); // Adds the row to the osquery results table + return Status::success(); +} + +// generateRow populates an osquery Row from a parsed WELEvent: +void WindowsEventSubscriber::generateRow(Row& row, + const WELEvent& windows_event) { + row["channel"] = windows_event.channel; + row["datetime"] = windows_event.datetime; + row["eventid"] = BIGINT(windows_event.eventId); + // ... additional fields mapped from WELEvent +} +``` + +## Dependencies + +- **`WindowsEventLogPublisher`** — the publisher this subscriber binds to via the `EventSubscriber` template +- **`WindowsEventLogParser`** — provides the `WELEvent` struct populated before `Callback` is invoked +- **`boost::property_tree`** — available as namespace alias `pt` for XML/JSON event data parsing \ No newline at end of file diff --git a/osquery/tables/forensic/.carves.md b/osquery/tables/forensic/.carves.md new file mode 100644 index 00000000000..6aaf7e21885 --- /dev/null +++ b/osquery/tables/forensic/.carves.md @@ -0,0 +1,30 @@ + +Implements the `carves` osquery virtual table, which exposes file carving status and allows initiating new file carves via SQL queries. + +## Key Components + +- **`stringToRow`** — Helper that safely extracts a string field from a JSON document into a query result row. +- **`enumerateCarves`** — Scans the carver database (keyed by `kCarverDBPrefix`) and deserializes each stored carve entry into a `Row`, handling both string and integer-encoded `size` fields for backwards compatibility. +- **`genCarves`** — Table generator registered with osquery. Resolves path constraints (including glob patterns), detects carve requests (`carve = 1` predicate), and triggers `carvePaths` when the carver is enabled. + +## Usage Example + +```cpp +// Query all known carves +SELECT * FROM carves; + +// Initiate a new carve by setting carve = 1 in the WHERE clause +SELECT * FROM carves WHERE path = '/etc/passwd' AND carve = 1; + +// Check carve status for a specific path pattern +SELECT carve_guid, status, sha256, size +FROM carves +WHERE path LIKE '/home/%/.ssh/id_rsa'; +``` + +## Notes + +- Carving is suppressed when the `--disable_carver` flag is set. +- Each carve entry is stored in the osquery database under `kCarves` and identified by a GUID. +- The `carve` column in results is `1` only for the newly initiated carve in the current query, allowing callers to distinguish new vs. historical entries. +- `size` deserialization handles both JSON integer and string types for backward compatibility with older stored records. \ No newline at end of file diff --git a/osquery/tables/networking/.curl.md b/osquery/tables/networking/.curl.md new file mode 100644 index 00000000000..a1e86c832ea --- /dev/null +++ b/osquery/tables/networking/.curl.md @@ -0,0 +1,44 @@ + +HTTP table generator for osquery that executes GET requests via TLS-enabled HTTP client, exposing response metadata (status code, round-trip time, body, byte size) as queryable table rows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kOsqueryUserAgent` | `const std::string` | Default User-Agent header value (`"osquery"`) | +| `processRequest` | `Status(Row&)` | Executes a single HTTP GET, populates response fields, measures RTT | +| `genCurl` | `QueryData(QueryContext&)` | Table generator; resolves URL/user-agent constraints and dispatches requests | + +### Row Fields Populated + +| Field | Type | Description | +|---|---|---| +| `url` | string | Request URL (from query constraint) | +| `method` | string | Always `"GET"` | +| `user_agent` | string | Custom or default osquery agent | +| `response_code` | INTEGER | HTTP status code | +| `round_trip_time` | BIGINT | Elapsed time in microseconds | +| `result` | string | Response body | +| `bytes` | BIGINT | Response body size | + +## Usage Example + +```cpp +// Typical osquery SQL usage against the curl virtual table: +// SELECT url, response_code, round_trip_time, bytes +// FROM curl +// WHERE url = 'https://example.com/api/health'; + +// With a custom User-Agent: +// SELECT response_code, result +// FROM curl +// WHERE url = 'https://api.example.com' +// AND user_agent = 'my-monitor/1.0'; +``` + +## Constraints & Limitations + +- Only `EQUALS` constraints on `url` are meaningful; `LIKE` clauses are ignored with a warning. +- Only a **single** `user_agent` value is accepted per query; multiple values abort with a warning and return empty results. +- All requests use TLS transport options from `TLSTransport`. +- Exceptions from the HTTP client surface as `Status::failure` and are logged as warnings without halting iteration over remaining URLs. \ No newline at end of file diff --git a/osquery/tables/networking/.curl_certificate.md b/osquery/tables/networking/.curl_certificate.md new file mode 100644 index 00000000000..009e33686af --- /dev/null +++ b/osquery/tables/networking/.curl_certificate.md @@ -0,0 +1,32 @@ + +Implements TLS certificate retrieval and parsing for osquery's `curl_certificate` table, connecting to remote hosts via OpenSSL to extract and populate detailed X.509 certificate metadata into query results. + +## Key Components + +- **`pem(X509*)`** — Converts an X509 certificate to PEM-encoded string using OpenSSL BIO. +- **`certversion(X509*)`** — Returns the human-readable certificate version (1-indexed). +- **`signature_algorithm(X509*)`** — Extracts the signature algorithm short name (e.g., `sha256WithRSAEncryption`). +- **`signature(X509*)`** — Returns the raw certificate signature as a colon-separated hex string. +- **`certificate_extensions(X509*, int nid)`** — Extracts a named X.509v3 extension by NID, returning its value as a semicolon-delimited string. +- **`has_cert_expired(X509*)`** — Returns `true` if the certificate's `notAfter` date has passed. +- **`fillRow(Row&, X509*, int, int)`** — Populates an osquery `Row` with all certificate fields: subject, issuer, validity, fingerprints (SHA1/SHA256), extensions, version, signature, and optional PEM dump. +- **`getTLSCertificate(hostname, results, dump_certificate, timeout)`** — Establishes a TCP/TLS connection to the specified host (default port 443), performs an SSL handshake, retrieves the server certificate chain, and calls `fillRow` for each certificate. + +## Usage Example + +```cpp +QueryData results; +int dump_pem = 1; +int timeout = DEFAULT_READ_TIMEOUT; // 4 seconds + +auto status = getTLSCertificate("example.com:443", results, dump_pem, timeout); +if (!status.ok()) { + LOG(ERROR) << status.getMessage(); +} else { + for (const auto& row : results) { + std::cout << row.at("common_name") << " expires: " << row.at("valid_to") << "\n"; + } +} +``` + +> **Note:** On Windows, Winsock is initialized automatically via `WSAStartup`/`WSACleanup` with scope guards. IPv4 addresses are preferred over IPv6 when both are returned by DNS resolution. \ No newline at end of file diff --git a/osquery/tables/networking/.etc_hosts.md b/osquery/tables/networking/.etc_hosts.md new file mode 100644 index 00000000000..7cf938c0f08 --- /dev/null +++ b/osquery/tables/networking/.etc_hosts.md @@ -0,0 +1,36 @@ + +Parses the system's `hosts` file(s) to expose hostname-to-IP mappings as osquery table rows, with cross-platform support for Linux/macOS and Windows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kEtcHosts` | `fs::path` | Path to the primary hosts file (`/etc/hosts` on Unix; `%SystemRoot%\system32\drivers\etc\hosts` on Windows) | +| `kEtcHostsIcs` | `fs::path` | Windows-only path to the ICS (Internet Connection Sharing) hosts file | +| `parseEtcHostsContent()` | `QueryData` | Parses raw hosts file text into rows with `address`, `hostnames`, and `pid_with_namespace` fields | +| `genEtcHostsImpl()` | `QueryData` | Reads the hosts file(s) from disk and delegates to the parser; logs warnings on read failure | +| `genEtcHosts()` | `QueryData` | Table entry point; dispatches to namespace-aware generation or standard GLOG-logged execution | + +## Usage Example + +```cpp +// Standalone parsing (e.g., for unit testing) +std::string content = + "127.0.0.1 localhost\n" + "::1 localhost ip6-localhost # comment\n" + "# this line is skipped\n"; + +QueryData rows = osquery::tables::parseEtcHostsContent(content); +// rows[0]: { address="127.0.0.1", hostnames="localhost", pid_with_namespace="0" } +// rows[1]: { address="::1", hostnames="localhost ip6-localhost", pid_with_namespace="0" } + +// Normal osquery table invocation (registered automatically) +// SELECT address, hostnames FROM etc_hosts; +``` + +## Parsing Behavior + +- Lines beginning with `#` are skipped entirely +- Inline comments (tokens starting with `#`) terminate hostname collection for that line +- Multiple hostnames per address are joined with a single space +- On Windows, both `hosts` and `hosts.ics` are read and merged into a single result set \ No newline at end of file diff --git a/osquery/tables/networking/.etc_protocols.md b/osquery/tables/networking/.etc_protocols.md new file mode 100644 index 00000000000..69cb568ab3f --- /dev/null +++ b/osquery/tables/networking/.etc_protocols.md @@ -0,0 +1,41 @@ + +Parses the system protocols file (`/etc/protocols` on Unix, `system32\drivers\etc\protocol` on Windows) and exposes its contents as an osquery table with protocol name, number, alias, and comment fields. + +## Key Components + +- **`kEtcProtocols`** — Cross-platform path constant pointing to the system protocols file. +- **`parseEtcProtocolsContent(const std::string& content)`** — Parses raw file content line-by-line, skipping empty lines and comments, and returns a `QueryData` collection of protocol rows. +- **`genEtcProtocols(QueryContext& context)`** — Table generator that reads the protocols file and delegates to the parser; logs errors on read failure. + +## Usage Example + +```cpp +// Parsing protocols file content directly (e.g., in unit tests) +std::string content = R"( +# Internet (IP) protocols +ip 0 IP # internet protocol, pseudo protocol number +tcp 6 TCP # transmission control protocol +udp 17 UDP # user datagram protocol +)"; + +QueryData results = osquery::tables::parseEtcProtocolsContent(content); + +// Each Row contains: +// r["name"] -> "tcp" +// r["number"] -> 6 +// r["alias"] -> "TCP" +// r["comment"] -> "transmission control protocol" + +// Table generator usage (called by osquery runtime) +QueryContext ctx; +QueryData protocols = osquery::tables::genEtcProtocols(ctx); +``` + +## Row Schema + +| Column | Type | Description | +|--------|------|-------------| +| `name` | `TEXT` | Protocol name (e.g., `tcp`) | +| `number` | `INTEGER` | Protocol number | +| `alias` | `TEXT` | Optional alias (e.g., `TCP`) | +| `comment` | `TEXT` | Optional inline comment | \ No newline at end of file diff --git a/osquery/tables/networking/.etc_services.md b/osquery/tables/networking/.etc_services.md new file mode 100644 index 00000000000..c47be5ecc11 --- /dev/null +++ b/osquery/tables/networking/.etc_services.md @@ -0,0 +1,37 @@ + +Parses the system's `/etc/services` file (or Windows equivalent) to expose network service definitions as osquery table rows. + +## Key Components + +- **`kEtcServices`** — Platform-resolved path to the services file (`/etc/services` on Unix, `%SystemRoot%\system32\drivers\etc\services` on Windows). +- **`parseEtcServicesContent(content)`** — Tokenizes raw file content line-by-line, skipping blank lines and comments, then extracts each service's name, port, protocol, aliases, and inline comment into a `QueryData` row set. +- **`genEtcServices(context)`** — Table generator entry point; reads the services file from disk and delegates to `parseEtcServicesContent`, logging on read failure. + +## Usage Example + +```cpp +// Directly parse a mock /etc/services payload (useful for unit testing) +std::string mockContent = + "http 80/tcp www www-http # World Wide Web HTTP\n" + "ssh 22/tcp # Secure Shell\n" + "dns 53/udp domain\n"; + +QueryData rows = osquery::tables::parseEtcServicesContent(mockContent); + +// rows[0]: name="http", port=80, protocol="tcp", +// aliases="www www-http", comment="World Wide Web HTTP" +// rows[1]: name="ssh", port=22, protocol="tcp", +// aliases="", comment="Secure Shell" +// rows[2]: name="dns", port=53, protocol="udp", +// aliases="domain", comment="" +``` + +## Parsed Row Schema + +| Column | Type | Description | +|---|---|---| +| `name` | TEXT | Service name (e.g. `http`) | +| `port` | INTEGER | Port number | +| `protocol` | TEXT | Transport protocol (`tcp`/`udp`) | +| `aliases` | TEXT | Space-separated alias names | +| `comment` | TEXT | Inline `#` comment text | \ No newline at end of file diff --git a/osquery/tables/networking/.listening_ports.md b/osquery/tables/networking/.listening_ports.md new file mode 100644 index 00000000000..8dfa4e81d0f --- /dev/null +++ b/osquery/tables/networking/.listening_ports.md @@ -0,0 +1,48 @@ + +Generates the `listening_ports` osquery virtual table by filtering and transforming data from `process_open_sockets`, returning only ports in a listening state across TCP, UDP, and Unix domain sockets. + +## Key Components + +### `genListeningPorts(QueryContext& context)` +The sole table-generation function. It: +- Queries all rows from `process_open_sockets` via `SQL::selectAllFrom` +- **Filters out** anonymous Unix domain sockets (AF_UNIX with no path) +- **Filters out** connected TCP/UDP sockets where `remote_port != "0"` (only listening sockets have `remote_port == 0`) +- Maps surviving rows into result columns, with Linux-specific `net_namespace` support + +### Address Family Constants + +| Constant | Value | Meaning | +|---|---|---| +| `kAF_UNIX` | `"1"` | Unix domain sockets | +| `kAF_INET` | `"2"` | IPv4 | +| `kAF_INET6` | `"10"` | IPv6 | + +## Output Columns + +| Column | Source / Notes | +|---|---| +| `pid` | Process owning the socket | +| `port` | `local_port` (or `"0"` for Unix sockets) | +| `address` | `local_address` (TCP/UDP only) | +| `path` | Socket filesystem path (Unix only) | +| `protocol` | Socket protocol number | +| `family` | Address family | +| `socket` | Socket inode (defaults to `"0"` if absent) | +| `fd` | File descriptor (defaults to `"0"` if absent) | +| `net_namespace` | Linux only — network namespace inode | + +## Usage Example + +```cpp +// Query via osquery SQL interface +SELECT pid, port, address, protocol, family, fd +FROM listening_ports +WHERE family = '2'; -- IPv4 listeners only + +-- Linux: correlate with Docker containers via net_namespace +SELECT lp.pid, lp.port, dc.name +FROM listening_ports lp +JOIN docker_containers dc + ON lp.net_namespace = dc.network_settings_sandbox_key; +``` \ No newline at end of file diff --git a/osquery/tables/networking/darwin/.interface_ip.md b/osquery/tables/networking/darwin/.interface_ip.md new file mode 100644 index 00000000000..0c7d8a66503 --- /dev/null +++ b/osquery/tables/networking/darwin/.interface_ip.md @@ -0,0 +1,43 @@ + +Implements the `interface_ip` osquery table for querying IPv6 network interface configuration on Darwin/BSD systems using sysctl and ioctl calls. + +## Key Components + +### Internal Helpers + +| Symbol | Description | +|---|---| +| `kIpv6SysctlObjects` | Static map of IPv6 attribute names to `{protocol, sysctl_object}` MIB pairs | +| `getSysIpv6Config(attr)` | Reads a system-wide IPv6 sysctl value by attribute name; returns `-1` on failure | + +### Exported Functions + +| Function | Description | +|---|---| +| `genIpv6FromIntf(iface, results)` | Queries per-interface IPv6 settings via `SIOCGIFINFO_IN6` ioctl and appends a row to `results` | +| `genInterfaceIpv6(context)` | Table generator entry point; iterates all interfaces and delegates to `genIpv6FromIntf` | + +### Queried Attributes + +| Column | Source | +|---|---| +| `hop_limit` | Interface-level (`nd6` current hop limit), falls back to `IPV6CTL_DEFHLIM` sysctl | +| `forwarding_enabled` | `IPV6CTL_FORWARDING` sysctl | +| `redirect_accept` | `ICMPV6CTL_REDIRACCEPT` sysctl | +| `rtadv_accept` | `IPV6CTL_ACCEPT_RTADV` sysctl | + +## Usage Example + +```cpp +// Invoked internally by the osquery table runtime: +QueryData rows = osquery::tables::genInterfaceIpv6(context); + +// Each row contains fields such as: +// rows[0]["interface"] → "en0" +// rows[0]["hop_limit"] → "64" +// rows[0]["forwarding_enabled"]→ "0" +// rows[0]["redirect_accept"] → "1" +// rows[0]["rtadv_accept"] → "0" +``` + +> **Note:** Darwin does not expose `forwarding_enabled`, `redirect_accept`, or `rtadv_accept` at the per-interface level, so these values are always sourced from system-wide sysctl MIBs. \ No newline at end of file diff --git a/osquery/tables/networking/darwin/.routes.md b/osquery/tables/networking/darwin/.routes.md new file mode 100644 index 00000000000..24e824c6a93 --- /dev/null +++ b/osquery/tables/networking/darwin/.routes.md @@ -0,0 +1,40 @@ + +Parses the BSD/macOS kernel routing table and ARP cache via `sysctl` to populate osquery's `routes` and `arp_cache` virtual tables. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kRouteTypes` | `const vector` | Maps RTF flag bitmasks to type strings (`gateway`, `static`, `blackhole`, etc.) | +| `kArpTypes` | `const vector` | Maps `RTF_LLINFO` to `"linklayer"` for ARP entries | +| `genInterfaceMap()` | `InterfaceMap` | Builds an index→name map from `getifaddrs()` for `AF_LINK` interfaces | +| `genRoute()` | `Status` | Extracts destination, gateway, netmask, MTU, and hop count from an `rt_msghdr` | +| `genArp()` | `Status` | Extracts IP address, MAC address, and permanence flag from an ARP `rt_msghdr` | +| `genRouteTableType()` | `void` | Queries the kernel via `sysctl(CTL_NET, PF_ROUTE, ...)` and iterates raw route messages | +| `genRoutes()` | `QueryData` | osquery table entry point for `routes`; supports `type` constraint filtering | +| `genArpCache()` | `QueryData` | osquery table entry point for `arp_cache` | + +## Usage Example + +```cpp +// Called internally by osquery when a query targets the routes table: +// SELECT * FROM routes WHERE type = 'gateway'; + +QueryContext context; +context.constraints["type"].add(Constraint(EQUALS, "gateway")); + +QueryData rows = genRoutes(context); +for (const auto &row : rows) { + // row["destination"], row["gateway"], row["netmask"], + // row["interface"], row["type"], row["mtu"], row["hopcount"] +} + +// ARP cache (no constraint filtering): +// SELECT * FROM arp_cache; +QueryData arp_rows = genArpCache(context); +for (const auto &row : arp_rows) { + // row["address"], row["mac"], row["permanent"] +} +``` + +> **Platform note:** This implementation is BSD/macOS-specific. It relies on ``, ``, and `sysctl` MIB paths (`CTL_NET / PF_ROUTE / NET_RT_FLAGS`) that are not available on Linux. Default routes (`0.0.0.0` / `::`) are always assigned netmask `"0"`. \ No newline at end of file diff --git a/osquery/tables/networking/darwin/.wifi_utils.md b/osquery/tables/networking/darwin/.wifi_utils.md new file mode 100644 index 00000000000..9dbfc11c11e --- /dev/null +++ b/osquery/tables/networking/darwin/.wifi_utils.md @@ -0,0 +1,47 @@ + +Utility header for extracting and converting CoreWLAN Wi-Fi properties on macOS into human-readable or structured formats for use in osquery tables. + +## Key Components + +| Function | Description | +|---|---| +| `getPropertiesFromDictionary` | Retrieves a string value from a `CFDictionaryRef` by key | +| `extractSsid` | Converts raw `CFDataRef` SSID bytes to a string, mirroring Apple's representation | +| `getSecurityName` | Maps a `CWSecurity` constant to its encryption type name (e.g., WPA2) | +| `getChannelNumber` | Extracts the integer channel number from a `CWChannel` object | +| `getChannelWidth` | Extracts the channel width value from a `CWChannel` object | +| `getChannelBand` | Extracts the channel band value from a `CWChannel` object | +| `getInterfaceModeName` | Maps a `CWInterfaceMode` constant to its mode name string | + +## Usage Example + +```c +#include "wifi_utils.h" +#include + +namespace osquery { +namespace tables { + +void inspectInterface(CWInterface* iface) { + // Get security type as a readable string + std::string security = getSecurityName([iface security]); + + // Get channel details + CWChannel* channel = [iface wlanChannel]; + int num = getChannelNumber(channel); + int width = getChannelWidth(channel); + int band = getChannelBand(channel); + + // Get interface mode + std::string mode = getInterfaceModeName([iface interfaceMode]); + + // Extract SSID from raw data + CFDataRef ssidData = (__bridge CFDataRef)[iface ssidData]; + std::string ssid = extractSsid(ssidData); +} + +} // namespace tables +} // namespace osquery +``` + +> **Note:** This header is macOS-only and depends on the `CoreWLAN` framework. It is scoped to the `osquery::tables` namespace and intended exclusively for Wi-Fi-related osquery table implementations. \ No newline at end of file diff --git a/osquery/tables/networking/linux/.arp_cache.md b/osquery/tables/networking/linux/.arp_cache.md new file mode 100644 index 00000000000..8ffd136a52b --- /dev/null +++ b/osquery/tables/networking/linux/.arp_cache.md @@ -0,0 +1,31 @@ + +Parses `/proc/net/arp` on Linux to retrieve the system's ARP (Address Resolution Protocol) cache and return it as structured query results for osquery. + +## Key Components + +- **`kLinuxArpTable`** — Constant string path to the Linux ARP table (`/proc/net/arp`) +- **`genArpCache(QueryContext& context)`** — Main table generation function that reads and parses the ARP cache, returning a `QueryData` collection of rows + +## Usage Example + +```cpp +// Called internally by osquery's table registry +// Each returned Row contains the following fields: + +Row r; +r["address"] = "192.168.1.1"; // IP address of the cached host +r["mac"] = "00:11:22:33:44:55"; // Hardware (MAC) address +r["interface"] = "eth0"; // Network interface name +r["permanent"] = "1"; // 1 if ATF_COM | ATF_PERM (0x6), else 0 +``` + +## Parsing Logic + +1. Verifies `/proc/net/arp` is readable via `osquery::isReadable` +2. Skips the header line of the proc file +3. Splits each subsequent line on whitespace using `boost::split` with `token_compress_on` +4. Expects exactly **6 fields** per line: `IP`, `HW type`, `Flags`, `HW address`, `Mask`, `Device` +5. Maps fields at indices `0`, `3`, and `5` to `address`, `mac`, and `interface` respectively +6. Sets `permanent = "1"` when the flags field equals `"0x6"` (bitwise `ATF_COM | ATF_PERM`) + +> **Note:** Entries that do not produce exactly 6 fields after splitting are silently skipped. Published ARP entries (`ATF_PUB`) are detected but not separately flagged in the current implementation. \ No newline at end of file diff --git a/osquery/tables/networking/linux/.inet_diag.md b/osquery/tables/networking/linux/.inet_diag.md new file mode 100644 index 00000000000..e046dfdc4d7 --- /dev/null +++ b/osquery/tables/networking/linux/.inet_diag.md @@ -0,0 +1,56 @@ + +Linux kernel UAPI header defining data structures and constants for querying inet socket diagnostics via Netlink, used by osquery to inspect TCP/DCCP socket state. + +## Key Components + +### Constants +| Constant | Value | Description | +|---|---|---| +| `TCPDIAG_GETSOCK` | 18 | Netlink message type for TCP diagnostics | +| `DCCPDIAG_GETSOCK` | 19 | Netlink message type for DCCP diagnostics | +| `INET_DIAG_GETSOCK_MAX` | 24 | Maximum socket diagnostic message type | +| `INET_DIAG_NOCOOKIE` | `~0U` | Wildcard cookie value (no cookie filter) | + +### Structures +| Structure | Purpose | +|---|---| +| `inet_diag_sockid` | Socket identity: source/dest ports, IPs, interface, cookie | +| `inet_diag_req` | v1 request to query socket info (family, extensions, states) | +| `inet_diag_req_v2` | v2 request adding protocol field and padding | +| `inet_diag_bc_op` | Single bytecode filter instruction (opcode + jump offsets) | +| `inet_diag_hostcond` | Host condition for bytecode address/port filtering | +| `inet_diag_msg` | Response message with socket state, queues, uid, inode | +| `inet_diag_meminfo` | Memory usage per socket (read/write/forward/total) | +| `tcpvegas_info` | TCP Vegas congestion control statistics | + +### Enumerations +- **`INET_DIAG_BC_*`** — Bytecode opcodes: `NOP`, `JMP`, source/dest port comparisons (`S_GE`, `S_LE`, `D_GE`, `D_LE`), and host conditions (`S_COND`, `D_COND`) +- **`INET_DIAG_*`** — Extension types for extended socket info: `MEMINFO`, `INFO`, `VEGASINFO`, `CONG`, `TOS`, `TCLASS`, `SKMEMINFO`, `SHUTDOWN` + +## Usage Example + +```c +#include "inet_diag.h" +#include +#include + +/* Build a Netlink request to dump all TCP sockets in ESTABLISHED state */ +struct { + struct nlmsghdr nlh; + struct inet_diag_req_v2 req; +} msg = { + .nlh = { + .nlmsg_len = sizeof(msg), + .nlmsg_type = TCPDIAG_GETSOCK, + .nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + }, + .req = { + .sdiag_family = AF_INET, + .sdiag_protocol = IPPROTO_TCP, + .idiag_states = (1 << TCP_ESTABLISHED), + .idiag_ext = (1 << (INET_DIAG_MEMINFO - 1)), + }, +}; + +/* Send via NETLINK_INET_DIAG socket, then parse inet_diag_msg responses */ +``` \ No newline at end of file diff --git a/osquery/tables/networking/linux/.interface_ip.md b/osquery/tables/networking/linux/.interface_ip.md new file mode 100644 index 00000000000..b897daaee03 --- /dev/null +++ b/osquery/tables/networking/linux/.interface_ip.md @@ -0,0 +1,34 @@ + +Populates the `interface_ipv6` osquery table by reading IPv6 network configuration for each system interface from Linux `/proc/sys/net/ipv6/conf/` entries. + +## Key Components + +### Constants +- **`kIpv6SysConfig`** — Default config scope (`"all"`) used for global IPv6 settings +- **`kIpv6ProcEntry`** — Maps osquery column names to their corresponding `/proc` filesystem entry names + +### Internal Helpers +- **`getIpv6Attr(intf, attr)`** — Builds the full `/proc/sys/net/ipv6/conf//` path for a given interface and attribute +- **`getIpv6Config(attr, intf)`** — Reads and parses an integer value from the computed proc path; returns `-1` on failure + +### Public Functions +- **`genIpv6FromIntf(iface, results)`** — Constructs a result row for a single interface, applying Linux-specific logic to combine global and per-interface settings (OR/AND rules for `redirect_accept`, router advertisement handling for `rtadv_accept`) +- **`genInterfaceIpv6(context)`** — Table entry point; iterates over all interfaces via `genInterfaceDetails` and delegates to `genIpv6FromIntf` + +## Usage Example + +```cpp +// Called automatically by osquery's table registration system +QueryContext context; +QueryData results = osquery::tables::genInterfaceIpv6(context); + +for (const auto& row : results) { + std::cout << "Interface: " << row.at("interface") << "\n"; + std::cout << "Hop Limit: " << row.at("hop_limit") << "\n"; + std::cout << "Forwarding Enabled: " << row.at("forwarding_enabled") << "\n"; + std::cout << "Redirect Accept: " << row.at("redirect_accept") << "\n"; + std::cout << "RA Accept: " << row.at("rtadv_accept") << "\n"; +} +``` + +> **Note:** `redirect_accept` and `rtadv_accept` values are computed by combining both global (`all`) and per-interface proc entries using OR/AND logic, matching Linux kernel behavior described in `Documentation/networking/ip-sysctl.txt`. \ No newline at end of file diff --git a/osquery/tables/networking/linux/.iptables.md b/osquery/tables/networking/linux/.iptables.md new file mode 100644 index 00000000000..9b64bc720e1 --- /dev/null +++ b/osquery/tables/networking/linux/.iptables.md @@ -0,0 +1,37 @@ + +Parses Linux iptables firewall rules using the `iptc` (IP Tables Control) library proxy, exposing chain policies, rule targets, and match conditions as queryable osquery table rows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `parseIptcpRule` | Function | Converts a raw `iptcproxy_rule` struct into an osquery `Row`, extracting IPs, ports, interfaces, masks, protocol, and target | +| `genIPTablesRules` | Function | Iterates all chains and rules within a single iptables filter table (e.g. `filter`, `nat`, `mangle`) via `iptcproxy_*` calls | +| `genIptables` | Function | Entry point — reads `/proc/net/ip_tables_names` to discover active tables, then calls `genIPTablesRules` for each | +| `formatInvFlag` | Lambda | Prepends `"!"` to a field value when the corresponding iptables inversion flag is set | +| `kLinuxIpTablesNames` | Constant | Path to the kernel-exposed list of active iptables table names | + +## Usage Example + +```cpp +// Called automatically by the osquery table registry. +// Equivalent SQL query: +// SELECT chain, policy, target, src_ip, dst_ip, src_port, dst_port, +// protocol, iniface, outiface +// FROM iptables +// WHERE filter_name = 'filter'; + +// Each row produced by genIptables() contains: +// r["filter_name"] = "filter" +// r["chain"] = "INPUT" +// r["policy"] = "ACCEPT" +// r["target"] = "DROP" +// r["src_ip"] = "192.168.1.0" +// r["src_mask"] = "255.255.255.0" +// r["protocol"] = "6" // TCP +// r["src_port"] = "0:65535" +// r["iniface"] = "eth0" +// r["match"] = "yes" +``` + +Interface masks are hex-encoded byte-by-byte (e.g. `FF FF FF FF`) using the `kHexMap` lookup. Inversion flags from `ip_data.invflags` are reflected as a `"!"` prefix on the relevant field value. \ No newline at end of file diff --git a/osquery/tables/networking/linux/.iptc_proxy.md b/osquery/tables/networking/linux/.iptc_proxy.md new file mode 100644 index 00000000000..5d0148eb1cd --- /dev/null +++ b/osquery/tables/networking/linux/.iptc_proxy.md @@ -0,0 +1,68 @@ + +A C header providing an abstraction layer over `libiptc` for querying Linux netfilter/iptables rules and chains from osquery table implementations. + +## Key Components + +### Types + +| Type | Description | +|------|-------------| +| `iptcproxy_handle` | Opaque handle for an iptables filter context | +| `iptcproxy_chain` | Represents a chain with its name, policy, and packet/byte counters | +| `iptcproxy_rule` | Represents a firewall rule with target, port match data, and full IP header fields | + +### `invflags` Bitmask Constants + +| Constant | Value | Description | +|----------|-------|-------------| +| `IPTC_INV_VIA_IN` | `0x01` | Invert inbound interface match | +| `IPTC_INV_VIA_OUT` | `0x02` | Invert outbound interface match | +| `IPTC_INV_SRCIP` | `0x08` | Invert source IP match | +| `IPTC_INV_DSTIP` | `0x10` | Invert destination IP match | +| `IPTC_INV_PROTO` | `0x40` | Invert protocol match | +| `IPTC_INV_MASK` | `0x7F` | All flags mask | + +### Functions + +- **`iptcproxy_init(filter)`** — Opens a handle for the given iptables table (e.g. `"filter"`) +- **`iptcproxy_free(handle)`** — Releases resources for an open handle +- **`iptcproxy_first_chain / iptcproxy_next_chain`** — Iterates chains in the table +- **`iptcproxy_first_rule / iptcproxy_next_rule`** — Iterates rules within a named chain + +## Usage Example + +```c +#include "iptc_proxy.h" +#include + +void enumerate_rules(void) { + const iptcproxy_handle* h = iptcproxy_init("filter"); + if (!h) return; + + const iptcproxy_chain* chain = iptcproxy_first_chain(h); + while (chain) { + printf("Chain: %s Policy: %s Packets: %llu\n", + chain->chain, + chain->policy, + (unsigned long long)chain->policy_data.pcnt); + + const iptcproxy_rule* rule = iptcproxy_first_rule(chain->chain, h); + while (rule) { + printf(" Target: %s Proto: %u\n", + rule->target, + rule->ip_data.proto); + /* Check inverted source IP flag */ + if (rule->ip_data.invflags & IPTC_INV_SRCIP) { + printf(" (source IP is inverted)\n"); + } + rule = iptcproxy_next_rule(h); + } + + chain = iptcproxy_next_chain(h); + } + + iptcproxy_free(h); +} +``` + +> **Note:** Iteration is stateful per handle. Always call `iptcproxy_first_chain` or `iptcproxy_first_rule` before the corresponding `_next_*` calls. \ No newline at end of file diff --git a/osquery/tables/networking/linux/.process_open_sockets.md b/osquery/tables/networking/linux/.process_open_sockets.md new file mode 100644 index 00000000000..33e94f902ec --- /dev/null +++ b/osquery/tables/networking/linux/.process_open_sockets.md @@ -0,0 +1,44 @@ + +Implements the `process_open_sockets` osquery table for Linux, which correlates open network sockets with their associated processes by cross-referencing `/proc` filesystem data across three distinct collection steps. + +## Key Components + +### `genOpenSockets(QueryContext& context)` +The sole exported table generator function. Accepts an optional `pid` constraint for filtered queries and returns a `QueryData` collection of socket rows. + +**Three-phase data collection pipeline:** + +| Step | Source | Data Collected | +|------|--------|----------------| +| 1 | `/proc//fd` | Socket inode → `{pid, fd}` mapping via `procGetSocketInodeToProcessInfoMap` | +| 2 | `/proc//ns/net` | Network namespace inode per pid via `procGetProcessNamespaces` | +| 3 | `/proc//net/*` | Full socket details per namespace via `procGetSocketList` | + +**Address families queried:** +- `AF_INET` / `AF_INET6` — for each protocol in `kLinuxProtocolNames` +- `AF_UNIX` — with `IPPROTO_IP` +- `AF_PACKET` — all protocols (`0`) + +## Usage Example + +```cpp +// Typical osquery table query (no pid filter — returns all sockets) +SELECT pid, fd, family, protocol, + local_address, local_port, + remote_address, remote_port, + state, net_namespace +FROM process_open_sockets; + +// Filtered by specific pid +SELECT * FROM process_open_sockets WHERE pid = 1234; + +// Sockets with no associated process (pid = -1) +SELECT * FROM process_open_sockets WHERE pid = -1; +``` + +**Output columns:** `pid`, `fd`, `socket` (inode), `family`, `protocol`, `local_address`, `local_port`, `remote_address`, `remote_port`, `path` (Unix socket path), `state`, `net_namespace`. + +**Key behaviors:** +- Namespace deduplication ensures Step 3 runs only once per unique network namespace, avoiding redundant `/proc//net` reads +- When `pid = -1` is queried (or no filter applied), sockets without a matched process are included with `pid = -1`, `fd = -1` +- Non-fatal errors are logged via `VLOG(1)` and collection continues with potentially incomplete results \ No newline at end of file diff --git a/osquery/tables/networking/linux/.routes.md b/osquery/tables/networking/linux/.routes.md new file mode 100644 index 00000000000..28eb257a19f --- /dev/null +++ b/osquery/tables/networking/linux/.routes.md @@ -0,0 +1,37 @@ + +Implements the Linux `routes` osquery table by querying the kernel routing table via the Netlink socket interface (`NETLINK_ROUTE`), parsing `RTM_GETROUTE` responses into structured row data. + +## Key Components + +| Function | Description | +|---|---| +| `getNetlinkIP()` | Converts a raw binary address buffer to a human-readable IP string using `inet_ntop` for both IPv4 and IPv6 families | +| `getDefaultRouteIP()` | Returns the default route address (`0.0.0.0` or `::`) for a given address family | +| `readNetlink()` | Reads one or more Netlink messages from a socket with poll-based timeout (`5000ms`), handling multi-part messages and sequence validation | +| `genNetlinkRoutes()` | Parses a single `nlmsghdr` message, extracting route attributes (destination, gateway, source, interface, metric, MTU, hopcount, type, netmask) into a `QueryData` row | +| `genRoutes()` | Entry point — opens a `PF_NETLINK` socket, sends an `RTM_GETROUTE` dump request, reads the response, and iterates over all returned route messages | + +## Usage Example + +```cpp +// Called internally by osquery when the routes table is queried: +// SELECT * FROM routes; + +QueryContext context; +QueryData results = genRoutes(context); + +// Each row contains fields such as: +// destination, netmask, gateway, source, interface, +// type (gateway/local/broadcast/anycast/other), +// metric, mtu, hopcount, flags +for (const auto& row : results) { + std::cout << row.at("destination") << "/" + << row.at("netmask") << " via " + << row.at("gateway") << " dev " + << row.at("interface") << "\n"; +} +``` + +**Key constants:** +- `MAX_NETLINK_SIZE` — 64 KB receive buffer +- `kMaxPollWaitInMS` — 5 000 ms poll timeout before returning a timeout error \ No newline at end of file diff --git a/osquery/tables/networking/posix/.dns_resolvers.md b/osquery/tables/networking/posix/.dns_resolvers.md new file mode 100644 index 00000000000..f19ca8ca7c9 --- /dev/null +++ b/osquery/tables/networking/posix/.dns_resolvers.md @@ -0,0 +1,34 @@ + +Implements the `dns_resolvers` osquery table for POSIX systems, reading DNS resolver configuration from the system's `libresolv` global state (`_res`) and returning nameservers, sort lists, and search domains. + +## Key Components + +### `genDNSResolversImpl(QueryContext&, Logger&)` +Core implementation that calls `res_init()` to populate the `__res_state` global `_res`, then iterates over three resolver entry types: + +| Type | Source | Description | +|---|---|---| +| `nameserver` | `_res.nsaddr_list` | Configured DNS nameserver IPs | +| `sortlist` | `_res.sort_list` | Address sorting rules with netmasks | +| `search` | `_res.dnsrch` | DNS search domain suffixes | + +Each row includes: `id`, `type`, `address`, `netmask`, `options` (resolver flags as bitmask), and `pid_with_namespace`. + +### `genDNSResolvers(QueryContext&)` +Public table entry point. Routes execution through namespace isolation via `generateInNamespace()` when a namespace constraint is present, or falls back to direct execution with a `GLOGLogger`. + +## Usage Example + +```cpp +// Querying the dns_resolvers table via osquery SQL +SELECT id, type, address, netmask, options +FROM dns_resolvers; + +/* Example output: + id | type | address | netmask | options + 0 | nameserver | 8.8.8.8 | 32 | 1234 + 0 | search | example.com | | 1234 +*/ +``` + +> **Note:** This implementation relies on the POSIX `resolv.h` API (`res_init`, `_res`, `res_close`) and is not available on Windows. Namespace-aware queries are routed through the platform IPC container for correct resolver visibility in containerized environments. \ No newline at end of file diff --git a/osquery/tables/networking/posix/.interfaces.md b/osquery/tables/networking/posix/.interfaces.md new file mode 100644 index 00000000000..c51cb5040e5 --- /dev/null +++ b/osquery/tables/networking/posix/.interfaces.md @@ -0,0 +1,34 @@ + +Declares the `genInterfaceDetails` table generation function for querying network interface details within the osquery tables framework. + +## Key Components + +- **`genInterfaceDetails(QueryContext& context)`** — Generates rows of network interface data (e.g., name, MAC address, IP configuration) for the `interface_details` virtual table. + +## Usage Example + +```c +#include + +namespace osquery { +namespace tables { + +QueryData genInterfaceDetails(QueryContext& context) { + QueryData results; + // Enumerate network interfaces and populate rows + Row r; + r["interface"] = "eth0"; + r["mac"] = "00:1a:2b:3c:4d:5e"; + results.push_back(r); + return results; +} + +} // namespace tables +} // namespace osquery +``` + +## Notes + +- Depends on `osquery/core/core.h` and `osquery/core/tables.h`. +- Platform-specific implementations (Linux, macOS, Windows) are expected in corresponding `.cpp` source files. +- Registered as a virtual SQL table via osquery's table plugin system, queryable as `SELECT * FROM interface_details`. \ No newline at end of file diff --git a/osquery/tables/networking/posix/.utils.md b/osquery/tables/networking/posix/.utils.md new file mode 100644 index 00000000000..7586a4c1ead --- /dev/null +++ b/osquery/tables/networking/posix/.utils.md @@ -0,0 +1,37 @@ + +Utility header for network address conversion functions used in osquery's network interface tables. Provides cross-platform helpers to convert raw socket/IP structures into human-readable strings. + +## Key Components + +- **`AF_INTERFACE` macro** — Platform alias for interface address family (`AF_PACKET` on Linux, `AF_LINK` on BSD/macOS) +- **`ipAsString(sockaddr*)`** — Converts an IPv4/IPv6 `sockaddr` struct to a string +- **`ipAsString(in_addr*)`** — Converts an IPv4 `in_addr` struct to a string +- **`macAsString(ifaddrs*)`** — Extracts and formats a MAC address from an `ifaddrs` struct +- **`macAsString(char*)`** — Formats a MAC address from a raw byte buffer +- **`netmaskFromIP(sockaddr*)`** — Returns the CIDR prefix length from a `sockaddr` netmask + +## Usage Example + +```c +#include "utils.h" +#include + +struct ifaddrs *ifap; +if (getifaddrs(&ifap) == 0) { + for (struct ifaddrs *ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr) continue; + + // Get IP address string + std::string ip = osquery::tables::ipAsString(ifa->ifa_addr); + + // Get subnet prefix length + int prefix = osquery::tables::netmaskFromIP(ifa->ifa_netmask); + + // Get MAC address (on link-layer entries) + if (ifa->ifa_addr->sa_family == AF_INTERFACE) { + std::string mac = osquery::tables::macAsString(ifa); + } + } + freeifaddrs(ifap); +} +``` \ No newline at end of file diff --git a/osquery/tables/networking/tests/.networking_tables_tests.md b/osquery/tables/networking/tests/.networking_tables_tests.md new file mode 100644 index 00000000000..af1364eae82 --- /dev/null +++ b/osquery/tables/networking/tests/.networking_tables_tests.md @@ -0,0 +1,50 @@ + +Unit tests for osquery networking tables, validating parsing of `/etc/hosts`, `/etc/protocols`, and live network queries against the `listening_ports` and `interface_addresses` tables. + +## Key Components + +### Test Fixture +- **`NetworkingTablesTests`** — GTest fixture that initializes the platform, registry/plugin system, and database plugin before each test. + +### Helper Functions + +| Function | Description | +|---|---| +| `getEtcHostsContent()` | Reads `test_hosts.txt` from the test config directory | +| `getEtcHostsIcsContent()` | Reads `test_hosts_ics.txt` (Windows ICS format) | +| `getEtcProtocolsContent()` | Reads `test_protocols.txt` from the test config directory | +| `getEtcHostsExpectedResults()` | Returns expected `QueryData` for standard hosts parsing | +| `getEtcHostsIcsExpectedResults()` | Returns expected `QueryData` for ICS hosts parsing | +| `getEtcProtocolsExpectedResults()` | Returns expected `QueryData` for protocols parsing | + +### Test Cases + +| Test | What It Validates | +|---|---| +| `test_parse_etc_hosts_content` | Parses standard `/etc/hosts` entries (IPv4, IPv6, aliases) | +| `test_parse_etc_hosts_ics_content` | Parses Windows ICS-style hosts format | +| `test_parse_etc_protocols_content` | Parses `/etc/protocols` (name, number, alias, comment) | +| `test_listening_ports` | Starts a TLS server and confirms its PID appears in `listening_ports` | +| `test_address_details_join` | Validates SQL JOIN between `interface_details` and `interface_addresses` on loopback | + +## Usage Example + +```cpp +// Run all networking table tests via GTest +// From the osquery build directory: +// ./osquery_networking_tables_tests + +// Example expected row structure for /etc/hosts parsing: +Row row; +row["address"] = "127.0.0.1"; +row["hostnames"] = "localhost"; +row["pid_with_namespace"] = "0"; + +// Example SQL join test: +auto results = SQL( + "SELECT * FROM interface_details id, interface_addresses ia " + "ON ia.interface = id.interface " + "WHERE ia.address = '127.0.0.1';" +); +EXPECT_GT(results.rows().size(), 0U); +``` \ No newline at end of file diff --git a/osquery/tables/networking/tests/linux/.iptables_tests.md b/osquery/tables/networking/tests/linux/.iptables_tests.md new file mode 100644 index 00000000000..d2e5cec5256 --- /dev/null +++ b/osquery/tables/networking/tests/linux/.iptables_tests.md @@ -0,0 +1,38 @@ + +Unit test file for the `iptables` osquery table on Linux, validating that raw `iptcproxy_rule` structs are correctly parsed into osquery `Row` objects by `parseIptcpRule`. + +## Key Components + +| Component | Description | +|---|---| +| `getIpEntryContent()` | Constructs a synthetic `iptcproxy_rule` with hardcoded IP/network/interface values for testing | +| `getIpEntryExpectedResults()` | Returns the expected `Row` output after parsing the rule from `getIpEntryContent()` | +| `IptablesTests` | GTest fixture class wrapping the iptables test suite | +| `test_iptables_ip_entry` | Test case asserting `parseIptcpRule` output matches expected row fields | + +## Coverage + +The single test case exercises the following `parseIptcpRule` behaviors: + +- **Protocol** → numeric string (`"6"` for TCP) +- **Interfaces** → empty `iniface` renders as `"all"`, populated `outiface` passes through as-is +- **Source/destination IPs** → dotted-decimal format; `IPTC_INV_DSTIP` flag prepends `"!"` to the destination +- **Subnet masks** → dotted-decimal for IP masks, uppercase hex for interface masks +- **Target/match** → empty string and `"no"` respectively when not set + +## Usage Example + +```cpp +// Run via ctest or directly with the gtest runner: +// ./iptables_tests --gtest_filter=IptablesTests.test_iptables_ip_entry + +TEST_F(IptablesTests, test_iptables_ip_entry) { + Row row; + // Feed a synthetic rule into the parser + parseIptcpRule(getIpEntryContent(), row); + // Assert all fields match expectations + EXPECT_EQ(row, getIpEntryExpectedResults()); +} +``` + +> **Note:** `parseIptcpRule` is declared `extern` here and linked from the main `iptables` table implementation. The `iptc_proxy.h` header is included as a C linkage block to ensure compatibility with the underlying `libiptc` C API. \ No newline at end of file diff --git a/osquery/tables/networking/tests/windows/.windows_firewall_rules_tests.md b/osquery/tables/networking/tests/windows/.windows_firewall_rules_tests.md new file mode 100644 index 00000000000..bd358517b5e --- /dev/null +++ b/osquery/tables/networking/tests/windows/.windows_firewall_rules_tests.md @@ -0,0 +1,37 @@ + +Unit test file for the `windows_firewall_rules` osquery table, validating that Windows Firewall rule structs are correctly rendered into osquery table rows on Windows platforms. + +## Key Components + +### Test Fixture +- **`WindowsFirewallRulesTests`** — GTest fixture class scoped under `osquery::table_tests` + +### Helper Functions (anonymous namespace) + +- **`generateTestRules()`** — Builds a `WindowsFirewallRules` collection covering all major field variations: + - Actions: `NET_FW_ACTION_BLOCK`, `NET_FW_ACTION_ALLOW` + - Directions: `NET_FW_RULE_DIR_IN`, `NET_FW_RULE_DIR_OUT` + - IP versions: `V4`, `V6`, `ANY` + - Profile bitmask combinations: Domain, Public, Private (single and combined) + - Address/port wildcards and ICMP type codes + +- **`validateRendered(rule, row)`** — Asserts that each field of a `WindowsFirewallRule` struct maps correctly to its corresponding `Row` column, including enum-to-string conversions (`"Block"/"Allow"`, `"In"/"Out"`, `"TCP"/"UDP"/"Any"`) and bitmask-to-integer profile flags + +### Test Case + +- **`test_firewall_rules_render`** — Calls `renderWindowsFirewallRules()` on the generated test data and verifies that the output row count matches the input rule count, then validates every rule/row pair via `validateRendered` + +## Usage Example + +```cpp +// Run all WindowsFirewallRules tests +// From the osquery build directory: +``` + +```bash +# Build and run the specific test suite +cmake --build . --target osquery_tests +./osquery_tests --gtest_filter="WindowsFirewallRulesTests.*" +``` + +The test verifies enum serialization logic — any change to how `NET_FW_ACTION_*`, `NET_FW_RULE_DIR_*`, or `NET_FW_IP_VERSION_*` values are mapped to strings in `renderWindowsFirewallRules()` will cause assertion failures here. \ No newline at end of file diff --git a/osquery/tables/networking/windows/.arp_cache.md b/osquery/tables/networking/windows/.arp_cache.md new file mode 100644 index 00000000000..10152a32471 --- /dev/null +++ b/osquery/tables/networking/windows/.arp_cache.md @@ -0,0 +1,42 @@ + +Implements the Windows ARP cache table for osquery by querying neighbor entries via WMI (`MSFT_NetNeighbor`) and mapping raw numeric fields to human-readable strings. + +## Key Components + +### Lookup Maps +- **`kMapOfAddressFamily`** — Maps address family codes to `"IPv4"` / `"IPv6"` +- **`kMapOfStore`** — Maps store codes to `"Persistent"` / `"Active"` +- **`kMapOfState`** — Maps neighbor state codes (0–7) to strings like `"Reachable"`, `"Stale"`, `"Permanent"`, etc. + +### Functions + +| Function | Description | +|---|---| +| `genIPv4ArpCache(context)` | Queries `MSFT_NetNeighbor` via WMI; returns full neighbor table with address family, store, state, interface alias, IP, and MAC (dash-to-colon normalized) | +| `genArpCache(context)` | Filters `genIPv4ArpCache` results to IPv4-only entries with valid MACs; returns the simplified `arp_cache` table schema | + +## Usage Example + +```cpp +// Called internally by the osquery table registration system +QueryContext context; + +// Full neighbor detail (both IPv4 and IPv6, all states) +QueryData full = genIPv4ArpCache(context); + +// Filtered ARP cache (IPv4 only, valid MACs, simplified schema) +QueryData arp = genArpCache(context); + +// Each row in genArpCache contains: +// r["address"] → "192.168.1.1" +// r["mac"] → "aa:bb:cc:dd:ee:ff" +// r["interface"] → "Ethernet" +// r["permanent"] → "0" or "1" +``` + +## Notes + +- MAC addresses are normalized from Windows `-` separator format to standard `:` format via `boost::replace_all_copy` +- Entries with empty or all-zero MACs (`00:00:00:00:00:00`) are excluded from `genArpCache` output +- The loopback interface (`index 1`) is pre-seeded in the interface map with an empty MAC +- WMI query targets `ROOT\\StandardCimv2` namespace; failures are logged as warnings and return an empty result set \ No newline at end of file diff --git a/osquery/tables/networking/windows/.connectivity.md b/osquery/tables/networking/windows/.connectivity.md new file mode 100644 index 00000000000..db75768a909 --- /dev/null +++ b/osquery/tables/networking/windows/.connectivity.md @@ -0,0 +1,42 @@ + +Implements the `connectivity` osquery table for Windows, querying the Windows Network List Manager COM API to report the current network connectivity state of the host. + +## Key Components + +### `genConnectivity(QueryContext& context)` +The sole table generator function. It: +1. Instantiates `INetworkListManager` via `CoCreateInstance` +2. Calls `GetConnectivity()` to retrieve the `NLM_CONNECTIVITY` bitmask +3. Maps each bitmask flag to an integer column in the result row +4. Releases the COM object and returns the populated `QueryData` + +### Columns Produced + +| Column | NLM Flag | +|---|---| +| `disconnected` | `NLM_CONNECTIVITY_DISCONNECTED` | +| `ipv4_no_traffic` | `NLM_CONNECTIVITY_IPV4_NOTRAFFIC` | +| `ipv6_no_traffic` | `NLM_CONNECTIVITY_IPV6_NOTRAFFIC` | +| `ipv4_subnet` | `NLM_CONNECTIVITY_IPV4_SUBNET` | +| `ipv4_local_network` | `NLM_CONNECTIVITY_IPV4_LOCALNETWORK` | +| `ipv4_internet` | `NLM_CONNECTIVITY_IPV4_INTERNET` | +| `ipv6_subnet` | `NLM_CONNECTIVITY_IPV6_SUBNET` | +| `ipv6_local_network` | `NLM_CONNECTIVITY_IPV6_LOCALNETWORK` | +| `ipv6_internet` | `NLM_CONNECTIVITY_IPV6_INTERNET` | + +All columns are integer booleans (`0` or `1`). + +## Usage Example + +```cpp +// Querying the table via osquery SQL (Windows only) +// SELECT * FROM connectivity; + +// Example result row when online: +// disconnected=0, ipv4_internet=1, ipv6_internet=0, ... + +// Internal call pattern (osquery table registration): +REGISTER(genConnectivity, "table", "connectivity"); +``` + +> **Note:** This table is Windows-only. COM initialization (`CoInitialize`/`CoInitializeEx`) must be performed by the calling thread before `genConnectivity` is invoked. Failures at either the `CoCreateInstance` or `GetConnectivity` step are logged via `TLOG` and return an empty result set. \ No newline at end of file diff --git a/osquery/tables/networking/windows/.interfaces.md b/osquery/tables/networking/windows/.interfaces.md new file mode 100644 index 00000000000..666822b762d --- /dev/null +++ b/osquery/tables/networking/windows/.interfaces.md @@ -0,0 +1,25 @@ + +Declares the `genInterfaceDetails` function for querying network interface details within the osquery tables framework. + +## Key Components + +- **`genInterfaceDetails(QueryContext& context)`** — Table generator function that retrieves detailed network interface information, returning a `QueryData` result set for use in the `interfaces` virtual table. + +## Usage Example + +```c +#include "interfaces.h" + +namespace osquery { +namespace tables { + +QueryData genInterfaceDetails(QueryContext& context) { + QueryData results; + // Populate rows with network interface details + // (IP addresses, MAC, MTU, flags, etc.) + return results; +} + +} // namespace tables +} // namespace osquery +``` \ No newline at end of file diff --git a/osquery/tables/networking/windows/.process_open_sockets.md b/osquery/tables/networking/windows/.process_open_sockets.md new file mode 100644 index 00000000000..9086fe6c1a8 --- /dev/null +++ b/osquery/tables/networking/windows/.process_open_sockets.md @@ -0,0 +1,36 @@ + +Windows-specific osquery table implementation that enumerates all open network sockets (TCP/UDP, IPv4/IPv6) for the `process_open_sockets` table using the Windows IP Helper API. + +## Key Components + +### `WinSockets` Class +- **Constructor** — Allocates four socket tables at initialization: TCP IPv4, TCP IPv6, UDP IPv4, and UDP IPv6 via `allocateSocketTable()` +- **Destructor** — Frees all allocated socket table memory +- **`parseSocketTable(WinSockTableType, QueryData&)`** — Iterates entries in a given socket table type and populates result rows with fields: `protocol`, `local_address`, `local_port`, `remote_address`, `remote_port`, `pid`, `family`, and `state` +- **`allocateSocketTable(protocol, family)`** — Calls `GetExtendedTcpTable()` or `GetExtendedUdpTable()` with a two-pass pattern (size probe, then populate) and returns the allocated buffer + +### `tcpStateString(DWORD state)` +Internal helper that maps a Windows TCP state integer to a human-readable string (e.g., `ESTABLISHED`, `LISTEN`, `TIME_WAIT`) using the `kWinTcpStates` lookup table. + +### `genOpenSockets(QueryContext&)` +Entry point registered as the osquery table generator. Instantiates `WinSockets` and sequentially parses all four socket table types into a unified `QueryData` result set. + +## Usage Example + +```cpp +// Called internally by the osquery table framework +QueryContext context; +QueryData results = osquery::tables::genOpenSockets(context); + +// Each row contains fields like: +// results[i]["pid"] -> owning process ID +// results[i]["local_address"] -> "127.0.0.1" +// results[i]["local_port"] -> "8080" +// results[i]["remote_address"] -> "192.168.1.1" +// results[i]["remote_port"] -> "443" +// results[i]["protocol"] -> "6" (TCP) or "17" (UDP) +// results[i]["family"] -> "2" (AF_INET) or "23" (AF_INET6) +// results[i]["state"] -> "ESTABLISHED" +``` + +> **Note:** This implementation is Windows-only and depends on `win_sockets.h`. UDP rows always report `remote_address` as `"0"` and `remote_port` as `0` since UDP is connectionless. \ No newline at end of file diff --git a/osquery/tables/networking/windows/.routes.md b/osquery/tables/networking/windows/.routes.md new file mode 100644 index 00000000000..1691f4f9b4e --- /dev/null +++ b/osquery/tables/networking/windows/.routes.md @@ -0,0 +1,39 @@ + +Implements the Windows-specific `routes` osquery table by querying the IP forwarding table and network adapter information to enumerate system routing entries. + +## Key Components + +### `getAdapterAddressMapping()` +Builds a `std::map` indexed by adapter interface index using `GetAdaptersInfo`. Returns an empty map on failure. + +### `genRoutes(QueryContext&)` +Primary table generator that: +- Retrieves the full IP forwarding table via `GetIpForwardTable2` (both IPv4/IPv6 via `AF_UNSPEC`) +- Iterates each routing entry, resolving interface metrics and MTU via `GetIpInterfaceEntry` +- Handles IPv4 (`AF_INET`) and IPv6 (`AF_INET6`) address families separately +- Special-cases the software loopback interface (index `1`), which is excluded from `GetAdaptersInfo` +- Populates each `Row` with `destination`, `gateway`, `netmask`, `metric`, `mtu`, `interface`, `type`, and `flags` + +## Usage Example + +```cpp +// Called internally by osquery's table dispatcher +QueryData results = genRoutes(context); + +// Each row contains fields such as: +// r["destination"] = "192.168.1.0" +// r["gateway"] = "192.168.1.1" +// r["netmask"] = "24" +// r["metric"] = "256" +// r["mtu"] = "1500" +// r["interface"] = "192.168.1.100" +// r["type"] = "remote" +// r["flags"] = "-1" // not yet implemented +``` + +## Notes + +- MTU values are capped at `MAXINT32` when the returned value exceeds it (prevents overflow in the table column) +- `flags` field is a stub (`-1`) pending future implementation +- Loopback interface (`index == 1`) defaults `interface` to `"127.0.0.1"` and uses the raw next-hop address as the gateway +- IPv6 routes are always typed as `"local"` (matching `route print -6` behavior) \ No newline at end of file diff --git a/osquery/tables/networking/windows/.win_sockets.md b/osquery/tables/networking/windows/.win_sockets.md new file mode 100644 index 00000000000..94ad5be0b57 --- /dev/null +++ b/osquery/tables/networking/windows/.win_sockets.md @@ -0,0 +1,49 @@ + +Provides the `WinSockets` class interface for querying and parsing Windows network socket tables (TCP/UDP, IPv4/IPv6) using the Windows IP Helper API. + +## Key Components + +### Enum: `WinSockTableType` +Identifies the socket protocol/address family combination to query: + +| Value | Description | +|---|---| +| `tcp` | IPv4 TCP connections | +| `tcp6` | IPv6 TCP connections | +| `udp` | IPv4 UDP endpoints | +| `udp6` | IPv6 UDP endpoints | + +### Class: `WinSockets` *(non-copyable)* + +| Member | Description | +|---|---| +| `WinSockets()` | Allocates all four socket table structures via the Windows API | +| `~WinSockets()` | Deallocates all socket tables | +| `parseSocketTable(WinSockTableType, QueryData&)` | Parses entries for the given type and appends rows to `results` | +| `getStatus()` | Returns the `Status` of the last table retrieval operation | +| `allocateSocketTable(protocol, family)` | Internal helper that allocates a single MIB table by protocol and address family | + +**Private tables:** +- `MIB_TCPTABLE_OWNER_PID*` — IPv4 TCP +- `MIB_TCP6TABLE_OWNER_PID*` — IPv6 TCP +- `MIB_UDPTABLE_OWNER_PID*` — IPv4 UDP +- `MIB_UDP6TABLE_OWNER_PID*` — IPv6 UDP + +## Usage Example + +```cpp +#include "win_sockets.h" + +osquery::QueryData results; +osquery::tables::WinSockets sockets; + +if (sockets.getStatus().ok()) { + sockets.parseSocketTable( + osquery::tables::WinSockTableType::tcp, results); + sockets.parseSocketTable( + osquery::tables::WinSockTableType::udp6, results); + // results now contains socket rows +} +``` + +> **Note:** `WinSockets` is non-copyable. Always use it as a stack-allocated or uniquely-owned object. Check `getStatus()` before calling `parseSocketTable` to ensure table allocation succeeded. \ No newline at end of file diff --git a/osquery/tables/networking/windows/.windows_firewall_rules.md b/osquery/tables/networking/windows/.windows_firewall_rules.md new file mode 100644 index 00000000000..f8aedf7a821 --- /dev/null +++ b/osquery/tables/networking/windows/.windows_firewall_rules.md @@ -0,0 +1,52 @@ + +Defines types and interfaces for querying Windows Firewall rules within the osquery `tables` namespace, providing structured representations of firewall rule data and error states. + +## Key Components + +### `WindowsFirewallError` (enum class) +Enumerates all possible failure points when retrieving firewall rules via the Windows Firewall API (`netfw.h`), from policy access errors through individual rule property read failures. + +### `WindowsFirewallRule` (struct) +Represents a single Windows Firewall rule with the following fields: + +| Field | Type | Default | +|---|---|---| +| `name` | `std::string` | — | +| `appName` | `std::string` | — | +| `action` | `NET_FW_ACTION` | `NET_FW_ACTION_BLOCK` | +| `enabled` | `bool` | `false` | +| `grouping` | `std::string` | — | +| `direction` | `NET_FW_RULE_DIRECTION` | `NET_FW_RULE_DIR_IN` | +| `protocol` | `long` | `0` | +| `localAddresses` / `remoteAddresses` | `std::string` | — | +| `localPorts` / `remotePorts` | `std::string` | — | +| `icmpTypesCodes` | `std::string` | — | +| `profileBitmask` | `long` | `0` | +| `serviceName` | `std::string` | — | + +### `WindowsFirewallRules` (type alias) +```cpp +using WindowsFirewallRules = std::vector; +``` + +### `renderWindowsFirewallRules()` +Converts a collection of `WindowsFirewallRule` structs into osquery `QueryData` for table output. + +## Usage Example + +```cpp +#include "windows_firewall_rules.h" + +WindowsFirewallRules rules; + +WindowsFirewallRule rule; +rule.name = "Block Inbound Telnet"; +rule.action = NET_FW_ACTION_BLOCK; +rule.enabled = true; +rule.direction = NET_FW_RULE_DIR_IN; +rule.protocol = 6; // TCP +rule.localPorts = "23"; +rules.push_back(rule); + +QueryData result = renderWindowsFirewallRules(rules); +``` \ No newline at end of file diff --git a/osquery/tables/sleuthkit/.sleuthkit.md b/osquery/tables/sleuthkit/.sleuthkit.md new file mode 100644 index 00000000000..23c04178dc0 --- /dev/null +++ b/osquery/tables/sleuthkit/.sleuthkit.md @@ -0,0 +1,50 @@ + +Osquery table implementation for forensic file system analysis using The Sleuth Kit (libtsk), providing raw device inspection capabilities for `device_file` and `device_hash` virtual tables. + +## Key Components + +### `DeviceHelper` (class) +A non-copyable RAII wrapper around TSK image/volume handles that manages device lifecycle and file iteration. + +- **`open()`** — Lazily opens the disk image and volume partition table using auto-detection +- **`partitions(predicate)`** — Iterates over all volume partitions, invoking a callback for each +- **`inodes(inodes, fs, predicate)`** — Resolves a set of inode number strings to `TskFsFile*` objects and invokes a callback +- **`generateFile(...)`** — Emits a single `Row` into `QueryData` with metadata (inode, uid, gid, mode, size, timestamps, type) +- **`generateFiles(...)`** — Recursively walks a directory tree up to 1024 stack frames and 10,240 entries, populating results with regular files +- **`resetStack()`** — Resets recursion depth counters and loop-detection state + +### `hashInode(file)` (free function) +Streams a TSK file in 4 KB chunks and computes MD5, SHA1, and SHA256 simultaneously, returning a `MultiHashes` struct. + +### `genDeviceFile(context)` (table generator) +Implements the `device_file` osquery table. Requires `device` and `partition` constraints; optionally filters by `path` or `inode`. + +### `genDeviceHash(context)` (table generator) +Implements the `device_hash` osquery table. Requires `device`, `partition`, and `inode` constraints; returns cryptographic hashes for the specified inode. + +### `kTSKTypeNames` (constant map) +Maps TSK metadata type enums to human-readable strings (`regular`, `directory`, `symlink`, etc.). + +## Usage Example + +```cpp +// Query device files via osquery SQL +// SELECT * FROM device_file WHERE device='/dev/sda' AND partition='1'; + +// Query file hashes for a specific inode +// SELECT md5, sha1, sha256 +// FROM device_hash +// WHERE device='/dev/sda' +// AND partition='1' +// AND inode='12345'; + +// Internal: open a device and iterate partitions +DeviceHelper dh("/dev/sda"); +dh.partitions([&](const TskVsPartInfo* part) { + auto* fs = new TskFsInfo(); + if (fs->open(part, TSK_FS_TYPE_DETECT) == 0) { + dh.generateFiles("1", fs, "/", results); + } + delete fs; +}); +``` \ No newline at end of file diff --git a/osquery/tables/system/.cpuid.md b/osquery/tables/system/.cpuid.md new file mode 100644 index 00000000000..6cf3a2a006a --- /dev/null +++ b/osquery/tables/system/.cpuid.md @@ -0,0 +1,31 @@ + +Implements the osquery `cpuid` table, querying CPU features, identification strings, and capability flags directly via the `CPUID` instruction on both Windows and Linux/macOS. + +## Key Components + +- **`kCPUFeatures`** — Static map of CPUID leaf values (`1`, `7`, `0x80000001`) to feature definitions, each specifying a name, output register (`eax`/`ebx`/`ecx`/`edx`), and bit position. Covers features such as `sse`, `avx`, `avx2`, `aes`, `vmx`, `sgx`, `nx`, `lm`, and more. + +- **`cpuid(eax, ecx, regs)`** — Cross-platform inline wrapper around the `CPUID` instruction. Uses `__cpuidex` on Windows (``) and inline `asm volatile` on POSIX systems. + +- **`genStrings(results)`** — Populates string-type CPU metadata rows: vendor ID, product name (from leaves `0x80000002`–`0x80000004`), optional hypervisor ID (leaf `0x40000000`), and processor serial number. + +- **`genFamily(results)`** — Extracts the CPU family identifier from leaf `1` (`eax` bits 8–11) and appends it as a hex string row. + +- **`genCPUID(context)`** — Main table generator. Calls `genStrings` and `genFamily`, then iterates `kCPUFeatures` to emit one row per feature with its `0`/`1` bit value, plus raw SGX capability data from leaf `0x12`. + +- **`FEATURE(name, reg, bit)`** — Convenience macro for constructing `FeatureDef` pairs inline. + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT feature, value, output_register, output_bit, input_eax FROM cpuid; + +// Example rows returned: +// feature="vendor", value="GenuineIntel" +// feature="product_name", value="Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz" +// feature="avx", value="1", output_register="ecx", output_bit="28" +// feature="aes", value="1", output_register="ecx", output_bit="25" +// feature="hypervisor", value="0", output_register="ecx", output_bit="31" +// feature="family", value="0600" +``` \ No newline at end of file diff --git a/osquery/tables/system/.efi_misc.md b/osquery/tables/system/.efi_misc.md new file mode 100644 index 00000000000..eefa2e2a182 --- /dev/null +++ b/osquery/tables/system/.efi_misc.md @@ -0,0 +1,39 @@ + +EFI device path type definitions, structs, and navigation macros for parsing UEFI `DevicePath` protocol structures used in EFI variable and boot entry inspection. + +## Key Components + +**Structs** +- `EFI_DEVICE_PATH_PROTOCOL` — Base node header containing `Type`, `SubType`, and a 2-byte `Length` field (packed, 1-byte aligned) +- `HARDDRIVE_DEVICE_PATH` — Extended device path node for hard drive partitions, including partition number, start/size offsets, GUID signature, and MBR/signature type metadata + +**Constants** +| Constant | Value | Purpose | +|---|---|---| +| `EFI_END_ENTIRE_DEVICE_PATH` | `0xff` | End-of-path type marker | +| `EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE` | `0xff` | End-of-path subtype marker | +| `EFI_END_INSTANCE_DEVICE_PATH` | `0x01` | End-of-instance subtype marker | +| `EFI_END_DEVICE_PATH_LENGTH` | `sizeof(EFI_DEVICE_PATH_PROTOCOL)` | Terminal node size | + +**Navigation Macros** +- `EfiDevicePathNodeLength(a)` — Decodes little-endian 2-byte length from a node +- `EfiNextDevicePathNode(a)` — Advances pointer to the next node in the path +- `EfiDevicePathType(a)` — Extracts the 7-bit type field +- `EfiIsDevicePathEnd(a)` — Tests for full end-of-path terminator +- `EfiIsDevicePathEndInstance(a)` — Tests for end-of-instance node + +## Usage Example + +```c +#include "efi_misc.h" + +void walk_device_path(EFI_DEVICE_PATH_PROTOCOL *node) { + while (!EfiIsDevicePathEnd(node)) { + uint8_t type = EfiDevicePathType(node); + // Process node by type/subtype... + node = EfiNextDevicePathNode(node); + } +} +``` + +> **Note:** Both structs use `#pragma pack(1)` to match the UEFI spec's byte-aligned memory layout. Ensure any buffer containing these structs is similarly aligned when reading from EFI variables. \ No newline at end of file diff --git a/osquery/tables/system/.hash.md b/osquery/tables/system/.hash.md new file mode 100644 index 00000000000..4bf300bd124 --- /dev/null +++ b/osquery/tables/system/.hash.md @@ -0,0 +1,34 @@ + +Implements the osquery `hash` table, which computes and returns MD5, SHA1, and SHA256 hashes for files queried by path or directory, with optional LRU-based in-memory caching. + +## Key Components + +- **`FileHashCache`** — Thread-safe LRU cache struct storing per-file hashes keyed by path. Invalidates entries when inode, mtime, or size changes. Evicts `kHashCacheEvictSize` (5) entries when the cache exceeds `hash_cache_max` (default: 500). +- **`statInvalid()`** — Inline helper that compares a fresh `stat` result against a cached entry to detect file modification. +- **`FileHashCache::load()`** — Static method managing cache reads, inserts, updates, and LRU heap maintenance under a write lock. +- **`genHashForFile()`** — Computes MD5/SHA1/SHA256 for a single file and appends a result row. Respects both the global LRU cache and the per-query `QueryContext` cache. +- **`expandFSPathConstraints()`** — Resolves glob/LIKE path patterns from query constraints into concrete file paths. +- **`genHashImpl()`** — Core table generator; handles `path` and `directory` constraint columns, iterating files and calling `genHashForFile()`. +- **`genHash()`** — Entry point; dispatches to namespace-aware generation if needed, otherwise runs with a GLOG logger. + +### Flags + +| Flag | Default | Description | +|---|---|---| +| `disable_hash_cache` | `false` | Skip LRU cache; use only per-query cache | +| `hash_cache_max` | `500` | Max LRU cache entries | +| `hash_delay` | `20` | Millisecond delay after each hash (hidden) | + +## Usage Example + +```cpp +// Query via osquery SQL: +// SELECT path, md5, sha1, sha256 FROM hash WHERE path = '/etc/passwd'; +// SELECT path, md5 FROM hash WHERE directory = '/etc'; + +// Internal call path: +QueryContext context; +// context.constraints["path"] populated by SQL engine +QueryData results = osquery::tables::genHash(context); +// results contains rows with: path, directory, md5, sha1, sha256 +``` \ No newline at end of file diff --git a/osquery/tables/system/.npm_packages.md b/osquery/tables/system/.npm_packages.md new file mode 100644 index 00000000000..21d975c4003 --- /dev/null +++ b/osquery/tables/system/.npm_packages.md @@ -0,0 +1,32 @@ + +Enumerates installed npm (Node.js) packages by scanning `package.json` manifests in standard node_modules directories across Linux, macOS, and Windows platforms, exposing the results as an osquery virtual table. + +## Key Components + +- **`kNodeModulesPath`** — Platform-specific set of default npm library root paths (e.g., `/usr/local/lib`, `/usr/lib`, `%APPDATA%\Roaming\npm` on Windows). +- **`kPackageKeys`** — Fields extracted directly from `package.json`: `name`, `version`, `description`, `homepage`. +- **`kWinNodeInstallKey`** — Windows registry key used to locate the Node.js installation path (`SOFTWARE\Node.js\InstallPath`). +- **`genNodePackage(file, row, logger)`** — Reads and parses a single `package.json` file, populating a result row. Handles both current and deprecated `license` schemas (string vs. object) and both string and object forms of `author`. +- **`genNodeSiteDirectories(site, results, logger)`** — Resolves all `node_modules/%/package.json` patterns under a given directory and collects package rows. +- **`genWinNodePackages(keyGlob, results, logger)`** — Windows-only: queries the registry for Node.js install paths and delegates to `genNodeSiteDirectories`. +- **`genNodePackagesImpl(context, logger)`** — Core query logic; resolves paths from constraints or defaults, dispatches to Windows registry enumeration when needed. +- **`genNodePackages(context)`** — osquery table entry point; routes to namespace-aware generation or standard GLOG-logged execution. + +## Usage Example + +```cpp +// Query from osquery SQL shell: +// SELECT name, version, author, license, directory FROM npm_packages; + +// Constrained query to a specific directory: +// SELECT * FROM npm_packages WHERE directory = '/usr/local/lib'; + +// Internally, genNodePackage populates a Row from package.json: +Row r; +GLOGLogger logger; +genNodePackage("/usr/local/lib/node_modules/lodash/package.json", r, logger); +// r["name"] => "lodash" +// r["version"] => "4.17.21" +// r["license"] => "MIT" +// r["author"] => "John-David Dalton" +``` \ No newline at end of file diff --git a/osquery/tables/system/.python_packages.md b/osquery/tables/system/.python_packages.md new file mode 100644 index 00000000000..7a249aa3115 --- /dev/null +++ b/osquery/tables/system/.python_packages.md @@ -0,0 +1,39 @@ + +Implements the osquery `python_packages` virtual table, which discovers and enumerates installed Python packages across system-wide, user-specific, and platform-specific locations on Linux, macOS, and Windows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kPythonPath` | `const set` | Cross-platform glob patterns for common system-level Python site/dist-packages directories | +| `kDarwinPythonPath` | `const set` | macOS-specific framework Python installation paths | +| `kUserDirectoryPaths` | `const set` | User-level Python directory patterns (e.g., `.pyenv`, `.local/lib`) | +| `kWinPythonInstallKey` | `const string` | Windows registry key glob for Python install locations | +| `UserPath` | `struct` | Tagged union holding either an `int64` user ID or a `string` path value | +| `genPackage` | `function` | Parses a `METADATA` or `PKG-INFO` file to extract name, version, summary, author, and license fields | +| `genSiteDirectories` | `function` | Iterates a site-packages directory and calls `genPackage` for each `.dist-info` or `.egg-info` subdirectory | +| `traverseVersions` | `function` | Walks a versioned Python install directory, skipping symlinks, and resolves `site-packages` paths | +| `listWinPythonPaths` | `function` | Queries the Windows registry (WIN32 only) for Python install paths via registry glob expansion | +| `getUserPathList` | `function` | Builds per-user path lists from `usersFromContext`, handling Linux, macOS, and Windows user directory conventions | +| `genPythonPackagesImpl` | `function` | Core implementation: resolves all paths, enumerates packages, and handles namespace-aware logging | +| `genPythonPackages` | `function` | Public table entry point; dispatches to namespace-isolated or direct execution | + +## Usage Example + +Query from the osquery shell: + +```sql +-- All system-wide Python packages +SELECT name, version, summary, author, license, directory +FROM python_packages; + +-- Filter to a specific site-packages directory +SELECT name, version +FROM python_packages +WHERE directory = '/usr/local/lib/python3.11/site-packages'; + +-- Per-user installed packages +SELECT name, version, uid +FROM python_packages +WHERE uid != 0; +``` \ No newline at end of file diff --git a/osquery/tables/system/.smbios_utils.md b/osquery/tables/system/.smbios_utils.md new file mode 100644 index 00000000000..360c0cce728 --- /dev/null +++ b/osquery/tables/system/.smbios_utils.md @@ -0,0 +1,62 @@ + +Cross-platform utility header for parsing SMBIOS/DMI firmware tables within the osquery framework, providing structures, lookup maps, and helper functions to extract system hardware information (memory, processor, BIOS, etc.) from raw SMBIOS data. + +## Key Components + +### Structures +- **`SMBStructHeader`** — Packed struct representing a generic SMBIOS structure header (`type`, `length`, `handle`) +- **`DMIEntryPoint`** — Packed struct for the legacy DMI entry point containing table address, length, and structure count + +### Constants +- **`kSMBIOSType*`** — Constexpr type codes (`kSMBIOSTypeBIOS=0`, `kSMBIOSTypeProcessor=4`, `kSMBIOSTypeMemoryDevice=17`, etc.) +- **`kSMBIOS*Table`** — Extern lookup maps translating SMBIOS enum byte values to human-readable strings (form factor, memory type, error correction, etc.) per DMTF spec DSP0134 v3.1.1 + +### Classes +- **`SMBIOSParser`** — Abstract base class (non-copyable) providing a `tables()` method that walks SMBIOS data and applies a predicate callback; subclasses must populate `table_data_` and `table_size_` + +### Helper Functions + +| Function | Purpose | +|---|---| +| `genSMBIOSTable` | Generates rows for generic SMBIOS tables | +| `genSMBIOSMemoryDevices` | Parses memory device (type 17) entries | +| `genSMBIOSMemoryArrays` | Parses memory array (type 16) entries | +| `genSMBIOSMemoryErrorInfo` | Parses memory error information (type 18) | +| `genSMBIOSProcessor` | Parses processor (type 4) entries | +| `genSMBIOSOEMStrings` | Parses OEM string (type 11) entries | +| `dmiString` | Extracts a null-terminated string from stacked SMBIOS string data by index | +| `dmiBitFieldToStr` | Converts a bitfield value to a string using a lookup map | + +## Usage Example + +```c +// Implement SMBIOSParser in a platform-specific subclass +class LinuxSMBIOSParser : public SMBIOSParser { + public: + bool load() { + // populate table_data_ and table_size_ + return table_data_ != nullptr; + } +}; + +// Walk tables and generate query results +LinuxSMBIOSParser parser; +if (parser.load()) { + QueryData results; + parser.tables([&results](size_t index, + const SMBStructHeader* hdr, + uint8_t* address, + uint8_t* textAddrs, + size_t size) { + if (hdr->type == kSMBIOSTypeMemoryDevice) { + genSMBIOSMemoryDevices(index, hdr, address, textAddrs, size, results); + } + }); +} + +// Extract a string from SMBIOS stacked strings +std::string vendor = dmiString(textAddrs, 1, size); + +// Decode a bitfield (e.g., memory details) +std::string details = dmiBitFieldToStr(0x0006, kSMBIOSMemoryDetailsTable); +``` \ No newline at end of file diff --git a/osquery/tables/system/.ssh_configs.md b/osquery/tables/system/.ssh_configs.md new file mode 100644 index 00000000000..b457241863c --- /dev/null +++ b/osquery/tables/system/.ssh_configs.md @@ -0,0 +1,43 @@ + +Parses SSH client configuration files (`~/.ssh/config` and system-wide `ssh_config`) into structured query results, supporting both per-user and global SSH configurations across Linux, macOS, and Windows platforms. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kUserSshConfig` | `const string` | Relative path to user SSH config (`.ssh/config`) | +| `kSystemwideSshConfig` | `const string` | System-wide SSH config path for POSIX (`/etc/ssh/ssh_config`) | +| `kWindowsSystemwideSshConfig` | `const string` | System-wide SSH config path for Windows (`\ProgramData\ssh\ssh_config`) | +| `genSshConfig` | `function` | Reads and parses a single SSH config file into rows | +| `genSshConfigForUser` | `function` | Resolves a user's SSH config path and delegates to `genSshConfig` | +| `getSshConfigs` | `function` | Entry point — iterates all users and appends system-wide config results | + +## Parsing Logic + +Each SSH config file is parsed line by line into `Host`/`Match` blocks. Lines beginning with `host` or `match` define a new block context; all subsequent non-comment, non-empty lines are recorded as options belonging to that block. + +**Output row schema:** + +| Column | Value | +|---|---| +| `uid` | User ID owning the config | +| `block` | Active `Host`/`Match` block header | +| `option` | Configuration option line (lowercased) | +| `ssh_config_file` | Absolute path to the source file | + +## Usage Example + +```cpp +// Called internally by osquery's table infrastructure +QueryContext context; +QueryData results = getSshConfigs(context); + +for (const auto& row : results) { + // row["uid"] → e.g. "1000" + // row["block"] → e.g. "host github.com" + // row["option"] → e.g. "identityfile ~/.ssh/id_rsa" + // row["ssh_config_file"] → e.g. "/home/user/.ssh/config" +} +``` + +> **Note:** Lines are normalized to lowercase before storage. Comments (`#`) and blank lines are silently skipped. System-wide config is always queried with `uid`/`gid` set to `"0"`. \ No newline at end of file diff --git a/osquery/tables/system/.ssh_keys.md b/osquery/tables/system/.ssh_keys.md new file mode 100644 index 00000000000..f16a1fb1916 --- /dev/null +++ b/osquery/tables/system/.ssh_keys.md @@ -0,0 +1,41 @@ + +Enumerates SSH private keys found in user home directories (`~/.ssh`), parsing each file with OpenSSL to extract key metadata including type, length, security bits, and encryption status. + +## Key Components + +### Constants +- `kSSHUserKeysDir` — Subdirectory name (`.ssh`) searched within each user's home folder +- `kOpenSshHeader` — PEM header string identifying OpenSSH format keys +- `kOpenSshUnencryptedPrefix` — Base64 magic prefix used to detect unencrypted OpenSSH ed25519 keys + +### Functions + +| Function | Description | +|---|---| +| `isOpenSSHKey()` | Returns `true` if the file content matches the OpenSSH private key header | +| `isOpenSSHKeyEncrypted()` | Detects encryption by comparing the base64 prefix after the OpenSSH header | +| `parsePrivateKey()` | Attempts PEM parsing via OpenSSL; falls back to OpenSSH header detection; populates key type, length, security bits, and encryption flag | +| `keyTypeAsString()` | Maps OpenSSL `EVP_PKEY_*` integer constants to human-readable strings (`rsa`, `dsa`, `ec`, etc.) | +| `genSSHkeyForHosts()` | Lists files in a user's `.ssh` directory, parses each as a private key, and appends valid results to `QueryData` | +| `getUserSshKeysImpl()` | Iterates all users from query context and calls `genSSHkeyForHosts()` for each | +| `getUserSshKeys()` | Entry point; dispatches to namespace-aware generation or standard execution | + +## Usage Example + +This table is queried via osquery's SQL interface: + +```sql +SELECT uid, path, encrypted, key_type, key_group_name, key_length, key_security_bits +FROM user_ssh_keys; +``` + +Example output row: + +```text +uid | path | encrypted | key_type | key_length | key_security_bits +-----|----------------------------|-----------|----------|------------|------------------ +1001 | /home/alice/.ssh/id_rsa | 0 | rsa | 4096 | 140 +1001 | /home/alice/.ssh/id_ed25519| 1 | (none) | -1 | -1 +``` + +> **Note:** OpenSSH-format keys (ed25519, newer RSA) are detected via header/prefix heuristics since OpenSSL cannot parse them directly. Key length and security bits will be `-1` for such keys. \ No newline at end of file diff --git a/osquery/tables/system/.system_utils.md b/osquery/tables/system/.system_utils.md new file mode 100644 index 00000000000..c881c3304aa --- /dev/null +++ b/osquery/tables/system/.system_utils.md @@ -0,0 +1,56 @@ + +Utility header providing context-aware helper functions for resolving users and process IDs within osquery table implementations. + +## Key Components + +| Function | Description | +|---|---| +| `usersFromContext` | Resolves users from a query context; defaults to current user if none specified | +| `pidsFromContext` | Resolves process IDs from a query context; returns all PIDs by default | + +Both functions return a `QueryData` set of rows suitable for direct use in table implementations. + +## Usage Example + +```c +#include + +namespace osquery { +namespace tables { + +QueryData genUserFiles(QueryContext& context) { + QueryData results; + + // Resolve users from context (or current user if none provided) + QueryData users = usersFromContext(context); + + for (const auto& user : users) { + Row r; + r["username"] = user.at("username"); + results.push_back(r); + } + return results; +} + +QueryData genProcessInfo(QueryContext& context) { + // Returns all PIDs by default, or filtered PIDs if context specifies + QueryData pids = pidsFromContext(context); + + QueryData results; + for (const auto& pid : pids) { + Row r; + r["pid"] = pid.at("pid"); + results.push_back(r); + } + return results; +} + +} // namespace tables +} // namespace osquery +``` + +## Notes + +- `usersFromContext` defaults `all = false` — returns only the current user when no user constraint is present in the query. +- `pidsFromContext` defaults `all = true` — enumerates all running processes unless a specific PID constraint is provided. +- Both helpers simplify writing constraint-aware table generators by abstracting context parsing logic. \ No newline at end of file diff --git a/osquery/tables/system/.uptime.md b/osquery/tables/system/.uptime.md new file mode 100644 index 00000000000..70fc32b01e1 --- /dev/null +++ b/osquery/tables/system/.uptime.md @@ -0,0 +1,36 @@ + +Implements the `uptime` osquery virtual table, querying system uptime and decomposing it into human-readable time components. + +## Key Components + +- **`genUptime(QueryContext& context)`** — Table generator function that retrieves raw system uptime via `getUptime()` and populates a result row with broken-down time fields. + +| Column | Type | Description | +|---|---|---| +| `days` | INTEGER | Full days elapsed | +| `hours` | INTEGER | Remaining hours (0–23) | +| `minutes` | INTEGER | Remaining minutes (0–59) | +| `seconds` | INTEGER | Remaining seconds (0–59) | +| `total_seconds` | BIGINT | Total uptime in seconds | + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM uptime; +// +// Example output: +// days=1, hours=4, minutes=32, seconds=17, total_seconds=102737 + +QueryData results = genUptime(context); +if (!results.empty()) { + auto& row = results[0]; + std::cout << "Uptime: " + << row["days"] << "d " + << row["hours"] << "h " + << row["minutes"] << "m " + << row["seconds"] << "s\n"; +} +``` + +> If `getUptime()` returns a negative value (e.g., on retrieval failure), the function returns an empty `QueryData` with no rows inserted. \ No newline at end of file diff --git a/osquery/tables/system/.user_groups.md b/osquery/tables/system/.user_groups.md new file mode 100644 index 00000000000..d2c3e57df42 --- /dev/null +++ b/osquery/tables/system/.user_groups.md @@ -0,0 +1,43 @@ + +Provides cross-platform utilities for retrieving and mapping Unix user-to-group memberships, used within osquery's table generation infrastructure. + +## Key Components + +### Macro +- **`EXPECTED_GROUPS_MAX`** — Default buffer size (`64`) for group list allocation on non-Apple platforms. + +### Struct +- **`user_t`** — Generic struct holding a user's `name`, `uid`, and `gid`. Templated to support both `uid_t`/`gid_t` and broader integer types. + +### Functions + +- **`addGroupsToResults()`** — Templated helper that iterates over a group array and appends `{uid, gid}` rows into a `QueryData` result set. + +- **`getGroupsForUser()`** — Core logic for resolving all groups a user belongs to via `getgrouplist()`. Platform-specific behavior: + - **macOS**: Uses `getgroupcount()` (exported from `libSystem.B` since 10.6) to pre-allocate the exact buffer size. + - **Linux/Other**: Starts with a `EXPECTED_GROUPS_MAX` stack buffer; falls back to a heap-allocated buffer if the user belongs to more groups than the initial size allows. + +## Usage Example + +```c +#include "user_groups.h" + +// Resolve groups for a known user entry +struct passwd* pw = getpwnam("alice"); +if (pw != nullptr) { + osquery::tables::user_t user{ + pw->pw_name, + pw->pw_uid, + pw->pw_gid + }; + + osquery::QueryData results; + osquery::tables::getGroupsForUser(results, user); + + for (const auto& row : results) { + // Each row contains: uid, gid + } +} +``` + +> **Note:** On Linux, GLIBC versions prior to 2.3.3 may have a buffer overrun in `getgrouplist`. The fallback heap allocation path mitigates this safely. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.acpi_tables.md b/osquery/tables/system/darwin/.acpi_tables.md new file mode 100644 index 00000000000..909b3e9d8e5 --- /dev/null +++ b/osquery/tables/system/darwin/.acpi_tables.md @@ -0,0 +1,42 @@ + +Implements the `acpi_tables` osquery table for macOS, querying the IOKit `AppleACPIPlatformExpert` service to enumerate and return ACPI table metadata. + +## Key Components + +- **`kIOACPIClassName_`** — IOKit service class name (`"AppleACPIPlatformExpert"`) used to locate the ACPI platform expert. +- **`kIOACPIPropertyName_`** — IOKit property key (`"ACPI Tables"`) identifying the ACPI table dictionary on the matched service. +- **`genACPITable(key, value, results)`** — CFDictionary callback that extracts a single ACPI table entry, populating `name`, `size` (bytes), and `md5` hash into a `Row` appended to the result set. +- **`genACPITables(context)`** — Primary table generator; matches the ACPI IOKit service, retrieves the `CFDictionary` of tables, and applies `genACPITable` over each entry. + +## Usage Example + +This file is consumed internally by the osquery table registration system. The equivalent SQL query exposed to users is: + +```sql +SELECT name, size, md5 +FROM acpi_tables; +``` + +Typical output: + +```text +name | size | md5 +-------|-------|---------------------------------- +DSDT | 98304 | d41d8cd98f00b204e9800998ecf8427e +FACP | 116 | a87ff679a2f3e71d9181a67b7542122c +``` + +## Data Flow + +```mermaid +graph TD + A[IOServiceMatching] --> B[AppleACPIPlatformExpert] + B --> C[IORegistryEntryCreateCFProperty] + C --> D["CFDictionary: ACPI Tables"] + D --> E[CFDictionaryApplyFunction] + E --> F["genACPITable callback (per entry)"] + F --> G["Row: name, size, md5"] + G --> H[QueryData results] +``` + +> **Platform:** macOS only. Requires IOKit access; the table returns empty if no ACPI platform expert service is found or if the property is unavailable. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.ad_config.md b/osquery/tables/system/darwin/.ad_config.md new file mode 100644 index 00000000000..edfa5e44e40 --- /dev/null +++ b/osquery/tables/system/darwin/.ad_config.md @@ -0,0 +1,30 @@ + +Parses macOS Active Directory configuration plist files to extract AD domain settings, trust options, and module options as structured query results. + +## Key Components + +- **`kADConfigPath`** — Constant path to the macOS AD configuration directory (`/Library/Preferences/OpenDirectory/Configurations/Active Directory/`) +- **`genADConfig(path, results)`** — Internal helper that parses a single AD plist file via `SQL::selectAllFrom("plist", ...)`, extracts the trust domain, then iterates config rows to collect options across three key categories: + - `trustoptions`, `trustkerberosprincipal`, `trustaccount`, `trusttype` — Trust-related keys + - `options` — General AD options (subkey used as option name) + - `module options` — AD module settings (strips `ActiveDirectory/` prefix; skips `managed client template` entries) +- **`genADConfig(QueryContext&)`** — osquery table entry point that enumerates all plist files under `kADConfigPath` and delegates to the helper for each + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT domain, name, option, value FROM ad_config; + +// Each returned row contains: +// domain → e.g. "corp.example.com" +// name → config file key (top-level identifier) +// option → e.g. "trust domain", "DomainNameDNS", "useForAuthentication" +// value → the resolved string value for that option +``` + +## Notes + +- macOS-only; reads from `/Library/Preferences/OpenDirectory/Configurations/Active Directory/` +- Returns an empty result set if no AD domain is configured or plist parsing fails +- Supports multiple domain configurations by iterating all files in the config directory \ No newline at end of file diff --git a/osquery/tables/system/darwin/.asl.md b/osquery/tables/system/darwin/.asl.md new file mode 100644 index 00000000000..c66dcef35e1 --- /dev/null +++ b/osquery/tables/system/darwin/.asl.md @@ -0,0 +1,28 @@ + +Implements the `asl` osquery virtual table, querying the macOS Apple System Log (ASL) facility to retrieve system log entries. + +## Key Components + +- **`genAsl(QueryContext& context)`** — Table generation function that builds an ASL query from the provided `QueryContext`, executes it via `asl_search`, iterates over matching log rows, and returns the collected results as `QueryData`. + +## Usage Example + +```cpp +// Called internally by the osquery table framework when a query targets the `asl` table. +// Example SQL usage within osquery: +// SELECT time, sender, message FROM asl WHERE level <= 3; + +QueryContext context; +QueryData rows = genAsl(context); + +for (const auto& row : rows) { + // Each row contains ASL fields: time, message, sender, facility, level, etc. +} +``` + +## Notes + +- Depends on helper utilities in `asl_utils.h` (`createAslQuery`, `readAslRow`) for query construction and row parsing. +- The macOS ASL API (`asl_search`, `asl_next`, `asl_release`) is **deprecated as of macOS 10.12** (Sierra); Clang deprecation warnings are suppressed via `_Pragma` directives to allow continued compilation. +- Resources are explicitly released via `asl_release` on both the result set and the query object to prevent memory leaks. +- This table is **Darwin-only** and will not compile on other platforms. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.asl_utils.md b/osquery/tables/system/darwin/.asl_utils.md new file mode 100644 index 00000000000..3bd518f6117 --- /dev/null +++ b/osquery/tables/system/darwin/.asl_utils.md @@ -0,0 +1,39 @@ + +Utility header for bridging osquery query constraints with Apple System Log (ASL) API operations on macOS. + +## Key Components + +### Compatibility Macros +- **`OLD_ASL_API`** — Defined when `ASL_API_VERSION` is absent; enables inline wrappers (`asl_release`, `asl_next`) that map deprecated ASL API calls to their modern equivalents + +### Functions + +| Function | Description | +|---|---| +| `addQueryOp()` | Appends a single constraint operation (ANDed) to an ASL query, converting osquery `ConstraintOperator` and `ColumnType` to ASL equivalents | +| `createAslQuery()` | Builds a complete `aslmsg` query object from an osquery `QueryContext` | +| `readAslRow()` | Reads fields from an ASL message row and populates an osquery `Row` | +| `convertLikeRegex()` | Translates a SQL `LIKE`-style pattern string into a regex for ASL matching | + +## Usage Example + +```c +// Build an ASL query from an osquery context and iterate results +aslmsg query = createAslQuery(context); + +asl_object_t client = asl_open(nullptr, nullptr, 0); +aslresponse response = asl_search(client, query); + +aslmsg row; +while ((row = asl_next(response)) != nullptr) { + Row r; + readAslRow(row, r); + results.push_back(r); +} + +asl_release(response); +asl_release(query); +asl_close(client); +``` + +> **Note:** This header is macOS-only and depends on ``. The `OLD_ASL_API` compatibility shim handles pre-10.12 macOS SDK differences where `asl_free`/`aslresponse_free` were used instead of the unified `asl_release`. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.block_devices.md b/osquery/tables/system/darwin/.block_devices.md new file mode 100644 index 00000000000..486485b80a6 --- /dev/null +++ b/osquery/tables/system/darwin/.block_devices.md @@ -0,0 +1,49 @@ + +Enumerates macOS block devices by querying the IOKit `IOMedia` service and Disk Arbitration framework, populating an osquery table with device metadata such as UUID, name, size, vendor, model, and type. + +## Key Components + +- **`genIOMediaDevice(device, whole_devices, results)`** — Processes a single `IOMedia` service entry, extracting properties via IOKit and Disk Arbitration. Resolves parent-child relationships between whole disks and their partitions using the `Whole` property. +- **`genBlockDevs(context)`** — Entry point for the `block_devices` osquery table. Iterates all `IOMedia` services via `IOServiceGetMatchingServices` and delegates per-device processing to `genIOMediaDevice`. + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM block_devices; + +// Internally, the table generator follows this flow: +QueryData results; + +// 1. Match all IOMedia services +auto matching = IOServiceMatching("IOMedia"); + +// 2. Iterate matched devices +io_iterator_t it; +IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &it); + +io_service_t device; +std::vector whole_devices; +while ((device = IOIteratorNext(it))) { + // 3. Per device: reads IOKit properties + Disk Arbitration details + genIOMediaDevice(device, whole_devices, results); + IOObjectRelease(device); +} + +// Results contain rows with fields: +// uuid, name, parent, label, block_size, size, vendor, model, type +``` + +## Table Columns Populated + +| Column | Source | +|---|---| +| `uuid` | IOKit `UUID` property | +| `name` | IOKit `BSD Name` (prefixed `/dev/`) | +| `parent` | Derived from whole-disk matching | +| `label` | `IORegistryEntryGetName` | +| `block_size` | IOKit `Preferred Block Size` | +| `size` | IOKit `Size` / `Preferred Block Size` | +| `vendor` | Disk Arbitration `DADeviceVendor` | +| `model` | Disk Arbitration `DADeviceModel` | +| `type` | Disk Arbitration `DADeviceProtocol` | \ No newline at end of file diff --git a/osquery/tables/system/darwin/.cpu_info.md b/osquery/tables/system/darwin/.cpu_info.md new file mode 100644 index 00000000000..aa3e7bc5d7d --- /dev/null +++ b/osquery/tables/system/darwin/.cpu_info.md @@ -0,0 +1,41 @@ + +Provides CPU information for macOS systems, supporting both Intel (via SMBIOS parsing) and Apple Silicon AArch64 architectures through a unified query interface. + +## Key Components + +| Function | Description | +|---|---| +| `genCpuInfo(context)` | Entry point; dispatches to architecture-specific implementation at compile time | +| `genIntelCpuInfo(context)` | Queries CPU data via `DarwinSMBIOSParser`, decorates rows with friendly `device_id` names | +| `genAarch64CpuInfo(context)` | Queries Apple Silicon CPU data via `sysctl` and IOKit, populates model, core counts, and clock speed | +| `getAarch64MaxCPUFreq()` | Reads `voltage-states5-sram` from the `pmgr` IOKit device to determine the P-core max frequency in Hz | +| `getHybridCoresNumber()` | Reads `p-core-count` and `e-core-count` from the `cpus` IOKit device, returning efficiency/performance core counts | + +## Usage Example + +```cpp +// Called internally by osquery's table infrastructure +QueryContext context; +QueryData results = osquery::tables::genCpuInfo(context); + +for (const auto& row : results) { + // Access fields populated per architecture + // Intel: processor_type, device_id, manufacturer, model, ... + // AArch64: device_id ("CPU0"), model, manufacturer ("Apple"), + // number_of_cores, logical_processors, max_clock_speed, + // number_of_efficiency_cores, number_of_performance_cores +} +``` + +## Architecture Dispatch + +```mermaid +graph TD + A["genCpuInfo()"] --> B{"__aarch64__?"} + B -->|Yes| C["genAarch64CpuInfo()"] + B -->|No| D["genIntelCpuInfo()"] + C --> E["sysctl + IOKit pmgr/cpus"] + D --> F["DarwinSMBIOSParser"] +``` + +> **Note:** AArch64 max clock speed is derived from P-core voltage state entries (little-endian `uint32_t` Hz values), then converted to MHz for the `max_clock_speed` column. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.cpu_time.md b/osquery/tables/system/darwin/.cpu_time.md new file mode 100644 index 00000000000..6d4a2c7a47b --- /dev/null +++ b/osquery/tables/system/darwin/.cpu_time.md @@ -0,0 +1,36 @@ + +Implements the `cpu_time` osquery table for macOS, querying per-core CPU time statistics using the Mach kernel API. Inspired by [psutil's `per_cpu_times`](https://github.com/giampaolo/psutil/blob/ec1d35e41c288248818388830f0e4f98536b93e4/psutil/_psutil_osx.c#L739). + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `ticks_to_usecs` | Static helper | Converts raw CPU clock ticks to microseconds using `CLOCKS_PER_SEC` | +| `genCpuTime` | Table generator | Queries `host_processor_info` for per-core CPU states and returns results | + +**Reported columns per core:** + +| Column | CPU State | +|---|---| +| `core` | Core index | +| `user` | Time in user mode (µs) | +| `idle` | Time idle (µs) | +| `system` | Time in kernel/system mode (µs) | +| `nice` | Time in niced user processes (µs) | + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT core, user, system, idle, nice FROM cpu_time; + +// Internally, genCpuTime populates one Row per CPU core: +Row r; +r["core"] = INTEGER(core); +r["user"] = BIGINT(ticks_to_usecs(processor_times[core].cpu_ticks[CPU_STATE_USER])); +r["system"] = BIGINT(ticks_to_usecs(processor_times[core].cpu_ticks[CPU_STATE_SYSTEM])); +r["idle"] = BIGINT(ticks_to_usecs(processor_times[core].cpu_ticks[CPU_STATE_IDLE])); +r["nice"] = BIGINT(ticks_to_usecs(processor_times[core].cpu_ticks[CPU_STATE_NICE])); +``` + +> **Platform:** macOS only. Uses `mach_host_self()` + `host_processor_info()` with `PROCESSOR_CPU_LOAD_INFO`. Allocated processor info memory is released via `vm_deallocate` after use. Returns an empty result set if `host_processor_info` fails. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.crashes.md b/osquery/tables/system/darwin/.crashes.md new file mode 100644 index 00000000000..01ddd062aad --- /dev/null +++ b/osquery/tables/system/darwin/.crashes.md @@ -0,0 +1,38 @@ + +Implements the `crashes` osquery table for macOS, parsing application and mobile crash logs from both legacy `.crash` text format and modern `.ips` JSON format (macOS 12+). + +## Key Components + +### Constants +- `kIntelRegisters` — Set of x86/x64 register names extracted from crash logs +- `kCrashDumpKeys` — Mapping of `.crash` file field names to osquery column names +- `kDiagnosticReportsPath` — System-level crash log directory (`/Library/Logs/DiagnosticReports`) +- `kMobileDiagnosticReportsPath` — Mobile device crash log directory + +### Functions + +| Function | Description | +|---|---| +| `readCrashDumpJSON()` | Parses macOS 12+ `.ips` files containing two concatenated JSON objects (header + content) | +| `readCrashDump()` | Parses legacy `.crash` text files line-by-line, extracting fields, registers, and stack traces | +| `genCrashLogs()` | Table generator; walks system and per-user crash log directories, dispatching to the appropriate parser | + +## Usage Example + +```cpp +// Querying the table via osquery SQL: +// SELECT pid, path, exception_type, crashed_thread, stack_trace +// FROM crashes +// WHERE uid = 501; + +// Internally, genCrashLogs() resolves per-user directories and +// iterates both .crash and .ips files: +QueryData results = genCrashLogs(context); + +// Each Row 'r' is populated by either: +readCrashDump("/Library/Logs/DiagnosticReports/MyApp_2024-01-01.crash", r); +// or for macOS 12+: +readCrashDumpJSON("/Library/Logs/DiagnosticReports/MyApp_2024-01-01.ips", r); +``` + +> **Note:** `.crash` files use a colon-delimited key-value format with inline register states and threaded stack traces. `.ips` files contain two back-to-back JSON objects; the second holds the primary diagnostic content. Mobile device logs are discovered by enumerating subdirectories under each user's `MobileDevice` path. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.cups_destinations.md b/osquery/tables/system/darwin/.cups_destinations.md new file mode 100644 index 00000000000..7a27f8a2fc1 --- /dev/null +++ b/osquery/tables/system/darwin/.cups_destinations.md @@ -0,0 +1,48 @@ + +Implements an osquery table that enumerates CUPS (Common Unix Printing System) printer destinations and their configuration options using the CUPS API. + +## Key Components + +### `CupsDestinations` (class) +RAII wrapper around the CUPS destination list API. + +| Member | Description | +|---|---| +| `destination_list` | Raw pointer to array of `cups_dest_t` structs | +| `num_destinations` | Count of discovered printer destinations | +| `begin()` / `end()` | Iterator support for range-based `for` loops | + +Constructor calls `cupsGetDests()` on initialization; destructor calls `cupsFreeDests()` to prevent memory leaks. + +### `genCupsDestinations(QueryContext&)` (function) +Table generator function that queries all available CUPS destinations and flattens their options into rows. + +**Columns returned:** + +| Column | Description | +|---|---| +| `name` | Printer/destination name | +| `option_name` | Configuration option key (empty if no options) | +| `option_value` | Configuration option value (empty if no options) | + +## Usage Example + +```cpp +// Called internally by the osquery table infrastructure. +// Equivalent SQL query: +// SELECT * FROM cups_destinations; + +// Destinations with no options emit a single row with only 'name'. +// Destinations with options emit one row per option: +// name | option_name | option_value +// -------------+-------------------+-------------- +// Brother_HL | printer-is-shared | true +// Brother_HL | copies | 1 +// PDF_Printer | | +``` + +## Notes + +- macOS/Linux only — requires libcups at link time +- Each option key-value pair for a destination produces a separate row (one-to-many expansion) +- Destinations with zero options still appear in results with empty `option_name`/`option_value` fields \ No newline at end of file diff --git a/osquery/tables/system/darwin/.cups_jobs.md b/osquery/tables/system/darwin/.cups_jobs.md new file mode 100644 index 00000000000..b4dd0b5d429 --- /dev/null +++ b/osquery/tables/system/darwin/.cups_jobs.md @@ -0,0 +1,48 @@ + +Provides an osquery table implementation that queries CUPS (Common Unix Printing System) print jobs and exposes them as structured query results. + +## Key Components + +### `CupsJobs` Class +A RAII wrapper around the CUPS API for safe job list management: + +| Member | Description | +|---|---| +| `job_list` | Pointer to the CUPS job array returned by `cupsGetJobs()` | +| `num_jobs` | Total number of jobs retrieved | +| `begin()` / `end()` | Iterator support for range-based `for` loops | + +Constructor calls `cupsGetJobs()` with `CUPS_WHICHJOBS_ALL` to fetch all jobs (pending, active, and completed). Destructor calls `cupsFreeJobs()` to release memory. + +### `genCupsJobs(QueryContext&)` +The osquery table generator function. Iterates over all CUPS jobs and maps each to a `Row` with the following columns: + +| Column | Type | Source Field | +|---|---|---| +| `title` | TEXT | `job.title` | +| `destination` | TEXT | `job.dest` | +| `user` | TEXT | `job.user` | +| `format` | TEXT | `job.format` | +| `size` | INTEGER | `job.size` | +| `completed_time` | INTEGER | `job.completed_time` | +| `processing_time` | INTEGER | `job.processing_time` | +| `creation_time` | INTEGER | `job.creation_time` | + +## Usage Example + +```cpp +// Querying via osquery SQL interface +QueryContext ctx; +QueryData results = genCupsJobs(ctx); + +for (const auto& row : results) { + // Access print job fields + std::string title = row.at("title"); + std::string user = row.at("user"); +} +``` + +```bash +# Equivalent osquery shell query +osqueryi "SELECT title, user, destination, size FROM cups_jobs;" +``` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.extended_attributes.md b/osquery/tables/system/darwin/.extended_attributes.md new file mode 100644 index 00000000000..180683042ee --- /dev/null +++ b/osquery/tables/system/darwin/.extended_attributes.md @@ -0,0 +1,34 @@ + +Implements the `extended_attributes` osquery table for macOS, querying extended filesystem attributes (xattrs) from files and directories — including special handling for Apple quarantine metadata and `kMDItemWhereFroms` (download origin) attributes. + +## Key Components + +### Constants +- `kMetadataXattr` — xattr name for `com.apple.metadata:kMDItemWhereFroms` (download source URLs) +- `kQuarantineXattr` — xattr name for `com.apple.quarantine` +- `kQuarantineKeys` — mapping of table column names to `LSQuarantine*` property keys + +### Functions + +| Function | Description | +|---|---| +| `setRow()` | Appends a result row; auto-detects non-printable values and base64-encodes them | +| `parseWhereFrom()` | Reads `kMDItemWhereFroms` via `MDItemCopyAttribute`, emitting one row per source URL | +| `extractQuarantineProperty()` | Converts a `CFTypeRef` (string, date, or URL) quarantine property to a string row | +| `parseQuarantineFile()` | Fetches `NSURLQuarantinePropertiesKey` via `CFURLCopyResourcePropertyForKey` and iterates quarantine fields | +| `getFileData()` | Opens a file, enumerates all xattr names, and dispatches to specialized parsers or generic value extraction | +| `genXattr()` | Table generator — resolves `path` and `directory` constraints, then calls `getFileData()` for each matched file | + +## Usage Example + +```cpp +// Query via osquery SQL +// SELECT * FROM extended_attributes WHERE path = '/path/to/downloaded/file.dmg'; +// Returns rows like: +// path | key | value | base64 +// /path/to/downloaded/file.dmg | where_from | https://example.com | 0 +// /path/to/downloaded/file.dmg | quarantine_agent | Safari | 0 +// /path/to/downloaded/file.dmg | quarantine_timestamp | 1700000000 | 0 +``` + +Non-printable xattr values are automatically base64-encoded with the `base64` column set to `1`. Both `path` (file) and `directory` constraints support `EQUALS` and `LIKE` glob patterns, and directories are enumerated one level deep. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.firewall.md b/osquery/tables/system/darwin/.firewall.md new file mode 100644 index 00000000000..b9a214287d1 --- /dev/null +++ b/osquery/tables/system/darwin/.firewall.md @@ -0,0 +1,54 @@ + +Header file for osquery's macOS Application Layer Firewall (ALF) table implementation, providing parsing utilities for both legacy plist-based and modern `system_profiler`-based firewall data collection. + +## Key Components + +### Functions + +| Function | Description | +|---|---| +| `parseALFExceptionsTree()` | Parses the `exceptions` key from `com.apple.alf.plist` property tree | +| `parseALFExplicitAuthsTree()` | Parses the `explicitauth` key from `com.apple.alf.plist` property tree | +| `parseALFTree()` | Parses top-level string and integer keys from `com.apple.alf.plist` property tree | +| `genALFFromSystemProfiler()` | Generates `alf` table rows using `SPFirewallDataType` (macOS 15+) | +| `genALFExceptionsFromSystemProfiler()` | Generates `alf_exceptions` rows via `system_profiler` (macOS 15+) | + +### Constants + +| Constant | Type | Description | +|---|---|---| +| `kALFPlistPath` | `std::string` | Path to `com.apple.alf.plist` | +| `kTopLevelIntKeys` | `std::map` | Integer key-to-column mappings for `parseALFTree` | +| `kTopLevelStringKeys` | `std::map` | String key-to-column mappings for `parseALFTree` | + +## Usage Example + +```c +#include "firewall.h" +#include + +namespace osquery { +namespace tables { + +QueryRows queryFirewall() { + // Legacy plist-based path (macOS < 15) + pt::ptree tree; + pt::read_plist(kALFPlistPath, tree); + + QueryData alfData = parseALFTree(tree); + QueryData exceptions = parseALFExceptionsTree(tree); + QueryData explicitAuths = parseALFExplicitAuthsTree(tree); + + // Modern system_profiler path (macOS 15+) + QueryData alfModern = genALFFromSystemProfiler(); + QueryData exceptionsModern = genALFExceptionsFromSystemProfiler(); +} + +} // namespace tables +} // namespace osquery +``` + +## macOS Version Notes + +- **macOS < 15**: Full plist parsing via `com.apple.alf.plist` +- **macOS 15+**: Uses `system_profiler SPFirewallDataType`; columns `allow_signed_enabled`, `logging_option`, and `firewall_unload` are **unsupported** and return empty. `alf_exceptions` returns only signed app exceptions with bundle identifiers instead of file paths. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.gatekeeper.md b/osquery/tables/system/darwin/.gatekeeper.md new file mode 100644 index 00000000000..ac78d1ce70b --- /dev/null +++ b/osquery/tables/system/darwin/.gatekeeper.md @@ -0,0 +1,34 @@ + +Implements osquery virtual table generation for macOS Gatekeeper security policy status and approved applications by reading system policy plists and the SQLite policy database. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kGkeStatusPath` | `const std::string` | Path to `SystemPolicy-prefs.plist` controlling Gatekeeper enabled state | +| `kGkeBundlePath` | `const std::string` | Path to `gke.bundle` version plist | +| `kGkeOpaquePath` | `const std::string` | Path to `gkopaque.bundle` version plist | +| `kPolicyDb` | `const std::string` | Path to the SQLite `SystemPolicy` database | +| `isGateKeeperDevIdEnabled()` | `bool` | Queries `SystemPolicy` DB to check if Developer ID authority is enabled | +| `genGateKeeper()` | `QueryData` | Generates the `gatekeeper` table row with assessment status and bundle versions | +| `genGateKeeperApprovedAppRow()` | `void` | Populates a single `Row` from a prepared SQLite statement result | +| `genGateKeeperApprovedApps()` | `QueryData` | Generates rows for `gatekeeper_approved_apps` from non-expired, non-disabled authority entries | + +## Usage Example + +```cpp +// Query Gatekeeper status from osquery shell: +// SELECT assessments_enabled, dev_id_enabled, version, opaque_version +// FROM gatekeeper; + +// Query approved apps bypassing Gatekeeper assessment: +// SELECT path, requirement, ctime, mtime +// FROM gatekeeper_approved_apps; +``` + +## Behavior Notes + +- **`genGateKeeper`**: Absence of `SystemPolicy-prefs.plist` is treated as fully enabled (both `assessments_enabled` and `dev_id_enabled` default to `1`) +- **`isGateKeeperDevIdEnabled`**: Returns `false` if any `authority` row for `Developer ID` has `disabled = 1` +- **`genGateKeeperApprovedApps`**: Filters to active entries only — `disabled = 0`, not yet expired, and no user-specific flags (`flags & 1 = 0`) with no label set +- The policy database is opened read-only with private cache and no mutex for safe concurrent access \ No newline at end of file diff --git a/osquery/tables/system/darwin/.homebrew_packages.md b/osquery/tables/system/darwin/.homebrew_packages.md new file mode 100644 index 00000000000..1efd873bea6 --- /dev/null +++ b/osquery/tables/system/darwin/.homebrew_packages.md @@ -0,0 +1,41 @@ + +Implements the osquery `homebrew_packages` virtual table, which enumerates Homebrew-installed formulas and casks across standard and user-specified installation prefixes on macOS. + +## Key Components + +### Constants +- **`kHomebrewPrefixes`** — Default search roots (`/usr/local`, `/opt/homebrew`) used when no prefix constraint is provided. + +### Helper Functions + +| Function | Purpose | +|---|---| +| `getHomebrewAppInfoPlistPaths` | Lists subdirectories under a Cellar/Caskroom root | +| `getHomebrewNameFromInfoPlistPath` | Extracts package name from its directory path | +| `getHomebrewVersionsFromInfoPlistPath` | Lists version subdirectories, skipping `.metadata` | +| `getMetadataFileForCask` | Locates the `.json` or `.rb` metadata file inside a cask's `.metadata` directory | +| `checkAutoUpdatesInRubyFile` | Regex-scans a `.rb` manifest for `auto_updates true` | +| `getBooleanValueFromJsonFile` | Reads a boolean key (default: `auto_updates`) from a JSON manifest | +| `getAppNameFromJsonManifest` | Parses the `artifacts[].app[]` array in a JSON manifest | +| `getAppNameFromRubyManifest` | Regex-scans a `.rb` manifest for an `app 'Name.app'` declaration | +| `getHomebrewAutoUpdate` | Dispatches to JSON or Ruby parser to resolve `auto_updates` | +| `getInstalledAppNameFromMetadata` | Dispatches to JSON or Ruby parser to resolve the installed app name | + +### Table Generation Functions +- **`computeVersionsForFormulas`** — Populates rows from `/Cellar` with fields: `name`, `path`, `version`, `type`, `prefix`. +- **`computeVersionsForCasks`** — Populates rows from `/Caskroom`, adding `auto_updates` and `app_name` fields. +- **`packagesFromPrefix`** — Entry point per prefix; calls both formula and cask collectors. +- **`genHomebrewPackages`** — Table generator; honors a `prefix` equality constraint or falls back to `kHomebrewPrefixes`. + +## Usage Example + +```sql +-- Query all installed Homebrew packages using default prefixes +SELECT name, version, type, prefix, auto_updates, app_name +FROM homebrew_packages; + +-- Query a custom Homebrew prefix +SELECT name, version, type +FROM homebrew_packages +WHERE prefix = '/opt/my-homebrew'; +``` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.ibridge.md b/osquery/tables/system/darwin/.ibridge.md new file mode 100644 index 00000000000..7c45095a6e6 --- /dev/null +++ b/osquery/tables/system/darwin/.ibridge.md @@ -0,0 +1,32 @@ + +Implements the `ibridge` osquery table for macOS, querying IOKit to retrieve hardware information about Apple T1/T2 security coprocessors (iBridge chips), including coprocessor version, boot UUID, unique chip ID, and firmware version. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kCoprocessorVersions` | `unordered_map` | Maps raw coprocessor version codes to human-readable chip names (T1, T2) | +| `genBootUuid` | `static inline void` | Reads `boot-uuid` from the IOKit `:/chosen` device tree path | +| `genAppleCoprocessorVersion` | `static inline void` | Reads and decodes the `apple-coprocessor-version` key from `:/efi/platform` | +| `genIBridgeInfo` | `QueryData` | Main table generator; combines coprocessor version, boot UUID, chip ID, and firmware version | + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT * FROM ibridge_info; + +// Expected columns populated by genIBridgeInfo(): +// coprocessor_version → e.g., "Apple T2 Chip" +// boot_uuid → e.g., "A1B2C3D4-..." +// unique_chip_id → e.g., "0xDEADBEEF..." +// firmware_version → e.g., "17.16.16610.0.0,0" + +QueryData genIBridgeInfo(QueryContext& context); +``` + +## Notes + +- Returns an **empty result set** on Macs without a T1/T2 chip (no `AppleEmbeddedOSSupportHost` IOService match). +- Coprocessor version bytes are read as a raw `uint32_t` and looked up in `kCoprocessorVersions`; unrecognized values map to `"unknown"`. +- All IOKit object references (`IOObjectRelease`) and CF types (`CFRelease`) are properly released to avoid memory leaks. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.iokit_registry.md b/osquery/tables/system/darwin/.iokit_registry.md new file mode 100644 index 00000000000..2c17276652e --- /dev/null +++ b/osquery/tables/system/darwin/.iokit_registry.md @@ -0,0 +1,39 @@ + +Provides osquery table implementations for querying the macOS IOKit registry, exposing device firmware versions, the IODeviceTree plane, and the full IOService registry hierarchy. + +## Key Components + +### Constants +- **`kFirmwareProperties`** — Set of IOKit property keys used to identify firmware version fields (`"Firmware Revision"`, `"IOFirmwareVersion"`, `"FirmwareVersionString"`). + +### Types +- **`IOKitEnumerator`** — `std::function` callback type invoked per registry entry during recursive traversal, receiving the device, parent, plane, depth, and results output. + +### Internal Helpers +- **`genFirmware`** — CFDictionary apply callback that locates a known firmware property key in a device's property dictionary. +- **`genIOKitFirmware`** — Populates a `Row` with `device`, `version`, and `type` fields for firmware-bearing IOKit entries. +- **`genIOKitDevice`** — Populates a `Row` with `name`, `class`, `id`, `parent`, `depth`, `device_path`, `service`, `busy_state`, and `retain_count` for a single IOKit registry entry. +- **`genIOKitDeviceChildren`** — Recursively walks IOKit child iterators on a given plane, invoking the provided `IOKitEnumerator` at each node. + +### Table Generators (osquery entry points) +- **`genDeviceFirmware`** — Queries firmware entries across the `kIOServicePlane`. +- **`genIOKitDeviceTree`** — Queries device entries along the `kIODeviceTreePlane`. +- **`genIOKitRegistry`** — Queries all device entries across the `kIOServicePlane`. + +## Usage Example + +```cpp +// These functions are registered as osquery virtual table implementations. +// They are invoked by the osquery SQL engine, e.g.: + +// SELECT device, version, type FROM device_firmware; +QueryData results = genDeviceFirmware(context); + +// SELECT name, class, id, parent, depth FROM iokit_devicetree; +QueryData tree = genIOKitDeviceTree(context); + +// SELECT name, class, id, parent, busy_state FROM iokit_registry; +QueryData registry = genIOKitRegistry(context); +``` + +> **macOS only.** All three generators start from `IORegistryGetRootEntry` and recurse the full IOKit hierarchy using `genIOKitDeviceChildren`. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.kernel_extensions.md b/osquery/tables/system/darwin/.kernel_extensions.md new file mode 100644 index 00000000000..00592beb350 --- /dev/null +++ b/osquery/tables/system/darwin/.kernel_extensions.md @@ -0,0 +1,42 @@ + +Queries loaded macOS kernel extensions (kexts) using the `KextManager` framework and returns metadata for each loaded extension. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `getKextInt` | Helper | Reads a 32-bit integer from a kext `CFDictionaryRef` | +| `getKextBigInt` | Helper | Reads a 64-bit integer from a kext `CFDictionaryRef` | +| `getKextString` | Helper | Safely reads an optional string value from a kext dictionary | +| `getKextLinked` | Helper | Reads sorted dependency indexes, formatted as `<1 2 3>` (matching `kextstat` output) | +| `genExtension` | Callback | `CFDictionaryApplyFunction` callback that maps a single kext dictionary to a `Row` | +| `genKernelExtensions` | Table generator | Entry point; calls `KextManagerCopyLoadedKextInfo` and iterates all loaded kexts | + +### Output Columns + +| Column | Source Key | +|---|---| +| `name` | `CFBundleIdentifier` | +| `idx` | `OSBundleLoadTag` | +| `refs` | `OSBundleRetainCount` | +| `size` | `OSBundleLoadSize` | +| `version` | `CFBundleVersion` | +| `linked_against` | `OSBundleDependencies` | +| `path` | `OSBundlePath` | + +## Usage Example + +```cpp +// Invoked automatically by the osquery table registry. +// Equivalent SQL query: +// SELECT name, idx, refs, size, version, linked_against, path +// FROM kernel_extensions; + +QueryContext context; +QueryData rows = genKernelExtensions(context); +for (const auto& row : rows) { + std::cout << row.at("name") << " @ " << row.at("path") << "\n"; +} +``` + +> **macOS only.** Requires `IOKit` and `CoreFoundation` frameworks. `KextManagerCopyLoadedKextInfo` returns `nullptr` when called without sufficient permissions or when no kexts are loaded, in which case an empty result set is returned. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.kernel_info.md b/osquery/tables/system/darwin/.kernel_info.md new file mode 100644 index 00000000000..1e47b7bde26 --- /dev/null +++ b/osquery/tables/system/darwin/.kernel_info.md @@ -0,0 +1,35 @@ + +Provides the `kernel_info` osquery table implementation for macOS, querying IOKit and EFI device path data to expose kernel version, boot arguments, boot device, and kernel path. + +## Key Components + +### `getCanonicalEfiDevicePath(const CFDataRef& data)` +Parses a raw EFI device path (`CFDataRef`) by iterating `EFI_DEVICE_PATH_PROTOCOL` nodes. Handles two subtypes: +- **`MEDIA_FILEPATH_DP`** — strips UTF-16 characters to build a UTF-8 file path string. +- **`MEDIA_HARDDRIVE_DP`** — extracts and converts the partition GUID signature (byte-swapped) into an uppercase UUID string. + +### `genKernelInfo(QueryContext& context)` +The osquery table generator function. Uses IOKit to: +1. Obtain the `IOMasterPort` and open the `IODeviceTree:/chosen` registry entry. +2. Read `boot-args`, `boot-device-path`, and `boot-file` properties to populate `arguments`, `device`, and `path` columns. +3. Read `kIOKitBuildVersionKey` from the IOKit root entry to extract the Darwin kernel version string into the `version` column. + +## Usage Example + +```cpp +// Queried via osquery SQL interface (macOS only): +// SELECT version, arguments, device, path FROM kernel_info; + +// Internally triggered by the osquery table registry: +QueryContext context; +QueryData results = osquery::tables::genKernelInfo(context); + +for (const auto& row : results) { + // row["version"] → e.g. "22.6.0" + // row["arguments"] → e.g. "" or "-v debug=0x100" + // row["device"] → UUID or EFI path string + // row["path"] → e.g. "/System/Library/Kernels/kernel" +} +``` + +> **Note:** This file is macOS-specific. It depends on IOKit, CoreFoundation, and EFI struct definitions from `efi_misc.h`. The Darwin kernel version is parsed from the `IOKitBuildVersion` string format: `Darwin Kernel Version VERSION: DATE; root:BUILD/TAG`. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.kernel_panics.md b/osquery/tables/system/darwin/.kernel_panics.md new file mode 100644 index 00000000000..ae3a61bb39c --- /dev/null +++ b/osquery/tables/system/darwin/.kernel_panics.md @@ -0,0 +1,43 @@ + +Parses macOS kernel panic log files from `/Library/Logs/DiagnosticReports`, extracting structured diagnostic data from both modern JSON-based and legacy plain-text `.panic` log formats. + +## Key Components + +### Constants +- **`kDiagnosticReportsPath`** — Path to macOS kernel panic logs directory +- **`kPanicStringKey`** — Architecture-aware JSON key (`panicString` on ARM, `macOSPanicString` on x86) +- **`kDays`** — Day-of-week set used for timestamp detection in legacy log format +- **`kKernelPanicKeys`** — Key mapping from raw log labels to normalized column names + +### Functions + +| Function | Description | +|---|---| +| `readKernelPanic(path, results)` | Parses a single `.panic` file, handling both JSON (macOS 10.15+) and legacy plain-text (10.14 and earlier) formats | +| `genKernelPanics(context)` | Entry point; scans the diagnostic reports directory and invokes `readKernelPanic` for each `.panic` file (root-only) | + +### Extracted Fields + +| Field | Source | +|---|---| +| `time`, `os_version`, `kernel_version` | JSON header or parsed line | +| `name` | Panicked task/process name | +| `last_loaded`, `last_unloaded` | Kext load/unload events | +| `frame_backtrace`, `module_backtrace` | Stack and kext backtrace | +| `system_model`, `uptime` | System metadata | + +## Usage Example + +```cpp +// Called internally by osquery's table subsystem +QueryContext context; +QueryData results = genKernelPanics(context); + +for (const auto& row : results) { + std::cout << "Panic at: " << row.at("time") + << " | OS: " << row.at("os_version") + << " | Proc: " << row.at("name") << "\n"; +} +``` + +> **Note:** `genKernelPanics` only returns results when the query is executed as `uid=0` (root), as panic logs require elevated read access. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.keychain.md b/osquery/tables/system/darwin/.keychain.md new file mode 100644 index 00000000000..7564da9cb7e --- /dev/null +++ b/osquery/tables/system/darwin/.keychain.md @@ -0,0 +1,58 @@ + +Provides macOS keychain access utilities for osquery, including path resolution, item enumeration, and a thread-safe caching layer to throttle repeated reads of keychain files. + +## Key Components + +### Constants +- `kSystemKeychainPaths` — Default system-level keychain file paths +- `kUserKeychainPaths` — Default user-level keychain file paths + +### Flags (defined in `keychain_utils.cpp`) +- `keychain_access_cache` (`bool`) — Enables/disables the keychain result cache +- `keychain_access_interval` (`uint32`) — Throttle interval between keychain file accesses + +### `KeychainTable` (enum class) +Identifies which osquery table a cache entry belongs to: `CERTIFICATES`, `KEYCHAIN_ACLS`, or `KEYCHAIN_ITEMS`. + +### `KeychainCache` +Thread-safe LRU-style cache keyed on `(filepath, KeychainTable)`. Stores SHA-256 file hashes and `QueryData` results to avoid redundant keychain reads. + +| Method | Description | +|---|---| +| `Read(path, table, hash, results, err)` | Returns `true` on cache hit; populates `results` and `hash` | +| `Write(path, table, hash, results)` | Stores a new or updated cache entry | +| `Size()` | Returns the current number of cached entries | + +### Free Functions +| Function | Description | +|---|---| +| `expandPaths(paths)` | Resolves glob/directory paths to individual keychain files | +| `getKeychainPath(item)` | Returns the filesystem path for a given `SecKeychainItemRef` | +| `CreateKeychainItems(keychains, item_type)` | Enumerates keychain items of a given Security framework type | +| `getKeychainPaths()` | Returns the combined set of system and user keychain paths | + +### Globals +- `keychainCache` — Process-wide `KeychainCache` instance +- `keychainMutex` — Mutex guarding all cache and keychain access + +## Usage Example + +```c +// Enumerate all accessible keychain paths +std::set paths = osquery::tables::getKeychainPaths(); +std::set files = osquery::tables::expandPaths(paths); + +// Query the cache before hitting the filesystem +std::string hash; +QueryData results; +bool err = false; + +if (osquery::tables::keychainCache.Read(filePath, KeychainTable::CERTIFICATES, + hash, results, err)) { + // Cache hit — use results directly +} else { + // Cache miss — query keychain, then persist + osquery::tables::keychainCache.Write(filePath, KeychainTable::CERTIFICATES, + hash, results); +} +``` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.keychain_acl.md b/osquery/tables/system/darwin/.keychain_acl.md new file mode 100644 index 00000000000..07c3ae2e6a4 --- /dev/null +++ b/osquery/tables/system/darwin/.keychain_acl.md @@ -0,0 +1,41 @@ + +Implements the osquery `keychain_acl_apps` virtual table for macOS, which enumerates Access Control List (ACL) entries for items stored in macOS Keychain files, exposing authorized applications and their permissions. + +## Key Components + +### Data Structures +- **`KeychainItemMetadata`** — Holds `keychain_path` and `label` for a keychain item +- **`KeychainItemACL`** — Holds `authorizations`, `description`, and `applications` for a single ACL entry +- **`kACLAuthorizationTags`** — Maps `CSSM_ACL_AUTHORIZATION_TAG` enum values to human-readable strings (e.g., `encrypt`, `sign`, `delete`) + +### Core Functions + +| Function | Description | +|---|---| +| `parseKeychainItemACLEntry(SecACLRef, acls)` | Parses a single `SecACLRef` entry: extracts authorization tags, description, and trusted application paths | +| `parseKeychainItemACL(SecAccessRef, acls)` | Iterates all ACL entries from a `SecAccessRef`, delegating to `parseKeychainItemACLEntry` | +| `attributeBufferToString(data, length)` | Converts raw keychain attribute bytes to a printable string (hex-escapes non-printable chars) | +| `genKeychainACLAppsForEntry(...)` | Extracts metadata and ACL data for a single `SecKeychainItemRef`, emits `QueryData` rows | +| `genKeychainACLApps(path, results)` | Opens a keychain file, searches all items, populates results with cache read/write support | +| `genKeychainACLApps(QueryContext&)` | Table entry point; iterates keychain paths, acquires mutex, disables user interaction prompts | + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT keychain_path, label, authorizations, description, path +// FROM keychain_acl_apps; + +// Internally, the table calls: +QueryData results; +QueryContext ctx; +results = genKeychainACLApps(ctx); +// Each row contains: +// r["keychain_path"] = "/Users/alice/Library/Keychains/login.keychain-db" +// r["label"] = "My App Password" +// r["authorizations"]= "decrypt encrypt sign" +// r["description"] = "com.example.myapp" +// r["path"] = "/Applications/MyApp.app/Contents/MacOS/MyApp" +``` + +> **Note:** Uses deprecated macOS CSSM/Security APIs wrapped with `OSQUERY_USE_DEPRECATED`. Results are cached via `keychainCache` (keyed by canonical path + file hash) to avoid redundant keychain API calls. Keychain access is serialized via `keychainMutex`. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.keychain_items.md b/osquery/tables/system/darwin/.keychain_items.md new file mode 100644 index 00000000000..8fde41afd04 --- /dev/null +++ b/osquery/tables/system/darwin/.keychain_items.md @@ -0,0 +1,37 @@ + +Implements the osquery `keychain_items` virtual table for macOS, enumerating and querying items stored across one or more Keychain files using the Security framework APIs. + +## Key Components + +### Constants + +| Name | Type | Description | +|------|------|-------------| +| `kKeychainItemTypes` | `vector` | Security item classes to query (generic password, internet password, certificate, key, identity) | +| `kKeychainItemAttrs` | `map` | Maps Security attribute tags to output column names (label, description, pk_hash, comment, account, created, modified) | +| `kKeychainItemClasses` | `map` | Maps `SecItemClass` codes to human-readable type strings | + +### Functions + +- **`genKeychainItem(item, results)`** — Extracts attributes from a single `SecKeychainItemRef`, hex-encodes public key hash fields, resolves the item class label, and appends a result row including the keychain file path. + +- **`genKeychainItems(context)`** — Entry point for the osquery table. Resolves keychain search paths (from query constraints or system defaults), checks a per-file cache, opens each `.keychain` file via `SecKeychainOpen`, iterates all item types, and populates results. + +## Usage Example + +```cpp +// Query from osquery CLI or SQL interface: +// SELECT label, type, account, path FROM keychain_items; + +// Internally, results are built per item: +QueryData results; +genKeychainItem(itemRef, results); +// Each row contains: label, description, pk_hash, +// comment, account, created, modified, type, path +``` + +### Caching Behavior + +Results are cached per keychain file path using `keychainCache`. On a cache hit the file is skipped entirely; on a miss, new results are written back. An empty result is cached for inaccessible or unopenable keychain files to prevent redundant `SecKeychainOpen` calls. + +> **Note:** All keychain access is serialized via `keychainMutex` to prevent concurrent access across threads or tables. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.keychain_utils.md b/osquery/tables/system/darwin/.keychain_utils.md new file mode 100644 index 00000000000..56c21428354 --- /dev/null +++ b/osquery/tables/system/darwin/.keychain_utils.md @@ -0,0 +1,58 @@ + +Provides macOS Keychain utility functions for osquery, including path enumeration, keychain item querying, and a hash-based caching layer to throttle repeated keychain file accesses. + +## Key Components + +### Flags +| Flag | Default | Description | +|---|---|---| +| `keychain_access_cache` | `true` | Enable/disable caching of keychain accesses | +| `keychain_access_interval` | `5` | Minimum minutes between cache refreshes for a modified keychain file | + +### Global State +- **`keychainCache`** — Singleton `KeychainCache` instance shared across keychain table queries +- **`keychainMutex`** — Mutex for thread-safe keychain access +- **`kSystemKeychainPaths`** — System-level keychain search paths (`/System/Library/Keychains`, `/Library/Keychains`) +- **`kUserKeychainPaths`** — Per-user keychain subdirectory (`/Library/Keychains`) + +### Functions + +| Function | Description | +|---|---| +| `expandPaths(paths)` | Resolves a set of paths — expands directories to individual files, passes explicit paths through unchanged | +| `getKeychainPath(item)` | Retrieves the filesystem path of the keychain containing a given `SecKeychainItemRef` | +| `CreateKeychainItems(keychains, item_type)` | Queries a keychain list via `SecItemCopyMatching`, returning a `CFArrayRef` of matching items | +| `getKeychainPaths()` | Aggregates system and per-user home-directory keychain search paths | +| `KeychainCache::Read(...)` | Returns cached `QueryData` if the file hash matches; throttles re-reads on modified files within the interval window | +| `KeychainCache::Write(...)` | Stores query results, a SHA-256 hash, and a timestamp into the cache keyed by `(path, table)` | + +## Usage Example + +```cpp +// Retrieve all keychain search paths and expand them to individual files +std::set base_paths = getKeychainPaths(); +std::set keychain_files = expandPaths(base_paths); + +// Check the cache before reading a keychain file +std::string hash; +QueryData results; +bool err = false; + +bool cache_hit = keychainCache.Read( + "/Library/Keychains/System.keychain", + KeychainTable::CERTIFICATES, + hash, + results, + err +); + +if (!cache_hit && !err) { + // Perform the actual keychain read, then populate results... + keychainCache.Write( + "/Library/Keychains/System.keychain", + KeychainTable::CERTIFICATES, + hash, + results + ); +} +``` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.launchd.md b/osquery/tables/system/darwin/.launchd.md new file mode 100644 index 00000000000..c15b1f01d3e --- /dev/null +++ b/osquery/tables/system/darwin/.launchd.md @@ -0,0 +1,37 @@ + +Implements osquery table generators for macOS `launchd` daemons and agents, parsing plist files from standard system and user launch paths to expose service configuration and runtime overrides. + +## Key Components + +### Constants + +- **`kLaunchdSearchPaths`** — System-wide directories scanned for LaunchDaemon/LaunchAgent plists +- **`kUserLaunchdSearchPaths`** — Per-user subdirectories appended to each home directory +- **`kLaunchdTopLevelStringKeys`** / **`kLaunchdTopLevelArrayKeys`** — Maps translating plist keys to osquery column names +- **`kLaunchdOverridesPath`** — Glob pattern targeting `/var/db/launchd.db/*/overrides.plist` + +### Functions + +| Function | Description | +|---|---| +| `genLaunchdItem()` | Parses a single plist `ptree`, populates a `Row` with string and array fields | +| `genLaunchd()` | Entry point for the `launchd` table; enumerates all launchers and delegates to `genLaunchdItem` | +| `genLaunchdOverride()` | Parses a single overrides plist, emits one row per `label/key/value` triple | +| `genLaunchdOverrides()` | Entry point for the `launchd_overrides` table; resolves override files via glob | + +## Usage Example + +```cpp +// Querying via osquery SQL (consumer perspective): + +// List all registered launch daemons and agents: +// SELECT name, label, program, run_at_load, disabled FROM launchd; + +// Filter to a specific plist path: +// SELECT * FROM launchd WHERE path = '/Library/LaunchDaemons/com.example.service.plist'; + +// Inspect runtime overrides (e.g., disabled state changes via launchctl): +// SELECT uid, label, key, value FROM launchd_overrides WHERE key = 'Disabled'; +``` + +> **Note:** User-scoped agents are resolved by iterating `getHomeDirectories()`, so results include per-user `~/Library/LaunchAgents` entries. Override UIDs are parsed from the parent folder name (e.g., `user.501` → `uid = 501`). \ No newline at end of file diff --git a/osquery/tables/system/darwin/.managed_policies.md b/osquery/tables/system/darwin/.managed_policies.md new file mode 100644 index 00000000000..50d5dd69895 --- /dev/null +++ b/osquery/tables/system/darwin/.managed_policies.md @@ -0,0 +1,46 @@ + +Enumerates macOS managed preference policies from `/Library/Managed Preferences`, parsing per-user and system-wide MDM/MCX configuration profiles into structured query results. + +## Key Components + +### Constants +- **`kManagedPoliciesCache`** — Path to the macOS managed preferences cache directory (`/Library/Managed Preferences`) + +### Functions + +| Function | Signature | Description | +|---|---|---| +| `genPolicy` | `(path, username, results)` | Parses a single `.plist` policy file, extracting domain, UUID, manual flag, and key/value settings | +| `genManagedPolicies` | `(QueryContext&)` | Entry point; enumerates system-wide and per-user policy plists, populates result rows | + +### Output Schema + +| Column | Source | +|---|---| +| `username` | Parent directory stem (empty = system-wide) | +| `domain` | Plist filename stem (e.g., `com.apple.dock`) | +| `manual` | `1` if `_manualProfile` key present | +| `uuid` | Value of `PayloadUUID` key | +| `name` / `value` | All remaining plist key/value pairs | + +> **Note:** Plist files named `complete` are skipped — they are aggregated meta-lists and not individual policies. + +## Usage Example + +```cpp +// Called automatically via osquery table registration. +// Equivalent SQL query: +// SELECT username, domain, uuid, manual, name, value +// FROM managed_policies +// WHERE domain = 'com.apple.dock'; + +QueryContext context; +QueryData rows = genManagedPolicies(context); +for (const auto& row : rows) { + // row["domain"] => "com.apple.dock" + // row["name"] => "orientation" + // row["value"] => "left" + // row["manual"] => "0" + // row["username"] => "" (system) or "jdoe" (user-specific) +} +``` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.mounts.md b/osquery/tables/system/darwin/.mounts.md new file mode 100644 index 00000000000..cf05ec9b3e1 --- /dev/null +++ b/osquery/tables/system/darwin/.mounts.md @@ -0,0 +1,52 @@ + +Implements the `mounts` osquery table for BSD/macOS systems, querying mounted filesystem information using `getmntinfo`. + +## Key Components + +### `genMounts(QueryContext& context)` +The sole table generator function that retrieves all active mount points via the BSD `getmntinfo` syscall and returns structured row data. + +**Columns populated per mount:** + +| Column | Source Field | Type | +|---|---|---| +| `path` | `f_mntonname` | TEXT | +| `device` | `f_mntfromname` | TEXT | +| `device_alias` | `canonicalize_file_name(f_mntfromname)` | TEXT | +| `type` | `f_fstypename` | TEXT | +| `flags` | `f_flags` | INTEGER | +| `blocks` | `f_blocks` | BIGINT | +| `blocks_free` | `f_bfree` | BIGINT | +| `blocks_available` | `f_bavail` | BIGINT | +| `blocks_size` | `f_bsize` | BIGINT | +| `inodes` | `f_files` | BIGINT | +| `inodes_free` | `f_ffree` | BIGINT | +| `owner` | `f_owner` | INTEGER | + +## Usage Example + +```cpp +// Query via osquery SQL interface +SELECT path, device, device_alias, type, flags, + blocks, blocks_free, blocks_available, blocks_size, + inodes, inodes_free, owner +FROM mounts; +``` + +```cpp +// Typical result row (macOS/BSD) +// path = "/" +// device = "/dev/disk1s1" +// device_alias = "/dev/disk1s1" // resolved canonical path +// type = "apfs" +// blocks = 976490568 +// blocks_free = 500123456 +// owner = 0 +``` + +## Notes + +- Calls `getmntinfo` with `MNT_WAIT` to ensure up-to-date mount data before reading. +- Returns an empty result set gracefully if `getmntinfo` fails (returns `0`). +- `device_alias` resolves symlinks via `canonicalize_file_name`, useful for identifying aliased block devices. +- BSD/macOS specific — relies on `` and `statfs` structure. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.nfs_shares.md b/osquery/tables/system/darwin/.nfs_shares.md new file mode 100644 index 00000000000..c2d37bbe167 --- /dev/null +++ b/osquery/tables/system/darwin/.nfs_shares.md @@ -0,0 +1,45 @@ + +Parses `/etc/exports` to enumerate NFS (Network File System) share configurations on Unix-like systems, exposing share paths, read-only status, and mount options as osquery table rows. + +## Key Components + +### `genNFSShare(share_line, results)` +Processes a single line from `/etc/exports`: +- Skips blank lines and comments (lines starting with `#`) +- Extracts one or more export paths (tokens beginning with `/`) +- Detects read-only flags (`-ro` or `-o`) +- Appends a result row per export path with `share`, `readonly`, and `options` fields + +### `genNFSShares(context)` +Entry point for the osquery `nfs_shares` virtual table: +- Reads `/etc/exports` via the osquery filesystem abstraction +- Splits content by newline and delegates each line to `genNFSShare` +- Returns an empty result set if the file cannot be read, logging the error via `VLOG(1)` + +## Usage Example + +```cpp +// Queried internally by osquery when the virtual table is accessed: +// SELECT * FROM nfs_shares; +// +// Example /etc/exports line: +// /shared /mnt/backup -ro client1.local +// +// Produces rows: +// { share: "/shared", readonly: "1", options: "-ro client1.local " } +// { share: "/mnt/backup", readonly: "1", options: "-ro client1.local " } + +QueryContext ctx; +QueryData rows = genNFSShares(ctx); +for (const auto& row : rows) { + std::cout << row.at("share") << " | " + << row.at("readonly") << " | " + << row.at("options") << "\n"; +} +``` + +## Notes + +- Only the first contiguous block of `/`-prefixed tokens per line is treated as export paths; remaining tokens become `options` +- The `options` field includes a trailing space due to `std::ostream_iterator` join behavior +- Read-only detection is limited to `-ro` and `-o` flags; other option formats are captured in `options` but not interpreted \ No newline at end of file diff --git a/osquery/tables/system/darwin/.nvram.md b/osquery/tables/system/darwin/.nvram.md new file mode 100644 index 00000000000..0549fae7ef7 --- /dev/null +++ b/osquery/tables/system/darwin/.nvram.md @@ -0,0 +1,45 @@ + +Queries macOS NVRAM variables by reading from the IOKit device tree registry (`IODeviceTree:/options`), converting CoreFoundation types to string representations for osquery table output. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kIODTOptionsPath` | `const std::string` | IOKit path to the NVRAM options registry entry | +| `stringFromNVRAM` | `Status` function | Converts a CF-typed NVRAM value to its type name and string representation | +| `genVariable` | `void` function | CFDictionary callback that builds a `Row` from a key/value NVRAM pair | +| `genSingleVariable` | `void` function | Fetches and converts a single named NVRAM variable via `IORegistryEntryCreateCFProperty` | +| `genNVRAM` | `QueryData` function | Table generator; dispatches to single-variable or full-scan lookup based on query constraints | + +## Supported CF Types + +`stringFromNVRAM` handles the following CoreFoundation types: + +- `CFBoolean` → `"true"` / `"false"` +- `CFNumber` → numeric string via `stringFromCFNumber` +- `CFString` → UTF-8 string via `stringFromCFString` +- `CFData` → hex/binary string via `stringFromCFData` + +## Usage Example + +```cpp +// osquery SQL usage (table: nvram) + +// Fetch all NVRAM variables +SELECT name, type, value FROM nvram; + +// Fetch a specific variable (optimized: uses genSingleVariable) +SELECT name, type, value FROM nvram WHERE name = 'boot-args'; +``` + +```cpp +// Direct invocation in osquery table context +QueryContext ctx; +// ctx with no constraints → full NVRAM dictionary scan +QueryData rows = genNVRAM(ctx); + +// ctx with EQUALS constraint on "name" → single variable lookup +// avoids iterating the full IORegistry dictionary +``` + +> **Note:** Requires macOS with IOKit access. Returns an empty result set on systems where NVRAM is unsupported or the IOMaster port is unavailable. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.packages.md b/osquery/tables/system/darwin/.packages.md new file mode 100644 index 00000000000..a305b1371cb --- /dev/null +++ b/osquery/tables/system/darwin/.packages.md @@ -0,0 +1,49 @@ + +Header file defining binary BOM (Bill of Materials) format structures and a parser class used to inspect macOS package metadata within osquery's tables module. + +## Key Components + +### Packed Structs (BOM Format) + +| Struct | Purpose | +|---|---| +| `BOMHeader` | File header with magic bytes, version, block count, and offsets | +| `BOMPointer` | Address/length pair referencing a block in the file | +| `BOMBlockTable` | Array of `BOMPointer` entries describing all blocks | +| `BOMTree` | B-tree metadata including child index and total path count | +| `BOMVar` / `BOMVars` | Named variable entries with index and length | +| `BOMPathIndices` | Dual-index pair for leaf (path info) or branch (sub-paths) nodes | +| `BOMPaths` | B-tree node containing leaf/branch flag, count, and path indices | +| `BOMPathInfo1` / `BOMPathInfo2` | File identity (id, parent pointer) and file metadata (type, mode, user, group, size, checksum) | +| `BOMFile` | Maps a parent path ID to a filename | + +All structs use `__attribute__((packed))` to prevent compiler padding, ensuring correct binary layout alignment. + +### `BOM` Class + +Primary parser class that wraps raw BOM binary data. + +- **`BOM(const char* data, size_t size)`** — Constructor; parses header, block table, and vars +- **`isValid()`** — Returns whether initial header parsing succeeded +- **`getPointer(int index, size_t* length)`** — Resolves a block table index to a data pointer +- **`getPaths(int index)`** — Returns a `BOMPaths` node at a given index +- **`getVariable(size_t* offset)`** — Iterates named variables using an offset cursor + +## Usage Example + +```cpp +#include "packages.h" + +// Load BOM file contents into a buffer +std::string data = readFile("/var/db/receipts/com.example.pkg.bom"); + +osquery::tables::BOM bom(data.c_str(), data.size()); + +if (!bom.isValid()) { + return; // Header parsing failed +} + +// Access the block table and vars +size_t offset = 0; +const osquery::tables::BOMVar* var = bom.getVariable(&offset); +``` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.password_policy.md b/osquery/tables/system/darwin/.password_policy.md new file mode 100644 index 00000000000..be86c2468c8 --- /dev/null +++ b/osquery/tables/system/darwin/.password_policy.md @@ -0,0 +1,46 @@ + +Implements the `password_policy` osquery table for macOS, querying both global node-level and per-user password policies via the OpenDirectory framework. + +## Key Components + +### `genRowsFromPolicy` +```cpp +void genRowsFromPolicy(const CFDictionaryRef& policies, + QueryData& results, + const std::string& uid = ""); +``` +Parses a `CFDictionaryRef` containing OpenDirectory account policies and appends rows to the result set. Extracts `policyContent`, `policyIdentifier`, and `policyContentDescription` from each entry under `policyCategoryPasswordContent`. A `uid` of `-1` indicates a global (node-level) policy. + +### `genPasswordPolicy` +```cpp +QueryData genPasswordPolicy(QueryContext& context); +``` +Main table generation function. Creates a local OpenDirectory node, fetches the global account policy, then iterates over relevant UIDs (from query constraints or the full `users` table) to fetch per-user policies via `ODRecordCopyAccountPolicies`. + +## Usage Example + +Query all password policies on a macOS host: + +```sql +SELECT uid, policy_identifier, policy_description, policy_content +FROM password_policy; +``` + +Filter by a specific user: + +```sql +SELECT uid, policy_identifier, policy_content +FROM password_policy +WHERE uid = '501'; +``` + +## Table Schema + +| Column | Type | Description | +|---|---|---| +| `uid` | BIGINT | User ID (`-1` = global policy) | +| `policy_identifier` | TEXT | Policy identifier string | +| `policy_description` | TEXT | English description of the policy | +| `policy_content` | TEXT | Raw policy content expression | + +> **Note:** This table is macOS-only. It uses `ODNodeCopyAccountPolicies` and `ODRecordCopyAccountPolicies`, as older OpenDirectory APIs have been deprecated by Apple. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.pci_devices.md b/osquery/tables/system/darwin/.pci_devices.md new file mode 100644 index 00000000000..b23b060ac49 --- /dev/null +++ b/osquery/tables/system/darwin/.pci_devices.md @@ -0,0 +1,32 @@ + +Enumerates PCI devices on macOS by querying the IOKit registry and extracting device properties into osquery table rows. + +## Key Components + +- **`genPCIDevice(device, results)`** — Retrieves properties for a single PCI device via `IORegistryEntryCreateCFProperties`, extracting PCI slot, vendor ID, model ID, PCI class, and driver into a `Row` using `getIOKitProperty` and `IOKitPCIProperties`. +- **`genPCIDevices(context)`** — Entry point for the `pci_devices` osquery table. Matches all IOKit services of type `kIOPCIDeviceClassName_`, iterates over them, and delegates per-device parsing to `genPCIDevice`. + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM pci_devices; +// +// Internally triggered as: +QueryContext context; +QueryData results = genPCIDevices(context); + +for (const auto& row : results) { + // row["pci_slot"] → e.g. "0:0:2" + // row["vendor_id"] → e.g. "0x8086" + // row["model_id"] → e.g. "0x1234" + // row["pci_class"] → e.g. "0x030000" + // row["driver"] → e.g. "AppleIntelFramebuffer" +} +``` + +## Notes + +- **macOS only** — depends on IOKit (`IOServiceMatching`, `IOServiceGetMatchingServices`, `IOIteratorNext`). +- Memory management follows CoreFoundation conventions: `CFRelease` is called on the property dictionary after each device is processed, and `IOObjectRelease` is called on each service object and the iterator. +- Returns an empty result set if no PCI devices are matched or if the IOKit service lookup fails. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.preferences.md b/osquery/tables/system/darwin/.preferences.md new file mode 100644 index 00000000000..4ef1592898f --- /dev/null +++ b/osquery/tables/system/darwin/.preferences.md @@ -0,0 +1,31 @@ + +Implements osquery table generation for macOS user preferences, reading application preference domains via the CoreFoundation `CFPreferences` API and parsing `.plist` files into queryable rows. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kPreferenceDepthLimit` | `const size_t` | Maximum recursion depth (20) for nested dictionary/array preference values | +| `TRowResults` | `struct` | Helper carrying a base row, result set reference, and current recursion depth for CF callback context | +| `genOSXPrefValues` | `function` | Recursively converts a `CFTypeRef` value (number, string, date, bool, array, dict) into one or more `Row` entries | +| `genOSXHashPref` | `function` | CoreFoundation dictionary-apply callback; appends the key to the `subkey` path and recurses | +| `genOSXListPref` | `function` | Iterates a `CFArrayRef`, appending the list index to the `subkey` path and recurses | +| `genOSXDomainPrefs` | `function` | Copies all preference keys/values for a given domain and user/host pair and emits rows | +| `genOSXDefaultPreferences` | `function` | Entry point for the `preferences` virtual table; filters by `username` and `domain` constraints | +| `genOSXPlistPrefValue` | `function` | Recursively walks a `boost::property_tree` from a parsed `.plist` file into rows | +| `genOSXPlist` | `function` | Entry point for the `plist` virtual table; resolves file paths and delegates to `genOSXPlistPrefValue` | + +## Usage Example + +```cpp +// Query all preferences for a specific application domain +// SQL equivalent: SELECT * FROM preferences WHERE domain = 'com.apple.finder'; + +// Query a plist file directly +// SQL equivalent: +// SELECT key, subkey, value +// FROM plist +// WHERE path = '/Library/Preferences/com.apple.loginwindow.plist'; +``` + +> **Note:** Listing all application preferences via `CFPreferencesCopyApplicationList` is deprecated by Apple. Always constrain by `domain` when possible to avoid the deprecated code path and improve query performance. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.process_open_descriptors.md b/osquery/tables/system/darwin/.process_open_descriptors.md new file mode 100644 index 00000000000..cee0020ce1a --- /dev/null +++ b/osquery/tables/system/darwin/.process_open_descriptors.md @@ -0,0 +1,42 @@ + +Enumerates open file descriptors and network sockets for running processes on macOS/Darwin using the `libproc` API, powering the osquery `process_open_sockets` and `process_open_files` virtual tables. + +## Key Components + +### Enums +- **`SOCKET_TYPE_LOCAL` / `SOCKET_TYPE_FOREIGN`** — Selects local or remote address when parsing socket info. +- **`descriptor_type`** — Distinguishes between vnode (file) and socket descriptor queries. + +### Internal Functions + +| Function | Description | +|---|---| +| `socketIpAsString()` | Converts raw `in_sockinfo` address to a human-readable IPv4/IPv6 string using `inet_ntop`. | +| `parseNetworkSocket()` | Populates a `Row` with local/remote address, port, and TCP connection state from `socket_fdinfo`. | +| `genFileDescriptor()` | Resolves a single vnode file descriptor to its filesystem path via `proc_pidfdinfo`. | +| `genSocketDescriptor()` | Resolves a single socket descriptor; handles `AF_INET`, `AF_INET6`, and `AF_UNIX` families. | +| `genOpenDescriptors()` | Lists all file descriptors for a PID via `proc_pidinfo` and dispatches to the appropriate handler. | + +### Exported Table Generators + +| Function | osquery Table | +|---|---| +| `genOpenSockets()` | `process_open_sockets` | +| `genOpenFiles()` | `process_open_files` | + +## Usage Example + +```cpp +// Query open sockets for a specific PID via osquery SQL: +// SELECT pid, fd, family, protocol, local_address, local_port, +// remote_address, remote_port, state +// FROM process_open_sockets +// WHERE pid = 1234; + +// Query open file handles: +// SELECT pid, fd, path +// FROM process_open_files +// WHERE pid = 1234; +``` + +> **Platform note:** This implementation is macOS-only. It relies on `libproc` (`proc_pidinfo`, `proc_pidfdinfo`) and Darwin-specific socket structs (`socket_fdinfo`, `tcp_sockinfo`). TCP state and address family constants are mapped from Darwin values to portable integers (e.g., `AF_INET6 == 30` on Darwin → reported as `10`). \ No newline at end of file diff --git a/osquery/tables/system/darwin/.processes.md b/osquery/tables/system/darwin/.processes.md new file mode 100644 index 00000000000..82d52a18eaf --- /dev/null +++ b/osquery/tables/system/darwin/.processes.md @@ -0,0 +1,44 @@ + +Provides macOS-specific process information retrieval for the osquery `processes` table, querying process credentials, paths, arguments, environment variables, CPU architecture, and memory details via `libproc` and `sysctl` APIs. + +## Key Components + +- **`getProcList()`** — Returns a set of active PIDs, either from query constraints or via `proc_listpids(PROC_ALL_PIDS, ...)`. Allocates 2× buffer to handle race conditions. +- **`genProcCredAndStartTime()`** — Populates UID/GID (real, effective, saved), parent PID, process group, state, nice value, and start time using `proc_pidinfo(PROC_PIDTBSDINFO)` with fallback to `PROC_PIDT_SHORTBSDINFO`. +- **`genProcRootAndCWD()`** — Retrieves current working directory and root path via `PROC_PIDVNODEPATHINFO`, skipping if neither column is queried. +- **`genProcNamePathAndOnDisk()`** — Resolves process name and executable path; handles kernel task (PID 0), zombie processes, and deleted executables. Sets `on_disk` to `1`, `0`, or `-1`. +- **`genProcNumThreads()`** — Reads thread count via `PROC_PIDTASKINFO`. +- **`genProcUniquePid()`** — Retrieves unique/parent unique process identifiers using `proc_pidinfo` flavor `17`. +- **`genProcArch()`** — Determines CPU type/subtype via flavor `19`; detects Rosetta 2 translation (`P_TRANSLATED` flag via `sysctl(KERN_PROC_PID)`). +- **`parseProcCmdline()`** — Parses raw `KERN_PROCARGS2` buffer into a space-delimited argument string, stripping argc, the executable name, and environment data. +- **`getProcEnv()`** — Extracts environment variables (`KEY=VALUE`) from `KERN_PROCARGS2` sysctl output into a `std::map`. + +## Usage Example + +```cpp +// Retrieve all running PIDs and generate process rows +std::set pids = getProcList(context); + +for (int pid : pids) { + ProcessesRow r; + proc_cred cred; + + if (!genProcCredAndStartTime(context, pid, cred, r)) { + continue; // Skip inaccessible processes + } + + genProcNamePathAndOnDisk(context, pid, cred, r); + genProcRootAndCWD(context, pid, r); + genProcNumThreads(context, pid, r); + genProcArch(context, pid, r); + genProcUniquePid(context, pid, r); + + // Parse command-line arguments + size_t argmax = genMaxArgs(); + std::string args = getProcArgs(pid, argmax); // reads KERN_PROCARGS2 + parseProcCmdline(args, argmax); + r.cmdline_col = args; +} +``` + +> **Note:** Most functions check `context.isAnyColumnUsed()` before performing expensive syscalls, enabling column-level query pushdown optimization. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.quicklook_cache.md b/osquery/tables/system/darwin/.quicklook_cache.md new file mode 100644 index 00000000000..b426e71a6aa --- /dev/null +++ b/osquery/tables/system/darwin/.quicklook_cache.md @@ -0,0 +1,34 @@ + +Implements the `quicklook_cache` osquery table, which reads and parses Apple QuickLook thumbnail cache SQLite databases to expose file preview metadata for macOS systems. + +## Key Components + +### Constants +- **`kReferenceDateOffset`** — Apple epoch offset (`978307200`), converts Apple's 2001-01-01 reference dates to standard UNIX timestamps +- **`kQuicklookPattern`** — GLOB pattern targeting QuickLook `index.sqlite` files under `/private/var/folders/` + +### Functions + +- **`genQuicklookRow(sqlite3_stmt*, Row&)`** — Processes a single SQLite result row, handling three column types: + - `SQLITE_TEXT` — direct string extraction + - `SQLITE_INTEGER` — numeric extraction with date offset correction for `last_hit_date` + - `SQLITE_BLOB` — binary plist parsing to extract `mtime`, `size`, and `label` from the `version` column; also decodes `fs_id` into `volume_id` and `inode`, and combines `folder` + `file_name` into a unified `path` + +- **`genQuicklookCache(QueryContext&)`** — Entry point; GLOBs for all QuickLook cache databases, opens each read-only, executes a JOIN query across `files` and `thumbnails` tables, and aggregates results + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT path, mtime, size, last_hit_date, hit_count, volume_id, inode, cache_path +// FROM quicklook_cache; + +// Internally, the JOIN query used against each cache database: +std::string query = + "SELECT f.*, last_hit_date, hit_count, icon_mode " + "FROM (SELECT rowid, * FROM files) f, " + "(SELECT *, max(last_hit_date) AS last_hit_date FROM thumbnails GROUP BY file_id) t " + "WHERE t.file_id = rowid;"; +``` + +> **Note:** Multiple cache databases may exist since QuickLook stores caches in randomized temporary folders per user session. Each discovered database is opened with `SQLITE_OPEN_READONLY | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_NOMUTEX` to avoid interference with the system. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.sandboxes.md b/osquery/tables/system/darwin/.sandboxes.md new file mode 100644 index 00000000000..66d6e1decd9 --- /dev/null +++ b/osquery/tables/system/darwin/.sandboxes.md @@ -0,0 +1,32 @@ + +Parses macOS sandbox container metadata by reading and extracting structured data from `Container.plist` files found in each user's home directory. + +## Key Components + +- **`kSandboxContainerPaths`** — Constant vector defining the search path (`/Library/Containers/`) for sandbox container directories. +- **`genSandboxContainer(container, results)`** — Parses a single sandbox container directory, reading its `Container.plist` to extract label, user, enabled state, build ID, bundle path, and container path into a result row. +- **`genSandboxContainers(context)`** — Entry point that iterates over all user home directories, discovers sandbox container subdirectories, and calls `genSandboxContainer` for each. + +## Usage Example + +```cpp +// Invoked by the osquery table runtime via the registered table callback +QueryContext context; +QueryData results = osquery::tables::genSandboxContainers(context); + +for (const auto& row : results) { + // Columns available per row: + // row["label"] - application_container_id + // row["user"] - owning user (_USER) + // row["enabled"] - com.apple.security.app-sandbox entitlement (0/1) + // row["build_id"] - sandbox_build_id + // row["bundle_path"] - application_bundle path + // row["path"] - full container directory path +} +``` + +## Notes + +- Silently skips containers where `Container.plist` is missing, unreadable, or malformed. +- Requires the `SandboxProfileDataValidationInfo` and `SandboxProfileDataValidationParametersKey` keys to be present in the plist; otherwise the container is skipped. +- macOS-specific; depends on `osquery::parsePlist` from the Darwin utilities. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.sharing_preferences.md b/osquery/tables/system/darwin/.sharing_preferences.md new file mode 100644 index 00000000000..6c33d9cc819 --- /dev/null +++ b/osquery/tables/system/darwin/.sharing_preferences.md @@ -0,0 +1,52 @@ + +Implements the macOS `sharing_preferences` osquery table, querying the enabled/disabled status of all system sharing services (screen sharing, file sharing, printer sharing, etc.) via `ServiceManagement`, CUPS, and plist files. + +## Key Components + +### Status Query Functions + +| Function | Method | Service Identifier | +|---|---|---| +| `getScreenSharingStatus()` | `SMJobIsEnabled` | `com.apple.screensharing` | +| `getFileSharingStatus()` | `SMJobIsEnabled` | `com.apple.smbd` + `com.apple.AppleFileServer` | +| `getRemoteLoginStatus()` | `SMJobIsEnabled` | `com.openssh.sshd` | +| `getRemoteAppleEventStatus()` | `SMJobIsEnabled` | `com.apple.AEServer` | +| `getDiscSharingStatus()` | `SMJobIsEnabled` | `com.apple.ODSAgent` | +| `getRemoteManagementStatus()` | Plist file existence check | `RemoteManagement.launchd` | +| `getPrinterSharingStatus()` | CUPS `cupsAdminGetServerSettings` | `_share_printers` option | +| `getInterNetSharingStatus()` | Plist key query | `com.apple.nat.plist` → `NAT.Enabled` | +| `getBluetoothSharingStatus()` | Per-user plist scan | `com.apple.Bluetooth.%` → `PrefKeyServicesEnabled` | +| `getContentCachingStatus()` | Plist key query | `com.apple.AssetCache.plist` → `Activated` | + +### Table Generator + +- **`genSharingPreferences(QueryContext&)`** — Aggregates all status checks into a single `QueryData` row with integer `0`/`1` values per sharing type. + +### Helper + +- **`remoteAppleManagementPlistExists()`** — Checks for the Remote Desktop management plist file; used by both screen sharing and remote management status checks to handle mutual exclusivity. + +## Usage Example + +```cpp +// osquery SQL query to retrieve macOS sharing preferences +SELECT + screen_sharing, + file_sharing, + printer_sharing, + remote_login, + remote_management, + remote_apple_events, + internet_sharing, + bluetooth_sharing, + disc_sharing, + content_caching +FROM sharing_preferences; + +// Each column returns 1 (enabled) or 0 (disabled) +// Example result row: +// screen_sharing=0, file_sharing=1, printer_sharing=0, +// remote_login=1, internet_sharing=0, content_caching=1 ... +``` + +> **Note:** This table is macOS-only and requires access to `ServiceManagement`, CUPS libraries, and read permissions on `/Library/Preferences/` plist files. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.sip_config.md b/osquery/tables/system/darwin/.sip_config.md new file mode 100644 index 00000000000..9febdcac55e --- /dev/null +++ b/osquery/tables/system/darwin/.sip_config.md @@ -0,0 +1,35 @@ + +Implements the `sip_config` osquery table for macOS, exposing the current System Integrity Protection (SIP) configuration by querying both the active CSR (Configurable Security Restrictions) runtime state and NVRAM-stored values. + +## Key Components + +- **`kRootlessConfigFlags`** — Map of human-readable SIP flag names to their corresponding bitmask values, sourced from Apple's `csr.h` (e.g., `allow_untrusted_kexts`, `allow_unauthenticated_root`). +- **`genCsrConfigFromNvram(uint32_t& config)`** — Reads the raw `csr-active-config` bytes from the IOKit `IODeviceTree:/options` registry entry, providing the NVRAM-persisted SIP configuration. +- **`genSIPConfig(QueryContext& context)`** — Main table generator; queries the OS version (requires macOS 10.11+), calls `csr_get_active_config()` for the live config, and emits one row for the overall SIP status plus one row per individual flag. + +## Usage Example + +```cpp +// Invoked internally by osquery's table registry. +// Equivalent SQL query: +// SELECT config_flag, enabled, enabled_nvram FROM sip_config; +// +// Example output rows: +// config_flag | enabled | enabled_nvram +// -----------------------------|---------|--------------- +// sip | 1 | 1 +// allow_untrusted_kexts | 0 | 0 +// allow_unrestricted_fs | 0 | 0 +// allow_unauthenticated_root | 0 | 0 +// ... + +QueryData genSIPConfig(QueryContext& context); +``` + +## Column Semantics + +| Column | Description | +|---|---| +| `config_flag` | Flag name (`sip` for overall status, or a specific CSR flag) | +| `enabled` | `1` if the restriction is active (runtime), `0` if bypassed | +| `enabled_nvram` | `1`/`0` from NVRAM; `-1` if NVRAM read failed | \ No newline at end of file diff --git a/osquery/tables/system/darwin/.smbios_tables.md b/osquery/tables/system/darwin/.smbios_tables.md new file mode 100644 index 00000000000..313b337ef1f --- /dev/null +++ b/osquery/tables/system/darwin/.smbios_tables.md @@ -0,0 +1,48 @@ + +Provides Darwin (macOS) SMBIOS table discovery and platform firmware information for osquery tables, handling both Intel (x86_64) and Apple Silicon (aarch64) architectures via IOKit. + +## Key Components + +### `FirmwareType` (enum class) +Enumerates supported firmware types: `EFI`, `iBoot`, and `OpenFirmware`. + +### `getFirmwareType()` +Probes the IORegistry device tree to detect which firmware type is active on the current system. + +### `DarwinSMBIOSParser::discover()` +Locates the `AppleSMBIOS` IOService, reads the `SMBIOS` property, and copies raw SMBIOS binary data into an internal buffer for structured parsing. + +### Table Generator Functions + +| Function | Description | +|---|---| +| `genSMBIOSTables` | Raw SMBIOS structure table entries | +| `genMemoryDevices` | Memory device info (dispatches to arch-specific impl) | +| `genMemoryArrays` | Physical memory array structures | +| `genMemoryArrayMappedAddresses` | Memory array mapped address ranges | +| `genMemoryErrorInfo` | Memory error information structures | +| `genMemoryDeviceMappedAddresses` | Memory device mapped address ranges | +| `genOEMStrings` | OEM string structures from SMBIOS | +| `genIntelPlatformInfo` | Firmware/ROM info for Intel Macs via `IODeviceTree:/rom` | +| `genAarch64PlatformInfo` | Firmware/ROM info for Apple Silicon via `IODeviceTree:/chosen` | + +## Usage Example + +```cpp +// Typical osquery table query flow +QueryContext context; + +// Enumerate SMBIOS tables (all architectures) +QueryData smbios_rows = genSMBIOSTables(context); + +// Architecture-dispatched memory device query +QueryData mem_rows = genMemoryDevices(context); +// → calls genAarch64MemoryDevices() on ARM +// → calls genIntelMemoryDevices() on x86_64 + +// Platform firmware info +QueryData platform_rows = genIntelPlatformInfo(context); +// Returns: vendor, version, date, address, firmware_type, revision, extra +``` + +> **Note:** All functions require macOS with IOKit access. Apple Silicon paths use `IODeviceTree:/chosen` and `sysctlbyname("hw.memsize")` instead of SMBIOS structures, which are unavailable on M1/M2 hardware. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.smbios_utils.md b/osquery/tables/system/darwin/.smbios_utils.md new file mode 100644 index 00000000000..0dd7b70c7c4 --- /dev/null +++ b/osquery/tables/system/darwin/.smbios_utils.md @@ -0,0 +1,39 @@ + +Darwin-specific SMBIOS parser header that extends the cross-platform `SMBIOSParser` base class to access SMBIOS data from the macOS DeviceTree IOKit registry. + +## Key Components + +### `DarwinSMBIOSParser` (class) +Inherits from `SMBIOSParser`. Implements macOS-specific SMBIOS discovery by reading from the IOKit device tree. + +| Member | Type | Description | +|---|---|---| +| `setData()` | `public` | Manually assigns raw SMBIOS table data and its byte length | +| `discover()` | `public` | Locates and loads SMBIOS data from the IOKit registry | +| `~DarwinSMBIOSParser()` | `public` | Frees the raw SMBIOS buffer if allocated | +| `smbios_data_` | `private` | Raw pointer to the allocated SMBIOS byte buffer | + +## Usage Example + +```c +// Discover and parse SMBIOS data on Darwin +DarwinSMBIOSParser parser; + +if (parser.discover()) { + // SMBIOS table data is now loaded and ready for parsing + // Call inherited SMBIOSParser methods to iterate structures + parser.tables([](size_t index, + const SMBStructHeader* hdr, + uint8_t* address, + uint8_t* end, + size_t size) { + // Process each SMBIOS structure + }); +} +``` + +## Notes + +- `discover()` handles all IOKit interaction internally; `setData()` is available for testing or manual injection of raw table bytes +- Memory for `smbios_data_` is allocated during `discover()` and freed in the destructor via `free()`, indicating C-style allocation (e.g., `malloc`) from the IOKit buffer copy +- Part of the `osquery::tables` namespace; relies on the shared parsing logic in the cross-platform `osquery/tables/system/smbios_utils.h` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.smc_keys.md b/osquery/tables/system/darwin/.smc_keys.md new file mode 100644 index 00000000000..70bf215c339 --- /dev/null +++ b/osquery/tables/system/darwin/.smc_keys.md @@ -0,0 +1,56 @@ + +Provides IOKit-based Apple SMC (System Management Controller) key reading utilities for macOS, enabling access to hardware sensor data including temperatures, voltages, currents, fan speeds, and power readings. + +## Key Components + +### Data Structures +- **`SMCKeyData_t`** — Raw SMC key/value payload exchanged with the IOKit driver +- **`SMCValue_t`** — Decoded SMC value with key, type, size, and raw bytes +- **`SMCCMDType`** — Enum of SMC command opcodes (`READ_BYTES`, `READ_INDEX`, `READ_KEYINFO`, etc.) + +### Key Sets (Global Constants) +| Constant | Description | +|---|---| +| `kSMCHiddenKeys` | Sensitive keys excluded from reads (OSK0/OSK1 removed) | +| `kSMCTemperatureKeys` | CPU, GPU, memory, and board temperature sensor keys | +| `kSMCVoltageKeys` | Rail and core voltage sensor keys | +| `kSMCCurrentKeys` | Current sensor keys | +| `kSMCPowerKeys` | Power consumption sensor keys | +| `kSMCFanSpeeds` | Fan speed keys (actual, min, max, target) | +| `kSMCKeyDescriptions` | Human-readable labels for all known SMC keys | + +### `SMCHelper` Class +Non-copyable RAII wrapper around the IOKit `AppleSMC` service connection. + +- **`open()`** — Acquires IOKit master port, matches `AppleSMC` service, opens connection +- **`close()`** — Releases the IOKit service connection +- **`read(key, val)`** — Reads a named SMC key into an `SMCValue_t` output parameter +- **`getKeys()`** — Enumerates all available SMC keys via index reads +- **`call(selector, in, out)`** — Raw `IOConnectCallStructMethod` wrapper + +### Helper Functions +- **`getConvertedValue(smcType, smcVal)`** — Converts raw hex SMC values to `double` supporting types: `ioft` (16.16 fixed-point), `flt` (32-bit float), and others +- **`strtoul` / `strtoull`** — Big-endian byte-array to integer converters + +## Usage Example + +```cpp +SMCHelper smc; +if (smc.open()) { + SMCValue_t val; + if (smc.read("TC0D", &val)) { + // Convert raw bytes to human-readable temperature + std::string hexBytes = /* encode val.bytes */; + double temp = getConvertedValue("sp78", hexBytes); + // temp now holds CPU Package temperature in °C + } + + // Enumerate all available keys + auto keys = smc.getKeys(); + for (const auto& key : keys) { + if (kSMCTemperatureKeys.count(key)) { + auto desc = kSMCKeyDescriptions.at(key); // e.g. "CPU 1 Package" + } + } +} // connection auto-closed on SMCHelper destruction +``` \ No newline at end of file diff --git a/osquery/tables/system/darwin/.startup_items.md b/osquery/tables/system/darwin/.startup_items.md new file mode 100644 index 00000000000..ec22459230f --- /dev/null +++ b/osquery/tables/system/darwin/.startup_items.md @@ -0,0 +1,43 @@ + +Implements the `startup_items` osquery table for macOS, enumerating both legacy Library Startup Items and per-user Login Items by parsing plists and scanning known system directories. + +## Key Components + +### Constants +- **`kLibraryStartupItemPaths`** — System directories scanned for legacy startup items (`/System/Library/StartupItems/`, `/Library/StartupItems/`) +- **`kLoginItemsPlistPath`** — Relative path to each user's login items plist (`Library/Preferences/com.apple.loginitems.plist`) +- **`kLoginItemsKeyPaths`** — Plist key paths searched for login item entries (`SessionItems.CustomListItems`, `privilegedlist.CustomListItems`) + +### Functions + +| Function | Description | +|---|---| +| `genLibraryStartupItems(sysdir, results)` | Iterates a given directory and records each subdirectory as a `Startup Item` row | +| `genLoginItems(sipath, results)` | Parses a user's login items plist and emits a `Login Item` row per entry, resolving alias data to filesystem paths | +| `genStartupItems(context)` | Main table generator — aggregates results from the root login items, all per-user plists, and system library startup directories | + +## Usage Example + +```cpp +// Invoked automatically by osquery's table registry. +// Equivalent manual call: +QueryContext context; +QueryData results = osquery::tables::genStartupItems(context); + +for (const auto& row : results) { + // Each row contains: name, path, type, status, source + std::cout << row.at("type") << ": " + << row.at("name") << " -> " + << row.at("path") << "\n"; +} +``` + +### Row Schema + +| Column | Example Value | +|---|---| +| `name` | `iTunesHelper` | +| `path` | `/Applications/iTunes.app/...` | +| `type` | `Login Item` / `Startup Item` | +| `status` | `enabled` | +| `source` | `/Users/foo/Library/Preferences/com.apple.loginitems.plist` | \ No newline at end of file diff --git a/osquery/tables/system/darwin/.sysctl_utils.md b/osquery/tables/system/darwin/.sysctl_utils.md new file mode 100644 index 00000000000..87314d4e3af --- /dev/null +++ b/osquery/tables/system/darwin/.sysctl_utils.md @@ -0,0 +1,39 @@ + +Provides macOS-specific sysctl MIB (Management Information Base) enumeration and value retrieval utilities for the osquery `sysctl_params` virtual table. + +## Key Components + +### Constants & Lookup Tables + +- **`kControlNames`** — Maps top-level MIB indices (1–8) to subsystem names (`kern`, `vm`, `vfs`, `net`, etc.) +- **`kControlTypes`** — Maps `CTLTYPE_*` constants to human-readable type strings (`int`, `string`, `quad`, `opaque`, `struct`) +- **`MAX_CONTROLS`** — Guards against infinite iteration loops (cap: 1024) + +### Functions + +| Function | Description | +|---|---| +| `opaquePushback()` | Appends a named field/value pair row into `QueryData` results | +| `opaqueControlInfo()` | Decodes opaque/struct MIB values (`clockinfo`, `timeval`, `loadavg`, `xsw_usage`) into individual named fields | +| `genControlInfo()` | Queries description, type, config value, and current value for a single MIB OID, then appends to results | +| `genControlInfoFromName()` | Resolves a sysctl name string to its OID via `sysctlnametomib()`, then delegates to `genControlInfo()` | +| `genAllControls()` | Iterates all (or subsystem-scoped) MIBs using `CTL_DEBUG_ITERATE`, calling `genControlInfo()` for each | + +## Usage Example + +```cpp +QueryData results; +std::map config; // from sysctl.conf + +// Enumerate all kern.* sysctl parameters +genAllControls(results, config, "kern"); + +// Look up a specific sysctl by name +genControlInfoFromName("kern.hostname", results, config); + +// Each row contains: +// r["oid"], r["name"], r["subsystem"], r["type"], +// r["current_value"], r["config_value"] +``` + +> **Platform note:** This file is macOS-specific. It relies on `mach/mach_types.h` and macOS-extended `CTLTYPE` format characters (`I`, `L`, `S`, `Q`) not present in standard BSD sysctl implementations. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.system_info.md b/osquery/tables/system/darwin/.system_info.md new file mode 100644 index 00000000000..f6499f201cf --- /dev/null +++ b/osquery/tables/system/darwin/.system_info.md @@ -0,0 +1,39 @@ + +Collects macOS system information for the osquery `system_info` table by querying Mach kernel APIs, IOKit device tree, sysctl, and SystemConfiguration frameworks. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `getSysctlString` | Function | Reads a named sysctl property and returns its value as a `std::string` | +| `genHostInfo` | Static Function | Populates CPU type/subtype, physical/logical core counts, and total RAM via `host_info()` | +| `genHardwareInfo` | Static Function | Reads hardware version, vendor, model, and serial number from the IOKit device tree | +| `genSystemInfo` | Function | Main table generator; assembles the full row and returns `QueryData` | + +## Data Sources + +```text +hostname / computer_name / local_hostname → SystemConfiguration framework +uuid → osquery::getHostUUID() +cpu_type / cpu_subtype / cores / memory → Mach host_info() +hardware_* → IOKit IODeviceTree:/ +cpu_brand → sysctl machdep.cpu.brand_string +``` + +## Usage Example + +```cpp +// Called automatically by the osquery table dispatcher. +// To invoke directly in a query context: +QueryContext ctx; +QueryData rows = osquery::tables::genSystemInfo(ctx); + +for (const auto& row : rows) { + std::cout << "Hostname: " << row.at("hostname") << "\n"; + std::cout << "CPU Brand: " << row.at("cpu_brand") << "\n"; + std::cout << "Cores: " << row.at("cpu_logical_cores") << "\n"; + std::cout << "RAM: " << row.at("physical_memory") << "\n"; +} +``` + +> **Note:** `genHardwareInfo` trims trailing spaces from IOKit string properties (version, manufacturer, model, serial) using `boost::trim` before storing them in the result row. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.time_machine.md b/osquery/tables/system/darwin/.time_machine.md new file mode 100644 index 00000000000..fde4420e7f1 --- /dev/null +++ b/osquery/tables/system/darwin/.time_machine.md @@ -0,0 +1,41 @@ + +Implements two osquery table-generation functions that parse macOS Time Machine preferences to expose backup and destination metadata via SQL-queryable tables. + +## Key Components + +**Constants** +- `kTimeMachinePrefs` — Path to the Time Machine plist (`/Library/Preferences/com.apple.TimeMachine.plist`) +- `kDestinationKey` / `kDestinationIdKey` — plist keys for navigating destination entries + +**Functions** + +- `genTimeMachineBackups(QueryContext&)` — Reads the Time Machine plist and returns one row per snapshot per destination, exposing `destination_id` and `backup_date` +- `genTimeMachineDestinations(QueryContext&)` — Returns one row per configured backup destination, exposing `destination_id`, `consistency_scan_date`, `root_volume_uuid`, `bytes_used`, `bytes_available`, `encryption`, and `alias` (decoded from a Base64 Mac Alias record) + +## Usage Example + +```cpp +// These functions back the osquery virtual tables: +// SELECT * FROM time_machine_backups; +// SELECT * FROM time_machine_destinations; + +// Internally, each function follows this pattern: +pt::ptree tree; +if (!osquery::parsePlist(kTimeMachinePrefs, tree)) { + return results; // plist missing or unreadable +} + +// genTimeMachineDestinations also decodes the BackupAlias field: +// - Base64-decodes the raw alias blob +// - Validates the record length from bytes [4:5] +// - Extracts the volume name using the Pascal-string length at byte [10] +std::string alias_data = base64::decode(dest.second.get("BackupAlias", "")); +unsigned char name_len = alias_data[10]; +r["alias"] = alias_data.substr(11, name_len); +``` + +## Notes + +- macOS only — depends on the Darwin plist utility and a plist path specific to macOS +- The `BackupAlias` field uses the classic Mac OS [Alias Record](https://developer.apple.com/library/archive/documentation/mac/pdf/Devices/AliasManager.pdf) binary format; the code validates record length before extracting the volume name to avoid out-of-bounds reads +- Returns an empty result set if the plist is absent or the `Destinations` key is missing \ No newline at end of file diff --git a/osquery/tables/system/darwin/.usb_devices.md b/osquery/tables/system/darwin/.usb_devices.md new file mode 100644 index 00000000000..0303c92f959 --- /dev/null +++ b/osquery/tables/system/darwin/.usb_devices.md @@ -0,0 +1,41 @@ + +Enumerates USB devices on macOS by querying the IOKit registry, extracting device metadata and returning it as osquery table rows. + +## Key Components + +- **`decodeUSBBCD(uint16_t bcd)`** — Converts a USB Binary-Coded Decimal (BCD) version field into a human-readable `"major.minor"` string (e.g., `"2.0"`). +- **`genUSBDevice(io_service_t& device, QueryData& results)`** — Reads IOKit properties for a single USB device and appends a populated `Row` to `results`. Skips devices missing both `vendor_id` and `model_id` (filters out PCI simulation hubs on macOS 10.11+). +- **`genUSBDevices(QueryContext& context)`** — Entry point for the osquery `usb_devices` virtual table. Iterates all IOKit services matching `kIOUSBDeviceClassName` and calls `genUSBDevice` for each. + +## Columns Populated + +| Column | IOKit Source | +|---|---| +| `usb_address` | `USB Address` | +| `usb_port` | `PortNum` | +| `model` | `USB Product Name` / registry name | +| `model_id` | `idProduct` (hex) | +| `vendor` | `USB Vendor Name` | +| `vendor_id` | `idVendor` (hex) | +| `version` | `bcdDevice` (BCD decoded) | +| `class` / `subclass` | `bDeviceClass` / `bDeviceSubClass` | +| `serial` | `USB Serial Number` / `iSerialNumber` | +| `removable` | `non-removable` property | + +## Usage Example + +```cpp +// Called internally by osquery when the usb_devices table is queried: +// SELECT * FROM usb_devices; + +QueryContext context; +QueryData rows = genUSBDevices(context); + +for (const auto& row : rows) { + std::cout << row.at("vendor") << " " + << row.at("model") << " v" + << row.at("version") << "\n"; +} +``` + +> **Platform note:** macOS only (`IOKit/usb/IOUSBLib.h`). Devices lacking both `vendor_id` and `model_id` are silently excluded to avoid surfacing non-USB PCI phantom entries. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.virtual_memory_info.md b/osquery/tables/system/darwin/.virtual_memory_info.md new file mode 100644 index 00000000000..b24d160d507 --- /dev/null +++ b/osquery/tables/system/darwin/.virtual_memory_info.md @@ -0,0 +1,39 @@ + +Queries macOS virtual memory statistics using the Mach kernel API and exposes them as an osquery table row. + +## Key Components + +### `genVirtualMemoryInfo(QueryContext& context)` +- The sole table-generation function registered with the osquery table system +- Calls `host_statistics64()` with `HOST_VM_INFO64` to retrieve a `vm_statistics64` struct from the Mach host +- On `KERN_SUCCESS`, maps 22 memory counters into a single `Row` and returns it + +### Columns Populated + +| Column | Source Field | Notes | +|---|---|---| +| `free` | `free_count - speculative_count` | Non-speculative free pages | +| `active` | `active_count` | Pages in active use | +| `inactive` | `inactive_count` | Reclaimable pages | +| `speculative` | `speculative_count` | Speculatively loaded pages | +| `wired` | `wire_count` | Non-pageable kernel pages | +| `faults` / `copy` | `faults` / `cow_faults` | Page fault counters | +| `compressor` / `compressed` | compressor fields | Compressed memory stats | +| `swap_ins` / `swap_outs` | `swapins` / `swapouts` | Swap activity | + +## Usage Example + +```cpp +// Invoked automatically by osquery's table dispatcher; +// equivalent manual call pattern: +QueryContext ctx; +QueryData results = genVirtualMemoryInfo(ctx); + +for (const auto& row : results) { + std::cout << "Free pages: " << row.at("free") << "\n"; + std::cout << "Active pages: " << row.at("active") << "\n"; + std::cout << "Swap ins: " << row.at("swap_ins") << "\n"; +} +``` + +> **Platform note:** This file is macOS-only. It depends on `` and the Mach `host_statistics64` API, which are not available on Linux or Windows. \ No newline at end of file diff --git a/osquery/tables/system/darwin/.xprotect.md b/osquery/tables/system/darwin/.xprotect.md new file mode 100644 index 00000000000..d77b322800a --- /dev/null +++ b/osquery/tables/system/darwin/.xprotect.md @@ -0,0 +1,40 @@ + +Implements osquery table generators for macOS XProtect malware detection data, parsing XProtect plist configuration files and user diagnostic reports to expose signature entries, metadata, and detection reports as queryable tables. + +## Key Components + +| Function | Description | +|---|---| +| `genMatches()` | Recursively parses `Matches` nodes from an XProtect plist entry, extracting file identity, type, and pattern metadata into rows | +| `genXProtectEntry()` | Processes a single XProtect dictionary entry, combining description, launch type, and file matches into result rows | +| `genXProtectEntries()` | Table generator that locates and parses `XProtect.plist` from known macOS version-specific directories | +| `genXProtectMeta()` | Table generator that parses `XProtect.meta.plist`, extracting Java minimum versions, blacklisted extensions, and blacklisted plugins | +| `genXProtectReports()` | Table generator that enumerates all users and reads their per-user XProtect detection report plists from `~/Library/Logs/DiagnosticReports` | +| `getXProtectReportFiles()` | Helper that lists files in a user's diagnostic reports directory, filtering for `XProtect`-prefixed filenames | +| `findAndParsePlist()` | Utility that checks path existence and parses a plist into a `boost::property_tree`, returning success status | + +### Key Constants + +- `kPotentialXProtectDirs` — two candidate directories covering different macOS versions where XProtect bundles reside +- `kXProtectReportsPath` — relative path within each home directory to find diagnostic report logs + +## Usage Example + +```cpp +// Called by osquery table dispatch — not invoked directly. +// Registered tables: xprotect_entries, xprotect_meta, xprotect_reports + +QueryContext context; + +// Query XProtect signature definitions +QueryData entries = genXProtectEntries(context); +// Each row: name, launch_type, identity, filetype, filename, optional, uses_pattern + +// Query XProtect metadata (plugin/extension blocklist) +QueryData meta = genXProtectMeta(context); +// Each row: identifier, min_version, type, developer_id + +// Query per-user XProtect detection reports +QueryData reports = genXProtectReports(context); +// Each row: name, user_action, time +``` \ No newline at end of file diff --git a/osquery/tables/system/freenux/.cpu_time.md b/osquery/tables/system/freenux/.cpu_time.md new file mode 100644 index 00000000000..d7bb6429829 --- /dev/null +++ b/osquery/tables/system/freenux/.cpu_time.md @@ -0,0 +1,42 @@ + +Parses `/proc/stat` to extract per-core CPU time statistics for the `cpu_time` osquery table on Linux. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kProcStat` | `const string` | Path constant pointing to `/proc/stat` | +| `procFromFile()` | Function | Reads `/proc/stat` and returns only per-core `cpu` lines, stripping the aggregate `cpu ` summary line | +| `genCpuTimeLine()` | Static Function | Parses a single CPU line into a `Row` with all time fields | +| `genCpuTime()` | Function | Table entry point — orchestrates file reading and row generation | + +## Data Fields Populated + +Each row represents one CPU core with the following fields sourced from `/proc/stat`: + +```text +core | user | nice | system | idle | iowait | irq | softirq | steal | guest | guest_nice +``` + +Fields `steal`, `guest`, and `guest_nice` default to `"0"` if not present in the kernel output (older kernels). + +## Usage Example + +```cpp +// Called by the osquery table infrastructure +QueryData genCpuTime(QueryContext& context) { + QueryData results; + + // Reads /proc/stat, filters per-core lines (cpu0, cpu1, ...) + auto proc_lines = procFromFile(kProcStat); + + // Each line parsed into a Row: core, user, nice, system, idle, ... + for (const auto& line : proc_lines) { + genCpuTimeLine(line, results); + } + + return results; // One Row per logical CPU core +} +``` + +> **Note:** The aggregate `cpu ` line (no core number) is intentionally dropped — only per-core entries (`cpu0`, `cpu1`, ...) are returned. A minimum of 8 fields per line is required; malformed lines are silently skipped. \ No newline at end of file diff --git a/osquery/tables/system/linux/.acpi_tables.md b/osquery/tables/system/linux/.acpi_tables.md new file mode 100644 index 00000000000..1be343b362b --- /dev/null +++ b/osquery/tables/system/linux/.acpi_tables.md @@ -0,0 +1,39 @@ + +Implements the `acpi_tables` osquery table for Linux, reading and hashing ACPI firmware table entries exposed by the kernel under `/sys/firmware/acpi/tables`. + +## Key Components + +- **`kLinuxACPIPath`** — Constant path (`/sys/firmware/acpi/tables`) where the Linux kernel exposes parsed ACPI table nodes. +- **`genACPITable(table, results)`** — Recursively processes a single ACPI table entry. If the path is a directory, it descends into child entries. For regular files, it reads the content, records the file size, and computes an MD5 hash. +- **`genACPITables(context)`** — Table generator entry point. Lists all entries under `kLinuxACPIPath` and delegates each to `genACPITable`, returning the accumulated `QueryData`. + +## Usage Example + +This table is queried via the osquery SQL interface: + +```sql +SELECT name, size, md5 FROM acpi_tables; +``` + +Example output: + +```text +name | size | md5 +-------|------|---------------------------------- +DSDT | 8192 | d41d8cd98f00b204e9800998ecf8427e +FACP | 116 | 9a0364b9e99bb480dd25e1f0284c8555 +APIC | 156 | 1679091c5a880faf6fb5e6087eb1b2dc +``` + +### Programmatic Row Structure + +Each result row contains the following columns: + +```cpp +Row r; +r["name"] = table_path.filename().string(); // ACPI table filename +r["size"] = INTEGER(table_content.size()); // File size in bytes (-1 on read error) +r["md5"] = hashFromBuffer(...); // MD5 hash of table binary content +``` + +> **Note:** This implementation is Linux-specific. If `/sys/firmware/acpi/tables` is inaccessible or not exposed by the kernel, the table returns an empty result set rather than an error. \ No newline at end of file diff --git a/osquery/tables/system/linux/.apparmor_profiles.md b/osquery/tables/system/linux/.apparmor_profiles.md new file mode 100644 index 00000000000..83c4e00d43b --- /dev/null +++ b/osquery/tables/system/linux/.apparmor_profiles.md @@ -0,0 +1,30 @@ + +Implements the osquery table generator for AppArmor profiles, parsing kernel security policy files from `/sys/kernel/security/apparmor/policy/profiles` into structured query results. + +## Key Components + +- **`AppArmorProfile`** — Internal struct holding profile fields: `path`, `name`, `attach`, `mode`, `sha1`, and `sha256`. +- **`generateProfile()`** — Reads individual profile attributes from sysfs files, with automatic fallback from `sha256` (Linux ≥ 6.8) to `sha1`. +- **`generateProfilePathQueue()`** — Enumerates subdirectories at a given path into a work queue for iterative traversal. +- **`generateProfileList()`** — Iteratively processes the profile path queue (including nested subprofiles under `profiles/` subdirectories), building the full `AppArmorProfileList`. +- **`genAppArmorProfiles()`** — Public osquery table entry point; guards against missing AppArmor support and maps the profile list into `QueryData` rows. + +## Usage Example + +```cpp +// Called internally by the osquery table registry. +// Equivalent SQL query: +// SELECT name, mode, attach, sha1, sha256, path +// FROM apparmor_profiles; + +QueryData results = genAppArmorProfiles(context); +// Each Row contains: +// row["name"] -> e.g. "firefox" +// row["mode"] -> e.g. "enforce" +// row["attach"] -> e.g. "/usr/lib/firefox/firefox" +// row["sha256"] -> hex digest (Linux >= 6.8) or empty +// row["sha1"] -> hex digest (older kernels) or empty +// row["path"] -> e.g. "firefox//subprofile" +``` + +> **Note:** Subprofiles are resolved recursively via an iterative queue. The `path` field uses `//` as the parent/child separator (e.g., `"parent//child"`), matching AppArmor's native namespace convention. Returns an empty result set if AppArmor is not present on the system. \ No newline at end of file diff --git a/osquery/tables/system/linux/.apt_sources.md b/osquery/tables/system/linux/.apt_sources.md new file mode 100644 index 00000000000..190773b5b21 --- /dev/null +++ b/osquery/tables/system/linux/.apt_sources.md @@ -0,0 +1,41 @@ + +Defines data structures and parsing utilities for APT (Advanced Package Tool) source list entries used by the `apt_sources` osquery table implementation. + +## Key Components + +### `AptSource` struct +Represents a single APT repository source with the following fields: + +| Field | Type | Description | +|---|---|---| +| `base_uri` | `std::string` | Repository base URI (e.g., `http://archive.ubuntu.com/ubuntu`) | +| `name` | `std::string` | Distribution/suite name | +| `cache_file` | `std::vector` | Components used to construct the local cache filename | + +### Functions + +- **`parseAptSourceLine`** — Parses a single line from a traditional `sources.list` format into an `AptSource` struct +- **`parseDeb822Block`** — Parses a multi-line [Deb822-format](https://wiki.debian.org/SourcesList#Deb822_format) stanza block into one or more `AptSource` entries +- **`getCacheFilename`** — Reconstructs the APT cache filename from the `cache_file` components vector + +## Usage Example + +```cpp +#include "apt_sources.h" + +osquery::tables::AptSource source; + +// Parse a classic one-line sources.list entry +std::string line = "deb http://archive.ubuntu.com/ubuntu focal main restricted"; +Status s = osquery::tables::parseAptSourceLine(line, source); + +if (s.ok()) { + std::string cache = osquery::tables::getCacheFilename(source.cache_file); + // cache -> "archive.ubuntu.com_ubuntu_dists_focal_main_binary-amd64_Packages" +} + +// Parse a Deb822-format block +std::vector sources; +std::string block = "Types: deb\nURIs: http://archive.ubuntu.com/ubuntu\nSuites: focal\nComponents: main"; +Status s2 = osquery::tables::parseDeb822Block(block, sources); +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.block_devices.md b/osquery/tables/system/linux/.block_devices.md new file mode 100644 index 00000000000..da2b550c922 --- /dev/null +++ b/osquery/tables/system/linux/.block_devices.md @@ -0,0 +1,57 @@ + +Implements the osquery `block_devices` table for Linux, enumerating block devices via `libudev` and enriching them with filesystem metadata from `blkid` and LVM physical/logical volume relationships. + +## Key Components + +### `populatePVChildren` +```cpp +void populatePVChildren(lvm_t lvm, + const std::string& devname, + const std::string& pvid, + std::map& lvm_lv2pv); +``` +Resolves LVM physical volume (PV) to logical volume (LV) mappings. For a given PV UUID, opens its volume group, iterates all LVs, and populates a `devname → parent` lookup map using kernel major/minor device numbers. + +### `getBlockDevice` *(static)* +```cpp +static void getBlockDevice(struct udev_device* dev, + QueryData& results, + std::map& lvm_lv2pv); +``` +Extracts per-device row data for a single `udev` device, collecting: +- **Name** — device node path +- **Parent** — resolved via udev subsystem traversal or LVM map fallback +- **Size / block_size** — from sysfs attributes +- **Model / vendor** — from parent SCSI device attributes (trimmed) +- **Type / UUID / label** — from `blkid` superblock probing +- Triggers `populatePVChildren` when an LVM filesystem type is detected + +### `genBlockDevs` +```cpp +QueryData genBlockDevs(QueryContext& context); +``` +Table entry point. Enumerates all `block` subsystem devices via `udev`, calls `getBlockDevice` for each, and returns the aggregated `QueryData`. Warns (via `VLOG`) when not running as root, as LVM data requires elevated privileges. + +## Usage Example + +```bash +# Query via osqueryi (must run as root for full LVM/type data) +sudo osqueryi "SELECT name, parent, size, block_size, type, uuid, label, model, vendor FROM block_devices;" +``` + +```text ++----------+--------+------------+------------+------+--------------------------------------+-------+-------+--------+ +| name | parent | size | block_size | type | uuid | label | model | vendor | ++----------+--------+------------+------------+------+--------------------------------------+-------+-------+--------+ +| /dev/sda | | 1953525168 | 512 | | | | VBOX | ATA | +| /dev/sda1|/dev/sda| 1953523712 | 512 | ext4 | a1b2c3d4-e5f6-... | root | | | ++----------+--------+------------+------------+------+--------------------------------------+-------+-------+--------+ +``` + +## Dependencies + +| Library | Purpose | +|---|---| +| `libudev` | Block device enumeration and sysfs attribute access | +| `blkid` | Filesystem type, UUID, and label probing | +| `lvm2app` | LVM physical/logical volume relationship resolution | \ No newline at end of file diff --git a/osquery/tables/system/linux/.certificates.md b/osquery/tables/system/linux/.certificates.md new file mode 100644 index 00000000000..58802726902 --- /dev/null +++ b/osquery/tables/system/linux/.certificates.md @@ -0,0 +1,37 @@ + +Implements the osquery `certificates` table for Linux/POSIX systems, parsing X.509 certificates from PEM bundle files using OpenSSL and exposing them as queryable rows. + +## Key Components + +### Constants +- **`kDefaultBundlePathList`** — Default PEM bundle paths searched when no `path` constraint is provided (`/etc/pki/tls/certs/ca-bundle.crt`, `/etc/ssl/certs/ca-certificates.crt`) + +### Types +- **`CertificateInformation`** — Struct holding all extracted certificate fields: subject, issuer, validity window, algorithms, key info, SHA1 digest, serial, CA/self-signed flags +- **`OpenSSLError`** — Enum of failure modes: `OpenFailed`, `ReadFailed`, `InvalidX509Info`, `InvalidX509` +- **`UniqueBIO` / `UniqueX509InfoStack` / `UniqueX509Info`** — RAII wrappers around OpenSSL types using `std::unique_ptr` with custom deleters + +### Internal Functions +| Function | Purpose | +|---|---| +| `createFileBasedBIO` | Opens a PEM file as an OpenSSL `BIO` | +| `readCertificateStackFromBIO` | Reads all `X509_INFO` entries from a BIO | +| `generateCertificateInformation` | Extracts all fields from a single `X509_INFO` into `CertificateInformation` | +| `parseX509InfoStack` | Iterates a list of `X509_INFO` and produces `CertificateInformationList` | +| `enumerateBundleCertificates` | Orchestrates file open → read → filter → parse pipeline for one bundle | + +### Table Entry Point +- **`genCerts(QueryContext&)`** — Registered osquery table generator; resolves bundle paths from query constraints or defaults, then emits one `Row` per certificate + +## Usage Example + +```cpp +// osquery SQL — query system CA certificates +SELECT common_name, issuer, not_valid_after, sha1, ca, self_signed +FROM certificates; + +// Query a specific bundle file +SELECT common_name, signing_algorithm, key_strength +FROM certificates +WHERE path = '/etc/ssl/certs/ca-certificates.crt'; +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.cpu_info.md b/osquery/tables/system/linux/.cpu_info.md new file mode 100644 index 00000000000..70187339944 --- /dev/null +++ b/osquery/tables/system/linux/.cpu_info.md @@ -0,0 +1,45 @@ + +Queries CPU hardware information on Linux by parsing SMBIOS (System Management BIOS) data structures and returning processor details as an osquery table. + +## Key Components + +- **`genCpuInfo(QueryContext& context)`** — Main table generation function registered as the `cpu_info` virtual table handler. Orchestrates SMBIOS discovery and result decoration. +- **`LinuxSMBIOSParser`** — Parses raw SMBIOS memory structures; calls `discover()` to locate the SMBIOS entry point, then iterates processor records via `tables()`. +- **`genSMBIOSProcessor(...)`** — (from `smbios_utils.h`) Extracts individual processor fields from each SMBIOS Type 4 structure into `QueryData` rows. +- **`kSMBIOSProcessorTypeFriendlyName`** — Lookup map that maps raw `processor_type` values to human-readable names (e.g., `"CPU"`, `"GPU"`), used to construct the synthesized `device_id` column. + +## Usage Example + +```cpp +// Called internally by the osquery table framework when a query runs against +// the cpu_info virtual table: +// SELECT * FROM cpu_info; + +QueryData genCpuInfo(QueryContext& context) { + QueryData results; + LinuxSMBIOSParser parser; + + if (!parser.discover()) { + VLOG(1) << "Failed to discover SMBIOS entry point"; + return results; // Returns empty set if SMBIOS is unavailable + } + + // Iterate SMBIOS processor structures + parser.tables(([&results](size_t index, + const SMBStructHeader* hdr, + uint8_t* address, + uint8_t* textAddrs, + size_t size) { + genSMBIOSProcessor(index, hdr, address, textAddrs, size, results); + })); + + // Synthesize device_id (not in SMBIOS spec) as "" + // e.g., "Central Processor0", "Central Processor1" + std::int32_t device_id = 0; + for (auto& row : results) { /* ... decorates each row */ } + + return results; +} +``` + +> **Note:** The `device_id` column is a synthetic field not present in the SMBIOS specification. It is constructed by combining the friendly processor type name with a zero-based sequential index per processor type. \ No newline at end of file diff --git a/osquery/tables/system/linux/.deb_packages.md b/osquery/tables/system/linux/.deb_packages.md new file mode 100644 index 00000000000..336439fea58 --- /dev/null +++ b/osquery/tables/system/linux/.deb_packages.md @@ -0,0 +1,41 @@ + +Implements the `deb_packages` osquery virtual table, querying installed Debian package metadata from the dpkg database on Linux systems. + +## Key Components + +### Constants +- **`kAdminDir`** — Default dpkg admin directory (`/var/lib/dpkg`), the standard location for dpkg package state files. + +### Structs +- **`ErrorLog`** — Buffers error messages and error codes for deferred logging, used to work around log file creation restrictions during privilege dropping (see issue #8286). + +### Functions + +| Function | Description | +|---|---| +| `logError()` | Formats and emits a GLOG_ERROR entry with error code description and admin directory context. | +| `genDebPackagesImpl()` | Core implementation: resolves admin directories, drops privileges to `nobody`, queries the dpkg database, and returns package rows. | +| `genDebPackages()` | Public entry point; dispatches to namespace-aware generation or direct execution depending on query context. | + +## Usage Example + +```cpp +// Query with default admindir (/var/lib/dpkg) +QueryContext context; +QueryData results = osquery::tables::genDebPackages(context); + +// Query with a custom admindir constraint +context.constraints["admindir"].add( + Constraint(EQUALS, "/custom/dpkg/admindir")); +QueryData results = osquery::tables::genDebPackages(context); + +// Each row contains: +// name, version, arch, status, revision, priority, +// section, source, size, maintainer, admindir +``` + +## Notes + +- Privileges are temporarily dropped to `nobody` via `DropPrivileges` before accessing the dpkg database. This is **not** a security boundary — it prevents accidental modification of the package database by libdpkg. +- Errors encountered during privilege-reduced execution are buffered in `errorLogBuffer` and only logged **after** privileges are restored, as reduced permissions may block log file creation. +- Supports namespace-aware execution via `generateInNamespace` for container/namespace query contexts. \ No newline at end of file diff --git a/osquery/tables/system/linux/.disk_encryption.md b/osquery/tables/system/linux/.disk_encryption.md new file mode 100644 index 00000000000..61d9a7b699a --- /dev/null +++ b/osquery/tables/system/linux/.disk_encryption.md @@ -0,0 +1,43 @@ + +Queries Linux block devices for Full Disk Encryption (FDE) status using `libcryptsetup`, populating an osquery table with per-device encryption state, type, and UUID. + +## Key Components + +### Constants +| Constant | Value | +|---|---| +| `kEncryptionStatusEncrypted` | `"encrypted"` | +| `kEncryptionStatusNotEncrypted` | `"not encrypted"` | +| `kEncryptionStatusUndefined` | `"undefined"` | + +### Functions + +**`genFDEStatusForBlockDevice(block_device, block_devices, encrypted_rows)`** +Recursively resolves the encryption status of a single block device via `libcryptsetup`. If a device has no direct crypt status (e.g. an LVM partition), it walks up to the parent device and inherits its encryption state. Populates the `type` field as a hyphen-joined string of cipher type, algorithm, and mode (e.g. `LUKS2-aes-xts-plain64`). + +**`genFDEStatus(context)`** *(osquery table generator)* +Entry point for the `disk_encryption` table. Must run as root. Queries the `block_devices` virtual table, caches results, then calls `genFDEStatusForBlockDevice` for each device. Uses a two-map caching strategy to avoid redundant lookups and `O(N²)` traversal cost. + +## Usage Example + +```cpp +// Called internally by the osquery table infrastructure. +// Exposes results via SQL: +// SELECT name, uuid, encrypted, encryption_status, type +// FROM disk_encryption; + +// Example row output for an active LUKS device: +// name = "dm-0" +// uuid = "abcd-1234-..." +// encrypted = "1" +// encryption_status = "encrypted" +// type = "LUKS2-aes-xts-plain64" + +// Example row output for an unencrypted root device: +// name = "sda" +// encrypted = "0" +// encryption_status = "not encrypted" +// type = "" +``` + +> **Note:** `genFDEStatus` returns an empty result set if the process is not running as `root` (`uid == 0`). Ensure osquery runs with appropriate privileges for disk encryption queries. \ No newline at end of file diff --git a/osquery/tables/system/linux/.extended_attributes.md b/osquery/tables/system/linux/.extended_attributes.md new file mode 100644 index 00000000000..c188118d498 --- /dev/null +++ b/osquery/tables/system/linux/.extended_attributes.md @@ -0,0 +1,47 @@ + +Implements the `extended_attributes` osquery table, which queries Linux extended file attributes (xattrs) and POSIX capabilities for specified file paths. + +## Key Components + +### Internal Helpers + +- **`getPathListFromConstraints(context)`** — Resolves file paths from SQL query constraints, supporting both `EQUALS` and `LIKE` (glob) patterns via `resolveFilePattern`. +- **`getCapabilities(capabilities, path)`** — Reads POSIX security capabilities from a file using `cap_get_file` / `cap_to_text`, returning a human-readable string. + +### Public Functions + +- **`generateXattrRowsForPath(output, path)`** — Core logic that builds `QueryData` rows for a single path: + - Fetches raw xattrs via `getExtendedAttributes()` + - Emits a decoded `security.capability` row when POSIX capabilities are present + - Falls back to raw xattr data if capability decoding fails + - Marks values as `base64=0` (printable) or `base64=1` (binary/encoded) based on null-terminator and `std::isprint` checks + +- **`genXattr(context)`** — Table generator entry point; iterates resolved paths and calls `generateXattrRowsForPath`, logging errors per-path without aborting. + +## Usage Example + +Querying via osquery SQL: + +```sql +SELECT path, key, value, base64 +FROM extended_attributes +WHERE path = '/usr/bin/ping'; +``` + +Or with glob patterns: + +```sql +SELECT path, key, value, base64 +FROM extended_attributes +WHERE path LIKE '/etc/%.conf'; +``` + +Each output row contains: + +| Column | Description | +|---|---| +| `path` | Full file path | +| `directory` | Parent directory | +| `key` | Xattr name (e.g., `security.capability`) | +| `value` | Printable string or base64-encoded binary | +| `base64` | `1` if value is base64-encoded, `0` otherwise | \ No newline at end of file diff --git a/osquery/tables/system/linux/.groups.md b/osquery/tables/system/linux/.groups.md new file mode 100644 index 00000000000..519eb4c44e9 --- /dev/null +++ b/osquery/tables/system/linux/.groups.md @@ -0,0 +1,32 @@ + +Implements the osquery `groups` table for Linux/macOS, querying system group information via POSIX group database APIs (`grp.h`). + +## Key Components + +- **`setGroupRow(Row&, const group*)`** — Populates a result row with group fields: `groupname`, `gid`, `gid_signed`, and `pid_with_namespace`. + +- **`genGroupsImpl(QueryContext&, Logger&)`** — Core implementation that retrieves group data using thread-safe POSIX calls: + - `getgrgid_r()` — targeted lookup when a `gid` equality constraint is present + - `getgrent_r()` — full enumeration of `/etc/group` (or NSS sources) with deduplication via a `std::set` + - Dynamically doubles buffer size on `ERANGE` to handle large group entries + +- **`genGroups(QueryContext&)`** — Table entry point; dispatches to `generateInNamespace()` when a namespace constraint is detected, otherwise runs `genGroupsImpl` directly with a `GLOGLogger`. + +## Usage Example + +```cpp +// osquery SQL — basic usage +SELECT groupname, gid, gid_signed FROM groups; + +// Filtered lookup by GID (uses getgrgid_r path) +SELECT groupname FROM groups WHERE gid = 0; + +// With namespace constraint (triggers generateInNamespace) +SELECT groupname, gid FROM groups WHERE pid_with_namespace = 1234; +``` + +## Notes + +- Deduplication prevents duplicate rows when the same GID appears multiple times in the group database. +- Buffer starts at 16 KB and doubles on `ERANGE`, ensuring compatibility with entries that have large member lists. +- `pid_with_namespace` is always `"0"` in the row itself; namespace routing is handled at the dispatch level in `genGroups`. \ No newline at end of file diff --git a/osquery/tables/system/linux/.intel_me.md b/osquery/tables/system/linux/.intel_me.md new file mode 100644 index 00000000000..a1866f60872 --- /dev/null +++ b/osquery/tables/system/linux/.intel_me.md @@ -0,0 +1,46 @@ + +Implements the Linux-specific osquery table for querying Intel Management Engine (ME) firmware version data via the MEI kernel driver. + +## Key Components + +- **`kIntelME`** — Constant string path to the Linux MEI device node (`/dev/mei0`). +- **`genIntelMEVersion(int mei_fd, QueryData& results)`** — Communicates with the MEI driver over an open file descriptor using `ioctl` and `read`/`write` syscalls to retrieve and parse the firmware version, appending a formatted `major.minor.hotfix.build` string to results. +- **`getIntelMEInfo(QueryContext& context)`** — Entry point for the osquery table; opens `/dev/mei0`, delegates to `genIntelMEVersion`, and returns the collected rows. + +## Usage Example + +This file backs the `intel_me_info` osquery virtual table. Query it from the osquery shell: + +```sql +SELECT version FROM intel_me_info; +``` + +Example output: + +```text ++------------------+ +| version | ++------------------+ +| 11.8.50.3425 | ++------------------+ +``` + +Programmatic flow within the implementation: + +```cpp +// 1. Open MEI device +auto mei_fd = open("/dev/mei0", O_RDWR); + +// 2. Send GUID via ioctl to negotiate API access +ioctl(mei_fd, INTEL_ME_LINUX_IOCTL, buffer); + +// 3. Send GetFirmwareVersion command (0x0) +write(mei_fd, buffer, 4); + +// 4. Read back structured mei_version +read(mei_fd, &version, sizeof(version)); + +close(mei_fd); +``` + +> **Note:** Requires the `mei` kernel module to be loaded and `/dev/mei0` to exist. If the device is inaccessible or returns an unsupported protocol version, the table returns an empty result set and logs a `VLOG(1)` message. \ No newline at end of file diff --git a/osquery/tables/system/linux/.kernel_info.md b/osquery/tables/system/linux/.kernel_info.md new file mode 100644 index 00000000000..eee8cb65dba --- /dev/null +++ b/osquery/tables/system/linux/.kernel_info.md @@ -0,0 +1,36 @@ + +Provides the osquery table implementation for querying Linux kernel information by parsing `/proc/cmdline` and `/proc/version`. + +## Key Components + +- **`kKernelArgumentsPath`** — Constant path to `/proc/cmdline`, which contains the kernel boot arguments. +- **`kKernelSignaturePath`** — Constant path to `/proc/version`, which contains the kernel version string. +- **`genKernelInfo(QueryContext&)`** — Table generator function that reads and parses both proc files, returning a single-row `QueryData` result. + +## Parsed Output Columns + +| Column | Source | Description | +|---|---|---| +| `path` | `/proc/cmdline` | Boot image path extracted from `BOOT_IMAGE=` argument | +| `device` | `/proc/cmdline` | Root device extracted from `root=` argument | +| `arguments` | `/proc/cmdline` | Remaining kernel boot arguments | +| `version` | `/proc/version` | Kernel version string (third token after `version` keyword) | + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT version, path, device, arguments FROM kernel_info; + +// Internally, genKernelInfo parses: +// /proc/cmdline → "BOOT_IMAGE=/boot/vmlinuz root=/dev/sda1 quiet splash" +// /proc/version → "Linux version 5.15.0 (gcc ...) #1 SMP ..." + +QueryData results = genKernelInfo(context); +// results[0]["version"] → "5.15.0" +// results[0]["path"] → "/boot/vmlinuz" +// results[0]["device"] → "/dev/sda1" +// results[0]["arguments"] → "quiet splash" +``` + +> **Note:** This implementation is Linux-specific, relying on the `/proc` virtual filesystem. Missing proc files are logged via `TLOG` but do not cause a hard failure — an empty row is still returned. \ No newline at end of file diff --git a/osquery/tables/system/linux/.kernel_keys.md b/osquery/tables/system/linux/.kernel_keys.md new file mode 100644 index 00000000000..e9c8e00239d --- /dev/null +++ b/osquery/tables/system/linux/.kernel_keys.md @@ -0,0 +1,45 @@ + +Parses `/proc/keys` on Linux to expose kernel keyring entries as structured osquery table rows. + +## Key Components + +- **`kProcKeysPath`** — Constant path string pointing to `/proc/keys`, the Linux kernel's keyring listing interface. +- **`genKernelKeys(QueryContext&)`** — Table generator function that reads and parses the kernel keys file, returning a `QueryData` collection of key entries. + +## Behavior + +The function performs three steps: + +1. **Path validation** — Verifies `/proc/keys` exists; returns empty if not found. +2. **File read** — Reads the full file content using `osquery::readFile`; logs and returns empty on failure. +3. **Line parsing** — Splits content by newline, then each line by whitespace into exactly 10 fields. Lines that don't match this format are skipped with a `VLOG` warning. + +Each valid row maps to the following columns: + +| Column | Source Field | +|---|---| +| `serial_number` | `details[0]` | +| `flags` | `details[1]` | +| `usage` | `details[2]` | +| `timeout` | `details[3]` | +| `permissions` | `details[4]` | +| `uid` | `details[5]` | +| `gid` | `details[6]` | +| `type` | `details[7]` | +| `description` | `details[8] + details[9]` | + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT * FROM kernel_keys; +// SELECT serial_number, type, uid, description FROM kernel_keys WHERE type = 'keyring'; + +QueryContext ctx; +QueryData rows = genKernelKeys(ctx); +for (const auto& row : rows) { + std::cout << row.at("serial_number") << " " << row.at("type") << "\n"; +} +``` + +> **Note:** This table is Linux-only and requires access to `/proc/keys`. Entries may be restricted based on process permissions and kernel keyring visibility rules. \ No newline at end of file diff --git a/osquery/tables/system/linux/.kernel_modules.md b/osquery/tables/system/linux/.kernel_modules.md new file mode 100644 index 00000000000..d634ae514eb --- /dev/null +++ b/osquery/tables/system/linux/.kernel_modules.md @@ -0,0 +1,43 @@ + +Parses `/proc/modules` on Linux to enumerate loaded kernel modules and their metadata, exposing the data as an osquery table. + +## Key Components + +- **`kKernelModulePath`** — Constant string pointing to `/proc/modules`, the Linux procfs source for loaded module data. +- **`genKernelModules(QueryContext&)`** — Table generator function that reads, parses, and returns all loaded kernel module entries as `QueryData`. + +## Usage Example + +This file backs the `kernel_modules` osquery virtual table. Query it directly via the osquery shell or SDK: + +```cpp +// Invoked internally by osquery's table registry +QueryContext context; +QueryData results = genKernelModules(context); + +for (const auto& row : results) { + // Each row contains: name, size, used_by, status, address + std::cout << row.at("name") << " @ " << row.at("address") << "\n"; +} +``` + +```sql +-- Example osquery SQL query +SELECT name, size, used_by, status, address +FROM kernel_modules +WHERE status = 'Live'; +``` + +## Parsed Fields + +Each row maps directly to columns from `/proc/modules` (space-delimited): + +| Column | Source Index | Description | +|--------|-------------|-------------| +| `name` | 0 | Module name | +| `size` | 1 | Memory size in bytes | +| `used_by` | 3 | Dependent modules | +| `status` | 4 | Load state (`Live`, `Loading`, `Unloading`) | +| `address` | 5 | Kernel memory address | + +> Lines with fewer than 6 fields are silently skipped. Trailing commas in dependency lists are stripped before storage. \ No newline at end of file diff --git a/osquery/tables/system/linux/.md_tables.md b/osquery/tables/system/linux/.md_tables.md new file mode 100644 index 00000000000..c48dff397b1 --- /dev/null +++ b/osquery/tables/system/linux/.md_tables.md @@ -0,0 +1,54 @@ + +Header file defining data structures and interfaces for querying Linux MD (Multiple Device) RAID array information within the osquery tables framework. + +## Key Components + +### Data Structures + +| Struct | Purpose | +|--------|---------| +| `MDDrive` | Represents a single drive in an MD array (name, position) | +| `MDAction` | Tracks ongoing array operations (progress, finish time, speed) | +| `MDBitmap` | Holds bitmap metadata (memory usage, chunk size, external file path) | +| `MDDevice` | Full MD array descriptor including drives, status, RAID level, and active actions | +| `MDStat` | Top-level container parsed from `/proc/mdstat` (personalities, devices, unused line) | + +### Classes + +- **`MDInterface`** — Abstract base class defining the contract for MD driver interaction. All methods are pure virtual, enabling dependency injection and testability. +- **`MD`** — Concrete implementation of `MDInterface` that performs actual ioctl-based system calls and file parsing. + +### Key Methods + +| Method | Description | +|--------|-------------| +| `getDiskInfo()` | Retrieves per-disk info via MD ioctl for a named array | +| `getArrayInfo()` | Retrieves array-level info via MD ioctl | +| `parseMDStat()` | Parses `/proc/mdstat` lines into an `MDStat` struct | +| `getPathByDevName()` | Resolves device path from short name (e.g., `md0`) | +| `getDevName()` | Resolves device name from major/minor numbers | +| `getSuperblkVersion()` | Returns the superblock version of an array | + +### Free Function + +- **`getDrivesForArray()`** — Populates `QueryData` with all drive information for a given array, using an `MDInterface` instance. + +## Usage Example + +```c +// Instantiate the concrete MD implementation +osquery::tables::MD md; +osquery::QueryData results; + +// Parse /proc/mdstat content +std::vector lines = { /* mdstat file lines */ }; +osquery::tables::MDStat stat; +md.parseMDStat(lines, stat); + +// Query drives for a specific array +osquery::tables::getDrivesForArray("md0", md, results); + +// Retrieve array-level info via ioctl +mdu_array_info_t arrayInfo; +bool ok = md.getArrayInfo("md0", arrayInfo); +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.memory_info.md b/osquery/tables/system/linux/.memory_info.md new file mode 100644 index 00000000000..0fcf5a7f763 --- /dev/null +++ b/osquery/tables/system/linux/.memory_info.md @@ -0,0 +1,33 @@ + +Parses `/proc/meminfo` on Linux to extract system memory statistics and exposes them as an osquery virtual table row. + +## Key Components + +- **`kMemInfoPath`** — Constant path to the Linux memory info file (`/proc/meminfo`). +- **`kMemInfoMap`** — Mapping of osquery column names to their corresponding `/proc/meminfo` keys (e.g., `"memory_total"` → `"MemTotal:"`). +- **`getMemoryInfo(QueryContext&)`** — Main table implementation function; reads and parses `/proc/meminfo`, returning a single-row `QueryData` result with memory values converted from kilobytes to bytes. + +## Usage Example + +```cpp +// Registered as an osquery table; query via SQL: +// SELECT * FROM memory_info; + +// Internally called by the osquery table framework: +QueryContext context; +QueryData results = osquery::tables::getMemoryInfo(context); + +// Each result row contains fields like: +// r["memory_total"] → total physical RAM in bytes +// r["memory_free"] → free physical RAM in bytes +// r["memory_available"] → available RAM in bytes +// r["swap_total"] → total swap space in bytes +// r["swap_free"] → free swap space in bytes +``` + +## Notes + +- All values are converted from kilobytes (as reported by the kernel) to **bytes** by multiplying by `1024`. +- Parsing splits each `/proc/meminfo` line on whitespace, reads the second token as the numeric value, and uses `tryTo` for safe integer conversion. +- Returns a single-row result regardless of parse success; unmatched fields will be absent from the row. +- Linux-only — depends on the `/proc/meminfo` virtual filesystem. \ No newline at end of file diff --git a/osquery/tables/system/linux/.memory_map.md b/osquery/tables/system/linux/.memory_map.md new file mode 100644 index 00000000000..625f9840999 --- /dev/null +++ b/osquery/tables/system/linux/.memory_map.md @@ -0,0 +1,39 @@ + +Parses `/proc/iomem` on Linux to expose the system's physical memory map as a queryable osquery table, returning each memory region's start address, end address, and name. + +## Key Components + +- **`kIOMemLocation`** — Constant string pointing to `/proc/iomem`, the Linux kernel's physical memory region file. +- **`genMemoryMap(QueryContext&)`** — Table generator function that reads, parses, and returns all memory region entries as `QueryData`. + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM memory_map; +// +// Example output rows produced by genMemoryMap(): +// +// start | end | name +// -------------|--------------|---------------------- +// 0x00000000 | 0x0009fbff | System RAM +// 0x000f0000 | 0x000fffff | System ROM +// 0x00100000 | 0x7fffffff | System RAM + +// Each line from /proc/iomem follows the format: +// 00000000-0009fbff : System RAM +// +// Parsed fields: +Row r; +r["start"] = "0x" + line.substr(0, b1); // before '-' +r["end"] = "0x" + line.substr(b1 + 1, b2); // between '-' and ' : ' +r["name"] = line.substr(b2 + 3); // after ' : ' +``` + +## Parsing Logic + +1. Reads the full contents of `/proc/iomem` into a string. +2. Splits content on newlines into individual region entries. +3. For each line, locates the `-` delimiter (between start/end addresses) and the ` : ` delimiter (separating address range from region name). +4. Lines missing either delimiter are skipped silently. +5. Address values are prefixed with `0x` before being stored. \ No newline at end of file diff --git a/osquery/tables/system/linux/.model_specific_register.md b/osquery/tables/system/linux/.model_specific_register.md new file mode 100644 index 00000000000..a5bdd2dc5cf --- /dev/null +++ b/osquery/tables/system/linux/.model_specific_register.md @@ -0,0 +1,53 @@ + +Implements the osquery `msr` table, which reads Intel Model Specific Registers (MSRs) from `/dev/cpu//msr` for each logical CPU core on Linux to expose low-level processor telemetry. + +## Key Components + +### `msr_record_t` struct +Defines a single MSR field descriptor with: +- `name` — column name in the query result +- `offset` — MSR address (e.g., `MSR_IA32_MISC_ENABLE`) +- `mask` — bitmask to isolate the relevant bits (`NO_MASK` = all bits) +- `is_flag` — if `true`, result is normalized to `0` or `1` + +### `fields[]` (static array) +The MSR entries collected per CPU core: + +| Field | MSR Address | Description | +|---|---|---| +| `turbo_disabled` | `0x1a0` | Turbo Boost disable flag | +| `turbo_ratio_limit` | `0x1ad` | Max turbo frequency ratios | +| `platform_info` | `0xce` | Platform capability info | +| `perf_status` | `0x198` | Current performance state | +| `perf_ctl` | `0x199` | Performance control | +| `feature_control` | `0x3a` | IA32 feature enable/disable | +| `rapl_power_limit` | `0x610` | RAPL package power limit | +| `rapl_energy_status` | `0x611` | RAPL energy counter | +| `rapl_power_units` | `0x606` | RAPL unit multipliers | + +### `getModelSpecificRegisterData(results, cpu_number)` +Opens `/dev/cpu//msr`, reads each field via `pread()` at the MSR offset, applies the bitmask, and appends a `Row` to `results`. Requires root privileges and the `msr` kernel module. + +### `msrScandirFilter(entry)` +`scandir` callback — accepts only numeric directory entries under `/dev/cpu/`. + +### `genModelSpecificRegister(context)` +Table entry point. Scans `/dev/cpu/` for per-core MSR devices and calls `getModelSpecificRegisterData` for each. + +## Usage Example + +```bash +# Requires: modprobe msr && run as root +osqueryi "SELECT processor_number, turbo_disabled, rapl_energy_status FROM msr;" +``` + +```text ++------------------+----------------+--------------------+ +| processor_number | turbo_disabled | rapl_energy_status | ++------------------+----------------+--------------------+ +| 0 | 0 | 14823916 | +| 1 | 0 | 14823916 | ++------------------+----------------+--------------------+ +``` + +> **Note:** The `msr` kernel module must be loaded (`modprobe msr`) and osquery must run as `root`. Missing entries for a field indicate the processor does not support that MSR. \ No newline at end of file diff --git a/osquery/tables/system/linux/.mounts.md b/osquery/tables/system/linux/.mounts.md new file mode 100644 index 00000000000..b965bfae1fe --- /dev/null +++ b/osquery/tables/system/linux/.mounts.md @@ -0,0 +1,43 @@ + +Implements the `mounts` osquery table for Linux, querying all currently mounted filesystems and returning their metadata including device info, mount paths, and filesystem statistics. + +## Key Components + +- **`genMounts(QueryContext&)`** — Table generation function that retrieves mounted filesystem data and maps it to osquery rows. Returns an empty result set if enumeration fails. + +### Columns Populated + +| Column | Source | +|---|---| +| `type` | Filesystem type (e.g., `ext4`, `tmpfs`) | +| `device` | Block device path | +| `device_alias` | Symlink/alias for the device | +| `path` | Mount point path | +| `flags` | Mount flags string | +| `blocks_size` | Block size in bytes | +| `blocks` | Total block count | +| `blocks_free` | Free blocks | +| `blocks_available` | Free blocks for unprivileged users | +| `inodes` | Total inode count | +| `inodes_free` | Free inode count | + +> **Note:** `statfs` fields (`blocks_*`, `inodes*`) default to `0` when `optional_statfs_info` is absent — a compatibility workaround for Boost 1.66, which lacks `optional::has_value`. + +## Usage Example + +```cpp +// Invoked automatically by the osquery table registry. +// Equivalent manual call: +QueryContext ctx; +QueryData rows = osquery::tables::genMounts(ctx); + +for (const auto& row : rows) { + std::cout << row.at("device") << " -> " << row.at("path") + << " [" << row.at("type") << "]" << std::endl; +} +``` + +```bash +# Query via osqueryi +osqueryi "SELECT device, path, type, blocks_size, inodes_free FROM mounts;" +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.os_version.md b/osquery/tables/system/linux/.os_version.md new file mode 100644 index 00000000000..ae13f541392 --- /dev/null +++ b/osquery/tables/system/linux/.os_version.md @@ -0,0 +1,41 @@ + +Implements the `os_version` osquery table for Linux/POSIX systems, collecting OS name, version, platform, architecture, and codename by parsing `/etc/os-release`, `/etc/redhat-release`, and `/etc/gentoo-release`, along with `uname` system calls. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kOSRelease` | `const string` | Path to `/etc/os-release` (systemd standard) | +| `kRedhatRelease` | `const string` | Path to `/etc/redhat-release` (RHEL/CentOS legacy) | +| `kGentooRelease` | `const string` | Path to `/etc/gentoo-release` | +| `kOSReleaseColumns` | `const map` | Maps `os-release` field keys to table column names | +| `genOSRelease()` | `void` | Parses `/etc/os-release` and populates a `Row` with name, version, platform, codename, and major/minor/patch fields | +| `genOSVersionImpl()` | `QueryData` | Core implementation; aggregates data from all release files and `uname`, with logger injection for testability | +| `genOSVersion()` | `QueryData` | Public table entry point; handles namespace-aware dispatch via `generateInNamespace` | + +## Usage Example + +```cpp +// Called internally by the osquery table registry. +// The table is queried via SQL: +// SELECT name, version, major, minor, patch, platform, arch FROM os_version; + +// Example output row populated by genOSVersionImpl: +Row r; +// r["name"] = "Ubuntu" +// r["version"] = "22.04.3 LTS (Jammy Jellyfish)" +// r["major"] = "22" +// r["minor"] = "04" +// r["patch"] = "3" +// r["platform"] = "ubuntu" +// r["platform_like"] = "debian" +// r["codename"] = "jammy" +// r["arch"] = "x86_64" +``` + +## Notes + +- Falls back to `"Unknown"` / `"0"` defaults if no release files are readable. +- Handles Funtoo edge case (empty `os-release` file) via file size check. +- Legacy RHEL/CentOS detection uses regex against the single-line release string. +- Namespace-aware: delegates to `generateInNamespace` when a PID namespace constraint is present. \ No newline at end of file diff --git a/osquery/tables/system/linux/.pci_devices.md b/osquery/tables/system/linux/.pci_devices.md new file mode 100644 index 00000000000..a2eb32b7272 --- /dev/null +++ b/osquery/tables/system/linux/.pci_devices.md @@ -0,0 +1,54 @@ + +Defines data structures and a database class for parsing and querying PCI device information from the system `pci.ids` database file. + +## Key Components + +### Structs + +- **`PciModel`** — Holds a PCI device model's ID, description, and a map of subsystem info keyed by `" "` +- **`PciVendor`** — Holds a vendor's ID, name, and a map of `PciModel` entries keyed by device model ID + +### Class: `PciDB` + +Parses a `pci.ids` filestream and exposes lookup methods: + +| Method | Description | +|---|---| +| `PciDB(istream&)` | Constructor — parses the `pci.ids` stream into an internal map | +| `getVendorName()` | Looks up vendor name by vendor ID | +| `getModel()` | Looks up model description by vendor and model ID | +| `getSubsystemInfo()` | Looks up subsystem description by vendor, model, and subsystem IDs | + +Private parsing helpers (`parseLine`, `parseVendor`, `parseModel`, `parseSubsystem`) drive the line-by-line ingestion of the database file. + +### Free Functions + +- **`extractVendorModelFromPciDBIfPresent()`** — Populates an osquery `Row` with vendor/model info from sysfs attributes using a `PciDB` instance +- **`extractPCIClassIDAttrs()`** — Populates an osquery `Row` with PCI class identifier data from a sysfs attribute string + +## Usage Example + +```cpp +#include "pci_devices.h" +#include + +// Load and query the PCI database +std::ifstream pci_ids("/usr/share/misc/pci.ids"); +osquery::tables::PciDB pcidb(pci_ids); + +std::string vendor_name, model_desc; + +// Lookup vendor +auto status = pcidb.getVendorName("8086", vendor_name); +if (status.ok()) { + // vendor_name == "Intel Corporation" +} + +// Lookup model +status = pcidb.getModel("8086", "1234", model_desc); + +// Populate an osquery row from sysfs +osquery::Row row; +osquery::tables::extractVendorModelFromPciDBIfPresent( + row, "0x80861234", "0x80865678", pcidb); +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.portage.md b/osquery/tables/system/linux/.portage.md new file mode 100644 index 00000000000..253692e5dd5 --- /dev/null +++ b/osquery/tables/system/linux/.portage.md @@ -0,0 +1,51 @@ + +Implements osquery table generation for Gentoo Linux's Portage package manager, parsing installed packages, USE flags, and keyword/mask configurations from the filesystem. + +## Key Components + +### Constants +Portage filesystem paths used across all parsing functions: + +| Constant | Path | +|---|---| +| `kPortagePackageDir` | `/var/db/pkg` | +| `kPortageKeywords` | `/etc/portage/package.keywords` | +| `kPortageMask` / `kPortageUnMask` | `/etc/portage/package.mask|unmask` | +| `kPortageUse` | `/etc/portage/package.use` | +| `kPortageWorld` | `/var/lib/portage/world` | + +### `PortagePackage` +Non-copyable container holding intermediate package data (name, version, keyword, mask/unmask flags) before writing to `QueryData`. + +### Core Functions + +| Function | Purpose | +|---|---| +| `portageSplitPackageVersion()` | Splits a package string (e.g. `=dev-libs/foo-1.2.3`) into `{name, version}` pair | +| `parsePortagePackages()` | Builds package rows from `/var/db/pkg` directories, enriching with SLOT, SIZE, EAPI, BUILD_TIME, and repository metadata | +| `parsePortageUseContent()` | Emits one row per active USE flag per installed package | +| `parsePortageKeywordSummaryContent()` | Merges keyword, mask, and unmask files into a unified per-package summary | + +### Table Entry Points +- `portagePackages()` — feeds the `portage_packages` table +- `genPortageUse()` — feeds the `portage_use` table +- `genPortageKeywordSummary()` — feeds the `portage_keyword_summary` table + +## Usage Example + +```cpp +// Called internally by osquery table infrastructure: +QueryContext ctx; + +// Query all installed Portage packages +QueryData packages = portagePackages(ctx); +// Each row contains: package, version, world, slot, size, eapi, build_time, repository + +// Query active USE flags per package +QueryData use_flags = genPortageUse(ctx); +// Each row contains: package, version, use + +// Query keyword/mask summary +QueryData keywords = genPortageKeywordSummary(ctx); +// Each row contains: package, version, keyword, mask, unmask +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.process_open_files.md b/osquery/tables/system/linux/.process_open_files.md new file mode 100644 index 00000000000..c19487319ab --- /dev/null +++ b/osquery/tables/system/linux/.process_open_files.md @@ -0,0 +1,41 @@ + +Implements the `process_open_files` osquery table, which enumerates file descriptors opened by running processes on Linux systems. + +## Key Components + +### `genDescriptors` +Iterates over a process's file descriptor map and filters out non-file descriptors (sockets, anonymous inodes, and pipes), appending only true vnode/file-backed entries to the results set. + +**Parameters:** +- `process` — PID string of the owning process +- `descriptors` — Map of `fd → resolved path` entries +- `results` — Output `QueryData` collection + +### `genOpenFiles` +Entry point for the `process_open_files` table. Resolves the target PID set (from query constraints or a full process scan via `procProcesses`), then calls `procDescriptors` and `genDescriptors` for each PID. + +**Returns:** `QueryData` — rows with columns `pid`, `fd`, and `path` + +## Usage Example + +```cpp +// Typical osquery SQL query against this table: +// SELECT pid, fd, path FROM process_open_files WHERE pid = 1234; + +// Internally, genOpenFiles is invoked with the QueryContext: +QueryData results = genOpenFiles(context); + +// Each result row contains: +// r["pid"] = "1234" +// r["fd"] = "3" +// r["path"] = "/etc/hosts" +``` + +## Filtering Logic + +| Descriptor Type | Prefix Match | Included? | +|---|---|:-:| +| Regular file | *(none)* | ✅ | +| Socket | `socket:` | ❌ | +| Anonymous inode | `anon_inode:` | ❌ | +| Pipe | `pipe:` | ❌ | \ No newline at end of file diff --git a/osquery/tables/system/linux/.process_open_pipes.md b/osquery/tables/system/linux/.process_open_pipes.md new file mode 100644 index 00000000000..f7a77e5809e --- /dev/null +++ b/osquery/tables/system/linux/.process_open_pipes.md @@ -0,0 +1,43 @@ + +Implements the `process_open_pipes` osquery table, which enumerates open pipe file descriptors (both anonymous and named) across all running processes on Linux, correlating pipe endpoints by inode to identify reader/writer pairs. + +## Key Components + +### Data Structures + +- **`pipe_info`** — Struct holding metadata for a single pipe endpoint: `pid`, `fd`, `mode` (r/w/rw), `inode`, and `type` (anonymous/named) +- **`PidToPipesMap`** — Maps PIDs to their associated pipe endpoints +- **`InodeToPipesMap`** — Maps inodes to all pipe endpoints sharing that inode (used to correlate partners) + +### Core Functions + +| Function | Description | +|---|---| +| `genPipes()` | Table entry point; iterates all processes and collects pipe data | +| `genPipePartners()` | Parses `/proc//fd/` descriptors and populates pipe maps | +| `getPipeInfo()` | Dispatches to anonymous or named pipe info extraction | +| `getNamedPipeInfo()` | Resolves named FIFOs via `stat()` and `S_ISFIFO` check | +| `getMode()` | Reads file permissions via `lstat()` to determine r/w mode | +| `parseInode()` | Extracts inode number from `pipe:[inode]` symlink strings | +| `genResults()` | Builds result rows, linking pipe partners or flagging unconnected pipes | +| `isReaderWriterPair()` | Determines if two pipe endpoints form a valid read/write pair | + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT pid, fd, mode, inode, type, partner_pid, partner_fd, partner_mode +// FROM process_open_pipes +// WHERE pid = 1234; + +// Internal flow: +osquery::procProcesses(pids); +for (const auto& process : pids) { + std::map descriptors; + if (osquery::procDescriptors(process, descriptors).ok()) { + genPipePartners(process, descriptors, pipe_desc, pipe_partners, pipe_structs); + } +} +``` + +Unconnected pipes (only one endpoint visible) are reported without partner fields; connected pairs emit a row per endpoint referencing its partner's `pid`, `fd`, and `mode`. \ No newline at end of file diff --git a/osquery/tables/system/linux/.processes.md b/osquery/tables/system/linux/.processes.md new file mode 100644 index 00000000000..ba08b30411a --- /dev/null +++ b/osquery/tables/system/linux/.processes.md @@ -0,0 +1,22 @@ + +Declares a utility function for parsing Linux process cgroup information within the osquery tables subsystem. + +## Key Components + +- **`parseProcCGroup(const std::string& content)`** — Parses the raw string content of a Linux `/proc//cgroup` file and returns the extracted cgroup information as a formatted string. + +## Usage Example + +```c +#include "processes.h" + +std::string cgroupContent = "12:cpu:/user.slice\n11:memory:/system.slice\n"; +std::string parsed = osquery::tables::parseProcCGroup(cgroupContent); +// parsed contains the processed cgroup hierarchy data +``` + +## Notes + +- Lives in the `osquery::tables` namespace, consistent with osquery's table-based query architecture. +- Intended for use in Linux process enumeration tables (e.g., `processes`). +- Input is typically the raw file content read from `/proc//cgroup`. \ No newline at end of file diff --git a/osquery/tables/system/linux/.rpm_packages.md b/osquery/tables/system/linux/.rpm_packages.md new file mode 100644 index 00000000000..2e2c0ffcda3 --- /dev/null +++ b/osquery/tables/system/linux/.rpm_packages.md @@ -0,0 +1,41 @@ + +Implements osquery table generators for querying installed RPM packages and their associated files on Linux systems using the librpm API. + +## Key Components + +### `getRpmAttribute` +Static helper that extracts a string representation of an RPM header tag, handling both numeric and string RPM tag classes. + +### `RpmEnvironmentManager` +RAII class (non-copyable) that manages the RPM runtime environment: +- Sets `RPM_CONFIGDIR` to `/usr/lib/rpm` if not already configured +- Installs/restores a custom `rpmlog` callback to suppress duplicate log noise +- Cleans up environment variables and callbacks on destruction + +### `genRpmPackagesImpl` +Core implementation for the `rpm_packages` table. Drops privileges to `nobody`, initializes the RPM transaction set, and iterates the RPM database to collect per-package metadata. + +**Columns returned:** `name`, `version`, `release`, `source`, `size`, `sha1`, `arch`, `epoch`, `install_time`, `vendor`, `package_group`, `pid_with_namespace` + +### `genRpmPackages` +Public entry point for the `rpm_packages` table. Routes to namespace-isolated execution via `generateInNamespace` when a namespace constraint is present. + +### `genRpmPackageFiles` +Streaming generator (via `RowYield`) for the `rpm_packages_files` table. Iterates files within each matched RPM package using `rpmfi`, enforcing a cap of `MAX_RPM_FILES` (65,536) per package. + +**Columns returned:** `package`, `path`, `username`, `groupname`, `mode`, `size`, `sha256` + +## Usage Example + +```cpp +// Query all installed RPM packages (called internally by osquery) +QueryContext context; +QueryData packages = genRpmPackages(context); + +// Query files for a specific package +context.constraints["package"].add(Constraint(EQUALS, "openssl")); +RowYield yield = [](TableRowHolder row) { /* process row */ }; +genRpmPackageFiles(yield, context); +``` + +> **Note:** Both generators drop privileges to `nobody` before accessing the RPM database. A `glibc < 2.17` compatibility shim for `__secure_getenv` is included for older system support. \ No newline at end of file diff --git a/osquery/tables/system/linux/.secureboot.md b/osquery/tables/system/linux/.secureboot.md new file mode 100644 index 00000000000..64ea8e24930 --- /dev/null +++ b/osquery/tables/system/linux/.secureboot.md @@ -0,0 +1,31 @@ + +Queries Linux EFI variable files to determine Secure Boot and Setup Mode status for the `secureboot` osquery table. + +## Key Components + +- **`kEfiVarsDirectory`** — Path constant pointing to `/sys/firmware/efi/efivars`, the world-readable EFI variables interface +- **`readBoolEfiVar()`** — Internal helper that reads a 5-byte EFI variable file, skipping the 4-byte attribute prefix and interpreting the 5th byte as a boolean (`0`, `1`, or `-1` for unknown) +- **`genSecureBoot()`** — Main table generation function; validates UEFI firmware presence, then populates `secure_boot` and `setup_mode` columns + +## Behavior Notes + +- Returns an empty result set if the firmware type cannot be determined or is not UEFI +- Silently skips EFI variable reads that fail (e.g., kernel lacks EFI var support, or EFI vars not mounted) +- Uses `/sys/firmware/efi/efivars` (world-readable single-file interface) over `/sys/firmware/efi/vars` (root-only) to avoid privilege requirements +- Kernel enforces a 100 reads/second rate limit on non-root EFI reads — noted but not mitigated + +## Usage Example + +```cpp +// Invoked internally by the osquery table registry +QueryData result = genSecureBoot(context); + +// Resulting row contains: +// result[0]["secure_boot"] => "0" | "1" | "-1" +// result[0]["setup_mode"] => "0" | "1" | "-1" +``` + +```bash +# Query via osquery CLI +osqueryi "SELECT secure_boot, setup_mode FROM secureboot;" +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.selinux_settings.md b/osquery/tables/system/linux/.selinux_settings.md new file mode 100644 index 00000000000..1650cdadbab --- /dev/null +++ b/osquery/tables/system/linux/.selinux_settings.md @@ -0,0 +1,37 @@ + +Implements the osquery `selinux_settings` virtual table, which reads and exposes SELinux configuration values from the `selinuxfs` filesystem on Linux. + +## Key Components + +### Public Functions +- **`genSELinuxSettings(QueryContext&)`** — Main table generator; locates the `selinuxfs` mount, then collects root keys, scoped keys, and class entries into a `QueryData` result set +- **`keyNameFromFilePath(key_name, selinuxfs_path, file_path)`** — Extracts a key name from an absolute file path relative to the SELinux FS root +- **`translateBooleanKeyValue(value, raw_value)`** — Converts raw boolean file content (`"0 0"` / `"1 1"`) to human-readable `"off"` / `"on"` strings + +### Internal Helpers (anonymous namespace) +- **`getSelinuxfsMountPath(path)`** — Scans mounted filesystems and returns the mount point of type `selinuxfs` +- **`generateScopeKey(row, scope, selinuxfs_path, key)`** — Reads a single key file under a given scope, applying boolean translation where needed +- **`generateScope(row_list, selinuxfs_path, scope)`** — Enumerates all files in a scope directory (e.g., `booleans`, `policy_capabilities`) and generates rows +- **`generateClasses(row_list, selinuxfs_path)`** — Walks the `class/` subtree and emits permission entries with `scope = "class"` + +### Key Constants +- **`kRootKeyList`** — Top-level SELinux keys: `enforce`, `mls`, `policyvers`, `checkreqprot`, etc. +- **`kScopeList`** — Subdirectory scopes: `booleans`, `policy_capabilities`, `initial_contexts` + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT scope, key, value FROM selinux_settings; +// +// Example output rows: +// scope="" key="enforce" value="1" +// scope="booleans" key="httpd_can_network_connect" value="on" +// scope="class" key="file" value="read" + +QueryContext ctx; +QueryData results = osquery::tables::genSELinuxSettings(ctx); +for (const auto& row : results) { + // row["scope"], row["key"], row["value"] +} +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.shadow.md b/osquery/tables/system/linux/.shadow.md new file mode 100644 index 00000000000..e712f6fd5e8 --- /dev/null +++ b/osquery/tables/system/linux/.shadow.md @@ -0,0 +1,38 @@ + +Implements the `shadow` osquery table for Linux, querying `/etc/shadow` to expose user account password metadata and status. + +## Key Components + +- **`kPasswordHashAlgRegex`** — Compiled regex pattern (`^\$(\w+)\$`) for extracting the hash algorithm identifier from a shadow password field. + +- **`genShadowForAccount(spwd, results)`** — Parses a single `spwd` struct into a result row, populating: + - Password aging fields (`last_change`, `min`, `max`, `warning`, `inactive`, `expire`, `flag`) + - `password_status`: one of `not_set`, `locked`, `empty`, or `active` + - `hash_alg`: extracted algorithm identifier (e.g., `1` for MD5, `6` for SHA-512) + +- **`genShadow(context)`** — Table generator entry point. Supports two query modes: + - **Filtered** — Uses `getspnam()` for specific username lookups via `WHERE username = '...'` + - **Full scan** — Iterates all entries via `getspent()` / `endspent()` + + A `WriteLock` around shadow database calls prevents concurrent access issues since the POSIX shadow APIs are not thread-safe. + +## Usage Example + +```cpp +// Typical osquery SQL usage (requires root or CAP_DAC_READ_SEARCH) + +// Full table scan +// SELECT username, password_status, hash_alg, last_change FROM shadow; + +// Filtered lookup (uses getspnam internally) +// SELECT * FROM shadow WHERE username = 'root'; + +// Programmatic call within osquery internals +QueryContext context; +context.constraints["username"].add(Constraint(EQUALS, "root")); +QueryData results = genShadow(context); +// results[0]["password_status"] => "active" +// results[0]["hash_alg"] => "6" (SHA-512) +``` + +> **Note:** Reading `/etc/shadow` requires elevated privileges. On most Linux systems, the osquery daemon runs as root to access this table. \ No newline at end of file diff --git a/osquery/tables/system/linux/.shared_memory.md b/osquery/tables/system/linux/.shared_memory.md new file mode 100644 index 00000000000..c0268a4acef --- /dev/null +++ b/osquery/tables/system/linux/.shared_memory.md @@ -0,0 +1,43 @@ + +Queries Linux shared memory segments using the `shmctl` system call, populating an osquery table with metadata for each active segment. + +## Key Components + +### `struct shm_info` +A local mirror of the Linux kernel's `shm_info` structure used to retrieve aggregate shared memory statistics via `shmctl(0, SHM_INFO, ...)`. Marked `__attribute__((unused))` to suppress compiler warnings from the cast. + +### `genSharedMemory(QueryContext &context)` +The primary table-generation function. Iterates all shared memory segment IDs from `0` to the kernel-reported maximum, calling `shmctl(id, SHM_STAT, ...)` on each. Skips invalid IDs and builds a result row per valid segment. + +**Columns populated per row:** + +| Column | Source | +|---|---| +| `shmid` | Return value of `shmctl` | +| `owner_uid` / `creator_uid` | `getpwuid` on `shm_perm.uid` / `cuid` | +| `pid` / `creator_pid` | `shm_lpid` / `shm_cpid` | +| `atime` / `dtime` / `ctime` | Attach, detach, change timestamps | +| `permissions` | `lsperms(ipcp->mode)` | +| `size` | `shm_segsz` | +| `attached` | `shm_nattch` | +| `status` | `"dest"` if `SHM_DEST` flag set | +| `locked` | `"1"` if `SHM_LOCKED` flag set | + +## Usage Example + +```cpp +// Called internally by the osquery table registry at query time. +// Equivalent SQL query: +// SELECT shmid, owner_uid, size, attached, permissions, status, locked +// FROM shared_memory; + +QueryContext ctx; +QueryData rows = osquery::tables::genSharedMemory(ctx); +for (const auto& row : rows) { + std::cout << "shmid=" << row.at("shmid") + << " size=" << row.at("size") + << " locked=" << row.at("locked") << "\n"; +} +``` + +> **Note:** This implementation is Linux-only. It requires the kernel to be compiled with System V IPC support (`CONFIG_SYSVIPC`). If `shmctl` returns a negative `maxid`, the function logs a `VLOG(1)` message and returns an empty result set. \ No newline at end of file diff --git a/osquery/tables/system/linux/.smbios_tables.md b/osquery/tables/system/linux/.smbios_tables.md new file mode 100644 index 00000000000..0f7729c4ef4 --- /dev/null +++ b/osquery/tables/system/linux/.smbios_tables.md @@ -0,0 +1,59 @@ + +Linux SMBIOS/DMI table parser and osquery table generator for system firmware information on Linux platforms. + +## Key Components + +### `LinuxSMBIOSParser` Methods + +| Method | Description | +|---|---| +| `discover()` | Entry point — detects and reads SMBIOS data via sysfs, EFI systab, or raw memory fallback | +| `readFromSysfs()` | Reads DMI tables directly from `/sys/firmware/dmi/tables/DMI` | +| `readFromSystab()` | Parses `/sys/firmware/efi/systab` to locate SMBIOS address, then reads from memory | +| `readFromAddress()` | Scans raw physical memory for the `_DMI_` magic header | +| `discoverTables()` | Reads raw DMI table data from a resolved physical address | + +### Query Generator Functions + +| Function | osquery Table | +|---|---| +| `genSMBIOSTables()` | `smbios_tables` — raw SMBIOS structure entries | +| `genMemoryDevices()` | `memory_devices` | +| `genMemoryArrays()` | `memory_arrays` | +| `genMemoryArrayMappedAddresses()` | `memory_array_mapped_addresses` | +| `genMemoryErrorInfo()` | `memory_error_info` | +| `genMemoryDeviceMappedAddresses()` | `memory_device_mapped_addresses` | +| `genOEMStrings()` | `oem_strings` | +| `genPlatformInfo()` | `platform_info` — BIOS vendor, version, date, address, size, revision, firmware type | + +## Discovery Priority + +```mermaid +graph TD + A[discover] --> B{sysfs DMI readable?} + B -->|Yes| C[readFromSysfs] + B -->|No| D{EFI systab readable?} + D -->|Yes| E[readFromSystab] + D -->|No| F[readFromAddress raw memory] +``` + +## Usage Example + +```cpp +// Typical pattern used by all gen* functions +LinuxSMBIOSParser parser; +if (!parser.discover()) { + return {}; +} + +QueryData results; +parser.tables([&results](size_t index, + const SMBStructHeader* hdr, + uint8_t* address, + uint8_t* textAddrs, + size_t size) { + genSMBIOSTable(index, hdr, address, size, results); +}); +``` + +> **Note:** `readFromAddress()` respects the `/dev/mem` strict boundary at `0x100000`, clamping read sizes to avoid kernel log warnings on hardened systems. \ No newline at end of file diff --git a/osquery/tables/system/linux/.smbios_utils.md b/osquery/tables/system/linux/.smbios_utils.md new file mode 100644 index 00000000000..613dccf5e05 --- /dev/null +++ b/osquery/tables/system/linux/.smbios_utils.md @@ -0,0 +1,40 @@ + +Provides the Linux-specific implementation of the SMBIOS parser, extending the cross-platform `SMBIOSParser` base class to discover and read SMBIOS data via Linux-specific facilities. + +## Key Components + +### `LinuxSMBIOSParser` (extends `SMBIOSParser`) + +| Method | Description | +|---|---| +| `readFromAddress(address, length)` | Reads system table and SMBIOS data directly from a memory address | +| `readFromSystab(systab)` | Parses the SMBIOS entry point address from an EFI `systab` file | +| `readFromSysfs(sysfs_dmi)` | Reads SMBIOS content from the sysfs DMI interface (modern systems) | +| `discover()` | Cross-version initializer; tries all available read strategies | +| `valid()` | Returns `true` if SMBIOS table data was successfully loaded | +| `discoverTables(address, length)` | Internal helper to locate and map SMBIOS tables from a given address | + +**Private Members:** +- `data_` — Raw SMBIOS memory buffer (`uint8_t*`) +- `table_data_` — Parsed table data buffer (managed by base class) + +## Usage Example + +```c +osquery::tables::LinuxSMBIOSParser parser; + +if (parser.discover() && parser.valid()) { + // Iterate SMBIOS structures using base class methods + parser.tables([](size_t index, + const SMBIOSStructure& structure, + uint8_t* data) { + // Process each SMBIOS structure entry + }); +} +``` + +## Notes + +- Discovery attempts multiple sources in order: **sysfs DMI** (preferred on modern kernels) → **EFI systab** → **direct memory address** +- The destructor explicitly frees both `data_` and `table_data_` raw buffers to prevent leaks +- Parsing logic is shared with other platforms via the `SMBIOSParser` base class in `osquery/tables/system/smbios_utils.h` \ No newline at end of file diff --git a/osquery/tables/system/linux/.startup_items.md b/osquery/tables/system/linux/.startup_items.md new file mode 100644 index 00000000000..b2b10a6a878 --- /dev/null +++ b/osquery/tables/system/linux/.startup_items.md @@ -0,0 +1,50 @@ + +Implements the `startup_items` osquery table for Linux, enumerating all startup entries from XDG autostart directories, init.d scripts, and systemd units via D-Bus. + +## Key Components + +### Constants +- **`kSystemItemPaths`** — System-wide XDG autostart directories (e.g., `/etc/xdg/autostart/`) +- **`kSystemScriptPaths`** — System-wide init script directories (e.g., `/etc/init.d/`) + +### Functions + +| Function | Description | +|---|---| +| `genAutoStartItems()` | Parses `.desktop`-style files in XDG autostart directories, extracting `Name=` and `Exec=` fields | +| `genAutoStartScripts()` | Enumerates plain script files in init.d-style directories, using filename as the item name | +| `genSystemdItems()` | Connects via D-Bus to enumerate all systemd units, fetching `FragmentPath`, `SourcePath`, and `User` properties | +| `genStartupItems()` | Entry point; aggregates results from all three sources for both user-level and system-level paths | + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM startup_items; + +// Each result row contains: +// name - human-readable name or unit ID +// path - executable or unit file path +// type - "Startup Item" or "systemd unit" +// status - "enabled" (scripts/items) or active_state (systemd) +// source - originating directory or source path +// username - owning user (if user-scoped path or systemd User= property) +``` + +## Data Sources + +```mermaid +graph TD + A[genStartupItems] --> B[User autostart dirs] + A --> C[System init.d scripts] + A --> D[XDG autostart items] + A --> E[systemd via D-Bus] + B --> F["~/.config/autostart/"] + B --> G["~/.config/autostart-scripts/"] + C --> H["/etc/init.d/"] + D --> I["/etc/xdg/autostart/"] + E --> J[ListUnits method] + J --> K[FragmentPath / SourcePath / User] +``` + +> **Note:** D-Bus connection failures for systemd enumeration are logged as errors but do not prevent XDG/init.d results from being returned. \ No newline at end of file diff --git a/osquery/tables/system/linux/.sysctl_utils.md b/osquery/tables/system/linux/.sysctl_utils.md new file mode 100644 index 00000000000..29c37e85c2f --- /dev/null +++ b/osquery/tables/system/linux/.sysctl_utils.md @@ -0,0 +1,41 @@ + +Provides Linux sysctl parameter enumeration by traversing the `/proc/sys/` virtual filesystem, reading kernel control values and comparing them against optional configuration mappings. + +## Key Components + +| Function | Description | +|---|---| +| `genControlInfo(mib_path, ...)` | Recursively traverses a `/proc/sys/` path; for each leaf file, reads the current kernel value and populates a `Row` with `name`, `subsystem`, `current_value`, `config_value`, and `type` | +| `genControlInfo(oid, oid_size, ...)` | BSD/POSIX overload that queries a sysctl OID integer array directly via `sysctl(3)`, returning raw string output | +| `genAllControls(...)` | Enumerates all top-level `/proc/sys/` subdirectories and dispatches to `genControlInfo`; accepts an optional `subsystem` filter | +| `genControlInfoFromName(...)` | Accepts a dotted MIB name (e.g. `kernel.hostname`), converts it to a `/proc/sys/` filesystem path, and delegates to `genControlInfo` | + +**Constant:** `kSystemControlPath = "/proc/sys/"` — the root mount point for all Linux sysctl entries. + +## Usage Example + +```cpp +#include + +// Read all kernel controls under the "net" subsystem +QueryData results; +std::map desiredConfig = { + {"net.ipv4.ip_forward", "1"} +}; + +osquery::tables::genAllControls(results, desiredConfig, "net"); + +// Query a specific control by dotted MIB name +osquery::tables::genControlInfoFromName( + "kernel.hostname", results, desiredConfig +); + +// Each Row in results contains: +// r["name"] → "kernel.hostname" +// r["subsystem"] → "kernel" +// r["current_value"] → current kernel value +// r["config_value"] → expected value from config (if present) +// r["type"] → "string" +``` + +> **Note:** The OID-based `genControlInfo` overload is included for POSIX compatibility but on Linux all values are typed as `"string"` since `/proc/sys/` provides no type metadata. \ No newline at end of file diff --git a/osquery/tables/system/linux/.system_info.md b/osquery/tables/system/linux/.system_info.md new file mode 100644 index 00000000000..1716428af18 --- /dev/null +++ b/osquery/tables/system/linux/.system_info.md @@ -0,0 +1,42 @@ + +Populates an osquery `system_info` table row with hardware and OS details gathered from multiple Linux system sources. + +## Key Components + +### `genSystemInfo(QueryContext& context)` +The sole exported table generator function. Collects and returns a single-row `QueryData` containing: + +| Field | Source | +|---|---| +| `hostname`, `computer_name`, `local_hostname` | `osquery::getFqdn()` / `getHostname()` | +| `uuid` | `osquery::getHostUUID()` | +| `cpu_brand` | `cpuid` virtual table (x86_64 only) | +| `cpu_logical_cores` | `std::thread::hardware_concurrency()` | +| `cpu_physical_cores` | `boost::thread::physical_concurrency()` | +| `physical_memory` | `sysconf(_SC_PHYS_PAGES * _SC_PAGESIZE)` | +| `cpu_type` | `uname()` syscall | +| `cpu_subtype`, `cpu_microcode`, `cpu_sockets` | `/proc/cpuinfo` parsing | +| `hardware_vendor/model/version/serial` | SMBIOS (`kSMBIOSTypeSystem`) | +| `board_vendor/model/version/serial` | SMBIOS (`kSMBIOSTypeBoard`) | + +**Notable behavior:** Lenovo systems have `hardware_model` and `hardware_version` swapped to match other vendors' conventions. + +## Usage Example + +```cpp +// Registered automatically via osquery table plugin system. +// Query from osquery shell: +// SELECT * FROM system_info; + +// Direct invocation in tests: +QueryContext ctx; +QueryData result = osquery::tables::genSystemInfo(ctx); + +if (!result.empty()) { + const Row& r = result[0]; + std::cout << "Hostname: " << r.at("hostname") << "\n"; + std::cout << "CPU brand: " << r.at("cpu_brand") << "\n"; + std::cout << "Physical memory: "<< r.at("physical_memory") << "\n"; + std::cout << "Hardware model: " << r.at("hardware_model") << "\n"; +} +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.systemd_units.md b/osquery/tables/system/linux/.systemd_units.md new file mode 100644 index 00000000000..f082f0df3cf --- /dev/null +++ b/osquery/tables/system/linux/.systemd_units.md @@ -0,0 +1,40 @@ + +Implements the `systemd_units` osquery virtual table for Linux, querying systemd unit information via D-Bus and exposing it as queryable rows. + +## Key Components + +### `PropertyQueryDesc` +Internal struct defining a D-Bus property to fetch, mapping a property name and interface to an output column name. + +### `kStringPropertyQueryList` +Static list of `PropertyQueryDesc` entries for additional string properties fetched per-unit via D-Bus: + +| Property | Column | Interface | +|---|---|---| +| `FragmentPath` | `fragment_path` | `org.freedesktop.systemd1.Unit` | +| `SourcePath` | `source_path` | `org.freedesktop.systemd1.Unit` | +| `User` | `user` | `org.freedesktop.systemd1.Service` | +| `UnitFileState` | `unit_file_state` | `org.freedesktop.systemd1.Unit` | + +### `genSystemdUnits(QueryContext&)` +Table generator function that: +1. Opens a D-Bus connection via `UniqueDbusConnection` +2. Calls `ListUnitsMethod` against `/org/freedesktop/systemd1` +3. Iterates units, populating core fields (`id`, `description`, `load_state`, `active_state`, `sub_state`, `following`, `object_path`, `job_id`, `job_type`, `job_path`) +4. For each unit, fetches additional string properties via `GetStringPropertyMethod` + +> **Note:** Errors on the `User` property are silently ignored, as it is only valid for `Service` units. + +## Usage Example + +```cpp +// Called automatically by osquery when the virtual table is queried: +// SELECT * FROM systemd_units WHERE active_state = 'active'; + +QueryContext context; +TableRows rows = osquery::tables::genSystemdUnits(context); + +for (const auto& row : rows) { + // Access fields: row["id"], row["active_state"], row["fragment_path"], etc. +} +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.usb_devices.md b/osquery/tables/system/linux/.usb_devices.md new file mode 100644 index 00000000000..8e8e029f54c --- /dev/null +++ b/osquery/tables/system/linux/.usb_devices.md @@ -0,0 +1,44 @@ + +Enumerates connected USB devices on Linux by querying the udev subsystem, returning device metadata such as vendor, model, serial number, class, and bus address. + +## Key Components + +### Constants + +| Constant | udev Property | Description | +|---|---|---| +| `kUSBKeyVendorID` | `ID_VENDOR_ID` | Numeric vendor identifier | +| `kUSBKeyVendor` | `ID_VENDOR_FROM_DATABASE` | Human-readable vendor name | +| `kUSBKeyModelID` | `ID_MODEL_ID` | Numeric model identifier | +| `kUSBKeyModel` | `ID_MODEL_FROM_DATABASE` | Human-readable model name | +| `kUSBKeyModelFallback` | `ID_MODEL` | Fallback model name if DB lookup fails | +| `kUSBDeviceReleaseNumber` | `ID_REVISION` | Device firmware/release version | +| `kUSBKeySerial` | `ID_SERIAL_SHORT` | Device serial number | +| `kUSBKeyAddress` | `BUSNUM` | USB bus number | +| `kUSBKeyPort` | `DEVNUM` | USB port/device number | +| `kUSBKeyType` | `TYPE` | Raw class/subclass/protocol string | + +### Functions + +- **`genUSBDevices(QueryContext&)`** — Main table generator. Initializes a udev handle, enumerates all devices matching the `usb` subsystem, extracts properties for each device, and returns a `QueryData` row set. Only devices with a valid bus address and port are included in results. + +## Usage Example + +```cpp +// Called internally by the osquery table framework +QueryContext context; +QueryData rows = osquery::tables::genUSBDevices(context); + +for (const auto& row : rows) { + // Each row contains fields like: + // row["vendor"], row["model"], row["vendor_id"], row["model_id"] + // row["serial"], row["version"], row["usb_address"], row["usb_port"] + // row["class"], row["subclass"], row["protocol"], row["removable"] +} +``` + +## Notes + +- The `TYPE` property is parsed by splitting on `/` into `class`, `subclass`, and `protocol` fields (e.g., `9/0/0` for a USB hub). +- `removable` is set to `-1` when the udev attribute value is `"unknown"`, and `1` otherwise. +- Rows missing either `usb_address` or `usb_port` are silently dropped. \ No newline at end of file diff --git a/osquery/tables/system/linux/.user_groups.md b/osquery/tables/system/linux/.user_groups.md new file mode 100644 index 00000000000..e5cb64f17fc --- /dev/null +++ b/osquery/tables/system/linux/.user_groups.md @@ -0,0 +1,31 @@ + +Implements the `genUserGroups` table function for osquery, which enumerates Unix user-to-group memberships by querying the system password database. + +## Key Components + +- **`genUserGroups(QueryContext& context)`** — Main table generator function that returns all group memberships for users. Supports optional filtering by `uid` via query constraints. +- **`getGroupsForUser()`** — Template helper (declared in `user_groups.h`) that resolves and appends all groups for a given user struct to the result set. +- **`user_t`** — Template struct used to pass user identity (`name`, `uid`, `gid`) into the group resolution helper. + +## Behavior + +- **Filtered mode**: If the query includes a `uid` equality constraint, only the matching user's group memberships are resolved using `getpwuid_r`. +- **Full scan mode**: Iterates all users via `getpwent_r`, deduplicating by `uid` using a `std::set`, and resolves groups for each unique user. +- Buffer size is capped at **16,384 bytes** to handle systems where `sysconf(_SC_GETPW_R_SIZE_MAX)` returns an indeterminate value. + +## Usage Example + +```cpp +// Typically invoked by the osquery table registry, not called directly. +// Equivalent SQL query: +// SELECT * FROM user_groups; +// SELECT * FROM user_groups WHERE uid = 1000; + +QueryContext ctx; +ctx.constraints["uid"].add(Constraint(EQUALS, "1000")); + +QueryData rows = genUserGroups(ctx); +for (const auto& row : rows) { + // Each row contains uid, gid, groupname fields +} +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/.users.md b/osquery/tables/system/linux/.users.md new file mode 100644 index 00000000000..fca14f4ede8 --- /dev/null +++ b/osquery/tables/system/linux/.users.md @@ -0,0 +1,34 @@ + +Implements the osquery `users` table for POSIX systems, querying user account information from the passwd database with support for both local (`/etc/passwd`) and remote (LDAP/NIS) user sources. + +## Key Components + +| Function | Description | +|---|---| +| `genUser()` | Converts a `passwd` struct into a result row with uid, gid, username, description, directory, and shell fields | +| `genUsersImplLocal()` | Reads users directly from `/etc/passwd` via `fgetpwent_r`, bypassing NSS/LDAP to avoid unintended remote directory queries | +| `genUsersImplIncludeRemote()` | Queries users via `getpwent_r`/`getpwuid_r`/`getpwnam_r`, which respects NSS and includes LDAP-backed accounts | +| `genUsersImpl()` | Dispatcher that routes to local or remote implementation based on the `include_remote` query constraint | +| `genUsers()` | Public table entry point; handles namespace constraints and logger initialization | + +## Usage Example + +```cpp +// Query all local users (default — skips LDAP) +SELECT uid, gid, username, directory, shell FROM users; + +// Include remote/LDAP users explicitly +SELECT uid, username FROM users WHERE include_remote = 1; + +// Filter by uid (works in both local and remote modes) +SELECT username, shell FROM users WHERE uid = 1000; + +// Filter by username +SELECT uid, gid, directory FROM users WHERE username = 'alice'; +``` + +## Notes + +- **LDAP-safe by default**: `genUsersImplLocal` deliberately avoids `getpwent_r` and related NSS functions to prevent osquery from triggering remote directory lookups on LDAP-configured hosts (see [osquery#8337](https://github.com/osquery/osquery/issues/8337)). +- Buffer size is capped at **16384 bytes** when `_SC_GETPW_R_SIZE_MAX` returns an indeterminate value. +- Signed uid/gid variants (`uid_signed`, `gid_signed`) are cast to `int32_t` for SQL compatibility. \ No newline at end of file diff --git a/osquery/tables/system/linux/.yum_sources.md b/osquery/tables/system/linux/.yum_sources.md new file mode 100644 index 00000000000..affb8d5d551 --- /dev/null +++ b/osquery/tables/system/linux/.yum_sources.md @@ -0,0 +1,35 @@ + +Parses YUM repository configuration files to expose repository metadata as queryable osquery table rows. + +## Key Components + +- **`kYumConf`** – Default path to the main YUM config file (`/etc/yum.conf`) +- **`kYumReposDir`** – Default repository directory (`/etc/yum.repos.d`) +- **`kYumConfigFileExtension`** – File extension filter (`.repo`) +- **`parseYumConf(source, stream, results, repos_dir)`** – Core INI parser; reads a YUM config stream, extracts `reposdir` from `[main]`, and collects repository fields (`baseurl`, `enabled`, `gpgcheck`, `name`, `gpgkey`, `mirrorlist`, `metalink`) into `QueryData` rows +- **`parseYumConf(source, results, repos_dir, logger)`** – File-opening overload; opens the file path, handles missing/unreadable files, and delegates to the stream variant with error logging +- **`genYumSrcsImpl`** – Orchestrates parsing of `yum.conf` then resolves and parses all `.repo` files in the configured repos directory +- **`genYumSrcs`** – Table entry point; dispatches to namespace-isolated execution or standard `GLOGLogger`-backed execution + +## Usage Example + +```cpp +// Called internally by the osquery table framework +QueryContext context; + +// Standard execution path +QueryData results = genYumSrcs(context); + +// Each row contains fields such as: +// results[i]["source"] → "/etc/yum.repos.d/fedora.repo" +// results[i]["name"] → "Fedora" +// results[i]["baseurl"] → "https://dl.fedoraproject.org/pub/fedora/..." +// results[i]["enabled"] → "1" +// results[i]["gpgcheck"] → "1" +``` + +## Notes + +- Falls back to `kYumReposDir` if `reposdir` is absent from `[main]` or the file is unreadable +- Skips the `[main]` section when building result rows +- Supports namespace-constrained queries via `generateInNamespace` \ No newline at end of file diff --git a/osquery/tables/system/linux/dbus/.uniquedbusconnection.md b/osquery/tables/system/linux/dbus/.uniquedbusconnection.md new file mode 100644 index 00000000000..40514efbe3f --- /dev/null +++ b/osquery/tables/system/linux/dbus/.uniquedbusconnection.md @@ -0,0 +1,49 @@ + +RAII wrapper for managing D-Bus connection lifetimes in osquery's Linux system tables, ensuring connections are automatically allocated and released. + +## Key Components + +### `UniqueDbusConnectionAllocator` +A policy class that implements allocation and deallocation strategies for `DBusConnection*` resources. + +| Member | Description | +|---|---| +| `ResourceType` | Alias for `DBusConnection*` | +| `allocate(connection, system)` | Opens a D-Bus connection; `system=true` connects to the system bus, `false` to the session bus | +| `deallocate(connection)` | Closes and unreferences the D-Bus connection | + +### `UniqueDbusConnection` +A type alias combining `UniqueDbusConnectionAllocator` with `UniqueResource<>`, providing automatic RAII lifecycle management for D-Bus connections. + +## Usage Example + +```c +#include + +namespace osquery { + +Status queryDbusSystemBus() { + // Allocate a system bus connection (auto-deallocated on scope exit) + UniqueDbusConnection connection; + + // true = system bus, false = session bus + auto status = connection.allocate(true); + if (!status.ok()) { + return status; + } + + DBusConnection* raw = connection.get(); + // Use raw D-Bus connection for message passing... + + return Status::success(); + // connection automatically deallocated here +} + +} // namespace osquery +``` + +## Notes + +- Inherits from `UniqueResource<>` — see `uniqueresource.h` for the full RAII interface +- The `bool` template parameter maps to the `system` flag passed to `allocate()` +- Part of osquery's Linux-only D-Bus infrastructure under `osquery/tables/system/linux/dbus/` \ No newline at end of file diff --git a/osquery/tables/system/linux/dbus/.uniquedbusmessage.md b/osquery/tables/system/linux/dbus/.uniquedbusmessage.md new file mode 100644 index 00000000000..0cb0b4a68b4 --- /dev/null +++ b/osquery/tables/system/linux/dbus/.uniquedbusmessage.md @@ -0,0 +1,40 @@ + +RAII wrapper for D-Bus message lifecycle management within the osquery Linux system tables infrastructure. + +## Key Components + +### `UniqueDbusMessageAllocator` +A protected allocator class managing `DBusMessage*` resource lifetime: + +- **`allocate()`** — Creates a new `DBusMessage*` for a method call, parameterized by destination service, object path, interface, and method name +- **`deallocate()`** — Safely releases the `DBusMessage*` resource + +### `UniqueDbusMessage` (type alias) +A `UniqueResource` specialization that combines `UniqueDbusMessageAllocator` with four `const std::string&` constructor arguments, providing automatic cleanup via RAII semantics. + +## Usage Example + +```cpp +#include + +// Create a scoped D-Bus method call message +UniqueDbusMessage message; +auto status = message.initialize( + "org.freedesktop.NetworkManager", // destination + "/org/freedesktop/NetworkManager", // path + "org.freedesktop.NetworkManager", // interface + "GetDevices" // method +); + +if (!status.ok()) { + // handle error +} + +// message is automatically freed when it goes out of scope +``` + +## Notes + +- Follows the `UniqueResource` pattern used across osquery's D-Bus table helpers for consistent resource safety +- Intended for **Linux only** — part of the `osquery/tables/system/linux/dbus/` subsystem +- Dual-licensed under Apache-2.0 or GPL-2.0-only \ No newline at end of file diff --git a/osquery/tables/system/linux/dbus/.uniqueresource.md b/osquery/tables/system/linux/dbus/.uniqueresource.md new file mode 100644 index 00000000000..3955c617a1f --- /dev/null +++ b/osquery/tables/system/linux/dbus/.uniqueresource.md @@ -0,0 +1,53 @@ + +RAII-style unique ownership wrapper for resources that require custom allocation and deallocation logic, templated on a `ResourceAllocator` policy class. + +## Key Components + +### `UniqueResource` +A move-only, non-copyable resource manager that inherits allocation behavior from `ResourceAllocator`. + +**Type Aliases** +- `ResourceType` — the underlying resource type, derived from `ResourceAllocator::ResourceType` + +**Methods** + +| Method | Description | +|--------|-------------| +| `operator bool()` | Returns `true` if the resource is non-null | +| `get()` | Returns the underlying resource value | +| `reset(resource)` | Releases current resource and takes ownership of a new one | +| `release()` | Deallocates the resource if currently held | +| `create(unique_resource, args...)` | Static factory — allocates via `ResourceAllocator::allocate()` and populates a `UniqueResource`; returns `Status` | + +**Constraints** +- Move constructible and assignable +- Copy construction and assignment are explicitly deleted + +## Usage Example + +```cpp +struct FileAllocator { + using ResourceType = int; // file descriptor + + static Status allocate(ResourceType& fd, const std::string& path) { + fd = ::open(path.c_str(), O_RDONLY); + if (fd < 0) return Status::failure("Failed to open file"); + return Status::success(); + } + + static void deallocate(ResourceType& fd) { + ::close(fd); + fd = -1; + } +}; + +UniqueResource file; +auto status = UniqueResource::create(file, "/etc/hosts"); + +if (status.ok() && file) { + int fd = file.get(); // use the file descriptor +} +// destructor automatically closes fd when file goes out of scope +``` + +> **Note:** `ResourceAllocator` must define `ResourceType`, a static `allocate()`, and a static `deallocate()` to satisfy the template contract. \ No newline at end of file diff --git a/osquery/tables/system/linux/dbus/methods/.dbusmethod.md b/osquery/tables/system/linux/dbus/methods/.dbusmethod.md new file mode 100644 index 00000000000..998bfc703aa --- /dev/null +++ b/osquery/tables/system/linux/dbus/methods/.dbusmethod.md @@ -0,0 +1,51 @@ + +Provides a generic, type-safe template for constructing, sending, and parsing D-Bus method calls within the osquery Linux system tables infrastructure. + +## Key Components + +### `DbusMethod` +A final template class that orchestrates the full D-Bus method call lifecycle. Inherits from a `MethodHandler` policy class that must define: +- `kDestination`, `kInterface`, `kMethod` — static string constants +- `Output` — the result type +- `parseReply(output, reply)` — reply parsing logic + +**Public method:** +- `call(output, connection, object_path, args...)` — builds the message, appends parameters, sends it synchronously, and parses the reply + +**Private helpers:** +- `sendMessage(...)` — wraps `dbus_connection_send_with_reply_and_block`, handles `DBusError` lifecycle +- `processParameter(...)` — appends a single `std::string` parameter to a message iterator +- `processParameterList(...)` — variadic fold over `processParameter` to append all arguments + +### `readDbusMessageField` +A generic helper that reads a typed field from a `DBusMessageIter`, optionally advancing the iterator. Throws `Status::failure` on type mismatch or iteration errors. + +### Pre-instantiated field readers +```c +readDbusMessageStringField // DBUS_TYPE_STRING → const char* +readDbusMessageObjectPathField // DBUS_TYPE_OBJECT_PATH → const char* +readDbusMessageUint32Field // DBUS_TYPE_UINT32 → std::uint32_t +``` + +## Usage Example + +```cpp +struct MyMethodHandler { + static constexpr auto kDestination = "org.freedesktop.MyService"; + static constexpr auto kInterface = "org.freedesktop.MyInterface"; + static constexpr auto kMethod = "GetInfo"; + + struct Output { std::string info; }; + + Status parseReply(Output& out, const UniqueDbusMessage& reply) const { + DBusMessageIter it{}; + dbus_message_iter_init(reply.get(), &it); + out.info = readDbusMessageStringField(it, false); + return Status::success(); + } +}; + +DbusMethod method; +MyMethodHandler::Output result; +auto status = method.call(result, connection, "/org/freedesktop/MyObject", "param1"); +``` \ No newline at end of file diff --git a/osquery/tables/system/linux/dbus/methods/.getstringproperty.md b/osquery/tables/system/linux/dbus/methods/.getstringproperty.md new file mode 100644 index 00000000000..d87c29e3df1 --- /dev/null +++ b/osquery/tables/system/linux/dbus/methods/.getstringproperty.md @@ -0,0 +1,40 @@ + +Defines a D-Bus method handler for retrieving a single string property from a systemd D-Bus interface using the `org.freedesktop.DBus.Properties.Get` method. + +## Key Components + +- **`GetStringPropertyMethodHandler`** — Handler class implementing the D-Bus `Get` property call against the `org.freedesktop.systemd1` destination. Contains: + - `kDestination` — Target D-Bus service (`org.freedesktop.systemd1`) + - `kInterface` — Property interface (`org.freedesktop.DBus.Properties`) + - `kMethod` — D-Bus method name (`Get`) + - `parseReply()` — Parses the D-Bus reply message into a `std::string` output, returning an osquery `Status` + +- **`GetStringPropertyMethod`** — Type alias instantiating `DbusMethod<>` with the handler and two `const std::string&` parameters (interface name and property name) + +## Usage Example + +```cpp +#include + +using namespace osquery; + +// Invoke to retrieve a string property from a systemd unit +GetStringPropertyMethod method; +std::string result; + +auto status = method.call( + connection, + "/org/freedesktop/systemd1/unit/sshd_2eservice", + "org.freedesktop.systemd1.Unit", + "ActiveState" +); + +if (status.ok()) { + // result contains the property value, e.g. "active" +} +``` + +## Notes + +- Protected constructor enforces use only through the `GetStringPropertyMethod` alias or subclassing +- Designed for use within osquery's Linux system tables that introspect systemd unit properties via D-Bus \ No newline at end of file diff --git a/osquery/tables/system/linux/dbus/methods/.listunitsmethodhandler.md b/osquery/tables/system/linux/dbus/methods/.listunitsmethodhandler.md new file mode 100644 index 00000000000..a2697f4e24d --- /dev/null +++ b/osquery/tables/system/linux/dbus/methods/.listunitsmethodhandler.md @@ -0,0 +1,44 @@ + +Defines the D-Bus method handler for invoking `ListUnits` on the systemd1 Manager interface, parsing the response into structured unit records for use in osquery's Linux system tables. + +## Key Components + +### `ListUnitsMethodHandler` +Core handler class that encodes D-Bus targeting constants and reply parsing logic. + +| Member | Description | +|---|---| +| `kDestination` | D-Bus destination: `org.freedesktop.systemd1` | +| `kInterface` | Target interface: `org.freedesktop.systemd1.Manager` | +| `kMethod` | Method name: `ListUnits` | +| `Unit` | POD struct holding per-unit fields (id, states, job info, object path) | +| `Output` | Alias for `std::vector` | +| `parseReply()` | Parses a raw `DBusMessage` reply into the `Output` vector | +| `readUnitInformation()` | Reads a single unit's fields from a `DBusMessageIter` | + +### `ListUnitsMethod` +Type alias combining `ListUnitsMethodHandler` with the generic `DbusMethod` template — the primary type used by callers. + +## Usage Example + +```c +#include + +osquery::ListUnitsMethodHandler::Output units; + +// Invoke via the composed method type +auto conn = /* acquire DBusConnection */; +osquery::ListUnitsMethod method; +auto status = method.call(conn, units); + +if (status.ok()) { + for (const auto& unit : units) { + // Access unit.id, unit.active_state, unit.load_state, etc. + } +} +``` + +## Notes + +- Constructor and destructor are `protected` — instantiation is intended only through the `DbusMethod` template. +- `Unit::job_id` defaults to `0U`; a non-zero value indicates a pending systemd job. \ No newline at end of file diff --git a/osquery/tables/system/posix/.augeas.md b/osquery/tables/system/posix/.augeas.md new file mode 100644 index 00000000000..e1816c92e34 --- /dev/null +++ b/osquery/tables/system/posix/.augeas.md @@ -0,0 +1,39 @@ + +Provides the osquery `augeas` virtual table implementation, bridging the Augeas configuration file parsing library with osquery's SQL query interface. + +## Key Components + +### Flags +- **`augeas_lenses`** — Configurable path to Augeas lens files (platform-specific defaults for macOS/Linux) + +### Functions + +| Function | Description | +|---|---| +| `reportAugeasError(aug)` | Logs Augeas API errors via osquery's logger | +| `matchAugeasPattern(aug, pattern, results, context)` | Executes an Augeas tree match and populates `QueryData` rows with `node`, `value`, `path`, and `label` columns | +| `patternsFromOsquery(patterns, input, isLike, isPath)` | Translates osquery SQL constraints (including `LIKE` wildcards) into Augeas path expressions, prepending `/files` for filesystem paths | +| `genAugeas(context)` | Main table generator — initializes Augeas, loads all configuration files, resolves query constraints, and dispatches pattern matching | + +### Classes + +- **`AugeasHandle`** — Singleton RAII wrapper around the `augeas*` handle. Uses `std::call_once` for thread-safe lazy initialization with flags `AUG_NO_ERR_CLOSE | AUG_NO_LOAD | AUG_NO_STDINC | AUG_SAVE_NOOP` + +## Usage Example + +```cpp +// Query via SQL in osquery shell: +// SELECT node, value, path, label FROM augeas WHERE path = '/etc/hosts'; +// SELECT node, value, path, label FROM augeas WHERE path LIKE '/etc/ssh%'; +// SELECT node, value FROM augeas WHERE node = '/files/etc/hosts/1/ipaddr'; + +// Wildcard translation: SQL LIKE '%' → Augeas '*' +// Double '%' → '/*' (single-level), single '%' → '*' +// Path queries automatically append '//*' for recursive tree traversal +``` + +## Notes + +- Augeas is initialized **once** per process via `kAugeasHandle` (static singleton) +- `aug_load()` loads **all** lenses/files on every query — targeted unloading is not currently supported +- `/files` prefix is stripped from `node` values to produce the clean filesystem `path` column \ No newline at end of file diff --git a/osquery/tables/system/posix/.authorized_keys.md b/osquery/tables/system/posix/.authorized_keys.md new file mode 100644 index 00000000000..4a9167b2c77 --- /dev/null +++ b/osquery/tables/system/posix/.authorized_keys.md @@ -0,0 +1,39 @@ + +Header file declaring functions for parsing and retrieving SSH authorized keys for system users within the osquery tables framework. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `genSSHkeysForUser` | Function | Parses the `~/.ssh/authorized_keys` file for a specific user, appending discovered key entries to the `QueryData` results | +| `getAuthorizedKeys` | Function | Entry point for the `authorized_keys` osquery virtual table; resolves user context and aggregates SSH key data across all eligible users | + +Both functions live inside the `osquery::tables` namespace. + +## Usage Example + +```c +#include "authorized_keys.h" + +// Populate SSH keys for a single user +osquery::QueryData results; +osquery::GlogLogger logger; + +osquery::tables::genSSHkeysForUser( + "1000", // uid + "1000", // gid + "/home/alice", // home directory + results, + logger +); + +// Or query via the full table interface (used internally by osquery) +osquery::QueryContext ctx; +osquery::QueryData allKeys = osquery::tables::getAuthorizedKeys(ctx); +``` + +## Notes + +- `genSSHkeysForUser` is designed to be called in a worker process context, accepting a `Logger&` reference to support osquery's inter-process logging via `GlogLogger`. +- `getAuthorizedKeys` integrates with the osquery table registration system and is typically invoked by the query engine rather than called directly. +- Both functions depend on `osquery/core/tables.h` (`QueryData`, `QueryContext`) and `osquery/logger/logger.h`. \ No newline at end of file diff --git a/osquery/tables/system/posix/.crontab.md b/osquery/tables/system/posix/.crontab.md new file mode 100644 index 00000000000..7d027e3c7f5 --- /dev/null +++ b/osquery/tables/system/posix/.crontab.md @@ -0,0 +1,30 @@ + +Parses system and user crontab files across common Linux and macOS cron directories, returning structured cron job entries as osquery table rows. + +## Key Components + +- **`kSystemCron`** — Path constant for the system-wide crontab (`/etc/crontab`) +- **`kCronSearchDirs`** — Vector of directories searched for additional cron files (system and per-user, covering CentOS, Debian, and macOS) +- **`cronFromFile(path, logger)`** — Reads a crontab file, strips blank lines and comments (`#`), and returns a vector of trimmed active lines +- **`genCronLine(path, line, results)`** — Parses a single cron line into fields (`minute`, `hour`, `day_of_month`, `month`, `day_of_week`, `command`), with support for `@`-style event shortcuts (e.g., `@reboot`) +- **`genCronTabImpl(context, logger)`** — Aggregates all cron entries from the system file and all search directories into a `QueryData` result set +- **`genCronTab(context)`** — Public table entry point; dispatches to namespace-aware generation or standard execution with a GLOG logger + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM crontab; + +// Typical output columns: +// path - Source file path (e.g., /etc/crontab) +// minute - Cron minute field (e.g., "0") +// hour - Cron hour field (e.g., "*/2") +// day_of_month - Day of month field +// month - Month field +// day_of_week - Day of week field +// event - @-style shortcut if present (e.g., "@reboot") +// command - The scheduled command to execute +``` + +Lines that resolve to no `command` value (e.g., variable assignments like `MAILTO=root`) are silently skipped. \ No newline at end of file diff --git a/osquery/tables/system/posix/.known_hosts.md b/osquery/tables/system/posix/.known_hosts.md new file mode 100644 index 00000000000..a5c383e0a86 --- /dev/null +++ b/osquery/tables/system/posix/.known_hosts.md @@ -0,0 +1,32 @@ + +Declares the interface for parsing SSH `known_hosts` files and enumerating trusted host keys for system users. + +## Key Components + +- **`getKnownHostsKeys(QueryContext& context)`** — Primary table generator that queries all known SSH host keys across users on the system. Integrates with the osquery table dispatch system. +- **`impl::genSSHkeysForHosts(...)`** — Internal helper that parses a specific user's `known_hosts` file given their `uid`, `gid`, and home `directory`, appending discovered key entries to the provided `QueryData` results set. + +## Usage Example + +```c +// Typical osquery table dispatch usage +namespace osquery { +namespace tables { + +// Called by the osquery table infrastructure +QueryData results = getKnownHostsKeys(context); + +// Internal usage: parse known_hosts for a specific user +QueryData rows; +impl::genSSHkeysForHosts("1000", "1000", "/home/alice", rows); + +// rows now contains entries from /home/alice/.ssh/known_hosts +} +} +``` + +## Notes + +- The `impl` namespace separates internal parsing logic from the public table interface, allowing unit-testable decomposition. +- Both functions operate within `osquery::tables`, following standard osquery table module conventions. +- The header depends on `osquery/core/query.h` and `osquery/core/tables.h`, which provide `QueryData` and `QueryContext` respectively. \ No newline at end of file diff --git a/osquery/tables/system/posix/.last.md b/osquery/tables/system/posix/.last.md new file mode 100644 index 00000000000..a29cd4c63be --- /dev/null +++ b/osquery/tables/system/posix/.last.md @@ -0,0 +1,31 @@ + +Declares the interface for querying last login/access records from the system's `utmpx` database on Unix-like systems. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `genLastAccess` | Function | Primary table generator that queries all last login/access records | +| `impl::genLastAccessForRow` | Function | Internal helper that processes a single `utmpx` entry into a result row | + +## Usage Example + +```c +#include "last.h" + +// Generate all last login records +osquery::QueryContext context; +osquery::QueryData results = osquery::tables::genLastAccess(context); + +// Or process a single utmpx row directly (internal use) +utmpx entry; +// ... populate entry via getutxent() or similar ... +osquery::QueryData rowResults; +osquery::tables::impl::genLastAccessForRow(entry, rowResults); +``` + +## Notes + +- Depends on `` — available on Linux, macOS, and other POSIX systems; **not available on Windows** +- `genLastAccessForRow` lives in the `impl` namespace, signaling it is an internal implementation detail not intended for direct external use +- Feeds the osquery `last` virtual table, which exposes data equivalent to running the `last` command in a terminal \ No newline at end of file diff --git a/osquery/tables/system/posix/.load_average.md b/osquery/tables/system/posix/.load_average.md new file mode 100644 index 00000000000..89fe0c86ea3 --- /dev/null +++ b/osquery/tables/system/posix/.load_average.md @@ -0,0 +1,35 @@ + +Implements the `load_average` osquery table, querying the system's 1-minute, 5-minute, and 15-minute load averages using the POSIX `getloadavg()` call. + +## Key Components + +- **`kPeriods`** — A compile-time `constexpr` array of three `boost::string_view` labels: `"1m"`, `"5m"`, and `"15m"`, used as period identifiers in query results. +- **`genLoadAverage(QueryContext&)`** — Table generator function that calls `getloadavg()` to retrieve system load averages and maps each value to its corresponding period label. + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT * FROM load_average; +// +// Expected output: +// period | average +// -------+--------- +// 1m | 0.520000 +// 5m | 0.610000 +// 15m | 0.480000 + +QueryContext context; +QueryData results = genLoadAverage(context); + +for (const auto& row : results) { + // row["period"] -> "1m", "5m", or "15m" + // row["average"] -> string-formatted double +} +``` + +## Behavior Notes + +- Returns **3 rows** always pre-allocated; rows remain empty/default if `getloadavg()` returns `-1` (failure). +- Period labels are stored as `boost::string_view` pointing to static string literals — no heap allocation for label data. +- Load values are converted from `double` to `std::string` via `std::to_string()`. \ No newline at end of file diff --git a/osquery/tables/system/posix/.logged_in_users.md b/osquery/tables/system/posix/.logged_in_users.md new file mode 100644 index 00000000000..e7dcc21a739 --- /dev/null +++ b/osquery/tables/system/posix/.logged_in_users.md @@ -0,0 +1,35 @@ + +Implements the `logged_in_users` osquery table by reading login session records from the system's `utmpx` database, returning details about active and historical user sessions. + +## Key Components + +- **`kLoginTypes`** — Mapping of `utmpx` integer type codes to human-readable strings (e.g., `USER_PROCESS` → `"user"`, `DEAD_PROCESS` → `"dead"`). FreeBSD excludes `runlevel` and `accounting` types via preprocessor guards. +- **`utmpxEnumerationMutex`** — A `WriteLock`-protected mutex ensuring thread-safe access to the non-reentrant `utmpx` API. +- **`genLoggedInUsers()`** — Table generator that enumerates all `utmpx` entries, skipping PID 1 (init/kernel boot entry), and maps each record to a result row. + +## Returned Columns + +| Column | Type | Source | +|--------|------|--------| +| `type` | TEXT | `ut_type` mapped via `kLoginTypes` | +| `user` | TEXT | `ut_user` | +| `tty` | TEXT | `ut_line` | +| `host` | TEXT | `ut_host` | +| `time` | INTEGER | `ut_tv.tv_sec` | +| `pid` | INTEGER | `ut_pid` | + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT user, tty, host, time, pid, type FROM logged_in_users; + +QueryContext context; +QueryData results = genLoggedInUsers(context); + +for (const auto& row : results) { + // row["user"], row["tty"], row["host"], row["time"], row["pid"], row["type"] +} +``` + +> **Note:** The function calls `utmpxname(_PATH_UTMPX)` and `setutxent()` to reset the cursor to the first entry on each invocation, ensuring consistent results across repeated queries. \ No newline at end of file diff --git a/osquery/tables/system/posix/.magic.md b/osquery/tables/system/posix/.magic.md new file mode 100644 index 00000000000..31a3ee2bd13 --- /dev/null +++ b/osquery/tables/system/posix/.magic.md @@ -0,0 +1,35 @@ + +Implements the osquery `magic` table, which uses the `libmagic` library to identify file types, MIME types, and MIME encodings for queried file paths. + +## Key Components + +- **`kMagicFiles`** — Default magic database file paths searched on the system (`magic.mgc` locations). +- **`kMagicFileDBSep`** — Colon-delimited separator used when joining multiple magic DB file paths. +- **`genMagicData(QueryContext&)`** — Primary table generation function; initializes a magic cookie, loads the magic database, and iterates over constrained paths to collect file type data. + +## Usage Example + +```cpp +// Equivalent SQL query against the osquery magic table: +// SELECT path, data, mime_type, mime_encoding +// FROM magic +// WHERE path = '/usr/bin/python3'; + +// Optionally override the magic database files: +// SELECT path, mime_type +// FROM magic +// WHERE path = '/tmp/unknown_file' +// AND magic_db_files = '/custom/magic.mgc'; +``` + +Each result row contains: + +| Column | Description | +|---|---| +| `path` | File path queried | +| `magic_db_files` | Magic DB(s) used (colon-separated) | +| `data` | Human-readable file type description | +| `mime_type` | MIME type (e.g., `text/plain`) | +| `mime_encoding` | MIME encoding (e.g., `us-ascii`) | + +> **Note:** The magic cookie is initialized with `MAGIC_NONE` and flags are toggled per-query (`MAGIC_MIME_TYPE`, `MAGIC_MIME_ENCODING`) to retrieve each field separately. The cookie is always closed on exit, even on error paths. \ No newline at end of file diff --git a/osquery/tables/system/posix/.openssl_utils.md b/osquery/tables/system/posix/.openssl_utils.md new file mode 100644 index 00000000000..b38f6f48488 --- /dev/null +++ b/osquery/tables/system/posix/.openssl_utils.md @@ -0,0 +1,45 @@ + +Utility header for extracting metadata and attributes from OpenSSL X.509 certificate objects within the `osquery::tables` namespace. + +## Key Components + +All functions accept a raw `X509*` pointer and return either `boost::optional`, `boost::optional`, or void. + +| Function | Returns | Description | +|---|---|---| +| `generateCertificateSHA1Digest` | `optional` | SHA1 fingerprint of the certificate | +| `getCertificateAttributes` | `void` | Populates CA and self-signed boolean flags | +| `getCertificateKeyUsage` | `optional` | Key usage extension value | +| `getCertificateSerialNumber` | `optional` | Certificate serial number | +| `getCertificateAuthorityKeyID` | `optional` | Authority Key Identifier extension | +| `getCertificateSubjectKeyID` | `optional` | Subject Key Identifier extension | +| `getCertificateIssuerName` | `optional` | Issuer DN (legacy format toggle via flag) | +| `getCertificateSubjectName` | `optional` | Subject DN (legacy format toggle via flag) | +| `getCertificateCommonName` | `optional` | CN field from the subject | +| `getCertificateSigningAlgorithm` | `optional` | Signature algorithm (e.g. `sha256WithRSAEncryption`) | +| `getCertificateKeyAlgorithm` | `optional` | Public key algorithm | +| `getCertificateKeyStregth` | `optional` | Key size/strength (note: typo in original symbol name) | +| `getCertificateNotValidBefore` | `optional` | Certificate validity start timestamp | +| `getCertificateNotValidAfter` | `optional` | Certificate validity end timestamp | + +## Usage Example + +```cpp +#include "openssl_utils.h" + +// cert is an X509* obtained from OpenSSL (e.g. via PEM/DER parsing) +X509* cert = /* ... */; + +bool is_ca = false, is_self_signed = false; +osquery::tables::getCertificateAttributes(cert, is_ca, is_self_signed); + +if (auto sha1 = osquery::tables::generateCertificateSHA1Digest(cert)) { + std::cout << "Fingerprint: " << *sha1 << "\n"; +} + +if (auto expiry = osquery::tables::getCertificateNotValidAfter(cert)) { + std::cout << "Expires: " << std::ctime(&(*expiry)); +} +``` + +> **Note:** `getCertificateKeyStregth` contains a typo in the original symbol name; use as-is to avoid linker errors. \ No newline at end of file diff --git a/osquery/tables/system/posix/.shell_history.md b/osquery/tables/system/posix/.shell_history.md new file mode 100644 index 00000000000..49b8f1564f9 --- /dev/null +++ b/osquery/tables/system/posix/.shell_history.md @@ -0,0 +1,42 @@ + +Declares internal helper functions for generating shell history table rows in osquery's `shell_history` virtual table implementation. + +## Key Components + +### Functions + +- **`genShellHistoryFromBashSessions`** — Parses shell history from Bash session files within a given directory for a specific user ID. Accepts a predicate callback for row emission, enabling testability without direct table coupling. + +- **`genShellHistoryForUser`** — Enumerates shell history entries for a user identified by `uid`/`gid` and home `directory`. Feeds results through a `DynamicTableRowHolder` predicate rather than returning data directly. + +## Usage Example + +```c +#include "shell_history.h" + +// Collect all history rows for a user +osquery::tables::genShellHistoryForUser( + "1000", // uid + "1000", // gid + "/home/alice", // home directory + [](osquery::DynamicTableRowHolder& row) { + // Process or assert on each emitted history row + std::cout << row["command"] << std::endl; + } +); + +// Collect Bash session history specifically +osquery::tables::genShellHistoryFromBashSessions( + "1000", + "/home/alice", + [](osquery::DynamicTableRowHolder& row) { + // Handle each session history row + } +); +``` + +## Notes + +- Both functions use a **predicate/callback pattern** (`std::function`) to decouple row generation from table registration, making unit testing straightforward without requiring a live osquery table context. +- Declared within `osquery::tables` namespace, consistent with osquery's virtual table architecture. +- Depends on `osquery/core/tables.h` and `osquery/sql/dynamic_table_row.h`. \ No newline at end of file diff --git a/osquery/tables/system/posix/.smbios_utils.md b/osquery/tables/system/posix/.smbios_utils.md new file mode 100644 index 00000000000..4e7578e328e --- /dev/null +++ b/osquery/tables/system/posix/.smbios_utils.md @@ -0,0 +1,49 @@ + +Parses and decodes raw SMBIOS (System Management BIOS) table data into structured, human-readable rows for osquery tables covering system hardware information such as memory devices, BIOS, processors, and enclosures. + +## Key Components + +### Lookup Tables +- **`kSMBIOSTypeDescriptions`** — Maps SMBIOS type codes (0–132) to descriptive strings (e.g., `{4, "Processor Information"}`) +- **`kSMBIOSMemoryFormFactorTable`** — Maps memory form factor codes to names (DIMM, SODIMM, FB-DIMM, etc.) +- **`kSMBIOSMemoryTypeTable`** — Maps memory type codes to names (DDR, DDR2, DDR4, LPDDR4, etc.) +- **`kSMBIOSMemoryDetailsTable`** — Maps memory detail bit-field indices to descriptions +- **`kSMBIOSMemoryArrayLocationTable`**, **`kSMBIOSMemoryArrayUseTable`**, **`kSMBIOSMemoryArrayErrorCorrectionTypesTable`**, **`kSMBIOSMemoryErrorTypeTable`**, **`kSMBIOSMemoryErrorGranularityTable`**, **`kSMBIOSMemoryErrorOperationTable`** — Additional SMBIOS spec-aligned lookup maps + +### Functions + +| Function | Description | +|---|---| +| `dmiToWord(address, offset)` | Reads a little-endian `uint16_t` from a raw SMBIOS byte buffer | +| `dmiToDWord(address, offset)` | Reads a little-endian `uint32_t` from a raw SMBIOS byte buffer | +| `dmiToQWord(address, offset)` | Reads a little-endian `uint64_t` from a raw SMBIOS byte buffer | +| `dmiWordToHexStr(address, offset)` | Returns a hex-formatted string of a WORD field (e.g., `"0x0004"`) | +| `toHexStr(num, width)` | Generic helper to format a numeric value as a zero-padded hex string | +| `SMBIOSParser::tables(predicate)` | Iterates over all SMBIOS structures, invoking a callback for each valid entry | +| `genSMBIOSTable(...)` | Generates a generic osquery row for any SMBIOS table entry (type, handle, MD5 hash, size) | +| `genSMBIOSMemoryDevices(...)` | Generates detailed osquery rows for SMBIOS Type 17 (Memory Device) structures | + +## Usage Example + +```cpp +SMBIOSParser parser; + +// Iterate all SMBIOS tables +parser.tables([](size_t index, + const SMBStructHeader* hdr, + uint8_t* address, + uint8_t* textAddrs, + size_t size) { + QueryData results; + genSMBIOSTable(index, hdr, address, size, results); + + // For memory devices specifically + if (hdr->type == kSMBIOSTypeMemoryDevice) { + genSMBIOSMemoryDevices(index, hdr, address, textAddrs, size, results); + } +}); + +// Read a WORD field from raw SMBIOS data +uint8_t* addr = /* raw SMBIOS bytes */; +uint16_t speed = dmiToWord(addr, 0x15); +``` \ No newline at end of file diff --git a/osquery/tables/system/posix/.ssh_keys.md b/osquery/tables/system/posix/.ssh_keys.md new file mode 100644 index 00000000000..2c8e65239b5 --- /dev/null +++ b/osquery/tables/system/posix/.ssh_keys.md @@ -0,0 +1,38 @@ + +Declares the interface for SSH key enumeration within osquery's tables subsystem, exposing functions to discover and collect SSH keys from user home directories. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `kSSHUserKeysDir` | `extern const std::string` | Default SSH keys directory path (e.g., `.ssh`) relative to a user's home | +| `genSSHkeyForHosts` | Function | Enumerates SSH keys for a specific user identified by `uid`/`gid` within the given `directory`, appending results to `QueryData` | +| `getUserSshKeys` | Function | Entry point for the `user_ssh_keys` virtual table; resolves user context and delegates to `genSSHkeyForHosts` | + +## Usage Example + +```c +#include "ssh_keys.h" + +// Direct enumeration for a known user +osquery::QueryData results; +osquery::GlogLogger logger; + +osquery::tables::genSSHkeyForHosts( + "1000", // uid + "1000", // gid + "/home/alice", // user home directory + results, + logger +); + +// Or via the osquery table query interface +osquery::QueryContext ctx; +osquery::QueryData tableResults = osquery::tables::getUserSshKeys(ctx); +``` + +## Notes + +- Both functions live inside the `osquery::tables` namespace. +- `genSSHkeyForHosts` is exposed (not `static`) to allow unit testing and reuse across platform-specific implementations. +- Logging is injected via `Logger&`, keeping I/O concerns separate from enumeration logic. \ No newline at end of file diff --git a/osquery/tables/system/posix/.sudoers.md b/osquery/tables/system/posix/.sudoers.md new file mode 100644 index 00000000000..129383a3026 --- /dev/null +++ b/osquery/tables/system/posix/.sudoers.md @@ -0,0 +1,32 @@ + +Declares the interface for parsing and querying sudoers configuration files within the osquery tables framework. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `genSudoersFile` | Function | Parses a single sudoers file at the given path, recursively handling `#include`/`@include` directives via the `level` depth parameter, and appends results to `QueryData` | +| `genSudoers` | Function | Entry point for the `sudoers` virtual table; dispatches file parsing and returns the full `QueryData` result set | + +Both symbols reside in the `osquery::tables` namespace. + +## Usage Example + +```cpp +#include "sudoers.h" + +// Query all sudoers entries via the osquery table interface +osquery::QueryContext ctx; +osquery::QueryData results = osquery::tables::genSudoers(ctx); + +// Or parse a specific sudoers file directly +osquery::QueryData fileResults; +osquery::tables::genSudoersFile("/etc/sudoers", 0, fileResults); + +for (const auto& row : fileResults) { + // Each row contains parsed sudoers rule fields + // e.g., row.at("source"), row.at("header"), row.at("rule") +} +``` + +> **Note:** The `level` parameter in `genSudoersFile` guards against circular or deeply nested `#include` directives. Pass `0` for the top-level file; recursive calls increment this value automatically. \ No newline at end of file diff --git a/osquery/tables/system/posix/.suid_bin.md b/osquery/tables/system/posix/.suid_bin.md new file mode 100644 index 00000000000..db442465346 --- /dev/null +++ b/osquery/tables/system/posix/.suid_bin.md @@ -0,0 +1,33 @@ + +Implements the `suid_bin` osquery virtual table, which scans common binary directories for executables with the SUID (Set User ID) or SGID (Set Group ID) permission bits set — a common attack surface for privilege escalation audits. + +## Key Components + +- **`kBinarySearchPaths`** — Default scan paths: `/bin`, `/sbin`, `/usr/bin`, `/usr/sbin`, `/usr/local/bin`, `/usr/local/sbin`, `/tmp` +- **`genBin(path, perms, results)`** — Collects metadata for a single binary: resolves owner username and group name via `getpwuid`/`getgrgid`, and encodes permission flags (`S` = SUID `04000`, `G` = SGID `02000`) +- **`genSuidBinsFromPath(path, results, logger)`** — Recursively walks a directory (skipping permission-denied entries) and calls `genBin` for any regular file with SUID or SGID bits set +- **`genSuidBinImpl(context, logger)`** — Iterates over all search paths and aggregates results +- **`genSuidBin(context)`** — Table entry point; dispatches to namespace-aware execution via `generateInNamespace` if a namespace constraint is present, otherwise runs directly with a GLOG logger + +## Usage Example + +```cpp +// Query via osquery SQL interface +SELECT path, username, groupname, permissions +FROM suid_bin; + +-- Example output: +-- path | username | groupname | permissions +-- /usr/bin/sudo | root | root | S +-- /usr/bin/newgrp | root | root | SG +``` + +## Row Schema + +| Column | Description | +|---|---| +| `path` | Absolute path to the binary | +| `username` | Owner username (falls back to UID string) | +| `groupname` | Owner group name (falls back to GID string) | +| `permissions` | `S` (SUID set), `G` (SGID set), or `SG` (both) | +| `pid_with_namespace` | Always `"0"`; reserved for namespace support | \ No newline at end of file diff --git a/osquery/tables/system/posix/.sysctl_utils.md b/osquery/tables/system/posix/.sysctl_utils.md new file mode 100644 index 00000000000..47fd7ab6424 --- /dev/null +++ b/osquery/tables/system/posix/.sysctl_utils.md @@ -0,0 +1,44 @@ + +Utility header for querying and enumerating sysctl kernel parameters on supported platforms within the osquery tables subsystem. + +## Key Components + +### Constants +- **`CTL_MAX_VALUE`** (`128`) — Maximum buffer size for sysctl value reads. +- **`CTL_DEBUG_MAXID`** — Fallback max debug ID, defined as `CTL_MAXNAME * 2` if not already provided by the platform. + +### Functions + +| Function | Description | +|---|---| +| `stringFromMIB(oid, oid_size)` | Converts a numeric MIB (Management Information Base) OID array into its human-readable string name. | +| `genAllControls(results, config, subsystem)` | Enumerates all sysctl controls for a given subsystem. **Platform-specific implementation required.** | +| `genControlInfo(oid, oid_size, results, config)` | Populates query results for a single sysctl entry identified by its OID. **Platform-specific implementation required.** | +| `genControlInfoFromName(name, results, config)` | Populates query results for a single sysctl entry looked up by name string. **Platform-specific implementation required.** | + +## Usage Example + +```c +#include "sysctl_utils.h" + +namespace osquery { +namespace tables { + +// Convert a known OID to its string name +int oid[] = {CTL_KERN, KERN_OSTYPE}; +std::string name = stringFromMIB(oid, 2); +// name == "kern.ostype" + +// Enumerate all controls under the "kern" subsystem +QueryData results; +std::map config; +genAllControls(results, config, "kern"); + +// Fetch info for a specific control by name +genControlInfoFromName("kern.ostype", results, config); + +} // namespace tables +} // namespace osquery +``` + +> **Note:** `genAllControls`, `genControlInfo`, and `genControlInfoFromName` are platform-specific and must be implemented separately for each supported OS (e.g., macOS, Linux, FreeBSD). \ No newline at end of file diff --git a/osquery/tables/system/posix/.system_controls.md b/osquery/tables/system/posix/.system_controls.md new file mode 100644 index 00000000000..0c9bb617e95 --- /dev/null +++ b/osquery/tables/system/posix/.system_controls.md @@ -0,0 +1,38 @@ + +Implements the `system_controls` osquery table for querying kernel sysctl parameters on POSIX systems. It reads sysctl configuration files and resolves MIB (Management Information Base) entries by name, OID string, or subsystem. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kControlSettingsFiles` | `const vector` | Primary sysctl config file (`/etc/sysctl.conf`) | +| `kControlSettingsDirs` | `const vector` | Glob patterns for sysctl drop-in config directories | +| `stringFromMIB()` | Function | Converts an integer-encoded MIB array into a dot-separated string (e.g., `1.2.3`) | +| `genControlInfoFromOIDString()` | Function | Parses a dot-notation OID string into an integer array and delegates to `genControlInfo()` | +| `genControlConfigFromPath()` | Function | Reads a sysctl config file and populates a key-value map, skipping comments (`#`, `;`) | +| `genSystemControls()` | Function | Main table generator; resolves config files and dispatches MIB queries by `name`, `oid`, or `subsystem` constraint | + +## Usage Example + +This table is queried via osquery SQL. The `genSystemControls()` function handles the following constraint patterns: + +```cpp +// Internally, constraints are dispatched as: + +// Query by name: +// SELECT * FROM system_controls WHERE name = 'kern.hostname'; +genControlInfoFromName(name, results, config); + +// Query by OID string: +// SELECT * FROM system_controls WHERE oid = '1.1.14'; +genControlInfoFromOIDString(oid_string, results, config); + +// Query by subsystem: +// SELECT * FROM system_controls WHERE subsystem = 'kern'; +genAllControls(results, config, subsystem); + +// Full table scan (no constraints): +genAllControls(results, config, ""); +``` + +Config parsing merges values from `/etc/sysctl.conf` and all matching drop-in `.conf` files before MIB resolution, allowing the table to annotate results with configured (desired) values alongside live kernel values. \ No newline at end of file diff --git a/osquery/tables/system/posix/.ulimit_info.md b/osquery/tables/system/posix/.ulimit_info.md new file mode 100644 index 00000000000..c01f328baf9 --- /dev/null +++ b/osquery/tables/system/posix/.ulimit_info.md @@ -0,0 +1,42 @@ + +Implements the osquery `ulimit_info` table, querying POSIX resource limits (`getrlimit`) for the current process and exposing them as structured query results. + +## Key Components + +### `kLimitsResourceMap` +A static map of resource limit names to their corresponding `RLIMIT_*` constants. Core limits (`cpu`, `fsize`, `data`, `stack`, `core`, `nofile`, `as`, `rss`, `memlock`, `nproc`) are always included; platform-specific limits (`locks`, `sigpending`, `msgqueue`, `nice`, `rtprio`, `sbsize`, `npts`, `swap`, `kqueues`, `umtxp`) are conditionally compiled via `#ifdef` guards. + +### `getLimit(QueryData& results)` +Iterates over `kLimitsResourceMap`, calls `getrlimit()` for each resource, and appends a row with three fields: +- `type` — resource name string +- `soft_limit` — current limit value or `"unlimited"` +- `hard_limit` — maximum limit value or `"unlimited"` + +Logs and skips entries where `getrlimit()` returns `-1`. + +### `genUlimitInfo(QueryContext& context)` +Table generator entry point registered with the osquery table framework. Delegates to `getLimit()` and returns the populated `QueryData`. + +## Usage Example + +```cpp +// Query via osquery SQL interface +// SELECT * FROM ulimit_info; +// +// Example output: +// type | soft_limit | hard_limit +// ---------|------------|------------ +// cpu | unlimited | unlimited +// nofile | 1024 | 65536 +// nproc | 63520 | unlimited +// stack | 8388608 | unlimited + +// Internal usage (table generator signature) +QueryContext ctx; +QueryData results = osquery::tables::genUlimitInfo(ctx); +for (const auto& row : results) { + // row["type"], row["soft_limit"], row["hard_limit"] +} +``` + +> **Note:** Available resource types vary by OS. Linux exposes `sigpending`, `msgqueue`, `nice`, and `rtprio`; BSD/macOS exposes `sbsize`, `npts`, `swap`, `kqueues`, and `umtxp`. \ No newline at end of file diff --git a/osquery/tables/system/tests/.system_tables_tests.md b/osquery/tables/system/tests/.system_tables_tests.md new file mode 100644 index 00000000000..2ee2f96c42a --- /dev/null +++ b/osquery/tables/system/tests/.system_tables_tests.md @@ -0,0 +1,50 @@ + +Integration test suite for osquery system tables, validating correctness of core SQL table queries including OS info, processes, users, groups, file hashing, and cross-table joins. + +## Key Components + +### `SystemsTablesTests` Fixture +GTest fixture that initializes the platform, registry/plugin system, and database before each test. On Windows, also starts/stops user and group background services. + +**Test cases:** +| Test | Description | +|---|---| +| `test_os_version` | Validates `os_version` returns one row with non-empty `major` and `name` | +| `test_hostname` | Checks `system_info` returns a non-empty hostname | +| `test_process_info` | Joins `osquery_info` with `processes`, validates UID and parent PID | +| `test_processes` | Verifies basic process listing and invalid PID constraint behavior | +| `test_users` | Validates user fields, full query stability, and invalid constraint handling | +| `test_groups` | Validates GID population and invalid constraint filtering | +| `test_processes_memory_cpu` | Checks RSS/total memory ≥ 1MB and CPU time delta is sane | +| `test_processes_disk_io` | Writes 1MB to disk and verifies `disk_bytes_written` increases (Linux/macOS only) | +| `test_abstract_joins` | Validates inner and left joins across `processes`, `file`, and `hash` tables | +| `test_table_constraints` | Tests `LIKE` and `=` constraints on the `file` table (cross-platform paths) | +| `test_win_drivers_query_time` | Ensures `drivers` table query completes within 10 seconds of CPU time (Windows only) | +| `test_win_crashes_parsing` | Validates `windows_crashes` stack trace field is non-empty when crashes exist | + +### `HashTableTest` Fixture +Standalone fixture that writes controlled content to a temp file and queries the `hash` table. + +**Test cases:** +| Test | Description | +|---|---| +| `hashes_are_correct` | Verifies MD5, SHA1, SHA256 match known-good values | +| `test_cache_works` | Confirms cached hash is returned when file mtime is unchanged | +| `test_cache_updates` | Confirms hash is refreshed when file mtime changes | + +## Usage Example + +```bash +# Build and run system table tests +./build/osquery/tables/system/systemtables_tests + +# Run a specific test +./build/osquery/tables/system/systemtables_tests \ + --gtest_filter=SystemsTablesTests.test_users +``` + +```cpp +// Example: querying processes table directly (mirrors test logic) +SQL results("select pid, name from processes limit 1"); +assert(!results.rows()[0].at("pid").empty()); +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.apps_tests.md b/osquery/tables/system/tests/darwin/.apps_tests.md new file mode 100644 index 00000000000..4a92ed51ec0 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.apps_tests.md @@ -0,0 +1,41 @@ + +Unit tests for the macOS `apps` table implementation, validating plist parsing and application discovery logic used by osquery's Darwin application enumeration. + +## Key Components + +| Component | Description | +|---|---| +| `getInfoPlistTree()` | Helper that loads and parses `test_info.plist` from the test config directory into a `boost::property_tree` | +| `AppsTests` | GTest fixture class wrapping all application table tests | +| `test_parse_info_plist` | Validates that `genApplication()` correctly parses a known plist into expected `QueryData` row fields | +| `test_sanity_check` | Integration-level test that scans `/Applications`, parses real app plists, and asserts Safari is present on the build host | + +## Usage Example + +```cpp +// Run all AppsTests via Google Test +// From the build directory: +// ./osquery_tests --gtest_filter="AppsTests.*" + +// The plist parsing test validates fields like: +Row expected = { + {"name", "Foobar.app"}, + {"bundle_identifier", "com.apple.PhotoBooth"}, + {"bundle_short_version", "6.0"}, + {"bundle_version", "517"}, + {"minimum_system_version", "10.7.0"}, + // ...additional fields +}; + +// Column-by-column comparison for clearer failure output: +for (const auto& column : expected) { + EXPECT_EQ(results[0][column.first], column.second); +} +``` + +## Notes + +- Tests exercise two internal functions declared externally: `genApplication()` and `genApplicationsFromPath()` +- `test_parse_info_plist` uses a fixture plist (`test_info.plist`) for deterministic unit testing +- `test_sanity_check` is a host-dependent integration check — it will fail on non-macOS or hosts without Safari installed +- Column-by-column assertion strategy is intentional, producing more actionable failure messages than whole-map comparison \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.asl_tests.md b/osquery/tables/system/tests/darwin/.asl_tests.md new file mode 100644 index 00000000000..830b7a2f3fe --- /dev/null +++ b/osquery/tables/system/tests/darwin/.asl_tests.md @@ -0,0 +1,29 @@ + +Unit tests for the macOS Apple System Log (ASL) virtual table implementation in osquery, validating query construction, row parsing, regex conversion, and live log entry read-back. + +## Key Components + +| Test | Description | +|---|---| +| `test_add_query_op` | Verifies `addQueryOp()` correctly maps osquery operators (`EQUALS`, `GREATER_THAN`, `LESS_THAN`, `LIKE`, etc.) and column names to ASL query ops and keys; confirms unsupported operators and the `extra` column are excluded | +| `test_create_asl_query` | Validates `createAslQuery()` builds a well-formed `aslmsg` from a `QueryContext` with mixed constraints and type affinities | +| `test_read_asl_row` | Confirms `readAslRow()` maps known ASL fields to lowercase osquery columns and serializes unknown fields into the `extra` JSON column | +| `test_convert_like_regex` | Checks `convertLikeRegex()` translates SQL `LIKE` wildcards (`%`, `_`) to POSIX regex equivalents (`.*`, `.`) | +| `test_actual_query` | End-to-end integration test: writes a timestamped `syslog()` entry and queries the `asl` virtual table to confirm read-back; gracefully skips on macOS 10.12+ SDK where syslog redirects to Unified Logging | + +## Usage Example + +```cpp +// Run all ASL tests via ctest or gtest directly +./osquery_tests --gtest_filter="AslTests.*" + +// Example of what the tests exercise: +aslmsg query = asl_new(ASL_TYPE_QUERY); +addQueryOp(query, "sender", "myapp", EQUALS, TEXT_TYPE); +// Adds: key="Sender", val="myapp", op=ASL_QUERY_OP_EQUAL + +std::string regex = convertLikeRegex("%foo_"); +// Returns: ".*foo." +``` + +> **Note:** Tests guarded by `#ifndef OLD_ASL_API` are skipped when building against legacy ASL SDK versions. The `test_actual_query` test self-skips on macOS 10.13+ due to Unified Logging replacing ASL writes via `syslog()`. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.certificates_tests.md b/osquery/tables/system/tests/darwin/.certificates_tests.md new file mode 100644 index 00000000000..f8161055a98 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.certificates_tests.md @@ -0,0 +1,35 @@ + +Unit tests for macOS keychain certificate parsing, validating OpenSSL-based extraction of X.509 certificate properties (SHA1 digest, issuer/subject names, key identifiers, validity dates, and CA attributes) using a PEM test fixture. + +## Key Components + +| Component | Description | +|---|---| +| `CACertsTests` | GTest fixture class that loads a PEM certificate, decodes it via Base64, creates a `SecCertificateRef`, and parses it into an OpenSSL `X509*` struct | +| `getCACertificateContent()` | Helper that reads `test_cert.pem` from the test config directory | +| `test_certificate_sha1` | Verifies `generateCertificateSHA1Digest()` produces the expected hex digest | +| `test_certificate_properties` | Validates issuer name, subject name, common name, subject key ID, not-valid-before timestamp, and CA flag extraction | + +## Usage Example + +```cpp +// Run only the certificate tests via GTest filter +// ./osquery_tests --gtest_filter="CACertsTests.*" + +// Expected values asserted in tests: +// Common Name: "localhost.localdomain" +// Subject Key ID: "f2b99b00e0ee60d57c426ce3e64e3fdc6f6411c0" +// SHA1 Digest: "f149bae28e3c754ff4bb062b2c1b8bac81b8783e" +// Not Valid Before: 1408475536 (Unix timestamp) +// Is CA: true + +// Fixture lifecycle: +// SetUp() → decode PEM → SecCertificateCreateWithData → d2i_X509 +// TearDown() → CFRelease(cert), CFRelease(cert_der_data), X509_free(x_cert) +``` + +## Notes + +- **Platform**: Darwin/macOS only — depends on `Security.framework` (`SecCertificateRef`) and the osquery `keychain.h` header. +- **Test data**: Requires `test_cert.pem` in the osquery test config directory (a self-signed CA certificate). +- Both `true`/`false` variants of issuer/subject name formatting are exercised to cover RFC 2253 vs. legacy name serialization paths. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.extended_attributes_tests.md b/osquery/tables/system/tests/darwin/.extended_attributes_tests.md new file mode 100644 index 00000000000..325417ff46f --- /dev/null +++ b/osquery/tables/system/tests/darwin/.extended_attributes_tests.md @@ -0,0 +1,39 @@ + +Unit tests for macOS extended attributes (xattr) parsing functions in osquery, verifying quarantine metadata, "where from" URLs, and arbitrary xattr values are correctly read and decoded from filesystem entries. + +## Key Components + +- **`ExtendedAttributesTests` fixture** — Google Test class that sets up and tears down a known set of xattrs on a test file (`test_xattrs.txt`) before and after each test case +- **`setxattrs()`** — Helper that writes four xattrs to a file: Apple quarantine (`com.apple.quarantine`), where-from metadata (`com.apple.metadata:kMDItemWhereFroms`), fsck data (`com.apple.diskimages.fsck`), and an arbitrary `foobar` key +- **`removexattrs()`** — Helper that clears all xattrs from a file using `getExtendedAttributesNames()` and `removexattr()` +- **`getExtendedAttributesNames()`** — Local wrapper around `osquery::getExtendedAttributesNames()` that opens a file descriptor and returns the attribute name list +- **`test_extended_attributes`** — Core test case that calls `getFileData()` and asserts parsed values match expected quarantine fields, download URLs, base64-encoded binary data, and arbitrary key/value pairs + +## Usage Example + +```cpp +// The test fixture automatically prepares xattrs on the test file. +// Assertions verify parsed output from getFileData(): +TEST_F(ExtendedAttributesTests, test_extended_attributes) { + QueryData results; + getFileData(results, kTestFilePath); + + // Expected parsed fields from quarantine and metadata xattrs + const std::map expected = { + {"quarantine_agent", "Google Chrome"}, + {"quarantine_type", "LSQuarantineTypeWebDownload"}, + {"foobar", "baz"}, + {kFsckKey, kDiskImagefsckBase64}, // binary → base64 + }; + + for (auto row : results) { + for (auto [key, value] : row) { + if (expected.count(key)) { + EXPECT_EQ(expected.at(key), value); + } + } + } +} +``` + +> **Note:** These tests are macOS-specific (uses `` with `XATTR_NOFOLLOW`) and depend on the osquery test config directory containing `test_xattrs.txt`. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.firewall_tests.md b/osquery/tables/system/tests/darwin/.firewall_tests.md new file mode 100644 index 00000000000..a0c2dc332a1 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.firewall_tests.md @@ -0,0 +1,37 @@ + +Unit tests for macOS Application Layer Firewall (ALF) plist parsing logic, validating the `parseALFTree`, `parseALFExceptionsTree`, and `parseALFExplicitAuthsTree` functions against fixture plist files. + +## Key Components + +| Symbol | Description | +|---|---| +| `getALFTree()` | Helper that reads `test_alf.plist` and returns a parsed `boost::property_tree` | +| `getALFTreeNestedPath()` | Helper that reads `test_alf_nested_path.plist` for nested path edge cases | +| `FirewallTests` | GTest fixture class grouping all ALF-related test cases | + +### Test Cases + +| Test | What It Validates | +|---|---| +| `test_parse_alf_tree` | Global ALF state fields (version, global_state, logging, stealth, etc.) | +| `test_parse_alf_exceptions_tree` | Firewall exception entries with path and state from fixture | +| `DISABLED_test_parse_alf_exceptions_tree_nested_path` | Nested XPC path parsing (currently disabled) | +| `test_parse_alf_explicit_auths_tree` | Explicitly authorized process bundle IDs | +| `test_errors` | `boost::property_tree` error types for missing/wrong-typed keys | +| `test_on_disk_format` | Real on-disk ALF plist structure (skipped on macOS 15+) | + +## Usage Example + +```bash +# Run only firewall tests from the osquery test suite +./osquery_tests --gtest_filter="FirewallTests.*" +``` + +```cpp +// Typical pattern used internally by each test +pt::ptree tree = getALFTree(); +auto results = parseALFTree(tree); +EXPECT_EQ(results, expected); +``` + +> **Note:** `test_on_disk_format` is automatically skipped on macOS 15+ because `/Library/Preferences/com.apple.alf.plist` was removed in that release. The nested-path test is permanently disabled via the `DISABLED_` prefix pending fixture/parser fixes. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.keychain_test.md b/osquery/tables/system/tests/darwin/.keychain_test.md new file mode 100644 index 00000000000..1e3f245c214 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.keychain_test.md @@ -0,0 +1,48 @@ + +Unit tests for the macOS keychain cache implementation, validating read/write operations, cache invalidation via access throttling, and error handling for invalid paths. + +## Key Components + +- **`KeychainTest`** — GTest fixture that creates and tears down a temporary working directory (`/tmp/osquery.test_working_dir.XXXX`) for each test case +- **`TEST_F(KeychainTest, keychain_cache)`** — Primary test covering the full `keychainCache` lifecycle +- **`TEST_F(KeychainTest, keychain_cache_bad_path)`** — Error-path test validating behavior when reading a non-existent keychain file + +## Usage Example + +The tests exercise `keychainCache` (a global `KeychainCache` instance) through four distinct scenarios: + +```cpp +// 1. Initial state — cache is empty +EXPECT_EQ(keychainCache.Size(), 0); + +// 2. Cache miss on a new file — Read() returns false, no error +EXPECT_FALSE(keychainCache.Read(path, table, hash, results, err)); + +// 3. Write then read — cached rows are returned correctly +keychainCache.Write(path, table, hash, new_results); +EXPECT_TRUE(keychainCache.Read(path, table, hash, new_results, err)); +EXPECT_EQ(new_results[0]["foo"], "bar"); + +// 4. Throttling (FLAGS_keychain_access_interval = 1) — stale cache +// is returned even after the file is modified on disk +FLAGS_keychain_access_interval = 1; +EXPECT_TRUE(keychainCache.Read(path, table, hash, new_results, err)); + +// 5. No throttle (interval = 0) — file change causes cache miss +FLAGS_keychain_access_interval = 0; +EXPECT_FALSE(keychainCache.Read(path, table, hash, new_results, err)); + +// 6. Bad path — Read() returns false with err = true +EXPECT_FALSE(keychainCache.Read(bad_path, table, hash, results, err)); +EXPECT_TRUE(err); +``` + +### Key Behaviors Validated + +| Scenario | `Read()` returns | `err` flag | +|---|---|---| +| Empty cache / new file | `false` | `false` | +| Valid cached entry | `true` | `false` | +| Throttled, file changed | `true` (stale data) | `false` | +| Throttle cleared, file changed | `false` (cache miss) | `false` | +| Non-existent path | `false` | `true` | \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.launchd_tests.md b/osquery/tables/system/tests/darwin/.launchd_tests.md new file mode 100644 index 00000000000..e56f4bca5c4 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.launchd_tests.md @@ -0,0 +1,40 @@ + +Unit test file for the `launchd` osquery table, verifying that macOS launchd plist files are correctly parsed into table rows. + +## Key Components + +- **`LaunchdTests`** — GTest fixture class for launchd table tests. +- **`genLaunchdItem`** — External function declared from the launchd table implementation; accepts a parsed `pt::ptree`, a file path, and a `QueryData` results container. +- **`test_parse_launchd_item`** — Single test case that reads `test_launchd.plist`, parses it, invokes `genLaunchdItem`, and validates each column value individually for clearer failure diagnostics. + +## Usage Example + +```cpp +// Run this specific test via ctest or directly: +// ./launchd_tests --gtest_filter=LaunchdTests.test_parse_launchd_item + +TEST_F(LaunchdTests, test_parse_launchd_item) { + pt::ptree tree; + auto launchd_path = getTestConfigDirectory() / "test_launchd.plist"; + + // Parse the plist file into a property tree + auto status = osquery::parsePlist(launchd_path, tree); + ASSERT_TRUE(status.ok()); + + // Generate a launchd table row from the parsed tree + QueryData results; + genLaunchdItem(tree, launchd_path, results); + ASSERT_EQ(results.size(), 1U); + + // Validate individual columns for readable failure output + for (const auto& column : expected) { + EXPECT_EQ(results[0][column.first], column.second); + } +} +``` + +## Notes + +- Depends on `test_launchd.plist` in the test config directory, which mirrors a real macOS `mDNSResponder` plist. +- Columns are validated individually (rather than as a whole map) so that a single parsing regression produces a targeted, readable failure message. +- macOS-only; relies on `osquery::parsePlist` from `osquery/utils/darwin/plist.h`. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.mdfind_tests.md b/osquery/tables/system/tests/darwin/.mdfind_tests.md new file mode 100644 index 00000000000..0e51e73a71d --- /dev/null +++ b/osquery/tables/system/tests/darwin/.mdfind_tests.md @@ -0,0 +1,42 @@ + +Unit test file for the macOS `mdfind` (Spotlight search) osquery table implementation, verifying that Spotlight queries execute correctly and return properly structured results. + +## Key Components + +- **`MdFindTests`** — GTest fixture class for Spotlight search tests +- **`test_mdfind_finds_osquery`** — Test case that validates the full Spotlight query pipeline + +### Externally Declared Functions Under Test + +| Function | Description | +|---|---| +| `genSpotlightSearches()` | Creates `MDQueryRef` objects from a set of query strings | +| `waitForSpotlight()` | Blocks until Spotlight query execution completes | +| `genResults()` | Extracts matched file metadata into `QueryData` rows | + +## Usage Example + +```cpp +// Mirrors what the test exercises internally: +// 1. Generate Spotlight queries +auto queries = genSpotlightSearches({"kMDItemTextContent == \"osquery\""}); + +// 2. Wait for Spotlight to finish indexing/searching +Status s = waitForSpotlight(queries); + +// 3. Collect results +QueryData results; +genResults(queries, results); + +// Each result row must contain "path" and "query" columns +for (const auto& row : results) { + assert(row.count("path") == 1); + assert(row.count("query") == 1); +} +``` + +## Notes + +- **macOS only** — depends on `CoreServices/CoreServices.h` and the `MDQueryRef` type from the Spotlight API +- The test uses `EXPECT_GE(results.size(), 0UL)` — a permissive size check that allows zero results (Spotlight index state may vary per machine) +- Each result row is validated to contain exactly the `path` and `query` columns, enforcing the table's output schema \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.packages_tests.md b/osquery/tables/system/tests/darwin/.packages_tests.md new file mode 100644 index 00000000000..df617913fed --- /dev/null +++ b/osquery/tables/system/tests/darwin/.packages_tests.md @@ -0,0 +1,36 @@ + +Unit test file for validating BOM (Bill of Materials) file parsing functionality used in macOS package inspection within osquery's Darwin system tables. + +## Key Components + +- **`PackagesTests`** — GTest fixture class inheriting from `testing::Test`, scoped to the `osquery::tables` namespace +- **`test_bom_parsing`** — Test case that validates the `BOM` class can correctly parse a `.bom` file, checking both validity and variable extraction + +## Usage Example + +```cpp +// The test exercises the BOM class as follows: +std::string content; +auto test_bom_path = (getTestConfigDirectory() / "test_bom.bom").string(); +if (!readFile(test_bom_path, content).ok()) { + return; // Skip if test fixture file is unavailable +} + +BOM bom(content.c_str(), content.size()); +ASSERT_TRUE(bom.isValid()); // BOM structure is valid + +size_t offset = 0; +auto var = bom.getVariable(&offset); +ASSERT_FALSE(nullptr == var); // At least one variable is extractable +``` + +## Test Dependencies + +| Dependency | Purpose | +|---|---| +| `test_bom.bom` | Fixture file loaded from the test config directory | +| `osquery/filesystem/filesystem.h` | `readFile()` utility for loading binary content | +| `osquery/tables/system/darwin/packages.h` | Provides the `BOM` class under test | +| `osquery/config/tests/test_utils.h` | `getTestConfigDirectory()` for fixture path resolution | + +> **Note:** The test gracefully skips (returns early) if the `.bom` fixture file is not present, making it non-fatal in environments without the macOS test assets. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.processes_tests.md b/osquery/tables/system/tests/darwin/.processes_tests.md new file mode 100644 index 00000000000..30bf0091cae --- /dev/null +++ b/osquery/tables/system/tests/darwin/.processes_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the Darwin (macOS) `processes` table implementation in osquery, validating process data retrieval, command-line parsing, architecture detection, and column bitmask handling. + +## Key Components + +| Test | Description | +|------|-------------| +| `test_unique_pid` | Verifies `genProcUniquePid` populates `upid_col` and `uppid_col` with valid (non-`-1`) values for PID 1 | +| `test_cmdline_parsing` | Validates `parseProcCmdline` correctly parses null-delimited argv buffers, filters environment variables, and rejects malformed/incomplete inputs | +| `test_process_arch` | Confirms `genProcArch` fills `cpu_type_col` and `cpu_subtype_col`; skipped when not running as root | +| `test_column_overflow` | Ensures the `QueryContext` bitmask correctly identifies only `CPU_SUBTYPE` across all 64 possible column bit positions | + +**Tested internal functions:** + +```cpp +void genProcUniquePid(QueryContext& context, int pid, ProcessesRow& r); +void genProcArch(QueryContext& context, int pid, ProcessesRow& r); +bool parseProcCmdline(std::string& args, size_t len); +``` + +## Usage Example + +```bash +# Run only Darwin process tests via osquery test binary +./osquery_tests --gtest_filter="DarwinProcessesTests.*" +``` + +```cpp +// Example: Simulating the cmdline buffer format tested +int argc = 2; +std::string cmdline("0000/bin/sh0/bin/sh0-c0PATH=/"); +std::replace(cmdline.begin(), cmdline.end(), '0', '\0'); +memcpy(&cmdline[0], &argc, sizeof(argc)); + +bool ok = parseProcCmdline(cmdline, cmdline.size()); +// ok == true, cmdline == "/bin/sh -c" +``` + +> **Note:** `test_process_arch` is a no-op unless executed as root (`uid == 0` and `gid == 0`), reflecting the privilege requirements for reading process architecture info on macOS. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.smc_tests.md b/osquery/tables/system/tests/darwin/.smc_tests.md new file mode 100644 index 00000000000..eb9db4bd883 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.smc_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the macOS System Management Controller (SMC) table functions, validating temperature and power sensor data parsing and unit conversion logic. + +## Key Components + +- **`TestData` struct** — Holds parameterized test inputs: SMC data type, byte size, raw hex value, and expected Celsius/Fahrenheit outputs +- **`TestVals[]`** — Comprehensive dataset of 35+ SMC type/value pairs covering `flt`, `ioft`, `fp*`, `sp*`, `ui*`, and `si*` fixed-point and integer formats +- **`SmcTests`** — GTest fixture class using `TestWithParam` for parameterized execution +- **`test_gen_temperature`** — Parameterized test validating that `genTemperature()` correctly parses raw hex SMC values and produces accurate Celsius and Fahrenheit readings +- **`test_gen_power`** — Fixed test validating that `genPower()` correctly decodes an `sp78`-format wattage value for a named CPU rail sensor + +## Usage Example + +```bash +# Run all SMC tests +./osquery_tests --gtest_filter="SmcTests*" + +# Run only temperature conversion tests +./osquery_tests --gtest_filter="SmcTestsConversionTests*" + +# Run only power test +./osquery_tests --gtest_filter="SmcTests.test_gen_power" +``` + +```cpp +// Example of how genTemperature is exercised internally per test case: +Row param = { + {"key", "TC0E"}, + {"type", "flt"}, + {"size", "4"}, + {"value", "9899e941"}, + {"hidden", "0"}, +}; +QueryData results; +genTemperature(param, results); +// results[0]["celsius"] == "29.2" +// results[0]["fahrenheit"] == "84.6" +``` + +> **Note:** Each column is asserted individually rather than comparing full maps, producing targeted failure messages when a specific type parser regresses. \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.startup_items_tests.md b/osquery/tables/system/tests/darwin/.startup_items_tests.md new file mode 100644 index 00000000000..80365478753 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.startup_items_tests.md @@ -0,0 +1,40 @@ + +Unit test file for the macOS `startup_items` osquery table, validating parsing of login items from a `.plist` file. + +## Key Components + +- **`StartupItemsTests`** — GTest fixture class (empty base, inherits `testing::Test`) +- **`genLoginItems(const fs::path&, QueryData&)`** — Forward declaration of the production function under test; parses a plist file into query result rows +- **`test_parse_startup_items`** — Single test case validating correct parsing of a macOS login items plist + +## What Is Tested + +| Assertion | Details | +|---|---| +| Result count | Expects exactly 2 parsed entries | +| Entry 0 path | Starts with `/Applications/iTunes.app/Contents/MacOS/iTunesHelper.app` | +| Entry 1 path | Starts with `/private/tmp/this_does_not_exist` (unresolvable bookmark) | + +## Usage Example + +```bash +# Run only startup items tests via osquery test binary +./osquery_tests --gtest_filter="StartupItemsTests.*" +``` + +```cpp +// The test exercises genLoginItems() against a fixture plist: +auto si_path = getTestConfigDirectory() / "test_startup_items.plist"; +QueryData results; +genLoginItems(si_path, results); +// results[0]["path"] → resolved app path +// results[1]["path"] → unresolvable bookmark path (raw fallback) +``` + +> **Note:** The second entry intentionally covers the bookmark-resolution failure path — entries whose bookmark data cannot be resolved fall back to the raw stored path. + +## Dependencies + +- `test_startup_items.plist` — fixture file located in the osquery test config directory +- `boost::filesystem` — path handling +- `gtest` — test framework \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.system_extensions_test.md b/osquery/tables/system/tests/darwin/.system_extensions_test.md new file mode 100644 index 00000000000..a0f2ea419d1 --- /dev/null +++ b/osquery/tables/system/tests/darwin/.system_extensions_test.md @@ -0,0 +1,40 @@ + +Unit tests for the `system_extensions` table implementation in osquery, validating parsing of macOS System Extension data from both JSON property trees and plist files. + +## Key Components + +- **`SystemExtensionTests`** — GTest fixture class housing all test cases for the system extensions table +- **`genExtensionsFromPtree`** — External function under test that converts a `boost::property_tree` into `QueryData` rows +- **`test_parse_ptree`** — Validates JSON-to-ptree parsing using a hardcoded real-world payload from the LuLu macOS firewall extension +- **`test_parse_plist`** — Validates end-to-end plist file parsing via `osquery::parsePlist`, using a fixture file (`db.plist`) from the test config directory + +## Usage Example + +```cpp +// The tested function signature (external to this file): +// QueryData genExtensionsFromPtree(const pt::ptree& ptree); + +// Typical test flow: +pt::ptree ptree; +std::stringstream ss; +ss << R"({ "extensions": [...] })"; +pt::read_json(ss, ptree); + +QueryData results = genExtensionsFromPtree(ptree); + +// Expected fields in each result row: +// results[0]["state"] → e.g. "activated_enabled" +// results[0]["category"] → e.g. "com.apple.system_extension.network_extension" +// results[0]["identifier"] → bundle identifier string +// results[0]["version"] → CFBundleShortVersionString +// results[0]["UUID"] → unique extension UUID +// results[0]["team"] → Apple Developer Team ID +// results[0]["path"] → full path to .systemextension bundle +// results[0]["bundle_path"] → parent application bundle path +// results[0]["mdm_managed"] → MDM management flag ("0" or "1") +``` + +## Notes + +- `test_parse_plist` gracefully skips if `db.plist` is absent, avoiding hard failures in environments without test fixtures +- The JSON payload in `test_parse_ptree` uses real metadata from [LuLu](https://objective-see.org/products/lulu.html), including a base64-encoded binary plist in `additionalLaunchdPlistEntries` \ No newline at end of file diff --git a/osquery/tables/system/tests/darwin/.unsigned_test.md b/osquery/tables/system/tests/darwin/.unsigned_test.md new file mode 100644 index 00000000000..92f100f198a --- /dev/null +++ b/osquery/tables/system/tests/darwin/.unsigned_test.md @@ -0,0 +1,24 @@ + +A minimal C++ test program that serves as an intentionally unsigned binary, used by the `SignatureTest.test_get_unsigned` unit test to verify osquery's ability to detect executables lacking a valid code signature. + +## Key Components + +- **`main()`** — Prints `"Hello world!"` and exits. The program's logic is intentionally trivial; its purpose is solely to exist as an unsigned binary artifact during testing. + +## Usage Context + +This binary is not called directly by developers. It is built by CMake, which then strips any ad hoc signature that macOS (Apple Silicon/M1) may automatically apply during compilation. The resulting unsigned binary is referenced by the signature verification unit test. + +```cpp +// The entire program — simplicity is intentional +int main(int argc, char** argv) { + printf("Hello world!\n"); + return 0; +} +``` + +## Notes + +- On M1/Apple Silicon systems, CMake applies a post-build step to remove any ad hoc code signature, ensuring the binary remains unsigned for test validity. +- Do not add logic or dependencies to this file — its unsigned state and minimal footprint are test requirements. +- Related test: `SignatureTest.test_get_unsigned` \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.apt_sources_tests.md b/osquery/tables/system/tests/linux/.apt_sources_tests.md new file mode 100644 index 00000000000..dbd2cde0e51 --- /dev/null +++ b/osquery/tables/system/tests/linux/.apt_sources_tests.md @@ -0,0 +1,38 @@ + +Unit tests for the `apt_sources` Linux table implementation, validating parsing of both traditional one-line APT source format and the modern Deb822 block format. + +## Key Components + +### Test Fixture +- **`AptSourcesImplTests`** — GTest fixture class wrapping all APT source parsing tests + +### Helper Struct +- **`AptTestCase`** — Holds test input/expected output fields: `input_line`, `base_uri`, `name`, and `cache_filename` + +### Test Cases + +| Test | Function Tested | Coverage | +|------|----------------|----------| +| `parse_apt_source_line` | `parseAptSourceLine()` | Normal lines, leading spaces, options blocks, trailing comments, multiple components, `ftp://` URIs, trailing slash stripping | +| `test_failures` | `parseAptSourceLine()` | Missing `deb` prefix, bad protocol, missing suite, empty URI, comment-only lines, unclosed options block | +| `parse_deb_822_block` | `parseDeb822Block()` | RFC 822-style `.sources` format with `Types`, `URIs`, `Suites`, `Components` fields; case-insensitive keys, comments, trailing slash stripping | +| `test_deb822_failures` | `parseDeb822Block()` | Empty input, missing `Suites`, invalid URL skipping, `Enabled: off` disabling | + +## Usage Example + +```cpp +// Typical pattern used throughout the tests: +AptSource apt_source; + +auto s = parseAptSourceLine( + "deb [arch=amd64] https://pkg.osquery.io/deb bionic main", + apt_source +); + +ASSERT_TRUE(s.ok()); +EXPECT_EQ(apt_source.base_uri, "https://pkg.osquery.io/deb"); +EXPECT_EQ(apt_source.name, "pkg.osquery.io/deb bionic"); + +auto cache_filename = getCacheFilename(apt_source.cache_file); +EXPECT_EQ(cache_filename, "pkg.osquery.io_deb_dists_bionic"); +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.extended_attributes_tests.md b/osquery/tables/system/tests/linux/.extended_attributes_tests.md new file mode 100644 index 00000000000..d67ca87272c --- /dev/null +++ b/osquery/tables/system/tests/linux/.extended_attributes_tests.md @@ -0,0 +1,41 @@ + +Unit test file for the `extended_attributes` osquery table, validating the `generateXattrRowsForPath` function against POSIX extended attributes (xattrs) and Linux capabilities on a temporary file. + +## Key Components + +### Test Fixture: `ExtendedAttributesTableTests` +- **`SetUp()`** — Creates a temporary file, writes user-space xattrs via `setxattr()`, and optionally sets Linux capabilities using `cap_from_text()` / `cap_set_file()`. Falls back gracefully if capability setting fails (e.g., non-root). +- **`TearDown()`** — Removes the temporary file via `boost::filesystem::remove`. + +### Test Data Constants +| Constant | Description | +|---|---| +| `kTestAttributeList` | Map of xattr name → `ExtendedAttributeTestValue`; covers binary (base64) and null-terminated printable values | +| `kCapabilitiesAttributeName` | `"security.capability"` | +| `kInputCapabilities` | Capability string set via `cap_from_text()` | +| `kExpectedCapabilities` | Normalized expected output after osquery parsing | +| `kCapabilitiesExtendedAttribute` | Expected query row for the capability attribute | + +### Test Case: `generate` +Calls `tables::generateXattrRowsForPath()` and asserts: +- Status is `ok()` +- Output row count ≥ expected attribute count +- Each row has exactly 5 columns: `path`, `directory`, `key`, `value`, `base64` +- Binary values are base64-encoded (`"1"`), printable null-terminated values are not (`"0"`) +- Capability attributes are verified only when `check_capabilities` is `true` + +## Usage Example + +```bash +# Run the specific test (requires root for capability tests) +sudo ./osquery_tests --gtest_filter="ExtendedAttributesTableTests.generate" + +# Run without root (capability tests are skipped automatically) +./osquery_tests --gtest_filter="ExtendedAttributesTableTests.generate" +``` + +```cpp +// Attribute encoding logic under test: +// Binary data → base64="1", value="AQEBAQ==" +// "Hello, osquery!\0" → base64="0", value="Hello, osquery!" +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.md_tables_tests.md b/osquery/tables/system/tests/linux/.md_tables_tests.md new file mode 100644 index 00000000000..c11ce67fc9b --- /dev/null +++ b/osquery/tables/system/tests/linux/.md_tables_tests.md @@ -0,0 +1,46 @@ + +Unit tests for the `getDrivesForArray` function in osquery's Linux MD (software RAID) tables implementation, verifying correct drive state classification across various array configurations. + +## Key Components + +### `MockMD` +A Google Mock class implementing `MDInterface`, providing mock methods for: +- `getDiskInfo` / `getArrayInfo` — IOCTL wrappers for disk and array metadata +- `parseMDStat`, `getPathByDevName`, `getDevName`, `getSuperblkVersion` — supporting MD utilities + +### `getDiskInfo` (helper) +Constructs an `mdu_disk_info_t` struct with the given number, raid disk slot, state, major, and minor device numbers. + +### `GetDrivesForArrayTestHarness` +The core test engine. Sets up mock expectations for a full MD array interrogation cycle (path resolution → array info → per-disk info) and invokes `getDrivesForArray`, writing results to a `QueryData` reference. + +### Test Cases (`GetDrivesForArrayTest`) + +| Test | Scenario | +|---|---| +| `all_drives_healthy` | All 6 disks active sync (state `6`) | +| `all_drives_removed` | No target disks; all slots report `removed` | +| `all_drives_faulty` | All disks faulty (state `1`) | +| `every_other_drives_faulty` | Alternating faulty/active sync disks | +| `some_drives_removed` | Mix of active sync and removed (unknown) slots | +| `some_faulty_some_removed` | Mixed faulty, active sync, and removed states | + +## Usage Example + +```cpp +// Run a specific test case +TEST_F(GetDrivesForArrayTest, all_drives_healthy) { + std::map targets; + for (int i = 0; i < 6; i++) { + targets[i] = getDiskInfo(i, i, /*state=*/6, i + 5, i + 10); + } + + QueryData got; + GetDrivesForArrayTestHarness("md0", 6, "/dev/sda", targets, got); + + // Expect each row to report "active sync" + for (const auto& row : got) { + EXPECT_EQ(row.at("state"), "active sync"); + } +} +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.pci_devices_tests.md b/osquery/tables/system/tests/linux/.pci_devices_tests.md new file mode 100644 index 00000000000..5ae72a7f934 --- /dev/null +++ b/osquery/tables/system/tests/linux/.pci_devices_tests.md @@ -0,0 +1,36 @@ + +Unit tests for the Linux PCI devices table implementation, validating the `extractVendorModelFromPciDBIfPresent` and `extractPCIClassIDAttrs` functions using a mock PCI database. + +## Key Components + +- **`PciDevicesTest`** — GTest fixture that initializes a static in-memory `PciDB` instance from a hardcoded mock database stream shared across all test cases +- **`extractVendorModelFromPciDBIfPresent` tests** — Covers lookup of vendor, model, subsystem vendor, and subsystem model fields from PCI and subsystem ID strings +- **`extractPCIClassIDAttrs` tests** — Covers parsing of PCI class and subclass IDs from raw class ID strings + +## Test Cases + +| Test | Scenario | +|---|---| +| `all_fields_exists` | Full vendor + subsystem lookup succeeds | +| `missing_subsystem_info` | Known device, unknown subsystem IDs — partial row returned | +| `missing_all_info` | Unknown vendor + subsystem — only IDs populated | +| `bad_pci_id` | Malformed PCI ID string — returns error status | +| `bad_subsys_id` | Malformed subsystem ID string — returns error status | +| `3_pci_ids` / `3_subsys_ids` | Too many colon-separated segments — returns error | +| `empty_pci_id` / `empty_subsys_ids` | Empty string inputs — returns error | +| `single_digit_class_id` | 5-char class string parsed correctly | +| `double_digit_class_id` | 6-char class string parsed correctly | +| `bad_length` | Oversized class string — returns error | + +## Usage Example + +```cpp +// Typical test invocation pattern +Row got; +auto status = extractVendorModelFromPciDBIfPresent( + got, "8002:131B", "174B:1001", *PciDevicesTest::pcidb_); + +EXPECT_TRUE(status.ok()); +EXPECT_EQ(got["vendor"], "Fake Vendor, Inc."); +EXPECT_EQ(got["model"], "Wrestler HDMI Audio"); +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.pcidb_tests.md b/osquery/tables/system/tests/linux/.pcidb_tests.md new file mode 100644 index 00000000000..b9bcc870957 --- /dev/null +++ b/osquery/tables/system/tests/linux/.pcidb_tests.md @@ -0,0 +1,43 @@ + +Unit test file for the `PciDB` class, validating the parsing and querying of PCI device database entries from a raw text stream format used in Linux PCI device identification. + +## Key Components + +- **`PciDBTest`** — GTest fixture class wrapping all PCI database unit tests +- **`basic_db_format`** — Single comprehensive test case covering: + - Vendor name lookup via `getVendorName()` + - Device model lookup via `getModel()` + - Subsystem info lookup via `getSubsystemInfo()` + - Boundary/negative cases including unknown IDs, illegal vendor IDs, and device class entries + +## Usage Example + +```cpp +// Construct a PciDB from a raw input stream (tab-indented PCI ID format) +std::istringstream raw_db( + "8002 Fake Vendor, Inc.\n" + "\t1313 Foobar [Some R7 Rapidfire]\n" + "\t\t174b 1001 90K Diffusion Mini\n" +); + +PciDB parsed_db(raw_db); +std::string result; + +// Lookup vendor by ID +parsed_db.getVendorName("8002", result); +// result == "Fake Vendor, Inc." + +// Lookup device model by vendor + model ID +parsed_db.getModel("8002", "1313", result); +// result == "Foobar [Some R7 Rapidfire]" + +// Lookup subsystem info by vendor + model + subvendor + subdevice IDs +parsed_db.getSubsystemInfo("8002", "1313", "174b", "1001", result); +// result == "90K Diffusion Mini" +``` + +## Test Coverage Notes + +- **Happy path** — Valid vendor, model, and subsystem lookups against a mock database +- **Negative cases** — Unknown vendor IDs, unknown model IDs, and non-existent subsystem entries return empty strings +- **Boundary enforcement** — Entries below the sentinel `ffff` vendor (device class section) are not retrievable via vendor/model queries, ensuring the parser correctly halts at the class definition boundary \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.portage_tests.md b/osquery/tables/system/tests/linux/.portage_tests.md new file mode 100644 index 00000000000..7e9bec061ba --- /dev/null +++ b/osquery/tables/system/tests/linux/.portage_tests.md @@ -0,0 +1,32 @@ + +Unit tests for the `portageSplitPackageVersion` function, which parses Gentoo Portage package strings into name/version pairs within the osquery tables namespace. + +## Key Components + +- **`PortageTests`** — GTest fixture class for Portage-related unit tests +- **`portageSplitPackageVersion`** — Forward declaration of the function under test; splits a Portage package atom string into a `(name, version)` pair + +## Test Cases + +| Test | Input | Expected Name | Expected Version | +|---|---|---|---| +| `specific_version` | `=sys-kernel/gentoo-sources-4.9.0` | `sys-kernel/gentoo-sources` | `4.9.0` | +| `less_or_equal_version` | `<=sys-kernel/gentoo-sources-4.9.0-r1` | `sys-kernel/gentoo-sources` | `<=4.9.0-r1` | +| `greater_or_equal_version` | `>=sys-kernel/gentoo-sources-4.9.0_alpha2` | `sys-kernel/gentoo-sources` | `>=4.9.0_alpha2` | +| `no_version` | `sys-kernel/gentoo-sources` | `sys-kernel/gentoo-sources` | `""` | + +## Usage Example + +```cpp +// Calling the function under test directly +auto result = portageSplitPackageVersion(">=sys-kernel/gentoo-sources-4.9.0"); + +// result.first → package name: "sys-kernel/gentoo-sources" +// result.second → version string: ">=4.9.0" +``` + +## Key Behaviors Verified + +- Version comparison operators (`=`, `<=`, `>=`) are stripped from the package name and prepended to the version string +- Revision suffixes (`-r1`) and pre-release suffixes (`_alpha2`) are preserved in the version output +- Package atoms with no version constraint return an empty version string \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.processes_tests.md b/osquery/tables/system/tests/linux/.processes_tests.md new file mode 100644 index 00000000000..9fae01640ab --- /dev/null +++ b/osquery/tables/system/tests/linux/.processes_tests.md @@ -0,0 +1,37 @@ + +Unit tests for the `parseProcCGroup` function, validating correct parsing of Linux `/proc//cgroup` file content across multiple cgroup formats and edge cases. + +## Key Components + +- **`CGroupTest`** — GTest fixture class for cgroup parsing tests +- **`parseProcCGroup`** — The function under test (declared in `processes.h`), responsible for extracting the cgroup path from `/proc` cgroup entries + +## Test Cases + +| Test | Input | Expected Output | +|---|---|---| +| `systemd_session` | cgroup v2 entry with trailing newline | Parsed cgroup path | +| `no_newline` | cgroup v2 entry without trailing newline | Parsed cgroup path | +| `version_1_single_group` | cgroup v1 entry with subsystem controllers | Parsed cgroup path | +| `invalid` | Malformed entry missing double-colon separator | Empty string `""` | + +## Usage Example + +```cpp +// Simulates what parseProcCGroup does internally when reading +// /proc//cgroup entries: + +// cgroup v2 format: "0::\n" +auto path = parseProcCGroup("0::/user.slice/user-1000.slice/session-6.scope\n"); +// path == "/user.slice/user-1000.slice/session-6.scope" + +// cgroup v1 format: "::\n" +auto path_v1 = parseProcCGroup("4:cpu,cpuset:/user.slice/session-6.scope\n"); +// path_v1 == "/user.slice/session-6.scope" + +// Invalid format (missing second colon): returns empty string +auto invalid = parseProcCGroup("0:/user.slice\n"); +// invalid == "" +``` + +The parser expects the standard three-field colon-delimited cgroup format; entries with fewer than two colons are treated as invalid and return an empty string. \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.rpm_packages_tests.md b/osquery/tables/system/tests/linux/.rpm_packages_tests.md new file mode 100644 index 00000000000..ec2846356bc --- /dev/null +++ b/osquery/tables/system/tests/linux/.rpm_packages_tests.md @@ -0,0 +1,41 @@ + +Unit tests for the `rpm_packages` osquery table, validating RPM database querying across three RPM database backends: BDB (Berkeley DB), SQLite, and NDB. + +## Key Components + +### `RpmTests` (Test Fixture) +A `::testing::Test` subclass that manages RPM environment setup and teardown: +- **`SetUp()`** — Suppresses RPM log output by installing a no-op callback. +- **`setConfig(path)`** — Redirects `RPM_CONFIGDIR` and RPM macros (`_dbpath`, `rpmdb`) to a test database directory. +- **`TearDown()`** — Restores the original RPM log callback and environment variables. + +### `PackageDetails` (Struct) +Holds per-package metadata extracted from RPM headers: `name`, `version`, and `sha1`. Implements `operator==` and `operator<<` for GoogleTest assertions and diagnostics. + +### `queryRpmDb(packageCallback)` +Initializes the RPM subsystem, opens the configured database, iterates all packages via `rpmdbNextIterator`, extracts `RPMTAG_NAME`, `RPMTAG_VERSION`, and `RPMTAG_SHA1HEADER`, and invokes a caller-supplied callback per package. Returns `Status::success()` on completion. + +### Test Cases +| Test | Backend | Packages Verified | +|---|---|---| +| `test_bdb_packages` | Berkeley DB | 3 packages (rpm 4.8.0 era) | +| `test_sqlite_packages` | SQLite | 9 packages (rpm 4.16.0 era) | +| `test_ndb_packages` | NDB | 3 packages (binutils, zlib-devel, ncurses-devel) | + +All tests drop privileges to `nobody` when running as root before accessing the test databases. + +## Usage Example + +```cpp +// Run a specific backend test +// Requires test fixtures under: /rpm/rpm-{bdb,sqlite,ndb}/ + +// Custom callback pattern used in tests: +std::vector packages; +queryRpmDb([&packages](PackageDetails& pd) { + packages.push_back(pd); +}); + +// Then assert against expected packages: +EXPECT_EQ(expected, packages); +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.selinux_settings_tests.md b/osquery/tables/system/tests/linux/.selinux_settings_tests.md new file mode 100644 index 00000000000..3b3fc1be80e --- /dev/null +++ b/osquery/tables/system/tests/linux/.selinux_settings_tests.md @@ -0,0 +1,35 @@ + +Unit tests for the `selinux_settings` table implementation in osquery, validating key SELinux filesystem parsing and boolean value translation utilities. + +## Key Components + +### Test Fixture +- **`SELinuxSettingsTests`** — GTest fixture class grouping all SELinux settings unit tests + +### Functions Under Test +| Function | Description | +|---|---| +| `keyNameFromFilePath` | Extracts a SELinux key name from a full filesystem path, relative to the SELinux fs mount point | +| `isBooleanKey` | Determines whether a given key name represents a boolean SELinux setting | +| `translateBooleanKeyValue` | Converts raw boolean file content (e.g., `"1 1"`) into human-readable `"on"`/`"off"` strings | + +### Test Cases +- **`keyNameFromFilePath`** — Validates path parsing against `/sys/fs/selinux`, covering edge cases (root path, trailing slash, nested paths like `initial_contexts/`, `class/`, and `policy_capabilities/`) +- **`translateBooleanKeyValue`** — Validates translation of raw kernel boolean values (`"1 1"` → `"on"`, `"0 0"` → `"off"`) and rejects malformed input (`"0"`) + +## Usage Example + +```cpp +// Typical pattern exercised by these tests: +std::string key_name; +Status status = keyNameFromFilePath( + key_name, + "/sys/fs/selinux", + "/sys/fs/selinux/policy_capabilities/nnp_nosuid_transition" +); +// status.ok() == true, key_name == "nnp_nosuid_transition" + +std::string value; +Status bool_status = translateBooleanKeyValue(value, "1 1"); +// bool_status.ok() == true, value == "on" +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/linux/.yum_sources_tests.md b/osquery/tables/system/tests/linux/.yum_sources_tests.md new file mode 100644 index 00000000000..ea7ebb96710 --- /dev/null +++ b/osquery/tables/system/tests/linux/.yum_sources_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the `parseYumConf` function, validating YUM repository configuration parsing behavior within the osquery `tables` namespace. + +## Key Components + +### Test Fixture +- **`YumSourcesTests`** — GTest fixture class inheriting from `testing::Test`; provides the test context for all YUM source parsing tests. + +### Tested Function +- **`parseYumConf(const std::string&, std::istream&, QueryData&, std::string&)`** — Declared externally; parses a YUM `.conf` or `.repo` file stream, populating repository records and extracting the `reposdir` path. + +### Test Cases + +| Test | Description | +|------|-------------| +| `parse_empty_yum_conf` | Verifies that an empty config stream produces zero results and falls back to the default `reposdir` of `/etc/yum.repos.d` | +| `parse_yum_conf` | Verifies correct parsing of a multi-section config with `[main]`, `[personal]`, and `[math]` blocks — validating field extraction, `reposdir` override, absent optional fields, and `source` tracking | + +## Usage Example + +```cpp +// Typical invocation pattern mirrored by the tests +QueryData results; +std::string repos_dir; +std::istringstream stream("[main]\nreposdir=/etc/yum.repos.d\n"); + +parseYumConf("/etc/yum.conf", stream, results, repos_dir); + +// repos_dir → "/etc/yum.repos.d" +// results → populated with any non-[main] repo sections +``` + +## Key Behaviors Validated + +- **Default `reposdir`** falls back to `/etc/yum.repos.d` when not set in `[main]` +- **`reposdir`** in non-`[main]` sections is ignored +- Fields `baseurl`, `enabled`, `name`, `mirrorlist`, `metalink`, `gpgcheck`, and `gpgkey` are correctly extracted per repo section +- The `source` field records the originating file path +- Optional fields (`gpgcheck`, `gpgkey`) are absent from results when not defined in the config \ No newline at end of file diff --git a/osquery/tables/system/tests/posix/.augeas_tests.md b/osquery/tables/system/tests/posix/.augeas_tests.md new file mode 100644 index 00000000000..ae76b8d4309 --- /dev/null +++ b/osquery/tables/system/tests/posix/.augeas_tests.md @@ -0,0 +1,35 @@ + +Unit tests for the osquery `augeas` virtual table, verifying SQL query behavior against the Augeas configuration file parsing library across various path, node, and wildcard expression scenarios. + +## Key Components + +### `AugeasTests` (Test Fixture) +Inherits from `testing::Test` and handles test environment initialization: +- `SetUp()` — Calls `platformSetup()`, `registryAndPluginInit()`, and `initDatabasePluginForTesting()`; sets `FLAGS_augeas_lenses` to the test config directory containing Augeas lens files. + +### Test Cases + +| Test | Description | +|---|---| +| `select_hosts_by_path_expression` | Queries `/etc/hosts` by `path` + `label`, validates `node`, `path`, `label`, and empty `value` | +| `select_etc_folder_by_path_expression` | Queries the `/etc` directory node, validates returned columns | +| `select_files_by_path_expression_with_or` | Validates `GROUP BY` + `ORDER BY` on `path` column | +| `select_files_by_path_or_node` | Queries by `node` column directly, validates grouping | +| `select_hosts_by_node` | Queries `/files/etc/hosts` via `node`, validates all returned columns | +| `select_augeas_load` | Validates the internal `/augeas/load` metadata node, expects empty `path` and `value` | +| `select_augeas_load_wildcards` | Tests `LIKE` wildcard patterns (`%` single-level, `%%` recursive) against `/augeas/load` subtree | +| `select_file_wildcards` | Validates Augeas partial-match behavior — trailing slash patterns return 0 rows; exact and `%%` suffix patterns return ≥1 | + +## Usage Example + +```cpp +// Run a specific test case using gtest filter +// ./augeas_tests --gtest_filter=AugeasTests.select_hosts_by_node + +// Example of the SQL pattern being tested: +auto results = SQL("select * from augeas where node = '/files/etc/hosts'"); +ASSERT_GE(results.rows().size(), 1U); +ASSERT_EQ(results.rows()[0].at("path"), "/etc/hosts"); +``` + +> **Note:** The `FLAGS_augeas_lenses` flag must point to valid lens files before any `augeas` table query executes. In CI, these are loaded from `getTestConfigDirectory() / "augeas/lenses"`. \ No newline at end of file diff --git a/osquery/tables/system/tests/posix/.authorized_keys_tests.md b/osquery/tables/system/tests/posix/.authorized_keys_tests.md new file mode 100644 index 00000000000..66010cf2749 --- /dev/null +++ b/osquery/tables/system/tests/posix/.authorized_keys_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the `authorized_keys` osquery table implementation, validating SSH authorized_keys file parsing via `genSSHkeysForUser`. + +## Key Components + +- **`AuthorizedKeysTests`** — GTest fixture class scoping all authorized keys test cases +- **`basic_authorized_keys`** — Primary test case that exercises parsing of a synthetic `~/.ssh/authorized_keys` file covering multiple entry formats + +## Test Coverage + +The test constructs a temporary `authorized_keys` file with the following line variants and asserts correct field extraction: + +| Line Type | Expected Behavior | +|---|---| +| Comment (`# ...`) | Skipped entirely | +| Key only (no comment/options) | `algorithm` + `key` extracted; no `comment`/`options` fields | +| Key + comment | `algorithm`, `key`, `comment` extracted | +| Key + options (e.g. `command=`) | `algorithm`, `key`, `options` extracted | +| Key + options + comment | All four fields extracted | +| Options-only line (no key type) | Only `options` extracted | +| Invalid key type (`ssh23rsa`) | Skipped (not included in results) | +| Missing key type with options | Skipped (not included in results) | + +Expected result count after parsing: **5 rows**. + +## Usage Example + +```cpp +// The test calls genSSHkeysForUser with uid, gid, home directory, and a results collector +auto results = QueryData{}; +GLOGLogger logger; +genSSHkeysForUser("0", "0", work_directory.string(), results, logger); + +// Each row contains fields such as: +results[0].at("key_file"); // absolute path to authorized_keys file +results[0].at("algorithm"); // e.g. "ssh-rsa" +results[0].at("key"); // base64-encoded public key blob +results[0].at("comment"); // optional trailing comment +results[0].at("options"); // optional prefix options (command=, environment=, from=, etc.) +``` \ No newline at end of file diff --git a/osquery/tables/system/tests/posix/.known_hosts_tests.md b/osquery/tables/system/tests/posix/.known_hosts_tests.md new file mode 100644 index 00000000000..319e4b1048f --- /dev/null +++ b/osquery/tables/system/tests/posix/.known_hosts_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the `known_hosts` table implementation, validating SSH known hosts file parsing via `impl::genSSHkeysForHosts`. + +## Key Components + +- **`KnownHostsImplTests`** — GTest fixture class for known hosts implementation tests +- **`get_from_file`** — Single test case that validates end-to-end parsing of a temporary `~/.ssh/known_hosts` file + +## What the Test Covers + +The `get_from_file` test: + +1. Creates a temporary directory with a synthetic `.ssh/known_hosts` file containing two entries (RSA keys for `github.com` and `gist.github.com`) +2. Calls `impl::genSSHkeysForHosts` with the current effective UID/GID and temporary directory +3. Asserts exactly 2 rows are returned +4. Validates each row contains the correct `key`, `uid`, and `key_file` fields + +## Usage Example + +```cpp +// Run this specific test suite via: +// ./osquery_tests --gtest_filter=KnownHostsImplTests.get_from_file + +// The function under test: +impl::genSSHkeysForHosts(uid, gid, home_directory, results); + +// Expected output row structure: +// results[0]["key"] -> full raw line from known_hosts +// results[0]["uid"] -> effective user ID as string +// results[0]["key_file"] -> absolute path to the known_hosts file +``` + +## Dependencies + +| Dependency | Purpose | +|---|---| +| `boost/filesystem` | Temp directory creation and cleanup | +| `gtest` | Test framework | +| `known_hosts.h` | `impl::genSSHkeysForHosts` under test | +| `scope_guard` | RAII cleanup of temporary test artifacts | \ No newline at end of file diff --git a/osquery/tables/system/tests/posix/.last_tests.md b/osquery/tables/system/tests/posix/.last_tests.md new file mode 100644 index 00000000000..cbd8e5016ae --- /dev/null +++ b/osquery/tables/system/tests/posix/.last_tests.md @@ -0,0 +1,45 @@ + +Unit tests for the `last` table implementation in osquery, validating the `genLastAccessForRow` function that parses `utmpx` login/logout records into query results. + +## Key Components + +- **`LastImplTests`** — GTest fixture class scoped to the `osquery::tables` namespace +- **`gen_row_from_utmpx`** — Single test case covering three scenarios: + - A valid user login entry (`USER_PROCESS`) + - An invalid/ignored entry (`INIT_PROCESS`) + - A logout/session-end entry (`DEAD_PROCESS`) + +## Usage Example + +```cpp +// The function under test (from osquery/tables/system/posix/last.h): +// impl::genLastAccessForRow(ut_entry, results); + +// Test verifies that only USER_PROCESS and DEAD_PROCESS types generate rows: +struct utmpx ut_login {}; +ut_login.ut_type = USER_PROCESS; // → included in results +ut_login.ut_pid = 1337; + +struct utmpx ut_badtype {}; +ut_badtype.ut_type = INIT_PROCESS; // → filtered out, not added to results + +struct utmpx ut_logout {}; +ut_logout.ut_type = DEAD_PROCESS; // → included, username/host are empty strings + +impl::genLastAccessForRow(ut_login, results); +impl::genLastAccessForRow(ut_badtype, results); +impl::genLastAccessForRow(ut_logout, results); + +// Expects exactly 2 rows (INIT_PROCESS is skipped) +ASSERT_EQ(results.size(), 2); +``` + +## Key Assertions + +| Field | Login row | Logout row | +|---|---|---| +| `username` | `"osquery"` | `""` | +| `tty` | `"line"` | `"line"` | +| `pid` | `1337` | `1337` | +| `type` | `USER_PROCESS` | `DEAD_PROCESS` | +| `host` | `"test_host"` | `""` | \ No newline at end of file diff --git a/osquery/tables/system/tests/posix/.shell_history_tests.md b/osquery/tables/system/tests/posix/.shell_history_tests.md new file mode 100644 index 00000000000..02fdfdfb9f1 --- /dev/null +++ b/osquery/tables/system/tests/posix/.shell_history_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the osquery `shell_history` table implementation, validating shell history parsing behavior across multiple edge cases on POSIX systems. + +## Key Components + +### Test Fixture +- **`ShellHistoryTests`** — GTest fixture class inheriting from `testing::Test`, providing the test harness for all shell history test cases. + +### Test Cases + +| Test | Function Tested | Scenario | +|---|---|---| +| `empty_timestamp` | `genShellHistoryForUser` | Commands without timestamps default to `"0"` | +| `bash_sessions_no_exist` | `genShellHistoryFromBashSessions` | Non-existent `.bash_sessions` dir returns no results | +| `bash_sessions_no_history` | `genShellHistoryFromBashSessions` | `.session` file without matching `.history` file returns no results | +| `bash_sessions_empty_ts` | `genShellHistoryFromBashSessions` | `.history` file without timestamps defaults to `"0"` | + +## Usage Example + +```cpp +// How genShellHistoryForUser is exercised in tests +std::vector results; +auto predicate = [&results](DynamicTableRowHolder& r) { + results.push_back(std::move(r)); +}; + +auto uid = std::to_string(geteuid()); +genShellHistoryForUser(uid, std::to_string(getegid()), directory.native(), predicate); + +// Assertions verify row fields: uid, time, command, history_file +EXPECT_EQ(results[0]["time"], "0"); // no timestamp → defaults to 0 +EXPECT_EQ(results[0]["command"], "ls -la"); +``` + +## Notes + +- Tests use `boost::filesystem` to create isolated temporary directories, ensuring no cross-test contamination. +- All temporary directories are cleaned up via `fs::remove_all` after each test. +- Test commands include special characters (`` [\]^_`!a"b#c$d `` etc.) to validate that the parser does not mangle non-alphanumeric input. \ No newline at end of file diff --git a/osquery/tables/system/tests/posix/.ssh_keys_tests.md b/osquery/tables/system/tests/posix/.ssh_keys_tests.md new file mode 100644 index 00000000000..09d3f41f745 --- /dev/null +++ b/osquery/tables/system/tests/posix/.ssh_keys_tests.md @@ -0,0 +1,41 @@ + +Unit tests for the `ssh_keys` osquery table implementation, validating detection and parsing of SSH private keys across multiple key types and encryption states. + +## Key Components + +- **`SshKeysTests`** — GTest fixture that creates a temporary directory structure (including a `.ssh` subdirectory) for each test and cleans up on teardown. +- **`genSSHkeyForHosts`** — The function under test; scans a user's SSH directory and populates a `QueryData` result set with key metadata. +- **Test fixtures (string constants)** — PEM-encoded sample keys for RSA, DSA, and Ed25519, both encrypted (`-N 'osquery'`) and unencrypted (`-N ''`). + +### Test Cases + +| Test | Key Type | Encrypted | Expected Fields | +|------|----------|-----------|-----------------| +| `invalid_key` | N/A | N/A | 0 results | +| `rsa_key_unencrypted` | RSA | No | `key_type=rsa`, `key_length=1024`, `key_security_bits=80` | +| `rsa_key_encrypted` | RSA | Yes | `key_type=""`, `key_length=-1` | +| `dsa_unencrypted` | DSA | No | `key_type=dsa`, `key_length=1024`, `key_security_bits=80` | +| `dsa_encrypted` | DSA | Yes | `key_type=""`, `key_length=-1` | +| `ed25519_unencrypted` | Ed25519 | No | `key_type=""`, `key_length=-1` | +| `ed25519_encrypted` | Ed25519 | Yes | `key_type=""`, `key_length=-1` | + +## Usage Example + +```cpp +// Tests are run via the osquery test suite; no direct invocation needed. +// To exercise genSSHkeyForHosts manually: +QueryData results; +GLOGLogger logger; +genSSHkeyForHosts( + "1000", // uid + "1000", // gid + "/home/user", // home directory path + results, + logger +); +// results[0]["encrypted"] == "0" or "1" +// results[0]["key_type"] == "rsa" | "dsa" | "" +// results[0]["key_length"] == "1024" | "-1" +``` + +> **Note:** Encrypted keys return `key_type=""` and `key_length=-1` because key metadata cannot be extracted without decryption. \ No newline at end of file diff --git a/osquery/tables/system/tests/posix/.sudoers_tests.md b/osquery/tables/system/tests/posix/.sudoers_tests.md new file mode 100644 index 00000000000..af847694540 --- /dev/null +++ b/osquery/tables/system/tests/posix/.sudoers_tests.md @@ -0,0 +1,35 @@ + +Unit tests for the `genSudoersFile` function in osquery's sudoers table implementation, verifying correct parsing of sudoers file syntax including rules, includes, and multi-line entries. + +## Key Components + +- **`SudoersTests`** — GTest fixture class housing all sudoers parsing test cases +- **`real_temp_path()`** — Helper that returns a canonicalized temporary directory path, required because `genSudoersFile` resolves paths to their canonical form +- **`basic_sudoers`** — Validates parsing of a simple sudoers rule while ignoring comments and malformed `#includedir` directives +- **`include_file`** — Tests recursive file inclusion via both `#include` (relative path) and `@include` (absolute path) directives +- **`include_dir`** — Tests directory-based inclusion via `#includedir` (relative) and `@includedir` (absolute), verifying all files within included directories are processed +- **`long_line`** — Tests backslash-continuation line joining for multi-line rules (`User_Alias`, `Cmnd_Alias`), escaped commas within quoted strings, and comment lines ending with backslash + +## Usage Example + +```cpp +// Each test creates an isolated temp directory, writes a sudoers file, +// calls genSudoersFile, then asserts on the resulting QueryData rows. + +auto results = QueryData{}; +genSudoersFile(sudoers_file.string(), 1, results); + +// Each row contains three fields: +EXPECT_EQ(results[0].at("source"), "/tmp/osquery.sudoers_tests.abcd-1234/sudoers"); +EXPECT_EQ(results[0].at("header"), "Defaults"); +EXPECT_EQ(results[0].at("rule_details"), "env_reset"); +``` + +## Test Coverage Summary + +| Test Case | What It Validates | +|---|---| +| `basic_sudoers` | Simple rules, comment filtering, bad include syntax | +| `include_file` | `#include` and `@include` with relative/absolute paths | +| `include_dir` | `#includedir` and `@includedir` directory expansion | +| `long_line` | Backslash continuation, escaped commas, alias chaining | \ No newline at end of file diff --git a/osquery/tables/system/tests/windows/.certificates_tests.md b/osquery/tables/system/tests/windows/.certificates_tests.md new file mode 100644 index 00000000000..2a652540aaf --- /dev/null +++ b/osquery/tables/system/tests/windows/.certificates_tests.md @@ -0,0 +1,37 @@ + +Unit tests for the Windows `certificates` table in osquery, validating the `parseSystemStoreString` function's parsing of Windows certificate system store path strings into their component parts. + +## Key Components + +### Test Fixture +- **`CertificatesTablesTest`** — Google Test fixture that initializes the osquery platform, registry/plugins, and database before each test. + +### Test Cases + +| Test | Input | Purpose | +|---|---|---| +| `test_only_store_non_special_case` | `L"My"` / `CurrentService` | Verifies non-prefixed store names resolve correctly (e.g., `"My"` → `"Personal"`) | +| `test_service` | `L"RpcSs\\My"` / `Services` | Validates a known service (`RpcSs`) resolves to `kNetworkService` SID | +| `test_user_default` | `L".DEFAULT\\My"` / `Users` | Verifies `.DEFAULT` user maps to `kLocalSystem` SID | +| `test_user_sid` | `L"S-1-5-18\\Root"` / `Users` | Validates a raw well-known SID resolves to `kLocalSystem` | +| `test_user_classes` | `L"S-1-5-21-...1001_Classes\\Root"` / `Users` | Verifies `_Classes`-suffixed SID strings are stripped to the base SID | + +## Usage Example + +```cpp +// Typical pattern exercised by these tests: +LPCWSTR input = L"RpcSs\\My"; +std::string storeLocation = "Services"; +std::string serviceNameOrUserId, sid, storeName; +ServiceNameMap cache; + +parseSystemStoreString( + input, storeLocation, cache, serviceNameOrUserId, sid, storeName); + +// Expected outputs: +// serviceNameOrUserId == "RpcSs" +// sid == kNetworkService +// storeName == "Personal" +``` + +> **Note:** These tests are Windows-only and depend on `osquery/tables/system/windows/certificates.h`. The `RpcSs` service test (`test_service`) assumes the Remote Procedure Call service is present on the test host. \ No newline at end of file diff --git a/osquery/tables/system/tests/windows/.programs_tests.md b/osquery/tables/system/tests/windows/.programs_tests.md new file mode 100644 index 00000000000..58999cfb4dc --- /dev/null +++ b/osquery/tables/system/tests/windows/.programs_tests.md @@ -0,0 +1,31 @@ + +Unit test file for the Windows `programs` table in osquery, specifically validating the `decodeMsiRegistryGuid` function used to parse MSI package GUIDs from the Windows registry. + +## Key Components + +- **`ProgramsTablesTest`** — GTest fixture class that initializes the osquery platform, registry, and database plugins before each test +- **`test_decode_msi_registry_guid`** — Test case covering three scenarios for `decodeMsiRegistryGuid`: + - Empty string input → returns empty string + - Invalid-length compact GUID (wrapped in braces) → returns empty string + - Valid 32-character compact GUID → returns properly formatted standard GUID + +## Usage Example + +```cpp +// Run this specific test via GTest filter: +// --gtest_filter=ProgramsTablesTest.test_decode_msi_registry_guid + +// The function under test converts MSI compact GUIDs to standard form: +std::string compactGUID = "0D8797326E7E4114DAECB3B66B9CD045"; +std::string result = decodeMsiRegistryGuid(compactGUID); +// result == "{237978D0-E7E6-4114-ADCE-3B6BB6C90D54}" + +// Invalid inputs are rejected: +decodeMsiRegistryGuid(""); // returns "" +decodeMsiRegistryGuid("{0D8797326E7E4114DAECB3B66B9CD045}"); // returns "" +``` + +## Notes + +- Windows-only test (targets `osquery/tables/system/windows/programs.h`) +- MSI registry GUIDs are stored in a reversed/compressed format in `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer` — this function normalizes them back to standard GUID notation \ No newline at end of file diff --git a/osquery/tables/system/tests/windows/.registry_tests.md b/osquery/tables/system/tests/windows/.registry_tests.md new file mode 100644 index 00000000000..0b3dbc6f5fb --- /dev/null +++ b/osquery/tables/system/tests/windows/.registry_tests.md @@ -0,0 +1,56 @@ + +Unit tests for the Windows Registry table implementation in osquery, validating key querying, glob expansion, path parsing, and subkey population against live registry data. + +## Key Components + +### Test Fixture +- **`RegistryTablesTest`** — GTest fixture that initializes the platform, registry plugins, and database before each test case + +### Test Constants +- **`kTestKey`** — `HKEY_LOCAL_MACHINE\SOFTWARE` (broad key for general queries) +- **`kTestSpecificKey`** — `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Control Panel` +- **`kInvalidTestKey`** — A non-existent registry path for negative-case testing + +### Test Cases + +| Test | What It Validates | +|---|---| +| `test_registry_existing_key` | `queryKey()` succeeds and returns rows for a valid key | +| `test_registry_non_existing_key` | `queryKey()` fails gracefully for a missing key | +| `test_expand_registry_globs` | `expandRegistryGlobs()` resolves `%` wildcards; resets output on empty input | +| `test_query_multiple_registry_keys` | `queryMultipleRegistryKeys()` returns additive results for multiple keys | +| `test_query_multiple_registry_paths` | `queryMultipleRegistryPaths()` mirrors key query behavior for path inputs | +| `test_explode_registry_path_normal` | `explodeRegistryPath()` correctly splits hive and subkey | +| `test_explode_registry_path_just_hive` | Handles hive-only paths with/without trailing separator | +| `test_registry_or_clause` | SQL `OR` on `key` column returns union of both result sets | +| `test_basic_registry_globbing` | Single `%` wildcard matches exactly one path depth level | +| `test_recursive_registry_globbing` | `%%` wildcard matches recursively across multiple depths | +| `test_registry_path_query_no_separators` | `path LIKE` prefix query returns only direct children | +| `test_registry_path_query_matches_key_data` | `key` and `path` queries return identical, consistent rows | +| `test_get_username_from_key` | `getUsernameFromKey()` succeeds for valid SID paths, fails for malformed inputs | +| `test_populate_subkeys_valid_key` | Expands a valid key set with child subkeys | +| `test_populate_subkeys_invalid_key` | Leaves an invalid key set unchanged | +| `test_populate_subkeys_invalid_middle_key` | Expands valid keys while skipping invalid ones in a mixed set | + +## Usage Example + +```cpp +// Run a specific test group on Windows: +// Build and execute with GTest filter +./osquery_tests --gtest_filter="RegistryTablesTest.*" + +// Example of what the tests exercise internally: +QueryData results; +auto status = queryKey("HKEY_LOCAL_MACHINE\\SOFTWARE", results); +// status.ok() == true, results.size() > 0 + +std::set expanded; +expandRegistryGlobs("HKEY_LOCAL_MACHINE\\SOFTWARE\\Micro%\\%", expanded); +// expanded contains matching two-level-deep subkeys + +std::string hive, key; +explodeRegistryPath("HKEY_LOCAL_MACHINE\\SOFTWARE\\Example", hive, key); +// hive == "HKEY_LOCAL_MACHINE", key == "SOFTWARE\\Example" +``` + +> **Note:** These tests require a live Windows environment with access to `HKEY_LOCAL_MACHINE` and `HKEY_USERS`. They will not pass in sandboxed or mock-only environments. \ No newline at end of file diff --git a/osquery/tables/system/tests/windows/.startup_items_tests.md b/osquery/tables/system/tests/windows/.startup_items_tests.md new file mode 100644 index 00000000000..feae82de5f1 --- /dev/null +++ b/osquery/tables/system/tests/windows/.startup_items_tests.md @@ -0,0 +1,35 @@ + +Unit tests for the `parseStartupPath` function from the Windows startup items table, validating path parsing logic across various executable path formats and edge cases. + +## Key Components + +- **`StartupItemsTests`** — GTest fixture that creates a temporary directory for filesystem-based tests and resets state (`Row r`, `std::error_code ec`) between each test case +- **`parseStartupPath`** — The function under test (declared in `startup_items.h`), responsible for splitting a raw startup path string into `path` and `args` fields within a `Row` + +## Test Cases + +| Test | Scenario | +|---|---| +| `test_no_path` | Empty string returns false, row stays empty | +| `test_path_no_spaces` | Simple path with no args parses correctly | +| `test_path_no_spaces_with_argument` | Path + args separated correctly | +| `test_path_with_spaces` | Unquoted path containing spaces resolved via filesystem | +| `test_path_with_spaces_and_args` | Unquoted spaced path with trailing args | +| `test_quoted_path_with_spaces_and_args` | Quoted path with args strips quotes correctly | +| `test_non_existing_path` | Non-existent unquoted path returns false | +| `test_quoted_non_existing_path` | Non-existent quoted path returns false | + +## Usage Example + +```cpp +// Mirrors how parseStartupPath is exercised in tests +Row r; +std::string raw = "C:\\Program Files\\App\\tool.exe /silent"; + +if (parseStartupPath(raw, r)) { + // r["path"] == "C:\\Program Files\\App\\tool.exe" + // r["args"] == "/silent" +} +``` + +The fixture uses `boost::filesystem::unique_path` to generate collision-safe temp directories, ensuring spaced-path tests reflect real filesystem resolution rather than string manipulation alone. \ No newline at end of file diff --git a/osquery/tables/system/tests/windows/.windows_eventlog_tests.md b/osquery/tables/system/tests/windows/.windows_eventlog_tests.md new file mode 100644 index 00000000000..678377e6141 --- /dev/null +++ b/osquery/tables/system/tests/windows/.windows_eventlog_tests.md @@ -0,0 +1,42 @@ + +Unit tests for the Windows Event Log table implementation, validating XML parsing and XPath filter generation for the `windows_eventlog` osquery table. + +## Key Components + +### Test Fixture +- **`WindowsEventLogTests`** — GTest fixture class inheriting from `testing::Test`, providing the test harness for all Windows Event Log unit tests. + +### Test Cases + +| Test | Description | +|---|---| +| `parse_wel_xml` | Validates successful parsing of a well-formed WEL XML event into a `Row`, checking all extracted fields | +| `parse_wel_xml_fails` | Validates that `parseWelXml` handles events not matching query constraints gracefully (returns empty row, no exceptions) | +| `gen_xfilter_test1` | Validates XPath filter generation using an explicit `time_range` constraint | +| `gen_xfilter_test2` | Validates XPath filter generation using a `timestamp` (millisecond timediff) constraint | + +## Usage Example + +```cpp +// Simulating what the tests verify: + +// 1. Parsing a Windows Event Log XML event +QueryContext context; +context.constraints["channel"].add(Constraint(EQUALS, "Application")); +context.constraints["eventid"].add(Constraint(EQUALS, "1000")); + +Row row; +parseWelXml(context, stringToWstring(xml_event), row); + +// Extracted fields available in row: +// row["datetime"], row["channel"], row["provider_name"], +// row["eventid"], row["task"], row["level"], row["data"] (JSON) + +// 2. Generating an XPath filter from query constraints +std::string xfilter; +context.constraints["timestamp"].add(Constraint(EQUALS, "43200000")); +genXfilterFromConstraints(context, xfilter); +// Result: *[System[(EventID=1000) and TimeCreated[timediff(@SystemTime) <= 43200000]]] +``` + +> **Note:** The `data` field in parsed rows is JSON-encoded. Windows path backslashes are double-escaped due to two levels of escaping (XML → JSON), e.g., `C:\\Windows\\` becomes `C:\\\\Windows\\\\` in the expected output. \ No newline at end of file diff --git a/osquery/tables/system/tests/windows/.windows_optional_features_tests.md b/osquery/tables/system/tests/windows/.windows_optional_features_tests.md new file mode 100644 index 00000000000..cfc870f1925 --- /dev/null +++ b/osquery/tables/system/tests/windows/.windows_optional_features_tests.md @@ -0,0 +1,36 @@ + +Unit test file for the Windows Optional Features osquery table, validating the `getDismPackageFeatureStateName` function that maps DISM package install state codes to human-readable strings. + +## Key Components + +- **`WinOptFeaturesTablesTest`** — GTest fixture class for Windows Optional Features table tests +- **`getDismPackageFeatureStateName(uint32_t state)`** — External function under test; converts a numeric DISM `InstallState` value to its string representation +- **`get_state_name`** — Test case verifying correct state name resolution for all known and unknown state codes + +## Usage Example + +```cpp +// Known state values (1–3) map to named states: +getDismPackageFeatureStateName(1); // Returns "Enabled" +getDismPackageFeatureStateName(2); // Returns "Disabled" +getDismPackageFeatureStateName(3); // Returns "Absent" + +// Any value outside 1–3 returns "Unknown": +getDismPackageFeatureStateName(0); // Returns "Unknown" +getDismPackageFeatureStateName(4); // Returns "Unknown" +getDismPackageFeatureStateName(999998); // Returns "Unknown" +``` + +## State Code Reference + +| Code | Name | +|------|------| +| `1` | Enabled | +| `2` | Disabled | +| `3` | Absent | +| Any other | Unknown | + +## Notes + +- Only state values `1`, `2`, and `3` are defined by the DISM API; all others fall through to `"Unknown"` +- The boundary cases `0` and `4` are explicitly tested to guard against off-by-one errors or accidental range expansion \ No newline at end of file diff --git a/osquery/tables/system/tests/windows/.windows_update_history_tests.md b/osquery/tables/system/tests/windows/.windows_update_history_tests.md new file mode 100644 index 00000000000..7ab8e4b524e --- /dev/null +++ b/osquery/tables/system/tests/windows/.windows_update_history_tests.md @@ -0,0 +1,34 @@ + +Unit test file for the `windows_update_history` osquery table on Windows, verifying that `WindowsUpdateHistory` entries are correctly rendered into osquery `Row` maps with proper string conversions for all enum fields. + +## Key Components + +- **`WindowsUpdateHistoryTests`** — GTest fixture class for grouping Windows Update History test cases. +- **`generateTestHistory()`** — Helper that builds a `WindowsUpdateHistory` vector covering all meaningful enum variants: both `updateOp` values (`uoInstallation`, `uoUninstallation`), all six `resultCode` values (`orcNotStarted` through `orcAborted`), and all four `serverSelection` values (`ssDefault` through `ssOthers`). +- **`validateRendered(entry, row)`** — Assertion helper that maps each `WindowsUpdateHistoryEntry` field to its expected `Row` column value, including switch-based string validation for the three enum fields. +- **`test_update_history_render`** — Primary test case that calls `renderWindowsUpdateHistory()`, asserts the output row count matches the input history size, then iterates each entry through `validateRendered()`. + +## Usage Example + +```cpp +// Run the test via ctest or directly: +// ctest -R WindowsUpdateHistoryTests + +// The test exercises renderWindowsUpdateHistory() like this: +WindowsUpdateHistory history = generateTestHistory(); +auto rows = renderWindowsUpdateHistory(history); + +// Each rendered Row is validated, e.g.: +ASSERT_EQ(row["operation"], "Installation"); // uoInstallation +ASSERT_EQ(row["result_code"], "Succeeded"); // orcSucceeded +ASSERT_EQ(row["server_selection"], "Default"); // ssDefault +ASSERT_EQ(row["hresult"], BIGINT(S_OK)); +``` + +## Enum Coverage + +| Field | Values Tested | +|---|---| +| `updateOp` | `uoInstallation`, `uoUninstallation` | +| `resultCode` | `orcNotStarted`, `orcInProgress`, `orcSucceeded`, `orcSucceededWithErrors`, `orcFailed`, `orcAborted` | +| `serverSelection` | `ssDefault`, `ssManagedServer`, `ssWindowsUpdate`, `ssOthers` | \ No newline at end of file diff --git a/osquery/tables/system/windows/.appcompat_shims.md b/osquery/tables/system/windows/.appcompat_shims.md new file mode 100644 index 00000000000..113abc3c114 --- /dev/null +++ b/osquery/tables/system/windows/.appcompat_shims.md @@ -0,0 +1,43 @@ + +Queries Windows Application Compatibility Shim Database (SDB) registry entries to enumerate installed shims and their associated executables via the osquery table interface. + +## Key Components + +### `struct sdb` +Internal data structure holding metadata for a Shim Database entry: +- `description` — human-readable database description +- `installTimestamp` — Unix timestamp (converted from Windows FILETIME) +- `path` — filesystem path to the `.sdb` file +- `type` — database type identifier + +### `genShims(QueryContext& context)` +Primary table generator function that: +1. Reads installed SDB metadata from `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\InstalledSDB` +2. Reads shim-to-executable mappings from `HKLM\...\AppCompatFlags\Custom` +3. Joins both datasets on the SDB GUID, converting Windows FILETIME to Unix epoch +4. Returns a `QueryData` row set consumable by the osquery engine + +## Registry Keys Queried + +| Key | Purpose | +|---|---| +| `AppCompatFlags\InstalledSDB` | SDB metadata (path, description, timestamp, type) | +| `AppCompatFlags\Custom` | Executable-to-SDB mappings | + +## Usage Example + +```cpp +// Called internally by osquery when the table is queried: +// SELECT * FROM appcompat_shims; + +// Timestamp conversion applied internally: +sdb.installTimestamp = (windowsFiletime / 10000000) - 11644473600; +// Converts Windows 100ns ticks since 1601-01-01 +// to Unix seconds since 1970-01-01 +``` + +## Notes + +- SDB GUIDs are extracted from registry subkey paths using `{` as the delimiter anchor +- Executable names are parsed from the trailing path token of each `Custom` subkey +- SDB IDs in the `Custom` key are stored with a 4-character suffix (e.g., `.sdb`) that is stripped before lookup \ No newline at end of file diff --git a/osquery/tables/system/windows/.authenticode.md b/osquery/tables/system/windows/.authenticode.md new file mode 100644 index 00000000000..f8a96d42372 --- /dev/null +++ b/osquery/tables/system/windows/.authenticode.md @@ -0,0 +1,47 @@ + +Extracts and validates Authenticode digital signature information from Windows PE files, populating structured signature result rows for osquery tables. + +## Key Components + +### Types & Structs + +- **`CustomUniquePtr`** — Generic RAII wrapper template for Windows handles with custom deleters +- **`unique_hcertstore`**, **`unique_certcontext`**, **`unique_hcryptmsg`** — RAII-managed Windows crypto handle types +- **`SignatureInformation`** — Holds extracted signature data: path, serial number, issuer/subject names, original program name, and a `Result` enum (`Valid`, `Trusted`, `Invalid`, `Missing`, `Distrusted`, `Untrusted`) + +### Key Functions + +| Function | Description | +|---|---| +| `generateRow()` | Converts a `SignatureInformation` struct into an osquery `Row` map | +| `getCatalogPathForFilePath()` | Resolves the Windows catalog (`.cat`) file for a given executable via `CryptCATAdmin*` APIs | +| `verifySignature()` | Runs `WinVerifyTrust` with full revocation checking and maps the result to a `SignatureInformation::Result` | +| `getOriginalProgramName()` | Decodes `SPC_SP_OPUS_INFO` from signer attributes to extract the publisher's program name | +| `getCertificateInformation()` | Retrieves serial number, issuer, and subject from a `PCCERT_CONTEXT` | +| `getSignatureInformation()` | Orchestrates `CryptQueryObject` to open the embedded PKCS7 message and certificate store | + +## Usage Example + +```cpp +SignatureInformation sig_info; +sig_info.path = wstringToString(file_path); + +// Verify the embedded Authenticode signature +SignatureInformation::Result result; +auto status = verifySignature(result, file_path); +if (status.ok()) { + sig_info.result = result; +} + +// Extract certificate metadata if signed +auto cert_status = getSignatureInformation(sig_info, file_path); +if (cert_status.ok()) { + Row row; + generateRow(row, sig_info); + // row["result"] → "trusted" | "valid" | "missing" | ... + // row["issuer_name"] → "DigiCert Inc" + // row["serial_number"] → "0a1b2c..." +} +``` + +> **Note:** This file is Windows-only and depends on `wincrypt.h`, `Softpub.h`, and `mscat.h`. All handle lifetimes are managed via RAII wrappers to prevent leaks on early returns. \ No newline at end of file diff --git a/osquery/tables/system/windows/.autoexec.md b/osquery/tables/system/windows/.autoexec.md new file mode 100644 index 00000000000..4c8a467d9e4 --- /dev/null +++ b/osquery/tables/system/windows/.autoexec.md @@ -0,0 +1,44 @@ + +Aggregates auto-execution entry points from multiple Windows system tables into a unified view, enabling detection of persistence mechanisms across startup items, services, scheduled tasks, IE extensions, and drivers. + +## Key Components + +### `kAutoExecTableMappings` + +A static map defining which osquery tables to query and how to alias their columns into a normalized `name`/`path` schema: + +| Source Table | `name` column | `path` column | +|---|---|---| +| `startup_items` | `name` | `path` | +| `services` | `name` | `module_path` | +| `scheduled_tasks` | `name` | `path` | +| `ie_extensions` | `name` | `path` | +| `drivers` | `description` | `image` | + +### `genAutoexec(QueryContext&)` + +The table generator function. Iterates over `kAutoExecTableMappings`, dynamically constructs a `SELECT` statement with column aliasing for each source table, executes it via the `SQL` helper, and merges all results into a single `QueryData` collection. Query failures are logged as warnings without halting execution. + +## Usage Example + +```cpp +// Called internally by the osquery table framework +// Equivalent SQL query issued at runtime for each mapped table: +// SELECT 'services' as source, name AS name, module_path AS path FROM services + +QueryContext ctx; +QueryData rows = genAutoexec(ctx); + +for (const auto& row : rows) { + // Each row contains: source, name, path + std::cout << row.at("source") << ": " + << row.at("name") << " -> " + << row.at("path") << "\n"; +} +``` + +**Resulting osquery virtual table usage:** + +```sql +SELECT source, name, path FROM autoexec; +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.background_activities_moderator.md b/osquery/tables/system/windows/.background_activities_moderator.md new file mode 100644 index 00000000000..ad5e49ad4f5 --- /dev/null +++ b/osquery/tables/system/windows/.background_activities_moderator.md @@ -0,0 +1,33 @@ + +Queries the Windows Background Activity Moderator (BAM) registry to retrieve executable execution history and timestamps per user SID. + +## Key Components + +- **`kBamRegPath`** — Registry glob pattern targeting the BAM service path under `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\bam` +- **`genBackgroundActivitiesModerator(QueryContext&)`** — Main query function that enumerates BAM registry entries, extracts per-user SID data, and returns execution records + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT path, sid, last_execution_time FROM background_activities_moderator; + +// Internally, the function: +// 1. Expands registry globs to find all BAM user keys +// 2. Filters for "UserSettings\S" entries containing SID (S-1-...) +// 3. Queries each key for executable path entries +// 4. Converts 16-byte little-endian timestamps to Unix epoch +// 5. Skips metadata keys ("SequenceNumber", "Version") + +Row r; +r["path"] = bKey.at("name"); // Executable path +r["sid"] = sid; // User SID (e.g. S-1-5-21-...) +r["last_execution_time"] = BIGINT(...); // Unix timestamp of last run +``` + +## Notes + +- Only processes keys containing `UserSettings\S` and `S-1` to isolate user-specific SID entries +- `SequenceNumber` and `Version` registry keys are skipped as they carry no execution data +- Timestamp parsing uses `littleEndianToUnixTime()` on the first 16 hex characters of the registry data value +- Windows-only implementation; depends on `osquery::tables::expandRegistryGlobs` and `queryKey` \ No newline at end of file diff --git a/osquery/tables/system/windows/.battery.md b/osquery/tables/system/windows/.battery.md new file mode 100644 index 00000000000..99a90f7ae02 --- /dev/null +++ b/osquery/tables/system/windows/.battery.md @@ -0,0 +1,37 @@ + +Implements the Windows battery information table for osquery, querying battery devices via the Windows Setup API and DeviceIoControl to expose hardware, capacity, and power state data. + +## Key Components + +- **`batteryQueryInformationString()`** — Helper that issues `IOCTL_BATTERY_QUERY_INFORMATION` to retrieve string fields (manufacturer, serial number, model) from a battery device handle using a given `BATTERY_QUERY_INFORMATION_LEVEL`. +- **`genBatteryInfo()`** — Main table generator. Enumerates up to 100 battery devices via `SetupDiGetClassDevs`, filters to system batteries (excluding UPS/short-term devices), and collects the following row fields: + +| Field | Source | +|---|---| +| `chemistry` | `BATTERY_INFORMATION.Chemistry` | +| `max_capacity`, `designed_capacity` | Converted from mWh → mAh (12V assumed) | +| `cycle_count` | `BATTERY_INFORMATION.CycleCount` | +| `state`, `charging`, `charged` | `BATTERY_STATUS.PowerState` | +| `current_capacity`, `voltage`, `amperage` | `BATTERY_STATUS` fields | +| `minutes_to_full_charge` | Calculated from rate/capacity delta | +| `percent_remaining`, `minutes_until_empty` | `SYSTEM_POWER_STATUS` | +| `manufacturer`, `serial_number`, `model` | `IOCTL_BATTERY_QUERY_INFORMATION` string queries | + +## Usage Example + +```cpp +// Called automatically by the osquery table infrastructure: +// SELECT * FROM battery; + +// Internally invoked as: +QueryContext context; +QueryData rows = osquery::tables::genBatteryInfo(context); + +for (const auto& row : rows) { + std::cout << "Model: " << row.at("model") + << " Chemistry: " << row.at("chemistry") + << " Remaining: " << row.at("percent_remaining") << "%\n"; +} +``` + +> **Note:** Capacity values are converted from mWh (Windows) to mAh using an assumed 12V design voltage to maintain schema compatibility with the macOS `battery` table. Batteries reporting relative units will emit a warning log. \ No newline at end of file diff --git a/osquery/tables/system/windows/.bitlocker_info.md b/osquery/tables/system/windows/.bitlocker_info.md new file mode 100644 index 00000000000..1f43a9afca1 --- /dev/null +++ b/osquery/tables/system/windows/.bitlocker_info.md @@ -0,0 +1,47 @@ + +Queries Windows BitLocker encryption status for all encryptable volumes via WMI, populating osquery table rows with volume identity, encryption state, and method details. + +## Key Components + +### `fetchMethodResultLong` +A static helper that executes a WMI method on a given object and extracts a `long` result parameter, converting it to an integer string. Returns `-1` on any failure. + +**Parameters:** +- `result` — output string to populate +- `req` — the active `WmiRequest` context +- `object` — the WMI result item to call the method on +- `method` — WMI method name (e.g., `"GetVersion"`) +- `param` — output parameter name to read from the method result + +### `genBitlockerInfo` +The main osquery table generator function. Queries `Win32_EncryptableVolume` from the `ROOT\CIMV2\Security\MicrosoftVolumeEncryption` WMI namespace and builds one row per volume. + +**Populated columns per row:** + +| Column | Source | +|---|---| +| `device_id` | WMI `DeviceID` | +| `drive_letter` | WMI `DriveLetter` | +| `persistent_volume_id` | WMI `PersistentVolumeID` | +| `conversion_status` | WMI `ConversionStatus` | +| `protection_status` | WMI `ProtectionStatus` | +| `encryption_method` | Mapped from `EncryptionMethod` integer | +| `version` | WMI method `GetVersion` | +| `percentage_encrypted` | WMI method `GetConversionStatus` | +| `lock_status` | WMI method `GetLockStatus` | + +## Usage Example + +```cpp +// Called internally by osquery table infrastructure +QueryContext context; +QueryData rows = osquery::tables::genBitlockerInfo(context); + +for (const auto& row : rows) { + // Example: row["drive_letter"] == "C:" + // row["encryption_method"] == "XTS_AES_256" + // row["protection_status"] == "1" +} +``` + +> **Note:** This file is Windows-only and requires WMI access to `ROOT\CIMV2\Security\MicrosoftVolumeEncryption`. Encryption method integers are mapped to human-readable strings (e.g., `6` → `XTS_AES_128`); unknown values fall back to `"UNKNOWN"`. \ No newline at end of file diff --git a/osquery/tables/system/windows/.certificates.md b/osquery/tables/system/windows/.certificates.md new file mode 100644 index 00000000000..921161b2584 --- /dev/null +++ b/osquery/tables/system/windows/.certificates.md @@ -0,0 +1,35 @@ + +Header for Windows certificate store parsing utilities within osquery's tables module. + +## Key Components + +### Type Aliases +- **`ServiceNameMap`** — `std::unordered_map` mapping service names to SIDs for caching lookups + +### Constants +| Constant | Value | Description | +|---|---|---| +| `kLocalSystem` | `S-1-5-18` | SID for the Local System account | +| `kLocalService` | `S-1-5-19` | SID for the Local Service account | +| `kNetworkService` | `S-1-5-20` | SID for the Network Service account | + +### Functions +- **`parseSystemStoreString`** — Parses a wide-character Windows certificate store path string, resolving the store location, service name or user ID, SID, and store name from the system store identifier + +## Usage Example + +```c +osquery::tables::ServiceNameMap sidCache; +std::string serviceNameOrUserId, sid, storeName; + +osquery::tables::parseSystemStoreString( + L"S-1-5-18\\My", // system store wide string + "LocalMachine", // store location context + sidCache, // service-to-SID cache (mutated) + serviceNameOrUserId, // out: resolved service name or user ID + sid, // out: resolved SID string + storeName // out: certificate store name (e.g. "My") +); +``` + +> **Note:** This header is Windows-specific. It depends on `LPCWSTR` (Windows API type) and is intended for use within osquery's certificate enumeration table implementation. The `ServiceNameMap` cache avoids redundant service-name-to-SID resolution across multiple store entries. \ No newline at end of file diff --git a/osquery/tables/system/windows/.chassis_info.md b/osquery/tables/system/windows/.chassis_info.md new file mode 100644 index 00000000000..d0b369a95b9 --- /dev/null +++ b/osquery/tables/system/windows/.chassis_info.md @@ -0,0 +1,45 @@ + +Queries Windows WMI (`Win32_SystemEnclosure`) to retrieve physical chassis/enclosure hardware information and exposes it as an osquery table. + +## Key Components + +- **`genChassisInfo(QueryContext&)`** — Main table generation function that executes a WMI query, maps raw SMBIOS chassis type codes to human-readable strings, and returns structured `QueryData`. +- **`enclosureTypes`** — SMBIOS DSP0134-compliant lookup map translating chassis type hex codes (`0x01`–`0x24`) to descriptive strings (e.g., `"Laptop"`, `"Blade"`, `"IoT Gateway"`). +- **`securityBreachStatus`** — Lookup map translating WMI `SecurityBreach` numeric codes to status strings (e.g., `"No Breach"`, `"Breach Successful"`). + +## Populated Row Fields + +| Field | Source | +|---|---| +| `audible_alarm` | `AudibleAlarm` (bool) | +| `breach_description` | `BreachDescription` | +| `chassis_types` | `ChassisTypes` (comma-separated) | +| `description` | `Description` | +| `lock` | `LockPresent` (bool) | +| `manufacturer` | `Manufacturer` | +| `model` | `Model` | +| `security_breach` | `SecurityBreach` (mapped) | +| `serial` | `SerialNumber` | +| `smbios_tag` | `SMBIOSAssetTag` | +| `sku` | `SKU` | +| `status` | `Status` | +| `visible_alarm` | `VisibleAlarm` (bool) | + +## Usage Example + +```cpp +// Called internally by osquery's table registry. +// Query via osquery shell or SDK: +// SELECT * FROM chassis_info; +// +// Example result: +// manufacturer = "Dell Inc." +// chassis_types = "Notebook" +// security_breach = "No Breach" +// lock = "True" +// serial = "ABC1234" + +QueryData results = genChassisInfo(context); +``` + +> **Note:** Multiple chassis types are concatenated as a comma-separated string. Unknown type codes are represented as `"Unknown (N)"` where `N` is the raw numeric value. Returns an empty result with a `WARNING` log if WMI is unavailable. \ No newline at end of file diff --git a/osquery/tables/system/windows/.chocolatey_packages.md b/osquery/tables/system/windows/.chocolatey_packages.md new file mode 100644 index 00000000000..bf35b2a95cd --- /dev/null +++ b/osquery/tables/system/windows/.chocolatey_packages.md @@ -0,0 +1,40 @@ + +Implements the osquery `chocolatey_packages` table, which enumerates installed Chocolatey packages on Windows by parsing `.nuspec` XML manifest files. + +## Key Components + +### `genPackage(nuspec, r)` +Reads and parses a single `.nuspec` XML file at the given path, populating a result row with package metadata extracted via Boost.PropertyTree. + +**Populated fields:** +| Field | XML Source | +|---|---| +| `path` | File path of the `.nuspec` | +| `name` | `package.metadata.id` | +| `version` | `package.metadata.version` | +| `summary` | `package.metadata.summary` | +| `author` | `package.metadata.authors` | +| `license` | `package.metadata.licenseUrl` | + +### `genChocolateyPackages(context)` +Entry point for the osquery table. Resolves the Chocolatey installation directory from the `ChocolateyInstall` environment variable, glob-matches all `.nuspec` files under `/lib/`, and delegates parsing to `genPackage`. + +## Usage Example + +```cpp +// Invoked internally by osquery when the table is queried: +// SELECT * FROM chocolatey_packages; + +QueryContext context; +QueryData results = genChocolateyPackages(context); + +for (const auto& row : results) { + std::cout << row.at("name") << " v" << row.at("version") << "\n"; +} +``` + +## Notes + +- Requires the `ChocolateyInstall` environment variable to be set; logs a warning and returns empty results if absent. +- Uses glob pattern `lib/%/%.nuspec` to match one `.nuspec` per package directory. +- XML parse failures per package are logged at `VLOG(1)` and skipped rather than aborting the full query. \ No newline at end of file diff --git a/osquery/tables/system/windows/.cpu_info.md b/osquery/tables/system/windows/.cpu_info.md new file mode 100644 index 00000000000..9578a067b75 --- /dev/null +++ b/osquery/tables/system/windows/.cpu_info.md @@ -0,0 +1,43 @@ + +Queries Windows WMI (`Win32_Processor`) to retrieve detailed CPU hardware information and exposes it as an osquery virtual table. + +## Key Components + +- **`genCpuInfo(QueryContext&)`** — Main table generation function. Issues a WMI `SELECT * FROM Win32_Processor` query and maps each result into an osquery `Row`. + +### Populated Row Fields + +| Field | Source WMI Property | Fallback | +|---|---|---| +| `device_id` | `DeviceID` | — | +| `socket_designation` | `SocketDesignation` | — | +| `model` | `Name` | — | +| `manufacturer` | `Manufacturer` | — | +| `processor_type` | `ProcessorType` | `-1` | +| `availability` | `Availability` | `-1` | +| `cpu_status` | `CpuStatus` | `-1` | +| `number_of_cores` | `NumberOfCores` | `-1` | +| `logical_processors` | `NumberOfLogicalProcessors` | `-1` | +| `address_width` | `AddressWidth` | `-1` | +| `current_clock_speed` | `CurrentClockSpeed` | `-1` | +| `max_clock_speed` | `MaxClockSpeed` | `-1` | +| `load_percentage` | `LoadPercentage` | `-1` | + +## Usage Example + +```cpp +// Called internally by osquery's table dispatcher. +// Equivalent SQL query from the osquery shell: +// SELECT * FROM cpu_info; + +QueryContext ctx; +QueryData rows = osquery::tables::genCpuInfo(ctx); + +for (const auto& row : rows) { + std::cout << "Model: " << row.at("model") + << " Cores: " << row.at("number_of_cores") + << " Speed: " << row.at("current_clock_speed") << " MHz\n"; +} +``` + +> **Note:** This implementation is **Windows-only**. Numeric fields default to `-1` when the WMI property is unavailable or the query fails entirely, logging a `WARNING` via the osquery logger. \ No newline at end of file diff --git a/osquery/tables/system/windows/.default_environment.md b/osquery/tables/system/windows/.default_environment.md new file mode 100644 index 00000000000..cc9c18eeab8 --- /dev/null +++ b/osquery/tables/system/windows/.default_environment.md @@ -0,0 +1,30 @@ + +Queries the Windows system-wide default environment variables by reading from the registry key `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment`. + +## Key Components + +- **`kEnvironmentKey`** — Static string constant holding the registry path for system-wide environment variables. +- **`genDefaultEnvironment(QueryContext&)`** — Table generator function that queries the registry key, maps each entry to a row with `variable`, `value`, and `expand` fields, and returns the collected results. + +## Usage Example + +```cpp +// Called internally by osquery's table dispatch when querying: +// SELECT * FROM default_environment; + +// Returned row structure per environment variable: +Row r; +r["variable"] = "PATH"; // Registry value name +r["value"] = "C:\\Windows\\System32"; // Registry value data +r["expand"] = "1"; // 1 if REG_EXPAND_SZ, 0 otherwise +``` + +### Output Columns + +| Column | Description | +|---|---| +| `variable` | Environment variable name (registry value name) | +| `value` | Environment variable data (registry value data) | +| `expand` | `1` if the value is an expandable string (`REG_EXPAND_SZ`), `0` otherwise | + +> **Note:** If the registry query fails, an error is logged via `TLOG` and an empty result set is returned. This table is Windows-only and relies on `queryKey()` from the osquery Windows registry utilities. \ No newline at end of file diff --git a/osquery/tables/system/windows/.deviceguard_status.md b/osquery/tables/system/windows/.deviceguard_status.md new file mode 100644 index 00000000000..efc3b363907 --- /dev/null +++ b/osquery/tables/system/windows/.deviceguard_status.md @@ -0,0 +1,46 @@ + +Queries Windows Device Guard security status via WMI, translating raw numeric values from `Win32_DeviceGuard` into human-readable security state strings. + +## Key Components + +### `genDeviceGuardStatus(QueryContext& context)` +The sole table-generating function registered with osquery. It: +- Queries `ROOT\MICROSOFT\WINDOWS\DEVICEGUARD` via WMI for `Win32_DeviceGuard` +- Maps numeric enum values to string labels for three status categories +- Returns a `QueryData` row per WMI result + +### Enum Mapping Vectors + +| Vector | Values | +|---|---| +| `vbs_methods` | `VBS_NOT_ENABLED`, `VBS_ENABLED_AND_NOT_RUNNING`, `VBS_ENABLED_AND_RUNNING` | +| `security_services` | `NONE`, `CREDENTIAL_GUARD`, `MEMORY_INTEGRITY`, `SYSTEM_GUARD_SECURE_LAUNCH`, `SMM_FIRMWARE_MEASUREMENT` | +| `enforcement_methods` | `OFF`, `AUDIT_MODE`, `ENFORCED_MODE` | + +### Output Columns + +- `version`, `instance_identifier` — raw WMI strings +- `vbs_status` — Virtualization Based Security state +- `code_integrity_policy_enforcement_status` — kernel-mode code integrity enforcement +- `umci_policy_status` — user-mode code integrity enforcement +- `running_security_services` — comma-separated list of active services +- `configured_security_services` — comma-separated list of configured services + +Out-of-range numeric values fall back to `"UNKNOWN"`. + +## Usage Example + +```cpp +// Invoked automatically by osquery when the virtual table is queried: +// SELECT * FROM deviceguard_status; + +// Typical SQL usage in osquery shell: +// osquery> SELECT vbs_status, running_security_services +// FROM deviceguard_status; +// +// Example output: +// vbs_status | running_security_services +// VBS_ENABLED_AND_RUNNING| CREDENTIAL_GUARD,MEMORY_INTEGRITY +``` + +> **Note:** This table is Windows-only and requires WMI access to `ROOT\MICROSOFT\WINDOWS\DEVICEGUARD`. An empty result set is returned with an error log if WMI is unavailable. \ No newline at end of file diff --git a/osquery/tables/system/windows/.disk_info.md b/osquery/tables/system/windows/.disk_info.md new file mode 100644 index 00000000000..69a8e72ad34 --- /dev/null +++ b/osquery/tables/system/windows/.disk_info.md @@ -0,0 +1,45 @@ + +Implements the `disk_info` osquery table for Windows, querying physical disk drive details via WMI (`Win32_DiskDrive`). + +## Key Components + +### `genDiskInfo(QueryContext& context)` +The sole table generation function. Issues a WMI query and maps result fields into osquery rows. + +**Mapped fields:** + +| Column | WMI Property | +|---|---| +| `partitions` | `Partitions` | +| `disk_index` | `Index` | +| `type` | `InterfaceType` | +| `pnp_device_id` | `PNPDeviceID` | +| `id` | `DeviceID` | +| `disk_size` | `Size` | +| `manufacturer` | `Manufacturer` | +| `hardware_model` | `Model` | +| `name` | `Name` | +| `serial` | `SerialNumber` | +| `description` | `Description` | + +## Usage Example + +```cpp +// Invoked internally by the osquery table registry. +// Equivalent SQL query at the osquery shell: +// SELECT * FROM disk_info; + +// WMI query issued internally: +// "select * from Win32_DiskDrive" + +QueryContext ctx; +QueryData rows = genDiskInfo(ctx); +for (const auto& row : rows) { + // Access fields e.g. row.at("name"), row.at("serial") +} +``` + +## Error Handling + +- Logs a `WARNING` and returns an empty result set if the WMI request fails or returns no results. +- **Windows-only** — depends on `WmiRequest` from `osquery/core/windows/wmi.h`. \ No newline at end of file diff --git a/osquery/tables/system/windows/.dns_cache.md b/osquery/tables/system/windows/.dns_cache.md new file mode 100644 index 00000000000..8ada6ff261f --- /dev/null +++ b/osquery/tables/system/windows/.dns_cache.md @@ -0,0 +1,31 @@ + +Implements the `dns_cache` osquery table for Windows, querying the system DNS resolver cache by dynamically loading `DNSAPI.dll` and enumerating all cached DNS entries with their names, record types, and flags. + +## Key Components + +- **`DNSCACHEENTRY` struct** — mirrors the internal Windows DNS cache linked-list node, holding a pointer to the next entry, the DNS record name (`PWSTR`), type, data length, and flags. +- **`DNS_GET_CACHE_DATA_TABLE`** — function pointer typedef for the undocumented `DnsGetCacheDataTable` export from `DNSAPI.dll`. +- **`dnsTypeToString(unsigned short wType)`** — maps numeric DNS record type codes to their standard string representations (e.g., `1` → `"A"`, `28` → `"AAAA"`, `257` → `"CAA"`). Falls back to the raw integer string for unknown types. +- **`genDnsCache(QueryContext& context)`** — table generator that loads `DNSAPI.dll` at runtime, calls `DnsGetCacheDataTable` to retrieve the cache linked list, and walks each entry to populate `name`, `type`, and `flags` columns. + +## Usage Example + +This file backs the `dns_cache` osquery virtual table. Query it directly via the osquery shell: + +```sql +SELECT name, type, flags FROM dns_cache; +``` + +Expected output: + +```text ++----------------------+-------+-------+ +| name | type | flags | ++----------------------+-------+-------+ +| example.com | A | 0 | +| mail.example.com | MX | 0 | +| cdn.example.com | CNAME | 0 | ++----------------------+-------+-------+ +``` + +> **Note:** This table is Windows-only. It dynamically resolves `DnsGetCacheDataTable` from `DNSAPI.dll` using `LOAD_LIBRARY_SEARCH_SYSTEM32` to avoid DLL hijacking. The first node returned by `DnsGetCacheDataTable` is a sentinel; iteration begins at `pEntry->pNext`. \ No newline at end of file diff --git a/osquery/tables/system/windows/.drivers.md b/osquery/tables/system/windows/.drivers.md new file mode 100644 index 00000000000..a3198a3810e --- /dev/null +++ b/osquery/tables/system/windows/.drivers.md @@ -0,0 +1,34 @@ + +Implements the `genDrivers` table function for osquery on Windows, querying installed device driver information by combining WMI (`Win32_PnPSignedDriver`) with the Windows Setup API and registry. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `genDrivers` | Function | Main table generator; merges WMI and Win32 API data into `QueryData` rows | +| `setupDevInfoSet` | Function | Creates a device information set via `SetupDiGetClassDevs` | +| `getDeviceList` | Function | Enumerates all present devices into a `SP_DEVINFO_DATA` vector | +| `getDeviceProperty` | Function | Retrieves a typed device property (`UINT32`, `INT32`, `STRING`, `FILETIME`) by `DEVPROPKEY` | +| `getDriverImagePath` | Function | Reads the `ImagePath` registry value under `HKLM\SYSTEM\CurrentControlSet\Services\` | +| `registrySubKeyExists` | Function | Validates existence of a registry sub-key without retaining the handle | +| `kNormalizeImage` | Helper | Normalizes driver image paths to a canonical `system32`-rooted form | +| `kAdditionalDeviceProps` | Constant | Maps column names (`service`, `driver_key`, `date`) to their `DEVPROPKEY` values | +| `device_infoset_t` | Type alias | RAII `unique_ptr` wrapping a `HDEVINFO` handle with `SetupDiDestroyDeviceInfoList` as deleter | + +## Usage Example + +```cpp +// Called automatically by the osquery table registry. +// To exercise manually in a plugin context: +QueryContext ctx; +QueryData rows = osquery::tables::genDrivers(ctx); + +for (const auto& row : rows) { + std::cout << "Device : " << row.at("device_name") << "\n" + << "Version: " << row.at("version") << "\n" + << "Signed : " << row.at("signed") << "\n" + << "Image : " << row.at("image") << "\n"; +} +``` + +The function merges two data sources: WMI supplies `device_name`, `version`, `manufacturer`, `signed`, and `provider`; the Setup API and registry supply `service`, `driver_key`, `date`, and the normalized `image` path. Results are joined on `DeviceID`. \ No newline at end of file diff --git a/osquery/tables/system/windows/.groups.md b/osquery/tables/system/windows/.groups.md new file mode 100644 index 00000000000..45ac15edd3f --- /dev/null +++ b/osquery/tables/system/windows/.groups.md @@ -0,0 +1,50 @@ + +Implements the Windows `groups` osquery virtual table, generating rows from the global groups cache with support for filtered lookups by SID or GID. + +## Key Components + +### `genGroup(const Group& group)` +Converts a `Group` struct into an osquery `Row` with the following columns: + +| Column | Type | Description | +|--------|------|-------------| +| `gid` | BIGINT | Group numeric ID | +| `gid_signed` | INTEGER | Signed representation of GID | +| `group_sid` | STRING | Windows Security Identifier (SID) | +| `comment` | STRING | Group description/comment | +| `groupname` | STRING | Group display name | + +### `genGroups(QueryContext& context)` +Main table generator that resolves query constraints and fetches groups from `GlobalUsersGroupsCache`. Supports three query paths: + +```mermaid +graph TD + A[genGroups called] --> B{SID constraint?} + B -->|Yes| C[Lookup by SID] + B -->|No| D{GID constraint?} + D -->|Yes| E[Lookup by GID] + D -->|No| F[Return all groups] +``` + +## Usage Example + +```cpp +// Querying via osquery SQL — filtered by SID +SELECT gid, groupname, group_sid, comment +FROM groups +WHERE group_sid = 'S-1-5-32-544'; + +// Filtered by GID +SELECT * FROM groups WHERE gid = 544; + +// Full table scan +SELECT * FROM groups; +``` + +## Query Resolution Logic + +- **SID filter** takes priority over GID filter +- **GID filter** applied only when no SID constraints exist; GID strings are parsed via `tryTo()` and invalid values are silently skipped +- **No filter** triggers a full cache scan via `getAllGroups()` + +All group data is sourced from `GlobalUsersGroupsCache`, avoiding repeated WinAPI calls across queries. \ No newline at end of file diff --git a/osquery/tables/system/windows/.ie_extensions.md b/osquery/tables/system/windows/.ie_extensions.md new file mode 100644 index 00000000000..487a4332851 --- /dev/null +++ b/osquery/tables/system/windows/.ie_extensions.md @@ -0,0 +1,32 @@ + +Enumerates Internet Explorer Browser Helper Objects (BHOs) and URL Search Hooks installed on a Windows system by querying registry keys and resolving associated executable metadata. + +## Key Components + +- **`kIEBrowserHelperKeys`** — Static list of registry paths (both 32-bit and 64-bit hives, HKLM and HKEY_USERS) where IE BHOs and URL Search Hooks are registered. +- **`getBHOs(QueryData& results)`** — Internal helper that queries the registry keys, resolves each BHO's class name and executable path(s), and collects version information into result rows. +- **`genIEExtensions(QueryContext& context)`** — Public osquery table generator that invokes `getBHOs` and returns the populated `QueryData`. + +## Usage Example + +```cpp +// Registered as an osquery virtual table; queried via SQL: +// SELECT name, path, version, registry_path FROM ie_extensions; + +// Each result row contains: +// r["name"] — Human-readable class name from COM registry +// r["path"] — Resolved long path to the BHO executable/DLL +// r["version"] — Product version from the binary's version info +// r["registry_path"] — Full registry path where the BHO is registered +``` + +## Registry Paths Queried + +```text +HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects +HKLM\SOFTWARE\WOW6432Node\...\Browser Helper Objects (32-bit on 64-bit OS) +HKEY_USERS\%\SOFTWARE\...\Browser Helper Objects (per-user) +HKEY_USERS\%\SOFTWARE\...\Internet Explorer\URLSearchHooks +``` + +> **Note:** Short paths returned by the registry are expanded to full long paths via `windowsShortPathToLongPath`. Version metadata is extracted using `windowsGetVersionInfo`. Failures at the per-BHO level are logged as warnings and skipped rather than aborting the full query. \ No newline at end of file diff --git a/osquery/tables/system/windows/.intel_me.md b/osquery/tables/system/windows/.intel_me.md new file mode 100644 index 00000000000..5ac8b5f5873 --- /dev/null +++ b/osquery/tables/system/windows/.intel_me.md @@ -0,0 +1,49 @@ + +Queries Intel Management Engine (ME) firmware version information on Windows systems by communicating with the HECI (Host Embedded Controller Interface) device driver via the SetupAPI and Win32 device I/O interface. + +## Key Components + +### Data Structures + +- **`IntelMEInformation`** — Top-level struct holding all collected ME data: + - `HECIVersion` — Major/minor/hotfix/build of the HECI driver + - `ProtocolInformation` — Max message length and protocol version + - `FirmwareVersionTypes0And1` — CODE/NFTP/FITC version fields (interface types 0 & 1) + - `FirmwareVersionType2` — SKU, PCH version, vendor, and build fields (interface type 2) + - `InterfaceType` — Enum selecting between three supported HECI interface variants + +### RAII Handle Wrappers + +- **`DeviceInformationSet`** — Wraps `HDEVINFO`, destroyed via `SetupDiDestroyDeviceInfoList` +- **`DeviceHandle`** / **`EventHandle`** — Wrap `HANDLE`, closed via `CloseHandle` +- **`GenericHandleDeleter`** — Template deleter that guards against `INVALID_HANDLE_VALUE` + +### Key Functions + +- **`getDeviceInformationSet()`** — Calls `SetupDiGetClassDevs` filtered by `HECI_INTERFACE_GUID` to locate ME devices +- **`getDeviceInterfacePath()`** — Retrieves the device filesystem path via `SetupDiGetDeviceInterfaceDetail` +- **`enumerateHECIDeviceInterfacePaths()`** — Iterates all matching HECI interfaces and collects their paths +- **`printIntelMEVersion()`** — Formats full ME info into a human-readable string for logging/debugging + +### Constants + +- `kGetHECIVersionCommand` / `kConnectDeviceCommand` — IOCTL control codes sent via `DeviceIoControl` +- `kConnectDeviceCommandData` — Per-interface-type GUIDs used in the connect handshake + +## Usage Example + +```cpp +// Enumerate available HECI device paths +std::unordered_set paths; +auto status = enumerateHECIDeviceInterfacePaths(paths); +if (!status.ok()) { + LOG(WARNING) << "HECI enumeration failed: " << status.getMessage(); +} + +// After full collection, format ME info for logging +IntelMEInformation info; +// ... populate via IOCTL commands ... +LOG(INFO) << printIntelMEVersion(info); +``` + +> **Reference:** Intel AMT Implementation Guide and the [INTEL-SA-00086 Detection Tool](https://downloadcenter.intel.com/download/27150/INTEL-SA-00086-Detection-Tool) are recommended for validating returned firmware versions against known ME releases. \ No newline at end of file diff --git a/osquery/tables/system/windows/.kernel_info.md b/osquery/tables/system/windows/.kernel_info.md new file mode 100644 index 00000000000..a8741455609 --- /dev/null +++ b/osquery/tables/system/windows/.kernel_info.md @@ -0,0 +1,38 @@ + +Implements the `kernel_info` osquery table for Windows, collecting kernel version, boot arguments, system drive GUID, and kernel path from the NT kernel executable and registry. + +## Key Components + +| Function | Description | +|---|---| +| `GetKernelVersion(Row& r)` | Reads `ntoskrnl.exe` file version info using `GetFileVersionInfoW`, populating `version` as a four-part string (e.g., `10.0.19041.1`) | +| `GetBootArgs(Row& r)` | Queries `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control` for `SystemStartOptions`, populating `arguments` | +| `GetSystemDriveGUID(Row& r)` | Resolves the system drive volume GUID via `GetVolumeNameForVolumeMountPoint`, populating `device` | +| `genKernelInfo(QueryContext&)` | Table entry point — calls all three helpers and appends the static `path` to `ntoskrnl.exe` | + +## Usage Example + +Queried via the osquery SQL interface on Windows: + +```sql +SELECT version, arguments, device, path FROM kernel_info; +``` + +Expected output row: + +```text +version | 10.0.19041.1415 +arguments | NOEXECUTE=OPTIN +device | \\?\Volume{a1b2c3d4-...}\ +path | C:\Windows\System32\ntoskrnl.exe +``` + +Programmatic table registration follows the standard osquery pattern: + +```cpp +// Registered automatically via REGISTER macro (not shown in this file) +// Results are returned as a single-row QueryData +QueryData result = genKernelInfo(context); +``` + +> **Note:** `GetKernelVersion` validates the `VS_FIXEDFILEINFO` magic signature (`0xfeef04bd`) before extracting version fields. Failures at any Win32 API call are logged via `TLOG` and result in empty fields rather than hard errors. \ No newline at end of file diff --git a/osquery/tables/system/windows/.kva_speculative_info.md b/osquery/tables/system/windows/.kva_speculative_info.md new file mode 100644 index 00000000000..de91f16f131 --- /dev/null +++ b/osquery/tables/system/windows/.kva_speculative_info.md @@ -0,0 +1,40 @@ + +Implements the osquery table generator for querying Windows Kernel Virtual Address (KVA) shadow and speculative execution control information, exposing Meltdown/Spectre mitigation status via `NtQuerySystemInformation`. + +## Key Components + +- **`SYSTEM_KERNEL_VA_SHADOW_INFORMATION`** — Custom struct (info class `196`) exposing KVA shadow flags: enabled state, user-global mapping, PCID, and INVPCID support. +- **`SYSTEM_SPECULATION_CONTROL_INFORMATION`** — Custom struct (info class `201`) exposing branch prediction and speculative execution control flags (IBRS, STIBP, BpbEnabled, etc.). +- **`genKvaSpeculative(QueryContext&)`** — Main table generator; calls `NtQuerySystemInformation` twice to populate a single result row with all mitigation flags. + +## Usage Example + +```cpp +// Registered as an osquery table; query via SQL: +// SELECT * FROM kva_speculative_info; + +// Internally calls: +NtQuerySystemInformation( + SystemKernelVaShadowInformation, &kvaInfo, sizeof(kvaInfo), nullptr); + +NtQuerySystemInformation( + SystemSpeculationControlInformation, &specInfo, sizeof(specInfo), nullptr); +``` + +## Returned Columns + +| Column | Description | +|---|---| +| `kva_shadow_enabled` | KVA shadow (Meltdown mitigation) active | +| `kva_shadow_user_global` | User-global mapping in use | +| `kva_shadow_pcid` | PCID optimization enabled | +| `kva_shadow_inv_pcid` | INVPCID instruction in use | +| `bp_mitigations` | Branch prediction mitigations (BpbEnabled) | +| `bp_system_policy_disabled` | Mitigations disabled by system policy | +| `bp_microcode_disabled` | Mitigations disabled due to no hardware support | +| `cpu_spec_ctrl_supported` | CPU SPEC_CTRL MSR enumerated | +| `cpu_pred_cmd_supported` | CPU PRED_CMD MSR enumerated | +| `ibrs_support_enabled` | IBRS present | +| `stibp_support_enabled` | STIBP present | + +> **Note:** Returns an empty result on unpatched systems (`STATUS_INVALID_INFO_CLASS`) or silently zero-fills KVA fields on x86 systems without active mitigations (`STATUS_NOT_IMPLEMENTED`). Windows-only. \ No newline at end of file diff --git a/osquery/tables/system/windows/.logged_in_users.md b/osquery/tables/system/windows/.logged_in_users.md new file mode 100644 index 00000000000..872a790ee33 --- /dev/null +++ b/osquery/tables/system/windows/.logged_in_users.md @@ -0,0 +1,48 @@ + +Windows implementation of the osquery `logged_in_users` table that queries active Windows Terminal Services (WTS) sessions to enumerate currently logged-in users and their session details. + +## Key Components + +### Constants + +- **`kSessionStates`** — Maps WTS connection state enum values (e.g., `WTSActive`, `WTSDisconnected`) to human-readable strings. + +### Functions + +- **`genLoggedInUsers(QueryContext& context)`** — Primary table generator. Enumerates active WTS sessions using `WTSEnumerateSessionsExW`, filters to interactive user sessions (non-zero session ID, `WTSActive` state), and collects per-session metadata. + +### Populated Row Fields + +| Field | Source | +|---|---| +| `user` | `WTSINFOW.UserName` | +| `type` | Session state string from `kSessionStates` | +| `tty` | Session name (e.g., `RDP-Tcp#0`) | +| `time` | `ConnectTime` converted from FILETIME to Unix timestamp | +| `host` | Client IP (IPv4/IPv6) or client name (AF_UNSPEC) | +| `pid` | Always `-1` (not available via WTS API) | +| `sid` | Security Identifier string derived from domain\\username | +| `registry_hive` | `HKEY_USERS\{SID}` path | + +## Usage Example + +```cpp +// Invoked automatically by the osquery table registry. +// Equivalent SQL query: +// SELECT user, type, tty, time, host, sid, registry_hive +// FROM logged_in_users; + +// Internal call flow: +QueryContext ctx; +QueryData rows = osquery::tables::genLoggedInUsers(ctx); +for (const auto& row : rows) { + // row["user"], row["host"], row["sid"], etc. +} +``` + +## Notes + +- Only `WTSActive` sessions with a non-zero session ID are included; session `0` (the non-interactive system session) is explicitly skipped. +- IPv6 client address resolution is incomplete (marked `TODO` in source). +- All WTS memory allocations are freed via `WTSFreeMemory` / `WTSFreeMemoryEx` to prevent leaks. +- Windows-only; depends on `Wtsapi32.lib` and `winsock2.h`. \ No newline at end of file diff --git a/osquery/tables/system/windows/.logical_drives.md b/osquery/tables/system/windows/.logical_drives.md new file mode 100644 index 00000000000..04bc9486c6a --- /dev/null +++ b/osquery/tables/system/windows/.logical_drives.md @@ -0,0 +1,32 @@ + +Queries Windows logical drive information using WMI, returning disk metadata including device ID, description, free space, size, file system, and boot partition status. + +## Key Components + +- **`genLogicalDrives(QueryContext&)`** — Main table generation function that performs two sequential WMI queries: + - `Win32_BootConfiguration` — Identifies boot drive letters to flag boot partitions. + - `Win32_LogicalDisk` — Retrieves per-drive metadata (device ID, description, free space, size, file system). + +## Behavior Notes + +- **Boot partition detection** — Collects boot directory drive letters from `Win32_BootConfiguration` into an unordered set, then sets `boot_partition` to `1` or `0` per disk via `bootDeviceIds.count()`. +- **Missing values** — `free_space` and `size` default to `-1` when WMI returns an empty string (e.g., for unmounted or unavailable drives). +- **`type` column** — Always returns `"Unknown"` due to a known WMI marshalling bug that prevented reliable type retrieval; preserved for backward compatibility. +- **Early return** — If the `Win32_LogicalDisk` query fails, an empty result set is returned immediately with a `LOG(WARNING)`. + +## Usage Example + +```cpp +// Invoked by the osquery table infrastructure — not called directly. +// Registered as the generator for the `logical_drives` virtual table. + +// Example of what a result Row contains: +Row r; +r["device_id"] = "C:"; +r["description"] = "Local Fixed Disk"; +r["free_space"] = "53687091200"; +r["size"] = "107374182400"; +r["file_system"] = "NTFS"; +r["type"] = "Unknown"; // always — WMI marshalling limitation +r["boot_partition"] = "1"; // INTEGER macro produces "0" or "1" +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.logon_sessions.md b/osquery/tables/system/windows/.logon_sessions.md new file mode 100644 index 00000000000..e5db719df0a --- /dev/null +++ b/osquery/tables/system/windows/.logon_sessions.md @@ -0,0 +1,43 @@ + +Implements the `logon_sessions` osquery table for Windows, enumerating active logon sessions via the LSA (Local Security Authority) API and returning detailed session metadata. + +## Key Components + +- **`kLogonTypeToStr`** — Static lookup map translating `SECURITY_LOGON_TYPE` enum values (e.g., `Interactive`, `Network`, `RemoteInteractive`) to human-readable strings. +- **`queryLogonSessions(QueryContext&)`** — Main table generator function; calls `LsaEnumerateLogonSessions` and `LsaGetLogonSessionData` to collect per-session data rows. + +### Columns Returned + +| Column | Source Field | +|---|---| +| `logon_id` | `LogonId.LowPart` | +| `user` | `UserName` | +| `logon_domain` | `LogonDomain` | +| `authentication_package` | `AuthenticationPackage` | +| `logon_type` | `LogonType` (mapped via `kLogonTypeToStr`) | +| `session_id` | `Session` | +| `logon_sid` | `Sid` | +| `logon_time` | `LogonTime` (Unix timestamp) | +| `logon_server` | `LogonServer` | +| `dns_domain_name` | `DnsDomainName` | +| `upn` | `Upn` | +| `logon_script` | `LogonScript` | +| `profile_path` | `ProfilePath` | +| `home_directory` | `HomeDirectory` | +| `home_directory_drive` | `HomeDirectoryDrive` | + +## Usage Example + +```cpp +// Queried via osquery SQL interface (Windows only) +// SELECT * FROM logon_sessions; + +// Internally invoked by the osquery table registry: +QueryContext context; +QueryData rows = queryLogonSessions(context); +for (const auto& row : rows) { + // row["user"], row["logon_type"], row["logon_time"], etc. +} +``` + +> **Note:** This table is Windows-only. Sessions where `LsaGetLogonSessionData` returns a non-success status are silently skipped. \ No newline at end of file diff --git a/osquery/tables/system/windows/.ntdomains.md b/osquery/tables/system/windows/.ntdomains.md new file mode 100644 index 00000000000..fcdfc2b2d10 --- /dev/null +++ b/osquery/tables/system/windows/.ntdomains.md @@ -0,0 +1,39 @@ + +Queries Windows NT domain information by executing a WMI request against the `Win32_NtDomain` class and returning structured domain data as an osquery table. + +## Key Components + +- **`genNtdomains(QueryContext&)`** — Table generation function that issues a WMI query (`select * from Win32_NtDomain`) and maps results into osquery rows. Returns an empty result set with a warning log if the WMI query fails or returns no data. + +### Populated Row Fields + +| Field | WMI Property | +|---|---| +| `name` | `Name` | +| `client_site_name` | `ClientSiteName` | +| `dc_site_name` | `DcSiteName` | +| `dns_forest_name` | `DnsForestName` | +| `domain_controller_address` | `DomainControllerAddress` | +| `domain_controller_name` | `DomainControllerName` | +| `domain_name` | `DomainName` | +| `status` | `Status` | + +## Usage Example + +```cpp +// Invoked internally by the osquery table registry. +// Query from the osquery shell or SQL interface: +// +// SELECT * FROM ntdomains; +// +// Equivalent WMI query executed internally: +// select * from Win32_NtDomain + +QueryContext ctx; +QueryData rows = osquery::tables::genNtdomains(ctx); +for (const auto& row : rows) { + // Access fields: row.at("name"), row.at("domain_name"), etc. +} +``` + +> **Platform:** Windows only. Requires WMI access. If the WMI result set is empty, a `WARNING` is logged. If the WMI request itself fails, a verbose log (`VLOG(1)`) captures the error message. \ No newline at end of file diff --git a/osquery/tables/system/windows/.ntfs_acl_permissions.md b/osquery/tables/system/windows/.ntfs_acl_permissions.md new file mode 100644 index 00000000000..e2a452cf54e --- /dev/null +++ b/osquery/tables/system/windows/.ntfs_acl_permissions.md @@ -0,0 +1,37 @@ + +Implements the `ntfs_acl_permissions` osquery table for Windows, which queries NTFS Access Control List (ACL) entries for specified file or directory paths using the Windows Security API. + +## Key Components + +### Lookup Maps +- **`kAccessCodeToStr`** — Maps Windows ACE type bytes (e.g., `ACCESS_ALLOWED_ACE_TYPE`) to human-readable strings like `"Grant"`, `"Deny"`, `"Audit"` +- **`kPermVals`** — Maps permission bitmask values (e.g., `DELETE`, `GENERIC_READ`) to descriptive strings for building access permission lists +- **`kInheritanceToStr`** — Maps ACE inheritance flags (e.g., `OBJECT_INHERIT_ACE`) to readable inheritance descriptions + +### Helper Functions +- **`accessCodeToStr(ACE_HEADER)`** — Resolves an ACE type byte to a string, appending `"Success"` or `"Failure"` for audit/alarm entries +- **`inheritCodeToStr(BYTE)`** — Resolves ACE inheritance flags to a string, defaulting to `"No Inheritance"` +- **`accessPermsToStr(unsigned long)`** — Iterates the permission bitmask and joins matching permission names with commas +- **`pSidToStrUserName(PSID)`** — Converts a Security Identifier (SID) to a username string via `LookupAccountSidW` + +### Table Generator +- **`genNtfsAclPerms(QueryContext&)`** — Entry point for the osquery table; iterates over queried paths, calls `GetNamedSecurityInfoW` to retrieve the DACL, then loops through each ACE to populate result rows + +## Usage Example + +```cpp +-- osquery SQL usage +SELECT path, type, principal, access, inherited_from +FROM ntfs_acl_permissions +WHERE path = 'C:\Windows\System32\drivers\etc\hosts'; +``` + +Each result row contains: + +| Column | Description | +|---|---| +| `path` | Queried file/directory path | +| `type` | ACE type (e.g., `"Grant"`, `"Deny"`) | +| `principal` | Account name resolved from SID | +| `access` | Comma-separated permission names | +| `inherited_from` | ACE inheritance descriptor | \ No newline at end of file diff --git a/osquery/tables/system/windows/.objects.md b/osquery/tables/system/windows/.objects.md new file mode 100644 index 00000000000..c8e0102bf1f --- /dev/null +++ b/osquery/tables/system/windows/.objects.md @@ -0,0 +1,40 @@ + +Enumerates Windows kernel object namespace entries (name + type pairs) for each terminal services session's Base Named Objects directory, feeding the `windows_objects` osquery virtual table. + +## Key Components + +| Symbol | Kind | Description | +|---|---|---| +| `obj_name_type_pair` | Type alias | `std::pair` holding an object name and its type name (UTF-16 internally) | +| `kBnoLinks` | Constant | Root path `\Sessions\BNOLINKS` where per-session symbolic links reside | +| `kMaxSupportedObjects` | Constant | Safety cap (1,048,576) on objects enumerated per directory | +| `kObjBufSize` | Constant | Per-query buffer size (8 KB) for `NtQueryDirectoryObject` calls | +| `enumerateObjectNamespace()` | Function | Opens a directory object and iterates its entries via `NtQueryDirectoryObject` (dynamically resolved from `ntdll`) | +| `enumerateBaseNamedObjectsLinks()` | Function | Resolves a `SymbolicLink` entry under `\Sessions\BNOLINKS\` via `NtQuerySymbolicLinkObject`, then delegates to `enumerateObjectNamespace()` | +| `genBaseNamedObjects()` | Function | osquery table generator; aggregates results into `QueryData` rows with `session_id`, `object_name`, and `object_type` columns | + +## Usage Example + +This module is invoked automatically by the osquery table engine. You can query it from the osquery shell: + +```sql +SELECT session_id, object_name, object_type +FROM windows_objects +WHERE session_id = 1; +``` + +Internally, the call chain is: + +```cpp +// 1. Enumerate symbolic links under \Sessions\BNOLINKS +enumerateObjectNamespace(kBnoLinks, sessions); + +// 2. For each SymbolicLink entry, resolve its target directory +// and enumerate the objects within it +enumerateBaseNamedObjectsLinks(session.first, session.second, objects); + +// 3. Results are UTF-8 converted and returned as QueryData rows +r["object_name"] = wstringToString(object.first); +``` + +> **Note:** All internal string handling uses `std::wstring` (UTF-16). Conversion to UTF-8 `std::string` via `wstringToString()` occurs only at the osquery interface boundary. \ No newline at end of file diff --git a/osquery/tables/system/windows/.os_version.md b/osquery/tables/system/windows/.os_version.md new file mode 100644 index 00000000000..75c959f6372 --- /dev/null +++ b/osquery/tables/system/windows/.os_version.md @@ -0,0 +1,42 @@ + +Queries Windows OS version details by combining WMI (`Win32_OperatingSystem`) and Registry data to populate an osquery virtual table row. + +## Key Components + +### `genOSVersion(QueryContext& context)` +The sole table generation function. It: +- Executes a WMI query for `Caption`, `Version`, `InstallDate`, and `OSArchitecture` +- Parses the dot-separated version string into `major`, `minor`, and `build` components +- Reads `UBR` (Update Build Revision) from the Windows Registry key `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` +- Returns a single `Row` with all populated fields + +### Populated Row Fields + +| Field | Source | Notes | +|---|---|---| +| `name` / `codename` | WMI `Caption` | e.g. `Windows 10 Pro` | +| `install_date` | WMI `InstallDate` | Converted from CIM datetime to Unix timestamp | +| `major` / `minor` / `build` | WMI `Version` | Split on `.` | +| `arch` | WMI `OSArchitecture` | e.g. `64-bit` | +| `platform` / `platform_like` | Hardcoded | Always `windows` | +| `version` | Assembled | `major.minor.build` | +| `revision` | Registry `UBR` | Cumulative update build revision | + +## Usage Example + +```cpp +// Called internally by the osquery table framework +QueryData results = genOSVersion(context); + +// Example output row: +// name → "Microsoft Windows 11 Pro" +// major → "11" +// minor → "0" +// build → "22621" +// revision → "2283" +// arch → "64-bit" +// platform → "windows" +// install_date → 1672531200 +``` + +> Returns an empty `QueryData` if the WMI request fails or produces no results, logging a warning in the failure case. \ No newline at end of file diff --git a/osquery/tables/system/windows/.patches.md b/osquery/tables/system/windows/.patches.md new file mode 100644 index 00000000000..29249ea7bb5 --- /dev/null +++ b/osquery/tables/system/windows/.patches.md @@ -0,0 +1,40 @@ + +Queries Windows installed patches/hotfixes via WMI and exposes the results as an osquery table. + +## Key Components + +- **`genInstalledPatches(QueryContext&)`** — Table generation function that executes a WMI query against `Win32_QuickFixEngineering` and maps each result to a `Row` with the following columns: + +| Column | WMI Property | +|---|---| +| `csname` | `CSName` | +| `hotfix_id` | `HotFixID` | +| `caption` | `Caption` | +| `description` | `Description` | +| `fix_comments` | `FixComments` | +| `installed_by` | `InstalledBy` | +| `install_date` | `InstallDate` | +| `installed_on` | `InstalledOn` | + +## Usage Example + +```cpp +// Querying the patches table via osquery SQL +// SELECT * FROM patches; + +// Internally, the table generator executes: +const auto wmiReq = WmiRequest::CreateWmiRequest( + "select * from Win32_QuickFixEngineering"); + +if (wmiReq && !wmiReq->results().empty()) { + for (const auto& item : wmiReq->results()) { + Row r; + item.GetString("HotFixID", r["hotfix_id"]); + item.GetString("InstalledOn", r["installed_on"]); + // ... additional fields populated + results.push_back(r); + } +} +``` + +> **Windows only** — relies on `WmiRequest` from `osquery/core/windows/wmi.h`. Returns an empty result set if the WMI query fails or returns no data. \ No newline at end of file diff --git a/osquery/tables/system/windows/.physical_disk_performance.md b/osquery/tables/system/windows/.physical_disk_performance.md new file mode 100644 index 00000000000..b56276acf6e --- /dev/null +++ b/osquery/tables/system/windows/.physical_disk_performance.md @@ -0,0 +1,47 @@ + +Implements the `physical_disk_performance` osquery table for Windows, querying real-time physical disk I/O metrics via WMI's `Win32_PerfFormattedData_PerfDisk_PhysicalDisk` performance class. + +## Key Components + +### `genPhysicalDiskPerformance(QueryContext& context)` +The sole table generation function. Issues a WMI query and maps each disk's performance counters into an osquery `Row`, returning a `QueryData` collection. + +**Mapped columns:** + +| Column | Type | Description | +|---|---|---| +| `name` | STRING | Disk identifier (e.g., `0 C:`) | +| `avg_disk_bytes_per_read` | BIGINT | Average bytes per read operation | +| `avg_disk_bytes_per_write` | BIGINT | Average bytes per write operation | +| `avg_disk_read_queue_length` | BIGINT | Average read queue depth | +| `avg_disk_write_queue_length` | BIGINT | Average write queue depth | +| `avg_disk_sec_per_read` | INTEGER | Average seconds per read | +| `avg_disk_sec_per_write` | INTEGER | Average seconds per write | +| `percent_disk_read_time` | INTEGER | % time spent on reads | +| `percent_disk_write_time` | INTEGER | % time spent on writes | +| `current_disk_queue_length` | INTEGER | Current I/O queue depth | +| `percent_disk_time` | INTEGER | % time disk is busy | +| `percent_idle_time` | INTEGER | % time disk is idle | + +## Usage Example + +```cpp +// Queried via osquery SQL interface (Windows only) +// osqueryi --tables physical_disk_performance +``` + +```sql +SELECT name, + avg_disk_bytes_per_read, + avg_disk_bytes_per_write, + percent_disk_time, + percent_idle_time, + current_disk_queue_length +FROM physical_disk_performance; +``` + +## Notes + +- **Windows-only**: Depends on `WmiRequest` and the `PerfDisk` WMI provider. +- All numeric values are parsed from WMI string output via `tryTo`, defaulting to `0` on parse failure. +- Returns an empty result set if the WMI request fails or returns a bad status. \ No newline at end of file diff --git a/osquery/tables/system/windows/.pipes.md b/osquery/tables/system/windows/.pipes.md new file mode 100644 index 00000000000..567af170f63 --- /dev/null +++ b/osquery/tables/system/windows/.pipes.md @@ -0,0 +1,38 @@ + +Enumerates all named pipes on a Windows system and collects metadata about each pipe, including the owning process ID, instance counts, and pipe configuration flags. + +## Key Components + +### `genPipes(QueryContext& context)` +Main table generation function that: +- Searches `\\.\pipe\*` using `FindFirstFileW`/`FindNextFileW` to discover all named pipes +- Opens each pipe handle with `CreateFileW` to query detailed metadata +- Resolves the owning PID via `GetNamedPipeServerProcessId` or `GetNamedPipeClientProcessId` +- Retrieves instance counts with `GetNamedPipeHandleState` +- Fetches pipe flags and max instances via `GetNamedPipeInfo` + +### Returned Row Columns + +| Column | Description | +|---|---| +| `name` | Pipe name (UTF-8 converted from wide string) | +| `pid` | Server or client process ID; `-1` on failure | +| `instances` | Current number of pipe instances; `-1` on failure | +| `max_instances` | Maximum allowed instances; `-1` on failure | +| `flags` | Comma-separated endpoint type and transfer mode (e.g. `PIPE_SERVER_END,PIPE_TYPE_BYTE`) | + +## Usage Example + +```cpp +// Invoked automatically by osquery when querying the pipes table: +// SELECT name, pid, instances, max_instances, flags FROM pipes; + +// Example output row: +// name → "MyAppPipe" +// pid → "1234" +// instances → "1" +// max_instances → "255" +// flags → "PIPE_SERVER_END,PIPE_TYPE_BYTE" +``` + +> **Note:** Pipes that cannot be opened (e.g. due to access restrictions) are still returned with the `name` field populated; other fields default to `-1`. This function is Windows-only and relies on Win32 APIs unavailable on other platforms. \ No newline at end of file diff --git a/osquery/tables/system/windows/.prefetch.md b/osquery/tables/system/windows/.prefetch.md new file mode 100644 index 00000000000..378fc0047c5 --- /dev/null +++ b/osquery/tables/system/windows/.prefetch.md @@ -0,0 +1,41 @@ + +Parses Windows Prefetch (`.pf`) files to extract execution metadata, supporting both compressed (MAM/LZXpress) and uncompressed (SCCA) formats across Windows 7, 8, 10, and 11 prefetch versions. + +## Key Components + +### Data Structures +- **`PrefetchHeader`** — File size, executable filename, and prefetch hash +- **`PrefetchFileInfo`** — Accessed files list, run count, last/other run timestamps +- **`PrefetchVolumeInfo`** — Accessed directories, volume serial numbers, and creation times +- **Packed C structs** (`PREFETCH_FILE_HEADER`, `PREFETCH_FILE_INFORMATION`, `PREFETCH_VOLUME_INFORMATION`, `PREFETCH_COMPRESSED_HEADER`) — Direct memory-mapped overlays onto raw binary data + +### Functions +- **`parseHeader()`** — Extracts filename, hash, and file size from the file header +- **`parseFileInfo()`** — Extracts accessed filenames and run timing data; branches on version (`v23`/`v26`/`v30v2`/`v31`) +- **`parseVolumeInfo()`** — Iterates volume entries to collect directory paths, serial numbers, and creation timestamps +- **`parsePrefetchData()`** — Validates signature/version, orchestrates parsing, and yields a table row +- **`parsePrefetch()`** — Reads a `.pf` file from disk, detects and decompresses LZXpress-compressed files, then delegates to `parsePrefetchData()` + +### Constants +| Constant | Value | Purpose | +|---|---|---| +| `kPrefetchLocation` | `%SystemRoot%\Prefetch\` | Default scan directory | +| `PREFETCH_SIGNATURE` | `SCCA` | Uncompressed file magic | +| `PREFETCH_SIGNATURE_COMPRESSED` | `MAM\x04` | Compressed file magic | + +## Usage Example + +```cpp +// Parsing a single prefetch file and collecting rows +RowYield yield = [](TableRowHolder row) { + // Process each yielded row +}; + +parsePrefetch("C:\\Windows\\Prefetch\\NOTEPAD.EXE-12345678.pf", yield); +// Yields a row with: path, filename, hash, size, +// accessed_files, accessed_files_count, volume_serial, +// volume_creation, accessed_directories, accessed_directories_count, +// last_run_time, other_run_times, run_count +``` + +> **Reference:** Binary format documented at [libscca's Windows Prefetch File format spec](https://github.com/libyal/libscca/blob/main/documentation/Windows%20Prefetch%20File%20(PF)%20format.asciidoc) \ No newline at end of file diff --git a/osquery/tables/system/windows/.processes.md b/osquery/tables/system/windows/.processes.md new file mode 100644 index 00000000000..233c4aa8c96 --- /dev/null +++ b/osquery/tables/system/windows/.processes.md @@ -0,0 +1,44 @@ + +Implements the Windows-specific `processes` table for osquery, enumerating running processes, their memory maps, command lines, and protection attributes by querying the Windows kernel via `NtQueryInformationProcess`, Toolhelp32 snapshots, and PEB memory reads. + +## Key Components + +- **`genMemoryMap(pid, results)`** — Enumerates all loaded modules and virtual memory regions for a given PID using `VirtualQueryEx`, formatting page permissions from `kMemoryConstants`. +- **`getProcList(pids)`** — Snapshots all active processes via `CreateToolhelp32Snapshot` / `Process32FirstW`, populating a PID set. +- **`getUserProcessParameters(proc, out, pid)`** — Reads the `RTL_USER_PROCESS_PARAMETERS` structure from a process's PEB using `NtQueryInformationProcess` + `ReadProcessMemory`. +- **`getProcessCommandLine(proc, out, pid)`** — Retrieves the command line on Windows 8.1+ using `ProcessCommandLine` (info class 60) via `NtQueryInformationProcess`. +- **`getProcessCommandLineLegacy(proc, out, pid)`** — Fallback for older Windows; reads the command line directly from PEB's `CommandLine` field. +- **`getProcessCurrentDirectory(proc, out, pid)`** — Reads the current working directory from `RTL_USER_PROCESS_PARAMETERS.CurrentDirectoryPath` in the PEB. + +### Key Constants & Types + +| Name | Purpose | +|---|---| +| `kMemoryConstants` | Maps `PAGE_*` flags to readable permission strings | +| `ProcessCommandLine` (60) | Undocumented info class for command-line retrieval | +| `ProcessProtectionInformation` (61) | Info class for process protection level | +| `_PROCESS_EXTENDED_BASIC_INFORMATION` | Exposes WOW64, protection, frozen, and secure process flags | +| `PS_PROTECTION` / `PS_PROTECTED_TYPE` | Encodes PPL (Protected Process Light) type and signer | + +## Usage Example + +```cpp +// Enumerate all PIDs +std::set pids; +auto s = getProcList(pids); + +// Get memory map for a specific process +QueryData results; +genMemoryMap(1234, results); +for (const auto& row : results) { + // row["start"], row["end"], row["permissions"], row["path"] +} + +// Read command line for an open process handle +HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); +std::string cmdline; +if (!getProcessCommandLine(proc, cmdline, pid).ok()) { + getProcessCommandLineLegacy(proc, cmdline, pid); +} +CloseHandle(proc); +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.programs.md b/osquery/tables/system/windows/.programs.md new file mode 100644 index 00000000000..91da9487db6 --- /dev/null +++ b/osquery/tables/system/windows/.programs.md @@ -0,0 +1,36 @@ + +Header file for Windows MSI program registry utilities within the osquery tables subsystem, providing GUID decoding functionality for installed software enumeration. + +## Key Components + +### `decodeMsiRegistryGuid` +```c +std::string decodeMsiRegistryGuid(const std::string& encoded); +``` +Converts a registry-encoded MSI GUID (compressed/shuffled format) into a standard human-readable GUID format. + +- **Input:** 32-character encoded GUID string (e.g., `"0D8797326E7E4114DAECB3B66B9CD045"`) +- **Output:** Standard GUID string (e.g., `"{237978D0-E7E6-4114-ADCE-3B6BB6C90D54}"`) or empty string on invalid input + +## Usage Example + +```c +#include "programs.h" + +// Decode a registry-encoded MSI GUID +std::string encoded = "0D8797326E7E4114DAECB3B66B9CD045"; +std::string guid = osquery::tables::decodeMsiRegistryGuid(encoded); + +if (!guid.empty()) { + // guid == "{237978D0-E7E6-4114-ADCE-3B6BB6C90D54}" + // Use guid to look up installed program metadata +} else { + // Handle invalid/malformed encoded input +} +``` + +## Notes + +- Input must be exactly **32 characters** long; any other length returns an empty string +- Used internally by osquery's `programs` table to enumerate installed Windows software from the MSI registry keys under `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer` +- Namespace: `osquery::tables` \ No newline at end of file diff --git a/osquery/tables/system/windows/.registry.md b/osquery/tables/system/windows/.registry.md new file mode 100644 index 00000000000..a7340e9b2dc --- /dev/null +++ b/osquery/tables/system/windows/.registry.md @@ -0,0 +1,56 @@ + +Windows Registry query utilities for osquery's table implementations, providing helpers to enumerate, expand, and parse registry keys, paths, hives, and COM class identifiers. + +## Key Components + +### Constants + +| Name | Value | Purpose | +|---|---|---| +| `kRegSep` | `"\\"` | Registry path separator | +| `kDefaultRegName` | `"(Default)"` | Default registry key name | +| `kRegMaxRecursiveDepth` | `32` | Maximum glob expansion depth | + +### Functions + +| Function | Description | +|---|---| +| `queryKey()` | Queries a single registry key path and populates results | +| `queryMultipleRegistryKeys()` | Batch-queries registry keys using LIKE pattern matching | +| `queryMultipleRegistryPaths()` | Batch-queries full registry paths using LIKE pattern matching | +| `getClassName()` | Resolves a COM Class ID (CLSID) to its human-readable class name | +| `getClassExecutables()` | Returns all executables (`.dll`, `.exe`, etc.) associated with a CLSID | +| `expandRegistryGlobs()` | Expands SQL glob patterns (e.g. `HKLM\%\Microsoft`) into matching key sets | +| `explodeRegistryPath()` | Splits a full registry path into its HIVE and KEY components | +| `getUsernameFromKey()` | Extracts a username from an `HKEY_USERS\\...` path | +| `populateSubkeys()` | Expands a key set with discovered subkeys, optionally replacing the input set | + +## Usage Example + +```c +#include + +using namespace osquery::tables; + +// Query a single key +QueryData results; +Status s = queryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft", results); + +// Expand a glob pattern to matching keys +std::set keys; +expandRegistryGlobs("HKEY_LOCAL_MACHINE\\%\\Microsoft", keys); + +// Split path into hive + key +std::string hive, key; +explodeRegistryPath("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft", hive, key); +// hive = "HKEY_LOCAL_MACHINE", key = "SOFTWARE\\Microsoft" + +// Resolve a CLSID to its class name +std::string className; +getClassName("{0000002F-0000-0000-C000-000000000046}", className); + +// Resolve a username from an HKEY_USERS SID path +std::string username; +getUsernameFromKey("HKEY_USERS\\S-1-5-19\\Software", username); +// username = "LOCAL SERVICE" +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.scheduled_tasks.md b/osquery/tables/system/windows/.scheduled_tasks.md new file mode 100644 index 00000000000..f749edfcae4 --- /dev/null +++ b/osquery/tables/system/windows/.scheduled_tasks.md @@ -0,0 +1,39 @@ + +Implements the `scheduled_tasks` osquery table for Windows, querying the Task Scheduler COM API to enumerate all registered scheduled tasks across root and subdirectory folders. + +## Key Components + +### Constants +- **`kTimeFormat`** — Time format string (`%H:%M:%S %b-%d-%Y`) used for timestamp formatting. +- **`kStateMap`** — Maps `TASK_STATE` enum values (`unknown`, `disabled`, `queued`, `ready`, `running`) to human-readable strings. + +### Functions + +- **`enumerateTasksForFolder(path, results)`** — Core enumeration function. Connects to the Windows Task Scheduler via COM (`ITaskService`), opens the specified folder, iterates all registered tasks, and populates a `QueryData` result set with per-task metadata including: + - `name`, `path`, `enabled`, `hidden`, `state` + - `last_run_time`, `next_run_time` (converted from COM `DATE` → `FILETIME` → Unix epoch) + - `last_run_message`, `last_run_code` (HRESULT of last execution) + - `action` (comma-joined list of exec paths, arguments, and working directories) + +- **`genScheduledTasks(context)`** — Table generator registered with osquery. Enumerates tasks starting from the root (`\`) folder, then walks `%SystemRoot%\System32\Tasks` subdirectories recursively, calling `enumerateTasksForFolder` for each discovered path. + +## Usage Example + +```cpp +// Invoked internally by osquery when querying the virtual table: +// SELECT * FROM scheduled_tasks; +// SELECT name, path, state, action FROM scheduled_tasks WHERE enabled = 1; + +QueryContext ctx; +QueryData rows = genScheduledTasks(ctx); + +for (const auto& row : rows) { + // row["name"] -> Task display name + // row["state"] -> "ready" | "running" | "disabled" | ... + // row["action"] -> "C:\Windows\System32 cmd.exe /c backup.bat" + // row["last_run_time"] -> Unix timestamp (BIGINT) + // row["next_run_time"] -> Unix timestamp (BIGINT) +} +``` + +> **Note:** This file is Windows-only and depends on COM interfaces (`taskschd.h`). Task action items are collected via `IExecAction` only; non-exec action types are silently skipped. \ No newline at end of file diff --git a/osquery/tables/system/windows/.secureboot.md b/osquery/tables/system/windows/.secureboot.md new file mode 100644 index 00000000000..b66e5c3b3e1 --- /dev/null +++ b/osquery/tables/system/windows/.secureboot.md @@ -0,0 +1,39 @@ + +Windows implementation of the osquery `secureboot` table that reads UEFI firmware environment variables to report Secure Boot and Setup Mode status. + +## Key Components + +### Internal Helpers (anonymous namespace) + +- **`readFirmwareBooleanVariable(namespace_guid, variable_name)`** — Calls `GetFirmwareEnvironmentVariableA` to read a single-byte boolean EFI variable. Returns `boost::optional` — `boost::none` on error, wrong size, or non-boolean value. +- **`enableSystemEnvironmentNamePrivilege()`** — Acquires `SeSystemEnvironmentPrivilege` on the current process token via `LookupPrivilegeValueW` + `AdjustTokenPrivileges`. Required to read UEFI variables on Windows. Returns `false` if privilege escalation fails. + +### Table Generator + +- **`genSecureBoot(QueryContext&)`** — Entry point for the osquery `secureboot` virtual table. Checks firmware type (UEFI only), acquires the necessary privilege (once, via `static`), then reads two EFI variables: + +| Column | EFI Variable | +|---|---| +| `secure_boot` | `kEFIBootGUID::kEFISecureBootName` | +| `setup_mode` | `kEFIBootGUID::kEFISetupModeName` | + +Values are returned as `1` (true), `0` (false), or `-1` (unreadable). + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT secure_boot, setup_mode FROM secureboot; +// +// Internally, genSecureBoot is registered as the table generator: + +QueryData genSecureBoot(QueryContext& context) { + // Privilege acquired once at first call (static initialization) + // Returns a single Row with integer values: + // secure_boot = 1 → Secure Boot enabled + // setup_mode = 0 → Not in UEFI setup mode + // secure_boot = -1 → Could not read EFI variable +} +``` + +> **Note:** This table requires `SeSystemEnvironmentPrivilege`. Running osquery without sufficient privileges will log an error and may return `-1` for all columns. Non-UEFI (e.g., legacy BIOS) systems return an empty result set. \ No newline at end of file diff --git a/osquery/tables/system/windows/.security_profile_info.md b/osquery/tables/system/windows/.security_profile_info.md new file mode 100644 index 00000000000..0d29072151b --- /dev/null +++ b/osquery/tables/system/windows/.security_profile_info.md @@ -0,0 +1,39 @@ + +Implements the `genSecurityProfileInformation` table generator for osquery on Windows, retrieving and exposing system security policy settings via the Security Configuration Engine (SCE) API. + +## Key Components + +### `genSecurityProfileInformation(QueryContext&)` +The sole exported table generator function. It: +1. Instantiates `SceProfileData` to fetch the current Windows security profile via `SceClientHelper` +2. Normalizes each integer field using `SceProfileData::getNormalizedInt()` +3. Converts wide-string fields (administrator/guest names) using `wstringToString()` +4. Returns a single-row `QueryData` containing all policy values + +### Populated Fields + +| Column | Type | Description | +|---|---|---| +| `minimum_password_age` / `maximum_password_age` | INTEGER | Password age policy bounds | +| `minimum_password_length` | INTEGER | Minimum required password length | +| `password_complexity` | INTEGER | Complexity enforcement flag | +| `password_history_size` | INTEGER | Number of passwords remembered | +| `lockout_bad_count` | INTEGER | Failed logon threshold before lockout | +| `force_logoff_when_expire` | INTEGER | Force logoff on password expiry | +| `new_administrator_name` / `new_guest_name` | TEXT | Renamed built-in account names | +| `clear_text_password` | INTEGER | Reversible encryption flag | +| `lsa_anonymous_name_lookup` | INTEGER | Anonymous SID lookup permission | +| `enable_admin_account` / `enable_guest_account` | INTEGER | Built-in account status | +| `audit_*` fields | INTEGER | Audit policy settings (logon, objects, privileges, etc.) | + +## Usage Example + +```cpp +// Invoked automatically by osquery when the table is queried: +// SELECT * FROM security_profile_info; + +QueryData results = genSecurityProfileInformation(context); +// Returns one row with all Windows local security policy settings +``` + +> **Note:** Returns an empty result set and logs an error if the SCE profile data cannot be retrieved. This table is Windows-only and depends on `security_profile_info_utils.h`. \ No newline at end of file diff --git a/osquery/tables/system/windows/.security_profile_info_utils.md b/osquery/tables/system/windows/.security_profile_info_utils.md new file mode 100644 index 00000000000..a0906e96118 --- /dev/null +++ b/osquery/tables/system/windows/.security_profile_info_utils.md @@ -0,0 +1,58 @@ + +Utility header providing Windows Security Configuration Engine (SCE) RPC client access for retrieving system security profile information via `scecli.dll`. + +## Key Components + +### `SceClientHelper` (Singleton Class) +Manages runtime dynamic linking to `scecli.dll` and exposes the undocumented SCE RPC Client API. + +| Member | Description | +|---|---| +| `instance()` | Returns the singleton `SceClientHelper` instance | +| `getSceSecurityProfileInfo()` | Calls `SceGetSecurityProfileInfo()` to fetch system security profile data | +| `releaseSceProfileData()` | Calls `SceFreeMemory()` to free SCE-allocated memory | +| `isWow64Process()` | Detects WoW64 (32-bit process on 64-bit Windows) via `IsWow64Process()` | +| `SceProfileInfo` | Raw data structure mapping the SCE RPC protocol response (password policy, audit settings, account settings, log configuration) | + +### `SceProfileData` (RAII Wrapper) +Scoped memory manager for `SceProfileInfo` data returned by the SCE RPC server. + +| Member | Description | +|---|---| +| `getProfileInfo()` | Returns typed pointer to the `SceProfileInfo` buffer | +| `~SceProfileData()` | Automatically calls `SceFreeMemory()` on destruction | +| `getNormalizedInt()` | Normalizes SCE protocol `DWORD` values — maps `(DWORD)-1` to `-1` integer, matching `secedit.exe` behavior | + +## Usage Example + +```cpp +#include "security_profile_info_utils.h" + +using namespace osquery::tables; + +void querySecurityProfile() { + // Scoped RAII wrapper handles memory cleanup automatically + SceProfileData profileData; + + // Fetch system security profile via SCE RPC + PVOID rawData = nullptr; + auto& sce = SceClientHelper::instance(); + Status s = sce.getSceSecurityProfileInfo(rawData); + + if (!s.ok()) { + LOG(ERROR) << "SCE call failed: " << s.getMessage(); + return; + } + + // Access typed profile fields + const auto* info = profileData.getProfileInfo(); + if (info != nullptr) { + int minAge = SceProfileData::getNormalizedInt(info->MinPasswdAge); + int maxAge = SceProfileData::getNormalizedInt(info->MaxPasswdAge); + // info->LockoutBadCount, info->AuditLogonEvents, etc. + } + // Memory freed automatically when profileData goes out of scope +} +``` + +> **Note:** This header is Windows-only and depends on undocumented `scecli.dll` exports (`SceGetSecurityProfileInfo`, `SceFreeMemory`) whose prototypes have remained stable since Windows 7. \ No newline at end of file diff --git a/osquery/tables/system/windows/.services.md b/osquery/tables/system/windows/.services.md new file mode 100644 index 00000000000..75f7333086f --- /dev/null +++ b/osquery/tables/system/windows/.services.md @@ -0,0 +1,44 @@ + +Implements the osquery `services` table for Windows, enumerating all installed Windows services and drivers with their configuration, status, and metadata by querying the Service Control Manager (SCM) and registry. + +## Key Components + +### Type Aliases +- `svc_handle_t` — RAII wrapper for `SC_HANDLE` using `CloseServiceHandle` +- `svc_query_t` — RAII wrapper for `QUERY_SERVICE_CONFIG` using `free` +- `svc_descr_t` — RAII wrapper for `SERVICE_DESCRIPTION` using `free` +- `enum_svc_status_t` — RAII wrapper for `ENUM_SERVICE_STATUS_PROCESS[]` using `free` + +### Lookup Tables +- `kSvcStartType[]` — Maps start type index to string (e.g., `BOOT_START`, `AUTO_START`) +- `kSvcStatus[]` — Maps current state index to string (e.g., `RUNNING`, `STOPPED`) +- `kServiceType` — Maps `dwServiceType` bitmask values to human-readable strings (e.g., `KERNEL_DRIVER`, `OWN_PROCESS`) + +### Functions + +| Function | Description | +|---|---| +| `getService()` | Opens a single service handle, queries config/description, resolves `ServiceDll` from the registry, and populates a result row | +| `getServices()` | Opens the SCM, enumerates all Win32 services and drivers, calls `getService()` for each | +| `genServices()` | Public table generator entry point; calls `getServices()` and returns `QueryData` | + +## Usage Example + +```cpp +// Invoked internally by osquery when the `services` table is queried: +// SELECT name, status, start_type, path, module_path FROM services; + +QueryContext context; +QueryData rows = genServices(context); + +for (const auto& row : rows) { + // row["name"] -> "wuauserv" + // row["status"] -> "RUNNING" + // row["start_type"] -> "AUTO_START" + // row["service_type"] -> "SHARE_PROCESS" + // row["module_path"] -> "C:\\Windows\\System32\\wuaueng.dll" + // row["pid"] -> "1234" +} +``` + +> **Note:** `module_path` is resolved from `HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\ServiceDll` with environment variable expansion applied. \ No newline at end of file diff --git a/osquery/tables/system/windows/.shared_resources.md b/osquery/tables/system/windows/.shared_resources.md new file mode 100644 index 00000000000..73017c732a5 --- /dev/null +++ b/osquery/tables/system/windows/.shared_resources.md @@ -0,0 +1,44 @@ + +Implements the `shared_resources` osquery table for Windows, querying WMI (`Win32_Share`) to enumerate all network shares on the local machine. + +## Key Components + +### Internal Helpers + +- **`kWin32ShareQuery`** — WMI query string targeting `Win32_Share` for all shared resources. +- **`kShareTypeNameMap`** — Maps raw WMI share type integers to human-readable names (e.g., `0` → `"Disk Drive"`, `3` → `"IPC"`, `2147483648` → `"Disk Drive Admin"`). +- **`getShareTypeName(long)`** — Looks up a share type integer in the map; returns an empty string for unknown types. + +### Exported Function + +- **`genShares(QueryContext&)`** — Entry point for the osquery table. Executes the WMI query, iterates results, and builds a `QueryData` row list with the following fields: + +| Field | Type | Source | +|---|---|---| +| `description` | string | WMI `Description` | +| `install_date` | string | WMI `InstallDate` | +| `status` | string | WMI `Status` | +| `name` | string | WMI `Name` | +| `path` | string | WMI `Path` | +| `allow_maximum` | integer | WMI `AllowMaximum` | +| `maximum_allowed` | bigint | WMI `MaximumAllowed` | +| `type` | bigint | WMI `Type` (raw) | +| `type_name` | text | Resolved via `getShareTypeName` | + +## Usage Example + +```cpp +// Invoked internally by the osquery table runtime: +QueryContext context; +QueryData results = osquery::tables::genShares(context); + +for (const auto& row : results) { + std::cout << row.at("name") << " -> " << row.at("type_name") << "\n"; +} +// Example output: +// ADMIN$ -> Disk Drive Admin +// C$ -> Disk Drive Admin +// IPC$ -> IPC Admin +``` + +> **Note:** This table is Windows-only and requires WMI access. Errors during WMI query construction or execution are logged via `LOG(ERROR)` and return an empty result set. \ No newline at end of file diff --git a/osquery/tables/system/windows/.shellbags.md b/osquery/tables/system/windows/.shellbags.md new file mode 100644 index 00000000000..d8af30e8e19 --- /dev/null +++ b/osquery/tables/system/windows/.shellbags.md @@ -0,0 +1,49 @@ + +Parses Windows Shellbag registry entries to reconstruct folder navigation history, extracting file system paths and access timestamps from binary shell item data stored in the Windows registry. + +## Key Components + +### Constants +- `kShellBagPath` — Registry path for shellbags under `USRCLASS.DAT` (Local Settings) +- `kShellBagPathNtuser` — Registry path for shellbags under `NTUSER.DAT` + +### Functions + +| Function | Description | +|---|---| +| `guidLookup(guid)` | Resolves a CLSID GUID string to a human-readable name via `HKLM\SOFTWARE\Classes\CLSID` | +| `parseShellData(shell_data, build_shellbag, results, sid, source)` | Dispatches binary shell item data to the appropriate parser based on item type signature, then appends the resolved path to results | + +### Shell Item Type Dispatch (via signature byte) + +| Signature | Item Type | +|---|---| +| `1F` | Root folder / GUID | +| `2F`, `23`, `25` | Drive letter, MTP root, USB device | +| `31`, `30`, `32` | Directory / file entry | +| `61` | FTP / URI | +| `71` | Control Panel item | +| `C3`, `41`, `42` | Network share | +| `00` | Variable format (GUID, FTP, MTP, Property Store) | + +## Usage Example + +```cpp +std::vector bag_path; +QueryData results; + +// Parse a hex-encoded shell item blob for a given user SID +parseShellData( + shell_hex_data, // hex-encoded binary shell item + bag_path, // path segments accumulated across recursive calls + results, // output rows written here + "S-1-5-21-...", // user SID + "NTUSER.DAT" // registry hive source +); + +// Each result row contains: +// results[i]["path"] → reconstructed filesystem path +// results[i]["sid"] → user SID +// results[i]["source"] → registry hive +// results[i]["accessed_time"] → last access timestamp (FTP items) +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.shimcache.md b/osquery/tables/system/windows/.shimcache.md new file mode 100644 index 00000000000..fe8c7bddfd9 --- /dev/null +++ b/osquery/tables/system/windows/.shimcache.md @@ -0,0 +1,33 @@ + +Parses the Windows Application Compatibility Cache (Shimcache) from the registry, extracting executable path history, last modified timestamps, and execution flags for forensic analysis. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kShimcacheControlset` | `const string` | Registry path to `AppCompatCache` under `CurrentControlSet` | +| `kWin8`, `kWin10PreCreator`, `kWin10Creator` | `const int` | Byte offsets for delimiter detection per Windows version | +| `kWin8Start`, `kWin10Start`, `kWin10CreatorStart` | `const string` | Magic header bytes identifying the Windows version format | +| `kWin8110ShimcacheDelimiter` | `const string` | Hex delimiter (`31307473`) separating individual cache entries | +| `ShimcacheData` | `struct` | Holds a single parsed entry: `path`, `last_modified`, and optional `execution_flag` | +| `parseShimcacheData()` | `function` | Decodes a single hex token into a `ShimcacheData` struct, handling endian swap, Unicode path decoding, and FILETIME conversion | +| `parseEntry()` | `function` | Splits raw registry binary data into tokens by delimiter, detects Windows version format, and builds result rows | +| `genShimcache()` | `function` | osquery table generator — expands registry glob, queries `AppCompatCache`, and drives entry parsing | + +## Usage Example + +```cpp +// Called automatically by the osquery table subsystem. +// To query manually via osqueryi: +// SELECT entry, path, modified_time, execution_flag FROM shimcache; + +// Internally, genShimcache drives the pipeline: +QueryData results = genShimcache(context); +// Each row contains: +// entry - sequential index (1-based) +// path - executable file path (decoded from UTF-16LE hex) +// modified_time - Unix timestamp of last file modification +// execution_flag - 1=ran, 0=not ran, -1=flag absent (Win10+) +``` + +> **Note:** Only `CurrentControlSet` is queried. Additional ControlSets (e.g., `ControlSet001`, `ControlSet002`) are not enumerated. Supported formats are Windows 8, Windows 10 pre-Creators Update, and Windows 10 Creators Update and later. \ No newline at end of file diff --git a/osquery/tables/system/windows/.smbios_tables.md b/osquery/tables/system/windows/.smbios_tables.md new file mode 100644 index 00000000000..2f72862ffa1 --- /dev/null +++ b/osquery/tables/system/windows/.smbios_tables.md @@ -0,0 +1,38 @@ + +Provides Windows SMBIOS table implementations for osquery, exposing BIOS/firmware platform information and physical memory device details via WMI queries. + +## Key Components + +### Helper Functions (anonymous namespace) +- **`getFormFactor(long id)`** — Maps a numeric ID to a memory form factor string (e.g., `DIMM`, `SODIMM`, `BGA`) using `kFormFactors` lookup table +- **`getMemoryType(int id)`** — Maps a numeric ID to a memory type string (e.g., `DDR4`, `SDRAM`) using `kMemoryTypes` lookup table +- **`getMemoryTypeDetails(int id)`** — Returns a human-readable memory type detail string (e.g., `RAMBUS`, `Synchronous`) from a bitmask value +- **`getMemorySize(const std::wstring&)`** — Converts a WMI capacity string (bytes) to megabytes as `uint32_t` + +### Public Functions +- **`to_iso8601_date(const FILETIME&)`** — Converts a Windows `FILETIME` to an ISO 8601 date string (`YYYY-MM-DD`) +- **`genPlatformInfo(QueryContext&)`** — Queries `Win32_BIOS` via WMI; returns vendor, BIOS version, revision, release date, and firmware type (UEFI/BIOS) +- **`genMemoryDevices(QueryContext&)`** — Queries `Win32_PhysicalMemory` via WMI; returns one row per installed memory module with form factor, type, size, speed, voltages, and identifiers + +## Usage Example + +```cpp +// These are registered as osquery virtual table generators +QueryContext ctx; + +// Retrieve BIOS/firmware platform info +QueryData biosInfo = genPlatformInfo(ctx); +// biosInfo[0]["vendor"] → "American Megatrends" +// biosInfo[0]["version"] → "F10" +// biosInfo[0]["firmware_type"] → "uefi" +// biosInfo[0]["date"] → "2023-04-15" + +// Retrieve installed physical memory modules +QueryData memDevices = genMemoryDevices(ctx); +// memDevices[0]["form_factor"] → "DIMM" +// memDevices[0]["memory_type"] → "DDR4" +// memDevices[0]["size"] → "16384" (MB) +// memDevices[0]["max_speed"] → "3200" (MHz) +``` + +> **Note:** This file is Windows-only and depends on WMI (`Win32_BIOS`, `Win32_PhysicalMemory`). Fields without WMI equivalents (`handle`, `array_handle`, `set`) are returned as empty strings for cross-platform schema compatibility. \ No newline at end of file diff --git a/osquery/tables/system/windows/.startup_items.md b/osquery/tables/system/windows/.startup_items.md new file mode 100644 index 00000000000..9c2967ef01f --- /dev/null +++ b/osquery/tables/system/windows/.startup_items.md @@ -0,0 +1,28 @@ + +Declares the `parseStartupPath` utility function for extracting executable paths and arguments from macOS startup item entries. + +## Key Components + +- **`parseStartupPath(const std::string& entry, Row& r)`** — Parses a raw startup path string, splitting it into `path` and `args` fields stored in the provided `Row`. Returns `true` on success, `false` if parsing fails. + +## Usage Example + +```cpp +#include "startup_items.h" + +osquery::Row r; +std::string entry = "/usr/bin/myapp --config /etc/myapp.conf"; + +if (osquery::tables::parseStartupPath(entry, r)) { + // r["path"] == "/usr/bin/myapp" + // r["args"] == "--config /etc/myapp.conf" +} else { + // Handle malformed startup entry +} +``` + +## Notes + +- Lives within the `osquery::tables` namespace, consistent with osquery's table implementation conventions. +- Intended as a helper for the `startup_items` table implementation, isolating path-parsing logic for testability. +- The `Row` type is defined in `osquery/core/tables.h` and maps column names to string values. \ No newline at end of file diff --git a/osquery/tables/system/windows/.system_info.md b/osquery/tables/system/windows/.system_info.md new file mode 100644 index 00000000000..713c20d7de8 --- /dev/null +++ b/osquery/tables/system/windows/.system_info.md @@ -0,0 +1,40 @@ + +Provides the `genSystemInfo` table function for the Windows implementation of the osquery `system_info` virtual table, collecting comprehensive hardware and CPU metadata from WMI, Win32 APIs, and the registry. + +## Key Components + +### `genSystemInfo(QueryContext& context)` +The sole exported table generator function. Populates a single-row result set with the following data sources: + +| Field(s) | Source | +|---|---| +| `hostname`, `computer_name`, `uuid` | osquery core system APIs | +| `cpu_brand` | `cpuid` virtual table via SQL | +| `cpu_logical_cores`, `cpu_sockets`, `cpu_physical_cores`, `hardware_vendor`, `hardware_model` | WMI `Win32_ComputerSystem` / `Win32_Processor` | +| `physical_memory` | `GetPhysicallyInstalledSystemMemory()` Win32 API | +| `cpu_microcode` | Registry: `HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0` | +| `hardware_serial` | WMI `Win32_Bios` | +| `cpu_type`, `emulated_cpu_type` | `GetSystemInfo()` + `IsWow64Process2()` | + +## Notable Behaviors + +- **Lenovo workaround** — When `hardware_vendor` is `"lenovo"`, the model is re-fetched from `Win32_ComputerSystemProduct.Version` due to incorrect data in `Win32_ComputerSystem.Model`. +- **Selective WMI query** — Only `NumberOfCores` is fetched from `Win32_Processor` (not `SELECT *`) to avoid a ~1 second per-core WMI penalty on large systems. +- **ARM emulation detection** — Dynamically resolves `IsWow64Process2` (Windows 10+) to detect x86 emulation on ARM64 hosts, populating `emulated_cpu_type`. +- **Memory reporting fix** — Uses `GetPhysicallyInstalledSystemMemory()` instead of WMI, which misreports post-BIOS-allocated memory. +- **Graceful fallbacks** — All fields default to `"-1"` on query or API failure. + +## Usage Example + +```cpp +// Invoked automatically by the osquery table registry; direct usage: +QueryContext ctx; +QueryData results = osquery::tables::genSystemInfo(ctx); + +for (const auto& row : results) { + std::cout << "CPU: " << row.at("cpu_brand") << "\n"; + std::cout << "Cores: " << row.at("cpu_physical_cores") << "\n"; + std::cout << "RAM: " << row.at("physical_memory") << " bytes\n"; + std::cout << "Serial: " << row.at("hardware_serial") << "\n"; +} +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.tpm_info.md b/osquery/tables/system/windows/.tpm_info.md new file mode 100644 index 00000000000..869d3512f65 --- /dev/null +++ b/osquery/tables/system/windows/.tpm_info.md @@ -0,0 +1,41 @@ + +Queries Windows TPM (Trusted Platform Module) information via WMI and exposes it as an osquery virtual table row. + +## Key Components + +### Constants + +| Identifier | Description | +|---|---| +| `kWin32TpmQuery` | WMI SELECT query targeting `Win32_Tpm` | +| `kTpmClassNamespace` | WMI namespace `root\cimv2\Security\MicrosoftTpm` | +| `kBooleanMethodList` | Maps WMI method names to column names (`IsActivated`, `IsEnabled`, `IsOwned`) | +| `kStringPropertyList` | Maps WMI string properties to column names (manufacturer info, spec/version strings) | + +### Functions + +- **`genTpmInfo(QueryContext&)`** — Primary table generator; allocates a WMI request, executes the query, reads string properties and boolean method results, and returns a single `Row` representing the TPM device. + +## Usage Example + +```cpp +// Called internally by osquery's table dispatcher: +QueryContext ctx; +QueryData result = osquery::tables::genTpmInfo(ctx); + +// Resulting row contains columns such as: +// result[0]["manufacturer_id"] -> INTEGER (e.g. 1314145024) +// result[0]["manufacturer_name"] -> TEXT (e.g. "IFX") +// result[0]["manufacturer_version"] -> TEXT (e.g. "5.61") +// result[0]["product_name"] -> TEXT +// result[0]["spec_version"] -> TEXT (e.g. "2.0") +// result[0]["activated"] -> INTEGER (0 or 1) +// result[0]["enabled"] -> INTEGER (0 or 1) +// result[0]["owned"] -> INTEGER (0 or 1) +``` + +## Notes + +- **Windows only** — depends on `Win32_Tpm` and the `root\cimv2\Security\MicrosoftTpm` WMI namespace. +- Returns an empty result set on any WMI allocation, query, or execution failure; errors are written via `LOG(ERROR)`. +- If the WMI query returns more than one TPM item (unexpected), only the last result is used and a warning is logged. \ No newline at end of file diff --git a/osquery/tables/system/windows/.user_groups.md b/osquery/tables/system/windows/.user_groups.md new file mode 100644 index 00000000000..721cbc20185 --- /dev/null +++ b/osquery/tables/system/windows/.user_groups.md @@ -0,0 +1,39 @@ + +Implements the `user_groups` osquery table for Windows, querying local group memberships for system users via the Windows Network Management API (`NetUserGetLocalGroups`). + +## Key Components + +### `processLocalUserGroups` +Fetches local group memberships for a single `User` object using `NetUserGetLocalGroups`. For each returned group, it resolves the group details from the `GroupsCache` and appends a `{uid, gid}` row to the results. Logs a warning if group membership exceeds buffer limits, and skips users for whom the API call fails. + +### `genUserGroups` +Entry point for the `user_groups` virtual table. Supports optional UID-based filtering via `QueryContext` constraints: +- **Filtered**: resolves only the specified UIDs from `GlobalUsersGroupsCache`, then calls `processLocalUserGroups` for each matched user. +- **Unfiltered**: iterates all cached users, skipping built-in service accounts (`SYSTEM`, `LOCAL SERVICE`, `NETWORK SERVICE`), and delegates to `processLocalUserGroups`. + +## Usage Example + +```cpp +// Typical osquery SQL query this table serves: +// SELECT uid, gid FROM user_groups WHERE uid = 1001; +// SELECT uid, gid FROM user_groups; + +// Internal call flow: +QueryContext ctx; +// Optionally add uid constraint: +ctx.constraints["uid"].add(Constraint(EQUALS, "1001")); + +QueryData rows = osquery::tables::genUserGroups(ctx); +for (const auto& row : rows) { + // row["uid"], row["gid"] +} +``` + +## Dependencies + +| Dependency | Purpose | +|---|---| +| `LM.h` / `NetUserGetLocalGroups` | Windows API for group membership lookup | +| `GlobalUsersGroupsCache` | Cached users and groups to avoid repeated API calls | +| `stringToWstring` / `wstringToString` | Wide-string conversion for Windows API interop | +| `tryTo` | Safe string-to-integer conversion for UID parsing | \ No newline at end of file diff --git a/osquery/tables/system/windows/.userassist.md b/osquery/tables/system/windows/.userassist.md new file mode 100644 index 00000000000..d9bdbdec12e --- /dev/null +++ b/osquery/tables/system/windows/.userassist.md @@ -0,0 +1,31 @@ + +Parses the Windows UserAssist registry key to extract application execution history, including ROT13-decoded paths, execution counts, and last execution timestamps for all user SIDs. + +## Key Components + +- **`executionNum(const std::string& assist_data)`** — Extracts and parses the execution count from raw UserAssist binary data by reversing endianness and converting from hexadecimal. +- **`genUserAssist(QueryContext& context)`** — Main table generator that walks `HKEY_USERS`, locates each user's UserAssist `\Count` subkeys, decodes ROT13-encoded program paths, and assembles result rows. +- **`kFullRegPath`** — Constant pointing to the UserAssist registry path under each user hive (`\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist`). + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT * FROM userassist; +// +// Internally, the generator iterates HKEY_USERS and decodes entries: + +QueryData results = genUserAssist(context); +// Each row contains: +// r["path"] — ROT13-decoded executable path +// r["count"] — Number of times the program was executed +// r["last_execution_time"] — Unix timestamp of last execution (BIGINT) +// r["sid"] — User SID owning the entry +``` + +## Notes + +- Registry values are ROT13-encoded by Windows; `rotDecode()` reverses this. +- Special control entries (`UEME_CTLCUACount:ctor`, `UEME_CTLSESSION`) are non-executable markers and are returned with empty `count` and `last_execution_time` fields. +- Timestamps stored as Windows FILETIME (little-endian) are converted to Unix epoch via `littleEndianToUnixTime()`. +- Malformed or zero-value timestamps result in an empty `count` field rather than a potentially misleading value. \ No newline at end of file diff --git a/osquery/tables/system/windows/.users.md b/osquery/tables/system/windows/.users.md new file mode 100644 index 00000000000..e177a46547b --- /dev/null +++ b/osquery/tables/system/windows/.users.md @@ -0,0 +1,35 @@ + +Implements the osquery `users` table for Windows, querying local user account information from the global users/groups cache and returning structured rows for SQL-based access. + +## Key Components + +- **`getUserShell(sid)`** — Returns a hardcoded shell path (`cmd.exe`) for cross-platform schema consistency; the `shell` column has no meaningful Windows equivalent. +- **`genUser(user)`** — Maps a `User` struct into an osquery `Row`, populating fields: `username`, `uid`, `gid`, `uid_signed`, `gid_signed`, `description`, `directory`, `shell`, `type`, and `uuid` (SID). +- **`genUsers(context)`** — Main table generator; supports filtered lookups by `uuid` (SID) or `uid` constraints via `GlobalUsersGroupsCache`, falling back to a full user enumeration when no constraints are provided. + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT username, uid, gid, description, directory, uuid FROM users; +// SELECT * FROM users WHERE uid = 500; +// SELECT * FROM users WHERE uuid = 'S-1-5-21-...'; + +// Internally, genUsers resolves constraints and delegates to the cache: +auto& users_cache = GlobalUsersGroupsCache::getUsersCache(); + +// SID-based lookup +auto opt_user = users_cache.getUserBySid("S-1-5-21-..."); + +// UID-based lookup +auto users = users_cache.getUsersByUid(500); + +// Full enumeration +auto all_users = users_cache.getAllUsers(); +``` + +## Notes + +- Relies on `GlobalUsersGroupsCache` rather than direct WinAPI calls (e.g., `NetUserEnum`), improving performance through caching. +- `uid_signed` and `gid_signed` are cast to `int32_t` to handle signed/unsigned representation parity across platforms. +- The `kRegProfilePath` constant references the Windows Profile List registry key but is not actively used in the current implementation. \ No newline at end of file diff --git a/osquery/tables/system/windows/.video_info.md b/osquery/tables/system/windows/.video_info.md new file mode 100644 index 00000000000..2932cbaf828 --- /dev/null +++ b/osquery/tables/system/windows/.video_info.md @@ -0,0 +1,46 @@ + +Queries Windows WMI (`Win32_VideoController`) to retrieve GPU/display adapter information and exposes it as an osquery table row. + +## Key Components + +### `genVideoInfo(QueryContext& context)` +The sole table generation function registered with the osquery `video_info` virtual table. It: +- Issues a WMI `SELECT * FROM Win32_VideoController` query via `WmiRequest` +- Extracts display adapter properties from the first result item +- Maps WMI fields to osquery column names and returns a single `Row` + +### WMI Field Mappings + +| WMI Property | osquery Column | Type | +|---|---|---| +| `CurrentBitsPerPixel` | `color_depth` | `INTEGER` | +| `InstalledDisplayDrivers` | `driver` | `STRING` | +| `DriverDate` | `driver_date` | `BIGINT` (Unix timestamp) | +| `DriverVersion` | `driver_version` | `STRING` | +| `AdapterCompatibility` | `manufacturer` | `STRING` | +| `VideoProcessor` | `model` | `STRING` | +| `Name` | `series` | `STRING` | +| `VideoModeDescription` | `video_mode` | `STRING` | + +> **Note:** Only the first WMI result (`wmiResults[0]`) is read. Systems with multiple GPUs will only report the first adapter. + +## Usage Example + +```cpp +// Called internally by osquery's table registry — no direct instantiation needed. +// Query via osquery SQL interface: +// +// SELECT color_depth, driver, driver_date, driver_version, +// manufacturer, model, series, video_mode +// FROM video_info; +// +// On WMI failure, genVideoInfo() logs a WARNING and returns an empty result set. + +QueryContext ctx; +QueryData rows = osquery::tables::genVideoInfo(ctx); +if (!rows.empty()) { + auto& row = rows[0]; + std::cout << "GPU: " << row.at("model") << "\n"; + std::cout << "Driver: " << row.at("driver_version") << "\n"; +} +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.windows_crashes.md b/osquery/tables/system/windows/.windows_crashes.md new file mode 100644 index 00000000000..99f6cd3155c --- /dev/null +++ b/osquery/tables/system/windows/.windows_crashes.md @@ -0,0 +1,53 @@ + +Implements the `windows_crashes` osquery table, which parses Windows minidump (`.dmp`) files to extract crash diagnostics including exception info, stack traces, register states, OS version, and process metadata using the Windows Debug Engine (DbgEng/DbgHelp) APIs. + +## Key Components + +### Constants +- **`kLocalDumpsRegKey`** — Registry path to Windows Error Reporting local dump folder configuration +- **`kSymbolPath`** — Symbol server path chain (local cache + Microsoft Symbol Server) for resolving stack frames +- **`kNumStackFramesToLog`** — Maximum stack frames captured per crash (10) +- **`kMinidumpTypeFlags`** — Mapping of `MINIDUMP_TYPE` bitmask values to human-readable flag names + +### Classes +- **`RegisterOutputCallbacks`** — Implements `IDebugOutputCallbacks` to intercept `DbgEng` register output and format it into the result row (normalizes `=` separators to `:0x` prefix) + +### Helper Functions + +| Function | Description | +|---|---| +| `logExceptionInfo()` | Extracts exception code, address, and human-readable message (handles `ACCESS_VIOLATION` and `IN_PAGE_ERROR`) | +| `logPIDAndTID()` | Logs crashing process and thread system IDs | +| `logProcessUptime()` | Records process uptime at crash time in seconds | +| `logDumpTime()` | Converts epoch timestamp to UTC datetime string | +| `logOSVersion()` | Captures Windows platform major/minor version and build number | +| `logDumpType()` | Decodes `MINIDUMP_TYPE` flags into comma-separated flag names | +| `logStackTrace()` | Walks up to 10 unmanaged stack frames with symbol names and offsets | +| `logRegisters()` | Captures CPU register state at crash time via `IDebugOutputCallbacks` | +| `logPEPathAndVersion()` | Retrieves the faulting executable path and file version | + +## Usage Example + +```cpp +// Typical DbgEng initialization flow used internally: +IDebugClient5* client = nullptr; +IDebugControl5* control = nullptr; +IDebugSymbols3* symbols = nullptr; + +DebugCreate(__uuidof(IDebugClient5), (void**)&client); +client->QueryInterface(__uuidof(IDebugControl5), (void**)&control); +client->QueryInterface(__uuidof(IDebugSymbols3), (void**)&symbols); + +// Open a minidump file +client->OpenDumpFile("C:\\CrashDumps\\app.dmp"); +control->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE); + +// Populate a result row +Row r; +logExceptionInfo(control, r); // r["exception_code"], r["exception_address"] +logStackTrace(control, symbols, r); // r["stack_trace"] +logOSVersion(control, r); // r["major_version"], r["build_number"] +logDumpType(control, r); // r["type"] +``` + +> **Note:** Stack trace resolution only covers unmanaged (native) frames. Mixed-mode (.NET + native) stacks require additional handling via `ICorDebug`. See the [Steve Niemitz blog post](http://blog.steveniemitz.com/building-a-mixed-mode-stack-walker-part-2/) referenced in the source. \ No newline at end of file diff --git a/osquery/tables/system/windows/.windows_eventlog.md b/osquery/tables/system/windows/.windows_eventlog.md new file mode 100644 index 00000000000..b4ccb613a64 --- /dev/null +++ b/osquery/tables/system/windows/.windows_eventlog.md @@ -0,0 +1,46 @@ + +Declares two helper functions for querying and parsing Windows Event Log data within the osquery tables framework. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `parseWelXml` | Function | Parses a Windows Event Log entry rendered as XML into an osquery `Row` | +| `genXfilterFromConstraints` | Function | Builds an XPath filter string from a `QueryContext` for use with the Windows `EvtQuery` API | + +## Usage Example + +```c +#include + +namespace osquery { +namespace tables { + +QueryRows genWindowsEventLog(QueryContext& context) { + QueryRows results; + std::string xfilter; + + // Build an XPath filter from the query constraints (e.g., channel, event ID) + genXfilterFromConstraints(context, xfilter); + + // ... open event log channel via EvtQuery using xfilter ... + + std::wstring xml_event = /* rendered XML from EvtRender */; + Row row; + Status status = parseWelXml(context, xml_event, row); + if (status.ok()) { + results.push_back(row); + } + + return results; +} + +} // namespace tables +} // namespace osquery +``` + +## Notes + +- Both functions live in the `osquery::tables` namespace and depend on `osquery/core/core.h` and `osquery/core/tables.h` +- `parseWelXml` accepts a `std::wstring` to handle the wide-character encoding used by the Windows Event Log XML renderer (`EvtRender`) +- `genXfilterFromConstraints` produces an XPath-compatible string, allowing osquery to push SQL `WHERE` clause predicates (channel, event ID, time range, etc.) down to the native Windows API, reducing unnecessary data transfer \ No newline at end of file diff --git a/osquery/tables/system/windows/.windows_optional_features.md b/osquery/tables/system/windows/.windows_optional_features.md new file mode 100644 index 00000000000..45dcb59d0d3 --- /dev/null +++ b/osquery/tables/system/windows/.windows_optional_features.md @@ -0,0 +1,36 @@ + +Implements the `windows_optional_features` osquery table, querying Windows optional feature installation states via WMI (`Win32_OptionalFeature`). + +## Key Components + +- **`genWinOptionalFeatures(QueryContext&)`** — Main table generator. Executes a WMI query for `Caption`, `Name`, and `InstallState` fields, handles `UINT32`/`I4` type ambiguity by falling back to `GetLong()`, and returns one row per feature (~140 rows on Windows 10 Pro). +- **`getDismPackageFeatureStateName(uint32_t)`** — Maps numeric `InstallState` values to human-readable strings per the [Win32_OptionalFeature CIM spec](https://docs.microsoft.com/en-us/windows/desktop/CIMWin32Prov/win32-optionalfeature): + +| State Value | Name | +|:-----------:|------| +| 0 | Unknown | +| 1 | Enabled | +| 2 | Disabled | +| 3 | Absent | +| 4+ | Unknown | + +## Usage Example + +```cpp +// Queried via osquery SQL interface: +// SELECT name, caption, state, statename +// FROM windows_optional_features +// WHERE statename = 'Enabled'; + +// Internally, genWinOptionalFeatures populates rows like: +Row r; +r["name"] = "NetFx3"; +r["caption"] = ".NET Framework 3.5"; +r["state"] = INTEGER(1); +r["statename"] = "Enabled"; +``` + +## Notes + +- WMI may report `InstallState` as `I4` (signed long) despite the schema declaring `UINT32`; the implementation handles both via a fallback cast. +- Returns an empty result set if the WMI request fails or yields no results. \ No newline at end of file diff --git a/osquery/tables/system/windows/.windows_search.md b/osquery/tables/system/windows/.windows_search.md new file mode 100644 index 00000000000..6fb4af56565 --- /dev/null +++ b/osquery/tables/system/windows/.windows_search.md @@ -0,0 +1,31 @@ + +Implements the `windows_search` osquery table, which queries the Windows Search index (via the Windows Search API and OLE DB) to return indexed file metadata such as name, path, size, dates, owner, and type. + +## Key Components + +- **`dateToUnixTime(DATE)`** — Converts a Windows OLE `DATE` value to a Unix timestamp using `VariantTimeToSystemTime` → `SystemTimeToFileTime` → `filetimeToUnixtime`. +- **`writePropVariant(REFPROPVARIANT, wstringstream&)`** — Serializes a `PROPVARIANT` value (including BSTR arrays) into a wide string stream, handling common variant types. +- **`ccomandColumnStringValue(CCommand&, DBORDINAL)`** — Extracts and converts a single column value from an ATL `CCommand` rowset into a UTF-8 string, dispatching on `DBTYPE` and `DBSTATUS`. +- **`executeWindowsSearchQuery(CSession&, string)`** — Executes a raw SQL string against the Windows Search OLE DB provider and returns results as `QueryData` (rows of `string → string` maps). +- **`generateSqlFromUserQuery(string, set, string, LONG)`** — Uses `ISearchManager` → `ISearchCatalogManager` → `ISearchQueryHelper` to convert a natural-language/AQS user query into a valid SQL statement, applying column selection, sort order, and result limit. +- **`genWindowsSearch(QueryContext&)`** — Main osquery table generator; initializes COM and OLE DB, resolves constraints (`query`, `max_results`, `additional_properties`, `sort`), builds and executes the query, then maps Windows property names back to osquery column names. + +## Usage Example + +```cpp +// Querying via osquery SQL: +// SELECT name, path, size, date_modified +// FROM windows_search +// WHERE query = 'report' +// AND max_results = 50 +// AND sort = 'System.DateModified DESC'; + +// Additional Windows Shell properties can be requested: +// SELECT name, path, additional_properties, query +// FROM windows_search +// WHERE query = 'invoice' +// AND additional_properties = 'System.Author,System.Keywords' +// AND max_results = 25; +``` + +> **Note:** This table requires the Windows Search service to be running and uses COM/OLE DB internally. Results reflect the Windows Search index, not a live filesystem scan. Default columns include `name`, `path`, `size`, `date_created`, `date_modified`, `owner`, and `type`. \ No newline at end of file diff --git a/osquery/tables/system/windows/.windows_security_center.md b/osquery/tables/system/windows/.windows_security_center.md new file mode 100644 index 00000000000..7d2c5ed382c --- /dev/null +++ b/osquery/tables/system/windows/.windows_security_center.md @@ -0,0 +1,46 @@ + +Queries the Windows Security Center (WSC) API to report the health status of key security providers, including firewall, antivirus, antispyware, automatic updates, and UAC. + +## Key Components + +- **`kSecurityProviderStates`** — Lookup map translating `WSC_SECURITY_PROVIDER_HEALTH` enum values to human-readable strings (`"Good"`, `"Poor"`, `"Snoozed"`, `"Not Monitored"`). + +- **`resolveProductHealthOrError(int productName)`** — Dynamically loads `wscapi.dll` at runtime (avoiding static-link crashes on some Windows hosts) and calls `WscGetSecurityProviderHealth` to retrieve a provider's health status. Returns `"Error"` on failure or `"Unknown"` for unmapped states. + +- **`windowsUpdateServicesEnabled()`** — Queries the `services` virtual table for `wuauserv` and `UsoSvc` start types, returning `false` if either service is explicitly `DISABLED`. + +- **`genWindowsUpdateHealth()`** — Wraps the WSC autoupdate health check with a secondary validation: overrides a `"Good"` WSC result with `"Poor"` if the underlying Windows Update services are disabled, compensating for a known WSC API reporting inconsistency. + +- **`gen_wsc(QueryContext&)`** — Main osquery table generator; populates a single result row with health states for all monitored security providers. + +## Usage Example + +```cpp +// Registered as the osquery virtual table: windows_security_center +// Query from osqueryi or fleet: +``` + +```sql +SELECT + firewall, + antivirus, + antispyware, + autoupdate, + internet_settings, + user_account_control, + windows_security_center_service +FROM windows_security_center; +``` + +```text +-- Example output: +firewall = Good +antivirus = Good +antispyware = Not Monitored +autoupdate = Poor -- WSC said Good, but wuauserv is DISABLED +internet_settings = Good +user_account_control = Good +windows_security_center_service = Good +``` + +> **Note:** `wscapi.dll` is loaded dynamically via `LoadLibraryExW` with `LOAD_LIBRARY_SEARCH_SYSTEM32` to prevent crashes observed when statically linking on certain Windows environments (e.g., CI servers). \ No newline at end of file diff --git a/osquery/tables/system/windows/.windows_security_products.md b/osquery/tables/system/windows/.windows_security_products.md new file mode 100644 index 00000000000..c5fb34209e1 --- /dev/null +++ b/osquery/tables/system/windows/.windows_security_products.md @@ -0,0 +1,31 @@ + +Queries Windows Security Center (WSC) via the `IWSCProductList` COM interface to enumerate installed security products — antivirus, firewall, and antispyware — and expose their state, signatures, and remediation info as an osquery table. + +## Key Components + +- **`kSecurityProviders`** — Maps `WSC_SECURITY_PROVIDER` enum values to human-readable type strings (`"Firewall"`, `"Antivirus"`, `"Antispyware"`). +- **`kSecurityProviderStates`** — Maps `WSC_SECURITY_PRODUCT_STATE` values to strings (`"On"`, `"Off"`, `"Snoozed"`, `"Expired"`). +- **`wsc_entry`** — POD struct holding a single security product's provider type, name, state, timestamp, remediation path, and signature status. +- **`GetSecurityProducts(provider, out_list)`** — Dynamically loads `wscapi.dll` at runtime (avoids a known crash from static linking), instantiates `IWSCProductList` via COM, and populates `out_list` with all registered products for the given provider type. +- **`GetAllSecurityProducts(out_list)`** — Calls `GetSecurityProducts` for all three provider types, logging warnings on individual failures without aborting. +- **`gen_wsp(context)`** — osquery table generator; converts the collected `wsc_entry` vector into `QueryData` rows with string-typed fields. + +## Usage Example + +```cpp +// Called automatically by the osquery table dispatcher. +// Equivalent manual call: +QueryContext ctx; +QueryData rows = osquery::tables::gen_wsp(ctx); + +for (const auto& row : rows) { + // row["type"] -> "Antivirus" | "Firewall" | "Antispyware" + // row["name"] -> e.g. "Windows Defender" + // row["state"] -> "On" | "Off" | "Snoozed" | "Expired" + // row["signatures_up_to_date"]-> "1" or "0" + // row["remediation_path"] -> executable path for remediation + // row["state_timestamp"] -> last state change timestamp +} +``` + +> **Note:** `wscapi.dll` is loaded dynamically via `LoadLibraryExW` with `LOAD_LIBRARY_SEARCH_SYSTEM32` to prevent crashes observed when statically linking the import library on certain Windows configurations (e.g., CI environments). The function returns a `Status::failure` if the DLL or its `CLSID_WSCProductList` export cannot be resolved. \ No newline at end of file diff --git a/osquery/tables/system/windows/.windows_update_history.md b/osquery/tables/system/windows/.windows_update_history.md new file mode 100644 index 00000000000..4abe6ae1aca --- /dev/null +++ b/osquery/tables/system/windows/.windows_update_history.md @@ -0,0 +1,47 @@ + +Header defining types and interfaces for querying Windows Update installation history via the Windows Update Agent (WUA) API. + +## Key Components + +### `WindowsUpdateHistoryError` (enum class) +Enumerates error conditions that can occur when retrieving update history, covering failures at each stage of the WUA query pipeline — from initializing the update searcher through extracting individual entry fields (title, date, description, HResult, etc.). + +### `WindowsUpdateHistoryEntry` (struct) +Represents a single Windows Update history record with fields mapped directly from the WUA `IUpdateHistoryEntry` COM interface: + +| Field | Type | Description | +|---|---|---| +| `clientAppID` | `std::string` | Application that initiated the update | +| `date` | `LONGLONG` | Install/uninstall timestamp | +| `description` | `std::string` | Update description text | +| `hresult` | `LONG` | Win32 result code | +| `updateOp` | `UpdateOperation` | Install or uninstall operation | +| `resultCode` | `OperationResultCode` | Success/failure outcome | +| `serverSelection` | `ServerSelection` | Source server (WSUS, Windows Update, etc.) | +| `updateID` | `std::string` | Unique update GUID | +| `updateRevision` | `LONG` | Revision number of the update | + +### `WindowsUpdateHistory` (type alias) +`std::vector` — a collection of update history entries. + +### `renderWindowsUpdateHistory()` +Converts a `WindowsUpdateHistory` collection into osquery's `QueryData` format for table output. + +## Usage Example + +```cpp +#include "windows_update_history.h" + +// After populating entries from the WUA COM API: +WindowsUpdateHistory history; +WindowsUpdateHistoryEntry entry; +entry.title = "Security Update KB5012345"; +entry.updateID = "a1b2c3d4-..."; +entry.hresult = S_OK; +entry.updateOp = uoInstallation; +entry.resultCode = orcSucceeded; +history.push_back(entry); + +// Render for osquery table response +QueryData rows = renderWindowsUpdateHistory(history); +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.wmi_bios_info.md b/osquery/tables/system/windows/.wmi_bios_info.md new file mode 100644 index 00000000000..8b03f68d77e --- /dev/null +++ b/osquery/tables/system/windows/.wmi_bios_info.md @@ -0,0 +1,30 @@ + +Queries Windows Management Instrumentation (WMI) to retrieve BIOS configuration data, supporting both standard `Win32_BIOS` fields and vendor-specific settings for HP, Lenovo, and Dell systems. + +## Key Components + +- **`getManufacturer()`** — Normalizes manufacturer strings to canonical vendor keys (`"hp"`, `"lenovo"`, `"dell"`, `"dell-legacy"`), probing WMI to detect whether a Dell machine uses the legacy `DCIM_BIOSEnumeration` class or the newer `EnumerationAttribute` class. +- **`getHPBiosInfo()`** — Extracts HP BIOS name/value pairs, applying a regex (`kHPBiosSettingRegex`) to parse the active setting from HP's `*value` format. +- **`getLenovoBiosInfo()`** — Parses Lenovo's `CurrentSetting` field, which encodes `name,value` as a comma-delimited string. +- **`getDellLegacyBiosInfo()`** — Resolves Dell legacy BIOS enum values by cross-referencing `PossibleValues` and `PossibleValuesDescription` arrays. +- **`getDellBiosInfo()`** — Retrieves Dell modern BIOS attribute name/value pairs from `EnumerationAttribute`. +- **`generateCommonBiosRows()`** — Queries `Win32_BIOS` and emits one `{name, value}` row per field, handling strings, string vectors, uint8/uint16 scalars, uint16 vectors, booleans, and FILETIME-encoded dates. +- **`kVendorSpecificQueryMap`** — Static map of vendor keys to WMI query strings and namespaces. + +## Usage Example + +```cpp +// Internally called by the osquery table generator +QueryData common_rows = generateCommonBiosRows(); +// Each row: { "name": "SMBIOSBIOSVersion", "value": "1.23.4" } + +// Vendor-specific row extraction +std::string vendor = getManufacturer("DELL INC."); // returns "dell" or "dell-legacy" +auto it = kVendorSpecificQueryMap.find(vendor); +auto wmiReq = WmiRequest::CreateWmiRequest( + std::get<0>(it->second), std::get<1>(it->second)); +for (const auto& item : wmiReq->results()) { + Row r = getDellBiosInfo(item); + // r["name"], r["value"] +} +``` \ No newline at end of file diff --git a/osquery/tables/system/windows/.wmi_cli_event_consumers.md b/osquery/tables/system/windows/.wmi_cli_event_consumers.md new file mode 100644 index 00000000000..a1f2da10309 --- /dev/null +++ b/osquery/tables/system/windows/.wmi_cli_event_consumers.md @@ -0,0 +1,37 @@ + +Queries WMI `CommandLineEventConsumer` subscriptions from the `ROOT\Subscription` namespace, returning details about registered WMI command-line event consumers. + +## Key Components + +- **`genWmiCliConsumers(QueryContext&)`** — Main table generation function that executes a WMI SELECT query and maps results into osquery rows. + +### Returned Columns + +| Column | WMI Property | +|---|---| +| `command_line_template` | `CommandLineTemplate` | +| `executable_path` | `ExecutablePath` | +| `name` | `Name` | +| `class` | `__CLASS` | +| `relative_path` | `__RELPATH` | + +## Usage Example + +```cpp +// Invoked internally by the osquery table infrastructure. +// Equivalent SQL query from the osquery shell: +// +// SELECT * FROM wmi_cli_event_consumers; +// +// Underlying WMI query executed: +// SELECT * FROM CommandLineEventConsumer +// Namespace: ROOT\Subscription + +QueryContext ctx; +QueryData rows = genWmiCliConsumers(ctx); +for (const auto& row : rows) { + std::cout << row.at("name") << ": " << row.at("command_line_template") << "\n"; +} +``` + +> **Security note:** `CommandLineEventConsumer` entries in `ROOT\Subscription` are a common persistence mechanism used by malware. This table is useful for detecting unauthorized WMI subscriptions during threat hunting or compliance audits. \ No newline at end of file diff --git a/osquery/tables/system/windows/.wmi_event_filters.md b/osquery/tables/system/windows/.wmi_event_filters.md new file mode 100644 index 00000000000..cc7c89bb727 --- /dev/null +++ b/osquery/tables/system/windows/.wmi_event_filters.md @@ -0,0 +1,32 @@ + +Queries the Windows Management Instrumentation (WMI) `ROOT\Subscription` namespace to enumerate all `__EventFilter` objects, which represent WMI event subscriptions used for persistence or monitoring. + +## Key Components + +- **`genWmiFilters(QueryContext&)`** — Main table generation function that executes a WMI query against `__EventFilter` and returns structured rows containing filter metadata. + +## Fields Returned + +| Field | WMI Property | +|---|---| +| `name` | `Name` | +| `query` | `Query` | +| `query_language` | `QueryLanguage` | +| `class` | `__CLASS` | +| `relative_path` | `__RELPATH` | + +## Usage Example + +```cpp +// Called internally by osquery's table dispatcher +// Equivalent SQL query via osquery shell: +// SELECT * FROM wmi_event_filters; +QueryContext ctx; +QueryData rows = genWmiFilters(ctx); +for (const auto& row : rows) { + // row["name"], row["query"], row["query_language"], + // row["class"], row["relative_path"] +} +``` + +> **Security Note:** WMI Event Filters in `ROOT\Subscription` are a known persistence mechanism. Enumerating them is useful for threat detection — any unexpected entries may indicate malware leveraging WMI subscriptions for persistence. \ No newline at end of file diff --git a/osquery/tables/system/windows/.wmi_filter_consumer_binding.md b/osquery/tables/system/windows/.wmi_filter_consumer_binding.md new file mode 100644 index 00000000000..b2a7f54c4ed --- /dev/null +++ b/osquery/tables/system/windows/.wmi_filter_consumer_binding.md @@ -0,0 +1,30 @@ + +Queries WMI's `__FilterToConsumerBinding` class to enumerate active WMI event subscription bindings between filters and consumers on Windows systems. + +## Key Components + +- **`genFilterConsumer(QueryContext&)`** — Main table generation function that queries the `ROOT\Subscription` WMI namespace and returns rows representing filter-to-consumer bindings, including the consumer name, filter name, WMI class, and relative path. + +## Usage Example + +```cpp +// Invoked automatically by the osquery table registry. +// Equivalent SQL query: +// SELECT * FROM wmi_filter_consumer_binding; + +// Internally executes against WMI: +// SELECT * FROM __FilterToConsumerBinding +// Namespace: ROOT\Subscription + +// Example result row: +// consumer -> "CommandLineEventConsumer.Name=\"MaliciousConsumer\"" +// filter -> "__EventFilter.Name=\"MaliciousFilter\"" +// class -> "__FilterToConsumerBinding" +// relative_path -> "__FilterToConsumerBinding.Consumer=...,Filter=..." +``` + +## Notes + +- Targets the `ROOT\Subscription` WMI namespace, which is where persistent WMI event subscriptions reside — a common persistence mechanism abused by malware. +- Useful for threat hunting and detecting unauthorized WMI subscriptions on Windows hosts. +- Returns an empty result set if the WMI request fails or returns no data. \ No newline at end of file diff --git a/osquery/tables/system/windows/.wmi_script_event_consumers.md b/osquery/tables/system/windows/.wmi_script_event_consumers.md new file mode 100644 index 00000000000..e7a92628cad --- /dev/null +++ b/osquery/tables/system/windows/.wmi_script_event_consumers.md @@ -0,0 +1,36 @@ + +Queries the Windows WMI `ActiveScriptEventConsumer` class to enumerate registered script-based event consumers from the `ROOT\Subscription` namespace, returning their properties as structured table rows. + +## Key Components + +- **`genScriptConsumers(QueryContext&)`** — Main table generation function that executes a WMI query against `ROOT\Subscription` and maps each `ActiveScriptEventConsumer` instance to an osquery `Row`. + +### Mapped WMI Fields + +| WMI Property | osquery Column | +|---|---| +| `ScriptText` | `script_text` | +| `ScriptFileName` | `script_file_name` | +| `ScriptingEngine` | `scripting_engine` | +| `Name` | `name` | +| `__CLASS` | `class` | +| `__RELPATH` | `relative_path` | + +## Usage Example + +```cpp +// Registered as an osquery virtual table; query via SQL: +// SELECT * FROM wmi_script_event_consumers; + +QueryContext ctx; +QueryData rows = genScriptConsumers(ctx); + +for (const auto& row : rows) { + // Access individual fields + std::string engine = row.at("scripting_engine"); // e.g., "VBScript" + std::string name = row.at("name"); + std::string script = row.at("script_text"); +} +``` + +> **Security note:** `ActiveScriptEventConsumer` entries are a well-known WMI persistence mechanism. This table is useful for threat hunting — presence of unexpected entries may indicate malicious activity. \ No newline at end of file diff --git a/osquery/tables/utility/.file.md b/osquery/tables/utility/.file.md new file mode 100644 index 00000000000..a260a2fc33a --- /dev/null +++ b/osquery/tables/utility/.file.md @@ -0,0 +1,33 @@ + +Cross-platform osquery table implementation for the `file` virtual table, providing file metadata (inode, permissions, timestamps, size, type) for both POSIX and Windows systems, including Windows `.lnk` shortcut parsing. + +## Key Components + +### Windows-Only + +- **`LnkData`** — Struct holding parsed `.lnk` shortcut fields: `target_path`, `target_type`, `target_location`, `start_in`, `run`, `comment` +- **`showCmdToString(int)`** — Converts a Win32 `SW_SHOW*` constant to a human-readable string (`"Normal window"`, `"Maximized"`, `"Minimized"`) +- **`parseLnkData(const fs::path&)`** — Uses COM `IShellLink`/`IPersistFile` to parse a `.lnk` file; validates the `ShellLinkHeader` magic (`0x4C`) before returning an `optional` +- **`genFileInfoWindows(...)`** — Builds a result `Row` using `platformStat` and optionally appends shortcut columns +- **`genFileWindows(QueryContext&, Logger&)`** — Entry point for Windows; resolves path/directory constraints and calls `genFileInfoWindows` + +### POSIX-Only + +- **`kTypeNames`** — Maps `boost::filesystem::file_type` enums to string labels (`"regular"`, `"symlink"`, etc.) +- **`genFileInfoPosix(...)`** — Builds a result `Row` using `lstat`/`stat`; detects symlinks and populates `symlink_target_path` + +### Shared Helpers + +- **`getPathsFromConstraints(QueryContext&)`** — Resolves `path` column constraints (EQUALS + LIKE glob expansion) +- **`getDirsFromConstraints(QueryContext&)`** — Resolves `directory` column constraints (EQUALS + LIKE folder glob expansion) + +## Usage Example + +```cpp +// Querying the file table (osquery SQL) +// SELECT path, size, mode, mtime, symlink FROM file WHERE path = '/etc/hosts'; + +// On Windows, also supports shortcut inspection: +// SELECT path, shortcut_target_path, shortcut_run +// FROM file WHERE path = 'C:\Users\Public\Desktop\MyApp.lnk'; +``` \ No newline at end of file diff --git a/osquery/tables/utility/.osquery.md b/osquery/tables/utility/.osquery.md new file mode 100644 index 00000000000..ce809ae2a60 --- /dev/null +++ b/osquery/tables/utility/.osquery.md @@ -0,0 +1,38 @@ + +Implements osquery virtual table generators that expose internal osquery runtime state as queryable SQL tables, allowing introspection of the osquery daemon itself. + +## Key Components + +| Function | Table | Description | +|---|---|---| +| `genOsqueryEvents` | `osquery_events` | Lists all event publishers and subscribers with subscription counts, event counts, and active state | +| `genOsqueryPacks` | `osquery_packs` | Enumerates loaded query packs with version, platform, shard, and discovery cache statistics | +| `genOsqueryFlags` | `osquery_flags` | Exposes all runtime configuration flags (name, type, description, default/current value) | +| `genOsqueryRegistry` | `osquery_registry` | Dumps all registered plugins across every registry, including external extension-owned routes | +| `genOsqueryExtensions` | `osquery_extensions` | Lists connected extensions with UUID, name, version, SDK version, and socket path | +| `genOsqueryInfo` | `osquery_info` | Returns a single-row snapshot of the running daemon (PID, version, config hash, build info, UUID) | +| `genOsquerySchedule` | `osquery_schedule` | Reports all scheduled queries with interval, denylist status, and detailed performance metrics | +| `genFlag` | *(helper)* | Serializes a single `FlagInfo` entry into a `QueryData` row | + +## Usage Example + +```cpp +// Query osquery internals via the SQL interface +auto results = SQL("SELECT name, type, active FROM osquery_events WHERE active = '1'"); + +// Inspect scheduled query performance +auto schedule = SQL( + "SELECT name, executions, wall_time_ms, average_memory " + "FROM osquery_schedule WHERE executions > 0" +); + +// Check loaded extensions +auto extensions = SQL("SELECT uuid, name, version, type FROM osquery_extensions"); + +// Review current flag values +auto flags = SQL( + "SELECT name, value, default_value FROM osquery_flags WHERE shell_only = '0'" +); +``` + +> **Note:** `genOsqueryRegistry` respects the `--disable_logging` and `--disable_events` flags, marking affected plugins as inactive rather than omitting them from results. \ No newline at end of file diff --git a/osquery/tables/utility/.time.md b/osquery/tables/utility/.time.md new file mode 100644 index 00000000000..bf29ea42a9c --- /dev/null +++ b/osquery/tables/utility/.time.md @@ -0,0 +1,39 @@ + +Implements the `time` osquery virtual table, returning current system time information in multiple formats and timezone representations. + +## Key Components + +### `genTime(QueryContext& context)` +The sole table generator function that populates a single-row result with the following columns: + +| Column | Type | Description | +|---|---|---| +| `weekday` | TEXT | Full weekday name (e.g. `"Monday"`) | +| `year` / `month` / `day` | INTEGER | UTC date components | +| `hour` / `minutes` / `seconds` | INTEGER | UTC time components | +| `timezone` | TEXT | Always `"UTC"` | +| `local_timezone` | TEXT | System local timezone abbreviation | +| `unix_time` | INTEGER | Unix epoch timestamp | +| `timestamp` | TEXT | ASCII-formatted UTC time | +| `datetime` / `iso_8601` | TEXT | ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`) | +| `win_timestamp` | BIGINT | Windows FILETIME (100-nanosecond intervals, Windows only) | + +## Platform Behavior + +- **Windows**: Uses `GetTimeZoneInformation()` for local timezone and `GetSystemTimeAsFileTime()` for the high-resolution `win_timestamp` column. +- **POSIX**: Uses `localtime_r()` + `strftime()` with `%Z` to extract the local timezone abbreviation. + +## Usage Example + +```cpp +// Querying the time table via osquery SQL +SELECT unix_time, datetime, local_timezone, win_timestamp +FROM time; + +// Example result row populated by genTime(): +// weekday → "Wednesday" +// year → 2024 +// unix_time → 1711929600 +// iso_8601 → "2024-04-01T00:00:00Z" +// local_timezone → "EST" +``` \ No newline at end of file diff --git a/osquery/tables/yara/.yara.md b/osquery/tables/yara/.yara.md new file mode 100644 index 00000000000..f0a3236bba5 --- /dev/null +++ b/osquery/tables/yara/.yara.md @@ -0,0 +1,47 @@ + +Implements the osquery YARA table, providing on-demand file scanning against YARA rules loaded from signature groups, local files, inline rule strings, or remote URLs. + +## Key Components + +### Types & Enums + +- **`YaraRuleType`** — Enum classifying rule sources: `YC_NONE`, `YC_GROUP`, `YC_FILE`, `YC_RULE`, `YC_URL` +- **`YaraScanContext`** — Set of `(YaraRuleType, string)` pairs representing the active scan targets +- **`YARAConfigParser`** — Shared pointer alias to `YARAConfigParserPlugin` + +### Key Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `yara_delay` | `50` ms | Sleep between file scans to reduce memory spikes | +| `yara_sigurl_authenticate` | `false` | Authenticate URL rule fetches using the node TLS key | +| `enable_yara_string` | `false` | Return matched YARA strings in results | + +### Core Functions + +- **`isRuleUrlAllowed()`** — Validates a URL against the configured allowlist using scheme, host, and regex path matching +- **`getRuleFromURL()`** — Fetches YARA rule content over HTTPS; supports optional node-key POST authentication +- **`doYARAScan()`** — Executes `yr_rules_scan_file()` on a single path and appends results to `QueryData` +- **`getYaraRules()`** — Compiles and caches rules from files, inline strings, or URLs into the parser's rules map +- **`genYaraImpl()`** — Main table generator: initializes YARA, builds `YaraScanContext` from query constraints, resolves file paths, and dispatches scans + +## Usage Example + +```sql +-- Scan a file against an inline YARA rule +SELECT * FROM yara +WHERE path = '/tmp/suspicious.elf' + AND sigrule = 'rule test { strings: $a = "malware" condition: $a }'; + +-- Scan using a pre-configured signature group +SELECT * FROM yara +WHERE path LIKE '/home/%/.ssh/%' + AND sig_group = 'ssh_rules'; + +-- Scan using a remote rule URL (must be in url_allow_set) +SELECT * FROM yara +WHERE path = '/usr/bin/curl' + AND sigurl = 'https://rules.example.com/curl_check.yar'; +``` + +Compiled rules are cached by SHA-256 hash to avoid recompilation on repeated queries. \ No newline at end of file diff --git a/osquery/tables/yara/.yara_events.md b/osquery/tables/yara/.yara_events.md new file mode 100644 index 00000000000..dce6c686657 --- /dev/null +++ b/osquery/tables/yara/.yara_events.md @@ -0,0 +1,57 @@ + +Subscribes to filesystem change events (create, modify, rename/move) and scans affected files using YARA rules, emitting rows to the `yara_events` table when matches are found. + +## Key Components + +### `YARAEventSubscriber` +An `EventSubscriber` that bridges the platform-specific filesystem event publisher (FSEvents on macOS, inotify on Linux) with YARA scanning. + +| Member | Description | +|---|---| +| `init()` | No-op initialization returning `Status(0)` | +| `configure()` | Reads YARA config, builds path subscriptions per category | +| `Callback(ec, sc)` | Invoked on file events; runs YARA scans and emits result rows | + +### Platform Aliases +Compile-time type aliases (`FileEventSubscriber`, `FileEventContextRef`, `FileSubscriptionContextRef`) and `FILE_CHANGE_MASK` are resolved per OS to either FSEvents or inotify equivalents. + +### Registry Registration +```cpp +REGISTER(YARAEventSubscriber, "event_subscriber", "yara_events"); +``` +Registers the subscriber into the osquery plugin registry under the name `yara_events`. + +## Usage Example + +This subscriber is driven by osquery configuration. A minimal config enabling it: + +```json +{ + "file_paths": { + "binaries": ["/usr/bin/%%"] + }, + "yara": { + "file_paths": { + "binaries": ["malware_rules"] + }, + "signatures": { + "malware_rules": ["/etc/osquery/yara/malware.yar"] + } + } +} +``` + +On a matching file event, `Callback` populates and emits a row with these fields: + +```cpp +r["action"] // "UPDATED" | "CREATED" | "MOVED_TO" +r["target_path"] // Absolute path of the changed file +r["category"] // Config category (e.g. "binaries") +r["transaction_id"] // FSEvents transaction ID (inotify: 0) +r["matches"] // Comma-separated matched rule names +r["strings"] // Matched YARA strings +r["tags"] // Matched YARA tags +r["count"] // Total match count +``` + +Rows are only added when at least one YARA rule matches the changed file. \ No newline at end of file diff --git a/osquery/tables/yara/.yara_utils.md b/osquery/tables/yara/.yara_utils.md new file mode 100644 index 00000000000..e77dd1ea1f2 --- /dev/null +++ b/osquery/tables/yara/.yara_utils.md @@ -0,0 +1,45 @@ + +Header file providing YARA integration utilities for osquery, enabling rule compilation, file scanning, and configuration parsing for threat detection workflows. + +## Key Components + +### `YaraRulesHandle` +RAII wrapper for `YR_RULES*` that ensures proper cleanup via `yr_rules_destroy()` on destruction. Non-copyable, move-only semantics prevent double-free issues. + +### `YaraCompilerError` / `YaraCompilerResult` +Error enum and `Expected` alias for typed compiler result handling. + +### `YARAConfigParserPlugin` +A `ConfigParserPlugin` that registers a `"yara"` top-level config key. Stores compiled rules grouped by category and maintains a URL allowlist for remote rule sources. + +### Key Functions + +| Function | Description | +|---|---| +| `yaraInitialize()` / `yaraFinalize()` | Lifecycle management for the YARA library | +| `compileSingleFile(file)` | Compiles a YARA rule from a file path | +| `compileFromString(buffer)` | Compiles YARA rules from an in-memory string | +| `handleRuleFiles(category, rule_files, rules)` | Processes a ptree of rule files into a compiled rules map | +| `yaraShouldSkipFile(path, st_mode)` | Guards against scanning files that may cause hangs | +| `YARACallback(...)` | Scan result callback invoked per YARA match event | +| `YARACompilerCallback(...)` | Compiler diagnostic callback for error/warning reporting | + +## Usage Example + +```c +// Initialize YARA, compile a rule file, and scan +if (yaraInitialize().ok()) { + auto result = compileSingleFile("/etc/osquery/yara/malware.yar"); + if (result) { + YaraRulesHandle handle = std::move(result.take()); + yr_rules_scan_file(handle.get(), "/tmp/suspect", 0, YARACallback, &row, 0); + } + yaraFinalize(); +} + +// Access parsed config rules via plugin +auto parser = Config::getParser("yara"); +auto& compiledRules = parser->rules(); // map +``` + +> **Note:** `kYARAHome` defaults to `OSQUERY_HOME/yara/` as the base directory for local rule files. \ No newline at end of file diff --git a/osquery/tables/yara/tests/.yara_tests.md b/osquery/tables/yara/tests/.yara_tests.md new file mode 100644 index 00000000000..c40228e1c61 --- /dev/null +++ b/osquery/tables/yara/tests/.yara_tests.md @@ -0,0 +1,43 @@ + +Unit test suite for osquery's YARA integration, validating rule compilation, file scanning, and file-type filtering behavior using the Google Test framework. + +## Key Components + +| Component | Description | +|---|---| +| `YARATest` | GTest fixture class managing YARA initialization/finalization lifecycle | +| `scanFile()` | Helper that compiles a YARA rule from a temp file and scans a temp binary | +| `scanString()` | Helper that compiles a YARA rule from an inline string and scans a temp binary | +| `alwaysTrue` / `alwaysFalse` / `invalidRule` | Constant YARA rule definitions used across test cases | + +## Test Cases + +| Test | What It Validates | +|---|---| +| `test_match_true` | File-based rule with `condition: true` produces a match count of `1` | +| `test_match_false` | File-based rule with `condition: false` produces a match count of `0` | +| `test_match_string_true` | String-based rule with `condition: true` produces a match count of `1` | +| `test_match_string_false` | String-based rule with `condition: false` produces a match count of `0` | +| `should_skip_file` | `yaraShouldSkipFile()` skips devices, dirs, sockets; scans regular files | +| `test_rule_compilation_failures` | Compiler returns errors for directories, invalid syntax, and missing files | + +## Usage Example + +```cpp +// Run all YARA tests via ctest or directly: +// ./osquery_tests --gtest_filter=YARATest.* + +// The fixture compiles a rule and scans a temp file internally: +Row r = scanFile("rule always_true { condition: true }"); +EXPECT_EQ(r["count"], "1"); + +// String-based compilation (no intermediate .sig file): +Row r2 = scanString("rule always_false { condition: false }"); +EXPECT_EQ(r2["count"], "0"); + +// Verify invalid inputs return errors, not crashes: +auto result = compileFromString("rule invalid { Not a valid rule }"); +EXPECT_TRUE(result.isError()); +``` + +> **Note:** `TearDown()` calls `yr_finalize()` after every test case, and each helper independently calls `yr_initialize()`, ensuring YARA state is fully reset between tests. \ No newline at end of file diff --git a/osquery/tables/yara/windows/.yara_events.md b/osquery/tables/yara/windows/.yara_events.md new file mode 100644 index 00000000000..7867aeca60d --- /dev/null +++ b/osquery/tables/yara/windows/.yara_events.md @@ -0,0 +1,42 @@ + +Subscribes `YARAEventSubscriber` to NTFS file system events on Windows and scans matched files with YARA rules, emitting rows only when rule matches are found. + +## Key Components + +- **`YARAEventSubscriber`** — Event subscriber class (aliased from `NTFSEventSubscriber`) that registers under the `"yara_events"` plugin name +- **`configure()`** — Reads the `yara` and `file_paths` config parsers to build include/exclude path subscriptions per category; resolves glob patterns via `resolveFilePattern()` +- **`Callback()`** — Fires on each NTFS event; iterates the event list, runs `yr_rules_scan_file()` against the target path for each configured YARA rule group, and batches rows where matches were found + +## Usage Example + +Configuration is driven by osquery's `yara` config parser. A typical osquery config excerpt: + +```json +{ + "file_paths": { + "system_binaries": ["/Windows/System32/%%"] + }, + "yara": { + "signatures": { + "malware_rules": "/etc/yara/malware.yar" + }, + "file_paths": { + "system_binaries": ["malware_rules"] + } + } +} +``` + +When an NTFS event fires on a monitored path, the callback produces rows with these columns: + +```text +action -- NTFS event type (e.g., "WRITE") +target_path -- Absolute path of the affected file +category -- Config category key (e.g., "system_binaries") +count -- Number of YARA rule matches +matches -- Matched rule names +strings -- Matched string identifiers +tags -- Matched rule tags +``` + +> **Note:** Only rows with at least one YARA match (`matches` non-empty) are emitted. Paths that do not exist at subscription time are silently skipped by `resolveFilePattern()`. \ No newline at end of file diff --git a/osquery/utils/.attribute.md b/osquery/utils/.attribute.md new file mode 100644 index 00000000000..94bb64eb039 --- /dev/null +++ b/osquery/utils/.attribute.md @@ -0,0 +1,29 @@ + +Cross-platform compiler attribute macro that enforces return value checking for functions whose results must not be ignored. + +## Key Components + +- **`OSQUERY_NODISCARD`** — A preprocessor macro that maps to the appropriate compiler-specific "warn on unused result" attribute based on the detected environment: + +| Environment | Expansion | +|---|---| +| C++17 and later | `[[nodiscard]]` | +| POSIX (pre-C++17) | `__attribute__((warn_unused_result))` | +| Windows / MSVC ≥ 2012 (pre-C++17) | `_Check_return_` | +| All other targets | *(empty — no-op)* | + +## Usage Example + +```c +// Mark a function so callers receive a warning if the return value is ignored +OSQUERY_NODISCARD Status openFile(const char* path); + +// Correct usage — result is checked +Status result = openFile("/etc/hosts"); +if (!result.ok()) { /* handle error */ } + +// Incorrect usage — triggers a compiler warning +openFile("/etc/hosts"); // warning: ignoring return value +``` + +> **Note:** The Windows branch contains a typo (`WIDOWS` instead of `WINDOWS`), which means the `_Check_return_` path is currently unreachable on MSVC. The macro silently degrades to a no-op on Windows builds until this is corrected. \ No newline at end of file diff --git a/osquery/utils/.base64.md b/osquery/utils/.base64.md new file mode 100644 index 00000000000..586f4812aeb --- /dev/null +++ b/osquery/utils/.base64.md @@ -0,0 +1,25 @@ + +Provides Base64 encoding and decoding utilities within the `osquery::base64` namespace. + +## Key Components + +| Function | Signature | Description | +|----------|-----------|-------------| +| `decode` | `std::string decode(std::string encoded)` | Decodes a Base64-encoded string and returns the original plaintext | +| `encode` | `std::string encode(const std::string_view unencoded)` | Encodes a raw string into its Base64 representation | + +## Usage Example + +```cpp +#include + +// Encode a plaintext string +std::string encoded = osquery::base64::encode("hello world"); +// encoded == "aGVsbG8gd29ybGQ=" + +// Decode back to original +std::string decoded = osquery::base64::decode(encoded); +// decoded == "hello world" +``` + +> Both functions operate on standard C++ `std::string` / `std::string_view` types, requiring no external dependencies beyond the STL. \ No newline at end of file diff --git a/osquery/utils/.chars.md b/osquery/utils/.chars.md new file mode 100644 index 00000000000..22888dbe309 --- /dev/null +++ b/osquery/utils/.chars.md @@ -0,0 +1,41 @@ + +Utility header providing character and string encoding helpers within the `osquery` namespace. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `isPrintable` | Function | Checks whether all characters in a `string_view` are ASCII printable | +| `incUtf8StringIterator` | Template Function | Advances an iterator past the current UTF-8 multi-byte sequence, returning bytes consumed | +| `utf8StringSize` | Function | Returns the logical character count (not byte length) of a UTF-8 encoded string | +| `unescapeUnicode` | Function | Converts unicode escape sequences (e.g. `\uXXXX`) in a string to their UTF-8 representation | + +## Usage Example + +```cpp +#include + +// Check if a query result value is safe to display +std::string value = "Hello, World!"; +if (osquery::isPrintable(value)) { + // safe to display +} + +// Get the display length of a UTF-8 string (character count, not bytes) +std::string utf8str = "caf\xC3\xA9"; // "café" +size_t len = osquery::utf8StringSize(utf8str); // returns 4, not 5 + +// Decode a unicode escape sequence +std::string escaped = "\\u0041"; // escaped 'A' +std::string decoded = osquery::unescapeUnicode(escaped); // returns "A" + +// Manually iterate UTF-8 characters using the inline helper +auto it = utf8str.begin(); +auto end = utf8str.end(); +while (it != end) { + size_t bytes = osquery::incUtf8StringIterator(it, end); + // bytes = number of bytes in the current character +} +``` + +> **Note:** `utf8StringSize` counts Unicode code points, not bytes. For ASCII-only strings both values are equal, but multi-byte sequences will differ. \ No newline at end of file diff --git a/osquery/utils/.enum_class_hash.md b/osquery/utils/.enum_class_hash.md new file mode 100644 index 00000000000..e8c11b400c2 --- /dev/null +++ b/osquery/utils/.enum_class_hash.md @@ -0,0 +1,29 @@ + +Provides a hash functor for enum class types, working around a known `libc++`/`libstdc++` bug ([LWG defect #2148](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148)) that prevents scoped enumerations from being used as keys in unordered containers. + +## Key Components + +- **`EnumClassHash`** — A functor struct in the `osquery` namespace with a templated `operator()` that casts any scoped enum value to `std::size_t`. The template is SFINAE-constrained via `std::enable_if` to only accept enum types. + +## Usage Example + +```cpp +#include "enum_class_hash.h" +#include + +enum class MyKey { Alpha, Beta, Gamma }; + +// Pass EnumClassHash as the third template argument +std::unordered_map myMap; + +myMap[MyKey::Alpha] = "alpha"; +myMap[MyKey::Beta] = "beta"; + +auto val = myMap[MyKey::Alpha]; // "alpha" +``` + +## Notes + +- This is a **temporary workaround** intended to be removed once the underlying standard library defect is resolved in all supported toolchains. +- The `std::enable_if` guard ensures a compile-time error if a non-enum type is accidentally passed, keeping usage type-safe. +- No external dependencies beyond `` from the C++ standard library. \ No newline at end of file diff --git a/osquery/utils/.map_take.md b/osquery/utils/.map_take.md new file mode 100644 index 00000000000..c9aca9f1d97 --- /dev/null +++ b/osquery/utils/.map_take.md @@ -0,0 +1,43 @@ + +Utility header providing safe, ergonomic key-value lookup functions for `std::map` and `std::unordered_map`, returning `Expected` results instead of requiring manual iterator checks. + +## Key Components + +- **`MapTakeError`** — Error enum with a single variant `NoSuchKey` returned when a key is absent from the map. +- **`impl::IsMap`** — Type trait (SFINAE helper) that constrains the template functions to only accept `std::map` or `std::unordered_map` types. +- **`tryTake(table, key)`** — Moves the value out of the map and erases the entry, returning `Expected`. Mutates the map. +- **`tryTakeCopy(table, key)`** — Returns a copy of the value without modifying the map. Accepts a `const` map reference. + +## Usage Example + +```cpp +#include + +std::map scores = {{"alice", 42}, {"bob", 7}}; + +// Copy lookup — map is unchanged +auto score = tryTakeCopy(scores, "alice").takeOr(0); +// score == 42 + +// Move lookup — entry is removed from the map +auto taken = tryTake(scores, "bob"); +if (taken) { + int val = taken.take(); // val == 7 + // "bob" is no longer in scores +} + +// Missing key — falls back to default +auto missing = tryTakeCopy(scores, "charlie").takeOr(-1); +// missing == -1 + +// Check error type explicitly +auto result = tryTakeCopy(scores, "ghost"); +if (!result) { + // result.getError() is MapTakeError::NoSuchKey +} +``` + +## Notes + +- Prefer `tryTakeCopy` for read-only access and `tryTake` when you intentionally want to consume and remove the entry. +- Both functions perform a **single lookup**, avoiding the double-lookup anti-pattern of `count()` + `at()`. \ No newline at end of file diff --git a/osquery/utils/.mutex.md b/osquery/utils/.mutex.md new file mode 100644 index 00000000000..d4da5fa5ac8 --- /dev/null +++ b/osquery/utils/.mutex.md @@ -0,0 +1,46 @@ + +Provides type aliases for Boost-based synchronization primitives used throughout the osquery codebase, centralizing mutex and lock definitions under the `osquery` namespace. + +## Key Components + +| Alias | Underlying Type | Purpose | +|---|---|---| +| `ConditionVariable` | `boost::condition_variable_any` | Thread condition signaling | +| `Mutex` | `boost::shared_timed_mutex` | Standard read/write mutex | +| `WriteLock` | `boost::unique_lock` | Exclusive write lock | +| `ReadLock` | `boost::shared_lock` | Shared read lock | +| `RecursiveMutex` | `boost::recursive_mutex` | Re-entrant mutex | +| `RecursiveLock` | `boost::unique_lock` | Lock for recursive mutex | +| `UpgradeLock` | `boost::upgrade_lock` | Upgradeable shared lock | +| `WriteUpgradeLock` | `boost::upgrade_to_unique_lock` | Promotes upgrade lock to exclusive | + +## Usage Example + +```cpp +#include + +osquery::Mutex dataMutex; +std::string sharedData; + +// Concurrent read access +void readData() { + osquery::ReadLock lock(dataMutex); + std::cout << sharedData; +} + +// Exclusive write access +void writeData(const std::string& value) { + osquery::WriteLock lock(dataMutex); + sharedData = value; +} + +// Upgrade from read to write +void upgradeWrite(const std::string& value) { + osquery::UpgradeLock upLock(dataMutex); + // ... inspect sharedData ... + osquery::WriteUpgradeLock writeLock(upLock); // promotes to exclusive + sharedData = value; +} +``` + +The upgrade lock pattern (`UpgradeLock` → `WriteUpgradeLock`) is particularly useful when a thread needs to read first and conditionally escalate to a write, avoiding a full lock/unlock cycle. \ No newline at end of file diff --git a/osquery/utils/.only_movable.md b/osquery/utils/.only_movable.md new file mode 100644 index 00000000000..bd15d7cdf85 --- /dev/null +++ b/osquery/utils/.only_movable.md @@ -0,0 +1,47 @@ + +Defines a base class mixin within the `osquery` namespace that enforces move-only semantics, preventing accidental copying of derived objects while explicitly permitting move construction and assignment. + +## Key Components + +### `only_movable` (class) + +A non-copyable, move-enabled abstract base class analogous to `boost::noncopyable`. Designed to be inherited by classes that manage exclusive ownership of resources (e.g., file handles, sockets, threads). + +| Member | Type | Description | +|---|---|---| +| `only_movable()` | `protected default` | Default constructor | +| `~only_movable()` | `protected default` | Default destructor | +| `only_movable(only_movable&&)` | `protected default` | Move constructor | +| `operator=(only_movable&&)` | `protected default` | Move assignment | +| `only_movable(const only_movable&)` | `public delete` | Copy constructor — **disabled** | +| `operator=(const only_movable&)` | `public delete` | Copy assignment — **disabled** | + +## Usage Example + +```cpp +#include + +namespace osquery { + +// Derived class inherits move-only semantics automatically +class ResourceHandle : public only_movable { + public: + ResourceHandle() = default; + + // Explicitly default move operations from the base + ResourceHandle(ResourceHandle&&) noexcept = default; + ResourceHandle& operator=(ResourceHandle&&) = default; + + // Copy is implicitly deleted via the base class +}; + +void example() { + ResourceHandle a; + ResourceHandle b = std::move(a); // OK: move allowed + // ResourceHandle c = b; // ERROR: copy deleted +} + +} // namespace osquery +``` + +> **Design note:** Deleting the copy members in `public` scope (rather than `private`) produces clearer compiler diagnostics — the error explicitly states the function is deleted rather than inaccessible. \ No newline at end of file diff --git a/osquery/utils/.rot13.md b/osquery/utils/.rot13.md new file mode 100644 index 00000000000..b811ecddd3f --- /dev/null +++ b/osquery/utils/.rot13.md @@ -0,0 +1,19 @@ + +Provides a ROT13 string decoding utility within the `osquery` namespace. + +## Key Components + +- **`rotDecode(const std::string& rot_string)`** — Decodes a ROT13-encoded string and returns the plaintext result. + +## Usage Example + +```c +#include "rot13.h" + +// Decode a ROT13-encoded string +std::string encoded = "Uryyb, Jbeyq!"; +std::string decoded = osquery::rotDecode(encoded); +// decoded == "Hello, World!" +``` + +> **Note:** ROT13 is a symmetric cipher — encoding and decoding use the same operation. Calling `rotDecode` on a plaintext string will also produce the ROT13-encoded output. \ No newline at end of file diff --git a/osquery/utils/.scope_guard.md b/osquery/utils/.scope_guard.md new file mode 100644 index 00000000000..c0c7f306ba2 --- /dev/null +++ b/osquery/utils/.scope_guard.md @@ -0,0 +1,31 @@ + +RAII-based scope guard utility that ensures a callable (functor, lambda, or function object) is invoked exactly once when the guard object goes out of scope, guaranteeing resource cleanup regardless of early returns or exceptions. + +## Key Components + +- **`Guard`** — Templated RAII class that stores a callable and invokes it in its destructor. Marked `final` to prevent inheritance. +- **`create(final_routine)`** — Helper factory function that constructs a `Guard` with perfect forwarding, enabling clean type deduction without explicit template parameters. + +## Usage Example + +```cpp +#include "scope_guard.h" +#include + +void processFile(const std::string& file_path) { + // Temp file is always removed when scope exits, + // even if an early return or exception occurs. + auto const cleaner = osquery::scope_guard::create( + [&file_path]() { std::filesystem::remove(file_path); } + ); + + if (someCondition()) { + return; // cleaner destructor fires here + } + + // ... more logic ... + +} // cleaner destructor fires here if not already +``` + +> **Note:** The guard is non-copyable and non-movable by default since no copy/move constructors are defined. Always capture the result of `create()` into a named `auto` variable — discarding it causes immediate destruction. \ No newline at end of file diff --git a/osquery/utils/aws/.aws_util.md b/osquery/utils/aws/.aws_util.md new file mode 100644 index 00000000000..239572a168a --- /dev/null +++ b/osquery/utils/aws/.aws_util.md @@ -0,0 +1,52 @@ + +Utility header for integrating the AWS SDK into osquery, providing HTTP clients, credential providers, and helpers for instantiating AWS service clients with osquery-specific configuration. + +## Key Components + +### Constants +| Constant | Description | +|---|---| +| `kEc2MetadataUrl` | EC2 instance metadata service URL | +| `kImdsTokenResource` | URL resource for IMDSv2 token requests | +| `kImdsTokenHeader` / `kImdsTokenTtlHeader` | HTTP headers for IMDSv2 API calls | + +### Classes +- **`AWSRegion`** — Validated wrapper around an AWS region string; constructed via `AWSRegion::make()` +- **`OsqueryHttpClientFactory`** / **`OsqueryHttpClient`** — Drop-in AWS SDK HTTP client backed by osquery's HTTP layer instead of libcurl +- **`OsqueryFlagsAWSCredentialsProvider`** — Reads credentials from osquery config flags (`aws_access_key_id`, `aws_secret_access_key`) +- **`OsquerySTSAWSCredentialsProvider`** — Obtains temporary credentials via AWS STS assume-role +- **`OsqueryAWSCredentialsProviderChain`** — Ordered credential resolution: STS → flags → profile → env vars → default profile → EC2 IMDS + +### Free Functions +- **`initAwsSdk()`** — One-time SDK initialization using `OsqueryHttpClientFactory` +- **`makeAWSClient()`** — Template factory that instantiates Kinesis, Firehose, STS, or EC2 clients with resolved region and credentials +- **`getIMDSToken()`** — Fetches an IMDSv2 session token with retry logic +- **`getInstanceIDAndRegion()`** — Returns EC2 instance ID and region from metadata service +- **`setAWSProxy()`** — Applies proxy config from osquery flags to a `ClientConfiguration` +- **`appendLogTypeToJson()`** — Injects a `log_type` key into a JSON log string +- **`enableFIPSInClientConfig()`** — Enables FIPS-compliant endpoints for a given service + +## Usage Example + +```cpp +// Initialize the SDK once at plugin startup +osquery::initAwsSdk(); + +// Build a validated region +auto region_result = osquery::AWSRegion::make("us-east-1"); +if (!region_result) { + return Status::failure("Invalid region"); +} + +// Instantiate a Kinesis client using osquery credentials + config +std::shared_ptr client; +Status s = osquery::makeAWSClient(client, region_result.get()); +if (!s.ok()) { + return s; +} + +// Append log type metadata before sending to AWS +std::string log = R"({"host":"web01","level":"info"})"; +osquery::appendLogTypeToJson("result", log); +// log → {"host":"web01","level":"info","log_type":"result"} +``` \ No newline at end of file diff --git a/osquery/utils/aws/tests/.aws_util_tests.md b/osquery/utils/aws/tests/.aws_util_tests.md new file mode 100644 index 00000000000..afc17465ee4 --- /dev/null +++ b/osquery/utils/aws/tests/.aws_util_tests.md @@ -0,0 +1,34 @@ + +Unit tests for AWS utility functions in osquery, validating credential provider chain behavior, region resolution, JSON log type appending, and proxy configuration. + +## Key Components + +### Test Fixture +- **`AwsUtilTests`** — GTest fixture that calls `initAwsSdk()` in `SetUp()` to initialize the AWS SDK before each test + +### Test Cases + +| Test | Description | +|------|-------------| +| `test_get_credentials` | Validates `OsqueryAWSCredentialsProviderChain` priority: flags → env vars → named profile → default profile | +| `test_get_region` | Validates `AWSRegion::make()` with valid/invalid regions, FIPS enforcement, profile-based region lookup, and validation bypass | +| `test_append_log_type_to_json` | Validates `appendLogTypeToJson()` with null, empty, and populated JSON strings | +| `test_set_proxy_valid` | Validates `setAWSProxy()` correctly maps proxy flags (host, port, scheme, credentials) to `ClientConfiguration` | +| `test_set_proxy_invalid` | Validates `setAWSProxy()` falls back to HTTPS when an invalid proxy scheme (e.g. `"htpt"`) is provided | + +## Usage Example + +```cpp +// Run all AWS util tests +./osquery_tests --gtest_filter="AwsUtilTests.*" + +// Run a specific test +./osquery_tests --gtest_filter="AwsUtilTests.test_get_credentials" +``` + +## Notes + +- **Credential priority order**: `FLAGS_aws_access_key_id/secret` → environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) → named AWS profile → `default` profile +- **Profile-based tests are skipped on Windows** — `OsqueryAWSCredentialsProviderChain` does not support credential file profiles on Windows +- **FIPS enforcement**: `AWSRegion::make()` returns `AWSRegionError::NotFIPSCompliant` for regions outside the FIPS-approved list when `FLAGS_aws_enforce_fips = true` +- Test credentials file is loaded from the test config directory at `aws/credentials` \ No newline at end of file diff --git a/osquery/utils/azure/.azure_util.md b/osquery/utils/azure/.azure_util.md new file mode 100644 index 00000000000..9978ff40a42 --- /dev/null +++ b/osquery/utils/azure/.azure_util.md @@ -0,0 +1,42 @@ + +Utility header for fetching and parsing Azure Instance Metadata Service (IMDS) responses within the osquery framework. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `getAzureKey` | Function | Extracts a string value by key from a parsed Azure metadata JSON document | +| `fetchAzureMetadata` | Function | Queries the Azure IMDS endpoint and populates a `JSON` document with the response | + +## Usage Example + +```c +#include + +namespace osquery { + +void readAzureInstanceInfo() { + JSON doc; + + // Fetch metadata from Azure IMDS endpoint + Status status = fetchAzureMetadata(doc); + if (!status.ok()) { + // Handle fetch error + return; + } + + // Extract individual fields from the metadata document + std::string vmId = getAzureKey(doc, "vmId"); + std::string region = getAzureKey(doc, "location"); + std::string vmSize = getAzureKey(doc, "vmSize"); +} + +} // namespace osquery +``` + +## Notes + +- Both functions live in the `osquery` namespace. +- `fetchAzureMetadata` returns an `osquery::Status`, allowing callers to distinguish between network failures, parse errors, and missing data. +- `getAzureKey` returns an empty `std::string` when the requested key is absent in the document. +- Depends on `osquery/utils/json/json.h` for the `JSON` wrapper type and `osquery/utils/status/status.h` for structured error reporting. \ No newline at end of file diff --git a/osquery/utils/caches/.lru-impl.md b/osquery/utils/caches/.lru-impl.md new file mode 100644 index 00000000000..1741ce1b7fc --- /dev/null +++ b/osquery/utils/caches/.lru-impl.md @@ -0,0 +1,32 @@ + +LRU cache template method implementations for the `osquery::caches::LRU` class, providing insert and lookup operations with automatic eviction of least-recently-used entries. + +## Key Components + +- **`LRU::insert(key, value)`** — Inserts or updates a key-value pair. On insertion, evicts the LRU entry if at capacity, prepends the key to the access queue, and stores the value with an iterator into the queue. On update, replaces the value and promotes the key to the front of the queue. + +- **`LRU::get(key)`** — Looks up a value by key. Returns `nullptr` on a cache miss. On a hit, promotes the key to the front of the access queue (unless already there) and returns a `const` pointer to the stored value. + +## Usage Example + +```cpp +#include "lru.h" // full LRU class declaration + +osquery::caches::LRU cache(3); + +// Insert entries +cache.insert("host", "server-01"); +cache.insert("ip", "192.168.1.1"); +cache.insert("os", "Linux"); + +// Lookup (promotes "host" to most-recently-used) +const std::string* val = cache.get("host"); +if (val) { + std::cout << *val; // "server-01" +} + +// Inserting a 4th entry evicts least-recently-used +cache.insert("arch", "x86_64"); // evicts "ip" (LRU) +``` + +> **Note:** Both methods return a `const ValueType*`. The pointer remains valid until the entry is evicted. Use `insert` to both add new entries and update existing ones in-place. \ No newline at end of file diff --git a/osquery/utils/caches/.lru.md b/osquery/utils/caches/.lru.md new file mode 100644 index 00000000000..603234e6256 --- /dev/null +++ b/osquery/utils/caches/.lru.md @@ -0,0 +1,53 @@ + +A generic, header-only LRU (Least Recently Used) cache implementation in the `osquery::caches` namespace, using a doubly-linked list and hash map to achieve O(1) insert and lookup operations. + +## Key Components + +### `LRU` (class template) + +| Member | Description | +|--------|-------------| +| `LRU(capacity)` | Constructor — sets the maximum number of cached entries | +| `insert(key, value)` | Inserts or replaces an entry; promotes it to the front of the queue | +| `get(key)` | Returns a `const` pointer to the cached value and promotes it, or `nullptr` if not found | +| `has(key)` | Checks key existence **without** affecting queue order | +| `size()` | Returns the current number of cached elements | +| `capacity()` | Returns the configured cache capacity | +| `evict()` *(private)* | Removes the least recently used entry (back of queue) | + +**Internal storage:** +- `QueueType` — `std::list` tracks access order +- `MapType` — `std::unordered_map` maps keys to values and their list iterators + +## Usage Example + +```cpp +#include + +// Create an LRU cache with a capacity of 3 +osquery::caches::LRU cache(3); + +// Insert entries +cache.insert("alpha", 1); +cache.insert("beta", 2); +cache.insert("gamma", 3); + +// Access a value (promotes "alpha" to front) +const int* val = cache.get("alpha"); +if (val != nullptr) { + // use *val == 1 +} + +// Check existence without changing queue order +if (cache.has("beta")) { + // "beta" is still in cache +} + +// Inserting a 4th entry evicts the LRU item ("beta" or "gamma") +cache.insert("delta", 4); + +std::size_t n = cache.size(); // <= 3 +std::size_t c = cache.capacity(); // == 3 +``` + +> **Note:** `insert` and `get` return a `const ValueType*`. A `nullptr` return from `get` indicates a cache miss. The implementation is defined in the companion header `lru-impl.h`, included at the bottom of this file. \ No newline at end of file diff --git a/osquery/utils/caches/tests/.lru.md b/osquery/utils/caches/tests/.lru.md new file mode 100644 index 00000000000..1f85d10ea89 --- /dev/null +++ b/osquery/utils/caches/tests/.lru.md @@ -0,0 +1,46 @@ + +LRU cache unit test suite for the `caches::LRU` implementation in osquery, validating core cache behaviors including insertion, retrieval, eviction, and pointer stability. + +## Key Components + +| Test Case | Description | +|---|---| +| `size` | Verifies `size()` accurately reflects number of inserted entries | +| `capacity` | Confirms `capacity()` returns the configured maximum and never changes | +| `get_non_existing` | Asserts `get()` returns `nullptr` for missing keys | +| `get_existing` | Asserts `get()` returns a valid pointer with correct value | +| `displace` | Validates LRU eviction — least-recently-used entry is displaced when cache is full | +| `reinsert` | Confirms re-inserting an existing key updates the stored value | +| `has` | Checks `has()` correctness for present and absent keys | +| `has_does_not_change_the_place_in_queue` | Ensures `has()` is a non-promoting read (does not affect eviction order) | +| `pointer_validity_after_insertions` | Guarantees returned pointers remain valid after subsequent insertions | + +## Usage Example + +```cpp +#include + +// Create a cache with capacity 4, mapping int keys to string values +auto cache = osquery::caches::LRU(4); + +// Insert entries; returns a pointer to the stored value +auto* ptr = cache.insert(1, "Arctic"); + +// Retrieve an entry by key (returns nullptr if not found) +auto* val = cache.get(1); +if (val != nullptr) { + // dereference safely + std::cout << *val; // "Arctic" +} + +// Check existence without promoting the entry in the eviction queue +if (cache.has(2)) { + // key 2 is present +} + +// Query current size vs. configured capacity +cache.size(); // number of stored entries +cache.capacity(); // maximum capacity (never changes) +``` + +> **Note:** `has()` intentionally does not promote entries in the LRU queue, meaning it will not prevent eviction. Use `get()` when access should count as a "use" for eviction purposes. \ No newline at end of file diff --git a/osquery/utils/config/.default_paths.md b/osquery/utils/config/.default_paths.md new file mode 100644 index 00000000000..31adc838811 --- /dev/null +++ b/osquery/utils/config/.default_paths.md @@ -0,0 +1,40 @@ + +Defines platform-specific default filesystem paths used by osquery for configuration, storage, logging, and runtime files across Linux, Windows, FreeBSD, and other Unix-like systems. + +## Key Components + +| Macro | Purpose | +|---|---| +| `OSQUERY_HOME` | Base directory for config, flagfile, extensions, and module autoload | +| `OSQUERY_DB_HOME` | RocksDB persistent storage location | +| `OSQUERY_SOCKET` | Unix domain socket (or Windows named pipe) path | +| `OSQUERY_PIDFILE` | Process ID file directory | +| `OSQUERY_LOG_HOME` | Log output directory (used by the filesystem logger plugin) | +| `OSQUERY_CERTS_HOME` | TLS/SSL certificate bundle directory | + +## Platform Defaults + +| Macro | Linux | Windows | FreeBSD | Other | +|---|---|---|---|---| +| `OSQUERY_HOME` | `/etc/osquery/` | `\Program Files\osquery\` | `/var/db/osquery/` | `/var/osquery/` | +| `OSQUERY_DB_HOME` | `/var/osquery/` | *(same as HOME)* | *(same as HOME)* | *(same as HOME)* | +| `OSQUERY_SOCKET` | `/var/osquery/` | `\\.\pipe\` | `/var/run/` | *(same as DB_HOME)* | +| `OSQUERY_LOG_HOME` | `/var/log/osquery/` | `...\log\` | `/var/log/osquery/` | `/var/log/osquery/` | +| `OSQUERY_CERTS_HOME` | `/opt/osquery/share/osquery/certs/` | `...\certs\` | `/etc/ssl/` | `...certs/` | + +## Usage Example + +```c +#include "default_paths.h" +#include + +int main(void) { + printf("Config home: %s\n", OSQUERY_HOME); + printf("Database home: %s\n", OSQUERY_DB_HOME); + printf("Log directory: %s\n", OSQUERY_LOG_HOME); + printf("Certificates: %s\n", OSQUERY_CERTS_HOME); + return 0; +} +``` + +> These macros serve as compile-time defaults and are typically overridden at runtime via CLI flags or configuration files. \ No newline at end of file diff --git a/osquery/utils/conversions/.castvariant.md b/osquery/utils/conversions/.castvariant.md new file mode 100644 index 00000000000..054c8294d97 --- /dev/null +++ b/osquery/utils/conversions/.castvariant.md @@ -0,0 +1,34 @@ + +Provides a type-safe string conversion utility for `boost::variant` types used within the osquery framework, ensuring consistent serialization of numeric and string values — particularly preserving floating-point notation (e.g., `0.0` instead of `0`). + +## Key Components + +### `CastVisitor` (class) +A `boost::static_visitor` that handles conversion of each supported variant type: + +| Overload | Behavior | +|---|---| +| `operator()(const long long&)` | Converts integer via `std::to_string` | +| `operator()(const double&)` | Formats with SQLite-compatible 15-digit precision; appends `.0` if no decimal point is present | +| `operator()(const std::string&)` | Returns string unchanged | + +### `castVariant` (inline function) +Applies `CastVisitor` to a `boost::variant`, returning a `std::string` representation. + +## Usage Example + +```c +#include "castvariant.h" + +boost::variant v1 = 42LL; +boost::variant v2 = 3.14; +boost::variant v3 = 0.0; +boost::variant v4 = std::string("hello"); + +osquery::castVariant(v1); // "42" +osquery::castVariant(v2); // "3.14" +osquery::castVariant(v3); // "0.0" (not "0") +osquery::castVariant(v4); // "hello" +``` + +> **Note:** The double precision is capped at `std::numeric_limits::digits10` (15 significant digits) to align with SQLite's precision constraints. \ No newline at end of file diff --git a/osquery/utils/conversions/.join.md b/osquery/utils/conversions/.join.md new file mode 100644 index 00000000000..bd9313400e4 --- /dev/null +++ b/osquery/utils/conversions/.join.md @@ -0,0 +1,30 @@ + +Provides a string joining utility within the `osquery` namespace, wrapping Boost's `algorithm::join` to concatenate sequence elements with a delimiter token. + +## Key Components + +- **`join(s, tok)`** — Template function that joins any sequence of strings (e.g., `std::vector`) into a single `std::string`, inserting `tok` between each element. Delegates to `boost::algorithm::join`. + +## Usage Example + +```c +#include "join.h" +#include +#include + +// Join a vector of strings with a comma delimiter +std::vector parts = {"alpha", "beta", "gamma"}; +std::string result = osquery::join(parts, ", "); +// result == "alpha, beta, gamma" + +// Works with any compatible sequence type +std::list flags = {"--verbose", "--json", "--output"}; +std::string cmd = osquery::join(flags, " "); +// cmd == "--verbose --json --output" +``` + +## Notes + +- Accepts any sequence type (`std::vector`, `std::list`, etc.) via the `SequenceType` template parameter. +- Thin wrapper — all behavior is governed by `boost::algorithm::join`. +- Defined as `inline` to avoid multiple-definition issues across translation units. \ No newline at end of file diff --git a/osquery/utils/conversions/.split.md b/osquery/utils/conversions/.split.md new file mode 100644 index 00000000000..80799ce338d --- /dev/null +++ b/osquery/utils/conversions/.split.md @@ -0,0 +1,35 @@ + +Utility header providing string splitting functions within the `osquery` namespace, offering delimiter-based and whitespace tokenization for `std::string` and `std::string_view` types. + +## Key Components + +| Function | Signature | Description | +|---|---|---| +| `split` | `(string, delim="\t ")` | Splits a string by a delimiter; defaults to whitespace/tab | +| `split` | `(string, char, occurrences)` | Splits a string by a char delimiter up to N times | +| `vsplit` | `(string_view, char)` | High-performance split returning `string_view` tokens (2x+ faster) | + +## Usage Example + +```cpp +#include + +// Whitespace split (default) +auto tokens = osquery::split("foo bar baz"); +// → ["foo", "bar", "baz"] + +// Custom string delimiter +auto parts = osquery::split("a,b,c", ","); +// → ["a", "b", "c"] + +// Limited occurrences +auto limited = osquery::split("a:b:c:d", ':', 2); +// → ["a", "b", "c:d"] + +// High-performance string_view split (no heap allocations per token) +std::string_view line = "col1|col2|col3"; +auto views = osquery::vsplit(line, '|'); +// → ["col1", "col2", "col3"] as string_views +``` + +> **Performance note:** Prefer `vsplit` over `split` when processing large strings or log lines where you need fast tokenization without unnecessary heap allocations. Since it returns `std::string_view` tokens, ensure the source string outlives the result vector. \ No newline at end of file diff --git a/osquery/utils/conversions/.to.md b/osquery/utils/conversions/.to.md new file mode 100644 index 00000000000..b50ce79f8e6 --- /dev/null +++ b/osquery/utils/conversions/.to.md @@ -0,0 +1,44 @@ + +Provides a non-failing, `noexcept` conversion utility within the `osquery` namespace for transforming values to other types without throwing exceptions. + +## Key Components + +### `to(FromType from)` +A template function that converts a value from one type to another. Currently specializes for: + +- **Enum → `std::string`**: Converts any scoped (`enum class`) or unscoped (`enum`) value into a human-readable string combining the demangled type name and underlying integer value. + +**Template constraints (via `std::enable_if`):** +- `FromType` must satisfy `std::is_enum` +- `ToType` must be `std::string` + +**Output format:** `"TypeName[underlyingValue]"` + +**Dependencies:** +- `boost::core::demangle` — resolves compiler-mangled type names at runtime +- `` — for `std::is_enum`, `std::is_same`, and `std::underlying_type` + +## Usage Example + +```cpp +#include "to.h" + +enum class Status { + Active = 1, + Inactive = 2, +}; + +enum LegacyFlag { + Enabled = 10, +}; + +// Scoped enum +std::string s1 = osquery::to(Status::Active); +// Result: "Status[1]" + +// Unscoped enum +std::string s2 = osquery::to(Enabled); +// Result: "LegacyFlag[10]" +``` + +> **Note:** The function is marked `noexcept`, making it safe for use in error-handling paths or contexts where exception propagation must be avoided. The demangled type name format may vary slightly across compilers (GCC, Clang, MSVC), but `boost::core::demangle` normalizes this where possible. \ No newline at end of file diff --git a/osquery/utils/conversions/.trim.md b/osquery/utils/conversions/.trim.md new file mode 100644 index 00000000000..a04e925d550 --- /dev/null +++ b/osquery/utils/conversions/.trim.md @@ -0,0 +1,21 @@ + +Provides a utility function for trimming leading and trailing whitespace from a `std::string_view` within the `osquery` namespace. + +## Key Components + +- **`osquery::trim(std::string_view input)`** — Returns a `std::string_view` with all leading and trailing spaces removed from the input. Non-owning; no heap allocation. + +## Usage Example + +```c +#include "trim.h" +#include + +int main() { + std::string_view raw = " hello world "; + std::string_view trimmed = osquery::trim(raw); + std::cout << trimmed; // "hello world" +} +``` + +> **Note:** The returned `std::string_view` references the original string's memory. Ensure the source string outlives the returned view. \ No newline at end of file diff --git a/osquery/utils/conversions/.tryto.md b/osquery/utils/conversions/.tryto.md new file mode 100644 index 00000000000..23304fc2f0e --- /dev/null +++ b/osquery/utils/conversions/.tryto.md @@ -0,0 +1,47 @@ + +Provides type-safe conversion utilities for transforming between C++ types (string-to-integer, string-to-bool, and identity conversions) using the `Expected` error-handling pattern instead of exceptions. + +## Key Components + +### `ConversionError` Enum +Error codes returned on failed conversions: +- `InvalidArgument` — input string cannot be parsed +- `OutOfRange` — parsed value exceeds the target type's range +- `Unknown` — unexpected error during conversion + +### `tryTo(from, base)` — Primary API +Overloaded template function with three specializations: +- **Identity conversion** — when `ToType` matches `FromType`, forwards the value directly +- **String → Integer** — converts `std::string`/`std::wstring` to any integer type (`int`, `long`, `unsigned long long`, etc.) with configurable base (default: 10) +- **String → Bool** — parses human-readable boolean strings (`"1"`, `"yes"`, `"y"`, `"true"`, `"0"`, `"no"`, `"n"`, `"false"`, etc.) + +### `impl::throwingStringToInt(from, base)` +Internal dispatch layer that selects the correct STL conversion function (`stoi`, `stol`, `stoll`, `stoul`, `stoull`) based on the target integer type. + +### `operator"" _sz` +User-defined literal converting `unsigned long long` to `uint64_t` for size expressions. + +## Usage Example + +```cpp +// String to integer +auto result = osquery::tryTo("42"); +if (result) { + int value = result.get(); // 42 +} + +// String to integer with base +auto hex = osquery::tryTo("FF", 16); + +// String to bool +auto flag = osquery::tryTo("yes"); +if (flag) { + bool val = flag.get(); // true +} + +// Error handling +auto bad = osquery::tryTo("not_a_number"); +if (bad.isError()) { + // bad.getError() == ConversionError::InvalidArgument +} +``` \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/.cfdata.md b/osquery/utils/conversions/darwin/.cfdata.md new file mode 100644 index 00000000000..41967c69b79 --- /dev/null +++ b/osquery/utils/conversions/darwin/.cfdata.md @@ -0,0 +1,27 @@ + +Utility header for converting Core Foundation `CFDataRef` objects to C++ `std::string` values on macOS/Darwin platforms. + +## Key Components + +### Functions + +| Function | Description | +|---|---| +| `stringFromCFData(const CFDataRef& cf_data)` | Converts a Core Foundation `CFDataRef` to a `std::string` | + +## Usage Example + +```c +#include "cfdata.h" +#include + +// Convert a CFDataRef to std::string +CFDataRef rawData = /* obtained from a CF API */; +std::string result = osquery::stringFromCFData(rawData); +``` + +## Notes + +- macOS/Darwin only — depends on the `CoreFoundation` framework +- Lives in the `osquery` namespace +- Typically used when consuming binary or raw byte data from macOS system APIs that return `CFDataRef` handles \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/.cfdictionary.md b/osquery/utils/conversions/darwin/.cfdictionary.md new file mode 100644 index 00000000000..a367642cbe1 --- /dev/null +++ b/osquery/utils/conversions/darwin/.cfdictionary.md @@ -0,0 +1,37 @@ + +Utility header for extracting typed values from Core Foundation `CFDictionaryRef` objects within the osquery macOS/Darwin data collection framework. + +## Key Components + +### Functions + +- **`getPropertiesFromDictionary(dict, key)`** — Looks up a key in a `CFDictionaryRef` and returns the corresponding value as a `std::string`. Relies on the sibling CF conversion helpers (`cfdata.h`, `cfnumber.h`, `cfstring.h`) to handle type coercion. + +### Dependencies + +| Header | Purpose | +|---|---| +| `cfdata.h` | CF `Data` → `std::string` conversion | +| `cfnumber.h` | CF `Number` → `std::string` conversion | +| `cfstring.h` | CF `String` → `std::string` conversion | +| `CoreFoundation/CoreFoundation.h` | Native CF types (`CFDictionaryRef`, etc.) | + +## Usage Example + +```cpp +#include "cfdictionary.h" +#include + +// Assume `plistDict` is a CFDictionaryRef obtained from a parsed plist +CFDictionaryRef plistDict = /* ... */; + +std::string version = osquery::getPropertiesFromDictionary(plistDict, "CFBundleVersion"); +std::string bundleId = osquery::getPropertiesFromDictionary(plistDict, "CFBundleIdentifier"); + +// Returns an empty string if the key is missing or conversion fails +if (!version.empty()) { + // use version string +} +``` + +> **Platform note:** This header is macOS/Darwin-only. It depends on `CoreFoundation` and is compiled exclusively for Apple platform targets within osquery. \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/.cfnumber.md b/osquery/utils/conversions/darwin/.cfnumber.md new file mode 100644 index 00000000000..b80a7db02a0 --- /dev/null +++ b/osquery/utils/conversions/darwin/.cfnumber.md @@ -0,0 +1,32 @@ + +Utility header for converting Apple CoreFoundation `CFNumberRef` values to C++ `std::string` objects within the osquery framework. + +## Key Components + +### Functions + +| Function | Description | +|---|---| +| `stringFromCFNumber(const CFDataRef&)` | Converts a `CFNumberRef` to a `std::string`, auto-detecting the number type | +| `stringFromCFNumber(const CFDataRef&, CFNumberType)` | Converts a `CFNumberRef` to a `std::string` using an explicitly specified `CFNumberType` | + +## Usage Example + +```cpp +#include +#include + +// Auto-detect number type +CFNumberRef cf_num = /* obtained from a CF API */; +std::string value = osquery::stringFromCFNumber( + reinterpret_cast(cf_num) +); + +// Explicit type conversion +std::string int_value = osquery::stringFromCFNumber( + reinterpret_cast(cf_num), + kCFNumberIntType +); +``` + +> **Note:** This header is macOS-only, as it depends on the CoreFoundation framework. It is intended for use in osquery Darwin-specific table implementations that parse CF property list or registry data structures. \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/.cfstring.md b/osquery/utils/conversions/darwin/.cfstring.md new file mode 100644 index 00000000000..2cc21378fec --- /dev/null +++ b/osquery/utils/conversions/darwin/.cfstring.md @@ -0,0 +1,25 @@ + +Utility header for converting Apple CoreFoundation `CFStringRef` objects to standard C++ `std::string` values within the osquery namespace. + +## Key Components + +- **`stringFromCFString(const CFStringRef& cf_string)`** — Converts a CoreFoundation `CFStringRef` to a `std::string`. Useful when working with macOS system APIs that return CF types. + +## Usage Example + +```c +#include "cfstring.h" +#include + +// Convert a CFStringRef returned by a macOS API to std::string +CFStringRef cf_name = CFStringCreateWithCString( + kCFAllocatorDefault, "example", kCFStringEncodingUTF8); + +std::string name = osquery::stringFromCFString(cf_name); +CFRelease(cf_name); +``` + +## Notes + +- macOS only — depends on `CoreFoundation`, which is not available on Linux or Windows. +- Caller is responsible for memory management of the `CFStringRef` (follow standard CF ownership rules). \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/.cftime.md b/osquery/utils/conversions/darwin/.cftime.md new file mode 100644 index 00000000000..bf5ea0812dd --- /dev/null +++ b/osquery/utils/conversions/darwin/.cftime.md @@ -0,0 +1,25 @@ + +Utility header for converting CoreFoundation `CFAbsoluteTime` values to standard C++ strings on macOS/Apple platforms. + +## Key Components + +- **`stringFromCFAbsoluteTime(const CFDataRef& cf_abstime)`** — Converts a `CFDataRef` containing a `CFAbsoluteTime` value into a human-readable `std::string`. + +## Usage Example + +```c +#include "cftime.h" +#include + +// Assume cf_data is a CFDataRef containing a CFAbsoluteTime +CFDataRef cf_data = /* ... obtain from system API ... */; + +std::string timeStr = osquery::stringFromCFAbsoluteTime(cf_data); +// timeStr now contains a formatted time string +``` + +## Notes + +- macOS/Apple only — depends on `CoreFoundation` framework. +- Lives within the `osquery` namespace. +- Typical use case: parsing macOS plist or system data where timestamps are stored as `CFAbsoluteTime` (seconds since Jan 1, 2001). \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/.iokit.md b/osquery/utils/conversions/darwin/.iokit.md new file mode 100644 index 00000000000..2dfd6cc9bab --- /dev/null +++ b/osquery/utils/conversions/darwin/.iokit.md @@ -0,0 +1,51 @@ + +Provides IOKit utility functions and constants for querying macOS hardware device information via Apple's IOKit framework. + +## Key Components + +### Device Class Name Constants + +String constants identifying IOKit device class types: + +| Constant | Device Type | +|---|---| +| `kIOUSBDeviceClassName_` | USB devices | +| `kIOPCIDeviceClassName_` | PCI devices | +| `kIOPlatformExpertDeviceClassName_` | Platform expert devices | +| `kIOACPIPlatformDeviceClassName_` | ACPI platform devices | +| `kIOPlatformDeviceClassName_` | Generic platform devices | +| `kAppleARMIODeviceClassName_` | Apple ARM I/O devices | + +### `IOKitPCIProperties` Struct + +Holds parsed PCI device properties extracted from the IOKit `"compatible"` property string: + +- `vendor_id` — PCI vendor identifier +- `model_id` — PCI model/device identifier +- `pci_class` — PCI device class +- `driver` — Associated driver name + +Constructed via `IOKitPCIProperties(const std::string& compatible)` which parses the raw compatible string automatically. + +### Functions + +- **`getIOKitProperty`** — Retrieves a string property value from a `CFMutableDictionaryRef` by key +- **`getNumIOKitProperty`** — Retrieves a numeric (`long long int`) property value from a `CFMutableDictionaryRef` by key +- **`idToHex`** — Converts a device ID string to its hexadecimal representation in-place + +## Usage Example + +```c +CFMutableDictionaryRef details = getDeviceDetails(); // IOKit registry entry + +// Read string and numeric properties +std::string vendor = osquery::getIOKitProperty(details, "vendor-id"); +long long int deviceClass = osquery::getNumIOKitProperty(details, "class-code"); + +// Parse PCI compatible string +osquery::IOKitPCIProperties pci("pci8086,1502"); +// pci.vendor_id == "8086", pci.model_id == "1502" + +// Convert ID to hex representation +osquery::idToHex(vendor); +``` \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/tests/.cfdictionary.md b/osquery/utils/conversions/darwin/tests/.cfdictionary.md new file mode 100644 index 00000000000..5657aa98914 --- /dev/null +++ b/osquery/utils/conversions/darwin/tests/.cfdictionary.md @@ -0,0 +1,39 @@ + +Unit test file for the `getPropertiesFromDictionary` Darwin utility, validating conversion of CoreFoundation dictionary values (strings, numbers, and booleans) to `std::string`. + +## Key Components + +- **`ConversionsTests`** — GTest fixture class housing CFDictionary conversion tests +- **`getPropertiesFromDictionary_test`** — Validates extraction and string conversion of CFDictionary values across three CoreFoundation types: + - `CFStringRef` → `std::string` + - `CFNumberRef` → decimal string representation + - `CFBooleanRef` → `"1"` / `"0"` + +## Usage Example + +```cpp +// Construct a mutable CFDictionary with mixed value types +CFMutableDictionaryRef dict = CFDictionaryCreateMutable( + kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + +CFDictionaryAddValue(dict, CFSTR("key"), CFSTR("value")); + +int num = 42; +CFNumberRef n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); +CFDictionaryAddValue(dict, CFSTR("count"), n); + +// Retrieve values as std::string +std::string val = getPropertiesFromDictionary(dict, "key"); // "value" +std::string count = getPropertiesFromDictionary(dict, "count"); // "42" + +CFRelease(n); +CFRelease(dict); +``` + +## Notes + +- Lives under `osquery::` namespace; depends on `cfdictionary.h` and `cfstring.h` Darwin conversion headers +- Booleans serialize as `"1"` (true) and `"0"` (false), matching typical osquery string-typed table column conventions +- Proper `CFRelease` calls are verified in teardown to guard against CF memory leaks in tests \ No newline at end of file diff --git a/osquery/utils/conversions/darwin/tests/.cfstring.md b/osquery/utils/conversions/darwin/tests/.cfstring.md new file mode 100644 index 00000000000..f19063c6028 --- /dev/null +++ b/osquery/utils/conversions/darwin/tests/.cfstring.md @@ -0,0 +1,35 @@ + +Unit test file for the `stringFromCFString` utility function, verifying correct conversion of CoreFoundation `CFStringRef` objects to `std::string` on Darwin (macOS) platforms. + +## Key Components + +- **`ConversionsTests`** — GTest fixture class providing the test suite context +- **`TEST_F(ConversionsTests, stringFromCFString)`** — Test case that validates round-trip UTF-8 conversion from a raw byte string through `CFStringRef` back to `std::string` + +## Usage Example + +```cpp +#include + +// Create a CFStringRef from UTF-8 bytes +std::string input = u8"空間"; // Unicode multi-byte string +CFStringRef cf_string_ref = CFStringCreateWithBytes( + kCFAllocatorDefault, + reinterpret_cast(input.data()), + input.size(), + kCFStringEncodingUTF8, + false +); + +// Convert back to std::string +std::string result = stringFromCFString(cf_string_ref); +// result == "空間" +``` + +## Test Coverage + +| Test Case | Input | Validates | +|---|---|---| +| `stringFromCFString` | UTF-8 Japanese characters `"空間"` | Lossless round-trip conversion from `CFStringRef` → `std::string` | + +The test specifically uses a multi-byte Unicode string (`空間`, meaning "space/interval" in Japanese) to ensure the conversion handles non-ASCII characters correctly, which is a common edge case in CoreFoundation string handling on macOS. \ No newline at end of file diff --git a/osquery/utils/conversions/tests/.join.md b/osquery/utils/conversions/tests/.join.md new file mode 100644 index 00000000000..8e3250f9e10 --- /dev/null +++ b/osquery/utils/conversions/tests/.join.md @@ -0,0 +1,22 @@ + +Unit test file validating the `join()` string utility function from `osquery/utils/conversions/join.h`. + +## Key Components + +- **`ConversionsTests`** — GTest fixture class for conversion utility tests +- **`test_join`** — Test case verifying that a vector of strings is correctly concatenated with a delimiter + +## Usage Example + +```cpp +#include + +std::vector parts = {"one", "two", "three"}; +std::string result = join(parts, ", "); +// result == "one, two, three" +``` + +## Notes + +- Tests the core behavior of `join()`: combining a `std::vector` with a string delimiter +- Part of the osquery conversions test suite under the `osquery` namespace \ No newline at end of file diff --git a/osquery/utils/conversions/tests/.split.md b/osquery/utils/conversions/tests/.split.md new file mode 100644 index 00000000000..cf06e474aeb --- /dev/null +++ b/osquery/utils/conversions/tests/.split.md @@ -0,0 +1,35 @@ + +Unit test file for the `split` utility functions in osquery's conversions library, verifying string splitting behavior across whitespace and delimiter-based scenarios. + +## Key Components + +- **`SplitConversionsTests`** — GTest fixture class for split conversion tests +- **`SplitStringTestData`** — Test data struct holding input string, delimiter, and expected output vector +- **`generateSplitStringTestData()`** — Generates test cases for the whitespace-based `split()` function (handles tabs, multiple spaces, leading/trailing whitespace) +- **`generateVSplitStringTestData()`** — Generates test cases for the delimiter-based `vsplit()` function (supports arbitrary single-character delimiters) +- **`test_split`** — Validates `split()` against whitespace-delimited test cases +- **`test_vplit`** — Validates `vsplit()` with single-character delimiters including comma and space +- **`test_split_occurrences`** — Validates `split()` with a max-occurrence limit (e.g., split on `:` at most once) + +## Usage Example + +```cpp +// Whitespace split (handles tabs and multiple spaces) +auto tokens = split("a b\tc"); +// tokens == {"a", "b", "c"} + +// Delimiter-based split +auto parts = vsplit("a,b,c", ','); +// parts == {"a", "b", "c"} + +// Split with max occurrences +auto limited = split("T: 'S:S'", ':', 1); +// limited == {"T", "'S:S'"} +``` + +## Test Coverage Notes + +- Empty strings return empty vectors +- Strings with only delimiters (e.g., `" "`) return empty vectors +- Leading/trailing delimiters are ignored for whitespace splits +- Non-space delimiters (`,`) preserve leading whitespace in tokens (e.g., `" ,a"` → `{" ", "a"}`) \ No newline at end of file diff --git a/osquery/utils/conversions/tests/.to.md b/osquery/utils/conversions/tests/.to.md new file mode 100644 index 00000000000..0dad83c6035 --- /dev/null +++ b/osquery/utils/conversions/tests/.to.md @@ -0,0 +1,31 @@ + +Unit tests for the `to<>` conversion utility in osquery, specifically validating string conversion behavior for scoped (`enum class`) enumerations. + +## Key Components + +- **`NonFailingConversionsTests`** — GTest fixture class grouping conversion tests that are expected to succeed without errors. +- **`TestGreenColor`** / **`TestOrangeColor`** — Local `enum class` types used as test subjects to verify generic enum-to-string conversion. +- **`to_string_from_enum_class`** — Verifies that `to()` produces a human-readable representation (e.g., `"TestGreenColor[0]"`) for scoped enum values. +- **`to_string_from_old_enum`** — Verifies the same behavior for a second enum type, covering all four enumerators including index-based formatting. + +## Usage Example + +```cpp +#include + +enum class TestGreenColor { + Green, // index 0 + Pine, // index 1 + Fern, // index 2 +}; + +// Converts enum value to a string containing "TestGreenColor[N]" +std::string result = to(TestGreenColor::Pine); +// result contains: "TestGreenColor[1]" +``` + +## Notes + +- The `to()` template produces output in the format `TypeName[index]`, where the index corresponds to the enum's underlying integer value. +- Tests use `find() != npos` rather than exact equality, allowing the implementation to include additional context around the formatted enum string. +- Both test cases confirm that the conversion is zero-indexed and consistent across different enum types. \ No newline at end of file diff --git a/osquery/utils/conversions/tests/.trim.md b/osquery/utils/conversions/tests/.trim.md new file mode 100644 index 00000000000..bb4ddc76dee --- /dev/null +++ b/osquery/utils/conversions/tests/.trim.md @@ -0,0 +1,38 @@ + +Unit test file for the `trim()` string utility function from `osquery/utils/conversions/trim.h`, validating whitespace removal from both ends of a string. + +## Key Components + +- **`TrimConversionsTests`** — GTest fixture class for grouping trim-related tests +- **`TrimStringTestData`** — Struct holding input/expected string pairs for parameterized test cases +- **`generateTrimStringTestData()`** — Generates a vector of test cases covering all standard whitespace characters and edge cases +- **`TEST_F(TrimConversionsTests, test_trim)`** — Iterates all test cases and asserts `trim()` output matches expected strings + +## Test Coverage + +| Scenario | Input | Expected | +|---|---|---| +| No whitespace | `"a bc"` | `"a bc"` | +| Leading spaces | `" a bc"` | `"a bc"` | +| Empty string | `""` | `""` | +| Trailing spaces | `"a b c "` | `"a b c"` | +| Both sides | `" a b c "` | `"a b c"` | +| Tab (`\t`) | `"\tabc\t"` | `"abc"` | +| Newline (`\n`) | `"\nabc\n"` | `"abc"` | +| Carriage return (`\r`) | `"\rabc\r"` | `"abc"` | +| Form feed (`\f`) | `"\fabc\f"` | `"abc"` | +| Vertical tab (`\v`) | `"\vabc\v"` | `"abc"` | +| All spaces | `" "` | `""` | + +## Usage Example + +```cpp +#include + +// Direct usage of the tested function +std::string result = trim(" hello world "); +// result == "hello world" + +std::string tab_result = trim("\thello\t"); +// tab_result == "hello" +``` \ No newline at end of file diff --git a/osquery/utils/conversions/tests/.tryto.md b/osquery/utils/conversions/tests/.tryto.md new file mode 100644 index 00000000000..e2ce9d36fc1 --- /dev/null +++ b/osquery/utils/conversions/tests/.tryto.md @@ -0,0 +1,43 @@ + +Unit test file for the `tryTo` conversion utility in osquery, validating type-safe string-to-value conversions across integer types, booleans, and same-type identity conversions. + +## Key Components + +- **`ConversionsTests`** — GTest fixture class housing all conversion test cases +- **`testTryToForUnsignedInt()`** — Template helper testing unsigned integer parsing including decimal, hex (`0x`/uppercase), binary, and octal bases, plus exhaustive invalid-input rejection +- **`testTryToForSignedInt()`** — Extends unsigned tests with negative number handling and sign-prefix edge cases +- **`testTryToForString/Value/Rvalue/LValue/ConstLValue`** — Template helpers verifying `tryTo` works correctly for all value categories (rvalue, lvalue, const lvalue) and both `std::string`/`std::wstring` +- **Boolean conversion tests** — Validates a wide set of truthy (`"1"`, `"yes"`, `"true"`, `"ok"`, `"enable"`) and falsy (`"0"`, `"no"`, `"false"`, `"disable"`) string inputs, plus rejection of ambiguous or unrecognized strings + +## Test Coverage Matrix + +| Test Case | Types Covered | +|---|---| +| Signed integers | `int`, `long`, `long long`, `int32_t`, `int64_t`, `intmax_t` | +| Unsigned integers | `unsigned`, `unsigned long`, `unsigned long long`, `uint32_t`, `uint64_t`, `uintmax_t`, `size_t` | +| Boolean strings | 28 valid inputs, 70+ invalid inputs | +| Same-type passthrough | Arbitrary class type | + +## Usage Example + +```cpp +// Parsing an integer from string +auto result = tryTo(std::string{"42"}); +if (!result.isError()) { + int value = result.get(); // 42 +} + +// Hex parsing +auto hex = tryTo(std::string{"0xFB"}, 16); +// hex.get() == 251 + +// Boolean parsing +auto flag = tryTo(std::string{"enable"}); +// flag.get() == true + +// Error handling +auto bad = tryTo(std::string{"abc"}); +if (bad.isError()) { + auto code = bad.getErrorCode(); // ConversionError::InvalidArgument +} +``` \ No newline at end of file diff --git a/osquery/utils/conversions/windows/.strings.md b/osquery/utils/conversions/windows/.strings.md new file mode 100644 index 00000000000..a1181fe9b65 --- /dev/null +++ b/osquery/utils/conversions/windows/.strings.md @@ -0,0 +1,39 @@ + +Windows-specific string utility header providing conversion functions between narrow/wide strings and Windows-native types within the osquery framework. + +## Key Components + +| Function | Description | +|---|---| +| `stringToWstring` | Converts `std::string` → `std::wstring` | +| `wstringToString(const std::wstring&)` | Converts `std::wstring` → `std::string` | +| `wstringToString(const wchar_t*)` | Converts wide C-string → `std::string` | +| `cimDatetimeToUnixtime` | Converts WMI CIM datetime string → Unix timestamp (`LONGLONG`) | +| `bstrToString` | Converts Windows `BSTR` → `std::string` | +| `swapEndianess` | Swaps byte order of a string (little ↔ big endian) | +| `errorDwordToString` | Converts a Windows `DWORD` error code → human-readable `std::string` | + +All functions reside in the `osquery` namespace and are Windows-only (depends on `comutil.h`). + +## Usage Example + +```c +#include "strings.h" + +// Wide/narrow string conversion +std::wstring wide = osquery::stringToWstring("Hello, Windows"); +std::string narrow = osquery::wstringToString(wide); + +// WMI CIM datetime to Unix timestamp +LONGLONG ts = osquery::cimDatetimeToUnixtime("20240101120000.000000+000"); + +// Convert BSTR from WMI query result +BSTR bstr = SysAllocString(L"WMI Result"); +std::string result = osquery::bstrToString(bstr); + +// Decode a Windows error code +std::string errMsg = osquery::errorDwordToString(GetLastError()); + +// Swap endianness (e.g., for GUID/UUID processing) +std::string swapped = osquery::swapEndianess(rawBytes); +``` \ No newline at end of file diff --git a/osquery/utils/conversions/windows/.windows_time.md b/osquery/utils/conversions/windows/.windows_time.md new file mode 100644 index 00000000000..dcc19b84842 --- /dev/null +++ b/osquery/utils/conversions/windows/.windows_time.md @@ -0,0 +1,38 @@ + +Windows-specific utility header providing timestamp conversion functions that normalize various Windows time formats into Unix epoch values for consistent cross-platform time handling within osquery. + +## Key Components + +| Function | Input | Description | +|---|---|---| +| `filetimeToUnixtime` | `const FILETIME&` | Converts Windows `FILETIME` structure to Unix epoch | +| `longIntToUnixtime` | `LARGE_INTEGER&` | Converts Windows `LARGE_INTEGER` value to Unix epoch | +| `littleEndianToUnixTime` | `const std::string&` | Converts little-endian encoded `FILETIME` (as used in the Windows Registry) to Unix epoch | +| `parseFatTime` | `const std::string&` | Parses and converts FAT/DOS timestamp format to Unix epoch | + +All functions return `LONGLONG` representing seconds since the Unix epoch. + +## Usage Example + +```c +#include + +namespace osquery { + +// Convert a FILETIME (e.g., from file metadata) to Unix epoch +FILETIME ft; +GetSystemTimeAsFileTime(&ft); +LONGLONG unixTime = filetimeToUnixtime(ft); + +// Convert a registry-stored little-endian FILETIME string +std::string regTimeData = getRegistryTimeValue(); +LONGLONG regUnixTime = littleEndianToUnixTime(regTimeData); + +// Convert a FAT/DOS timestamp from a filesystem entry +std::string dosTime = getFatTimestamp(); +LONGLONG fatUnixTime = parseFatTime(dosTime); + +} // namespace osquery +``` + +> **Note:** This header is Windows-only and guarded by `#pragma once`. It depends on `osquery/utils/system/system.h` for Windows type availability (`LONGLONG`, `FILETIME`, `LARGE_INTEGER`). Use these helpers whenever normalizing Windows-native timestamps to ensure consistent Unix epoch output across osquery tables. \ No newline at end of file diff --git a/osquery/utils/conversions/windows/tests/.strings.md b/osquery/utils/conversions/windows/tests/.strings.md new file mode 100644 index 00000000000..4fd079e5688 --- /dev/null +++ b/osquery/utils/conversions/windows/tests/.strings.md @@ -0,0 +1,41 @@ + +Unit test file for Windows string conversion utilities in the osquery framework, validating narrow/wide string conversions, CIM datetime parsing, and endianness swapping. + +## Key Components + +### Test Fixture +- **`ConversionsTests`** — GTest fixture that initializes and tears down COM via `CoInitializeEx`/`CoUninitialize` for each test case + +### Test Cases + +| Test | Function Tested | Description | +|---|---|---| +| `test_string_to_wstring` | `stringToWstring()` | ASCII narrow → wide string | +| `test_wstring_to_string` | `wstringToString()` | Wide → ASCII narrow string | +| `test_string_to_wstring_extended` | `stringToWstring()` | UTF-8 multi-byte → wide string | +| `test_wstring_to_string_extended` | `wstringToString()` | Wide → UTF-8 multi-byte string | +| `test_cim_datetime_to_unixtime` | `cimDatetimeToUnixtime()` | CIM datetime format → Unix timestamp | +| `test_swapendianiess` | `swapEndianess()` | Byte-pair endian swap on string | + +## Usage Example + +```cpp +#include + +// Narrow to wide conversion (ASCII) +std::string narrow = "Hello, World!"; +std::wstring wide = stringToWstring(narrow); + +// Wide to narrow conversion (UTF-8 preserved) +std::wstring wideUtf = L"fr\x00f8tz-jorn"; +std::string narrowUtf = wstringToString(wideUtf); // "fr\xc3\xb8tz-jorn" + +// CIM datetime to Unix timestamp +std::string cimDt = "20190724000000.000000-000"; +auto unix = cimDatetimeToUnixtime(cimDt); // 1563926400 + +// Endianness swap +std::string swapped = swapEndianess("IJGHEFCDAB"); // "ABCDEFGHIJ" +``` + +> **Note:** Tests require COM initialization (`CoInitializeEx`) on Windows, handled automatically by the `ConversionsTests` fixture setup and teardown. \ No newline at end of file diff --git a/osquery/utils/conversions/windows/tests/.windows_time.md b/osquery/utils/conversions/windows/tests/.windows_time.md new file mode 100644 index 00000000000..421745830e5 --- /dev/null +++ b/osquery/utils/conversions/windows/tests/.windows_time.md @@ -0,0 +1,38 @@ + +Unit test file for Windows time conversion utilities in the `osquery` project, validating three conversion functions that translate Windows-specific time formats to Unix timestamps. + +## Key Components + +### Test Fixture +- **`ConversionsTests`** — inherits from `testing::Test`; groups all Windows time conversion unit tests. + +### Test Cases + +| Test | Function Tested | Description | +|------|----------------|-------------| +| `test_filetime_to_unixtime` | `filetimeToUnixtime()` | Converts a `FILETIME` struct (100-nanosecond intervals since Jan 1, 1601) to a Unix timestamp | +| `test_long_int_to_unixtime` | `longIntToUnixtime()` | Converts a `LARGE_INTEGER` (used in Windows NT time structures) to a Unix timestamp | +| `test_fattime_to_unixtime` | `parseFatTime()` | Parses a FAT filesystem time string into a Unix timestamp | + +## Usage Example + +```cpp +#include + +// FILETIME to Unix timestamp +FILETIME ft; +GetSystemTimeAsFileTime(&ft); +time_t unix_ts = filetimeToUnixtime(ft); + +// LARGE_INTEGER to Unix timestamp +LARGE_INTEGER li; +li.HighPart = 30821541; +li.LowPart = 2060031803; +time_t unix_ts2 = longIntToUnixtime(li); + +// FAT time string to Unix timestamp +std::string fattime = "24450000"; +time_t unix_ts3 = parseFatTime(fattime); +``` + +> **Note:** The `FILETIME` epoch offset (`116444736000000000`) accounts for the difference between the Windows epoch (Jan 1, 1601) and the Unix epoch (Jan 1, 1970), scaled to 100-nanosecond intervals. \ No newline at end of file diff --git a/osquery/utils/darwin/.iokit_helpers.md b/osquery/utils/darwin/.iokit_helpers.md new file mode 100644 index 00000000000..9faa9ec1202 --- /dev/null +++ b/osquery/utils/darwin/.iokit_helpers.md @@ -0,0 +1,54 @@ + +RAII wrapper types for safely managing IOKit and CoreFoundation object lifetimes on macOS, using `std::unique_ptr` with custom deleters. + +## Key Components + +### Deleters + +- **`ObjectDeleter`** — Custom deleter for `io_object_t`-compatible types; calls `IOObjectRelease()`. Includes a `static_assert` to enforce type compatibility at compile time. Safely handles null (zero) values. +- **`TypeDeleter`** — Generic deleter for CoreFoundation types; calls `CFRelease()`. + +### IOKit Unique Pointer Aliases + +| Type | Wraps | +|---|---| +| `UniqueIoRegistryEntry` | `io_registry_entry_t` | +| `UniqueIoIterator` | `io_iterator_t` | +| `UniqueIoService` | `io_service_t` | + +### CoreFoundation Unique Pointer Aliases + +| Type | Wraps | +|---|---| +| `UniqueCFStringRef` | `CFStringRef` | +| `UniqueCFTypeRef` | `CFTypeRef` | +| `UniqueCFMutableDictionaryRef` | `CFMutableDictionaryRef` | + +## Usage Example + +```cpp +#include "iokit_helpers.h" + +using namespace osquery; + +void inspectDevice() { + // Automatically released when out of scope via IOObjectRelease() + UniqueIoService service( + IOServiceGetMatchingService(kIOMainPortDefault, + IOServiceMatching("IOUSBDevice")) + ); + + if (!service) { + return; + } + + // Automatically released via CFRelease() + UniqueCFStringRef name( + IORegistryEntryCreateCFProperty(service.get(), + CFSTR("USB Product Name"), + kCFAllocatorDefault, 0) + ); +} +``` + +> IOKit and CoreFoundation objects require explicit release calls — these RAII wrappers eliminate manual cleanup and prevent resource leaks in macOS system queries. \ No newline at end of file diff --git a/osquery/utils/darwin/.plist.md b/osquery/utils/darwin/.plist.md new file mode 100644 index 00000000000..c1aee214e4c --- /dev/null +++ b/osquery/utils/darwin/.plist.md @@ -0,0 +1,42 @@ + +Provides utility functions for parsing Apple Property List (plist) files and alias data within the osquery framework. + +## Key Components + +| Function | Description | +|---|---| +| `parsePlist` | Reads a plist file from disk and deserializes it into a `boost::property_tree::ptree` | +| `parsePlistContent` | Parses plist data from an in-memory string into a `boost::property_tree::ptree` | +| `pathFromPlistAliasData` | Resolves raw plist alias binary data into a POSIX path string | +| `pathFromNestedPlistAliasData` | Resolves base-64 encoded nested plist alias data into a POSIX path string | + +All functions return a `Status` object indicating success or failure (e.g., malformed input). + +## Usage Example + +```cpp +#include +#include + +// Parse a plist file from disk +boost::property_tree::ptree tree; +Status status = osquery::parsePlist("/Library/Preferences/com.example.app.plist", tree); +if (status.ok()) { + auto value = tree.get("SomeKey", ""); +} + +// Parse plist from string content +std::string raw_plist = "..."; +boost::property_tree::ptree content_tree; +osquery::parsePlistContent(raw_plist, content_tree); + +// Resolve a plist alias to a POSIX path +std::string alias_data = /* raw alias bytes */; +std::string resolved_path; +osquery::pathFromPlistAliasData(alias_data, resolved_path); + +// Resolve a base-64 encoded nested plist alias +std::string b64_alias = /* base-64 encoded plist */; +std::string nested_path; +osquery::pathFromNestedPlistAliasData(b64_alias, nested_path); +``` \ No newline at end of file diff --git a/osquery/utils/darwin/.system_profiler.md b/osquery/utils/darwin/.system_profiler.md new file mode 100644 index 00000000000..cea60a886ab --- /dev/null +++ b/osquery/utils/darwin/.system_profiler.md @@ -0,0 +1,34 @@ + +Provides a macOS-specific utility interface for querying the system profiler (`system_profiler`) tool and retrieving structured hardware and software information as an `NSDictionary`. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `getSystemProfilerReport` | Function | Queries macOS System Profiler for a specific data type and returns results via an `NSDictionary` pointer | +| `datatype` | Parameter | String matching a valid `system_profiler` data type (e.g., `SPHardwareDataType`) | +| `result` | Parameter | Output reference to an `NSDictionary*` populated with the profiler report | +| `Status` | Return type | osquery status object indicating success or failure of the query | + +## Usage Example + +```cpp +#include "system_profiler.h" + +@autoreleasepool { + NSDictionary* report = nil; + + osquery::Status status = osquery::getSystemProfilerReport( + "SPHardwareDataType", report + ); + + if (status.ok()) { + // Process the NSDictionary result + NSLog(@"Report: %@", report); + } else { + LOG(ERROR) << "System Profiler query failed: " << status.getMessage(); + } +} +``` + +> **Note:** This function should be called within an `@autoreleasepool` block to ensure proper Objective-C memory management. Available data types can be listed by running `system_profiler -listDataTypes` in a macOS terminal. \ No newline at end of file diff --git a/osquery/utils/debug/.debug_only.md b/osquery/utils/debug/.debug_only.md new file mode 100644 index 00000000000..bbd5ad0b00b --- /dev/null +++ b/osquery/utils/debug/.debug_only.md @@ -0,0 +1,47 @@ + +Provides debug-only assertion and verification utilities for the osquery framework, compiling to no-ops in release builds (`NDEBUG` defined). + +## Key Components + +**Free Functions** (`osquery::debug_only` namespace) + +| Function | Description | +|---|---| +| `fail(msg)` | Prints message to `stderr` and triggers `assert(false)` in debug builds | +| `verify(checker, msg)` | Calls `fail()` if the provided callable `checker` returns `false` | +| `verifyTrue(expected_true, msg)` | Calls `fail()` if a boolean condition is `false` | + +**Template Class** + +- `Var` — A debug-only variable wrapper. Holds a typed value in debug builds; zero-size in release. Exposes `verify()`, `verifyEqual()`, `set()`, and `update()` methods that all become no-ops in release. + +## Usage Example + +```cpp +#include "debug_only.h" + +using namespace osquery::debug_only; + +// Simple assertion with message +verifyTrue(ptr != nullptr, "Pointer must not be null"); + +// Callable-based verification +verify([&]() { return count > 0; }, "Count must be positive"); + +// Debug-only variable tracking +Var callCount(0); +callCount.update([](int v) { return v + 1; }); +callCount.verifyEqual(1, "Expected exactly one call"); + +// Unconditional debug abort +if (someFatalCondition) { + fail("Reached invalid state"); +} +``` + +## Notes + +- All utilities compile to **zero-cost no-ops** in release builds via `#ifdef NDEBUG` guards +- `boost::ignore_unused` prevents unused-variable warnings in release builds +- `Var` stores no data members in release builds, keeping object size at zero +- See `osquery/debug/tests/debug_only_tests.cpp` for additional usage examples \ No newline at end of file diff --git a/osquery/utils/debug/tests/.debug_only.md b/osquery/utils/debug/tests/.debug_only.md new file mode 100644 index 00000000000..7417ce0fb0a --- /dev/null +++ b/osquery/utils/debug/tests/.debug_only.md @@ -0,0 +1,43 @@ + +Unit tests for `osquery::debug_only` utilities, validating debug-mode assertion behavior for `fail()`, `verify()`, `verifyTrue()`, and the `Var` wrapper type across both debug and release builds. + +## Key Components + +### Test Suites + +| Suite | Description | +|---|---| +| `DebugOnly` | Tests free functions: `fail()`, `verify()`, `verifyTrue()`, and no-op behavior in release mode | +| `DebugOnlyVar` | Tests the `Var` template wrapper: construction, `set()`, `update()`, `verify()`, `verifyEqual()`, and release-mode no-ops | + +### Behaviors Under Test + +- **`debug_only::fail(msg)`** — triggers `ASSERT_DEATH` in debug (`NDEBUG` not defined); silent in release +- **`debug_only::verify(lambda, msg)`** — executes lambda only in debug mode; asserts death if lambda returns `false` +- **`debug_only::verifyTrue(bool, msg)`** — asserts death on `false` in debug; no-op in release +- **`debug_only::Var`** — zero-size in release (`sizeof(TestEmptyClass)`), full `sizeof(T)` in debug; supports `set()`, `update()`, `verify()`, `verifyEqual()` + +## Usage Example + +```cpp +// Typical debug_only::Var usage pattern (from Gun watchdog example) +struct Gun { + explicit Gun(int bullets) : bullets_(bullets) {} + + void shot() { + // Track over-fire incidents in debug builds only + dbg.update([this](auto v) { return bullets_ == 0 ? v + 1 : v; }); + --bullets_; + } + + int bullets_; + debug_only::Var dbg = 0; // zero-cost in release builds +}; + +// Verifying return values cheaply in debug mode only +debug_only::Var dbg = test_function(11); +dbg.verify([](const auto& str) { return !str.empty(); }, + "Return value must not be empty string"); +``` + +> **Note:** All `ASSERT_DEATH` checks are guarded by `#ifndef NDEBUG`. In release builds, `Var` compiles to an empty class and all lambda bodies are elided entirely — verified explicitly in `verify_in_non_debug_mode_should_not_be_run`. \ No newline at end of file diff --git a/osquery/utils/error/.error.md b/osquery/utils/error/.error.md new file mode 100644 index 00000000000..2217d4ccb04 --- /dev/null +++ b/osquery/utils/error/.error.md @@ -0,0 +1,36 @@ + +Defines the core error type system for osquery, providing a templated, chainable error class hierarchy that supports error codes, messages, and nested (underlying) error causes. + +## Key Components + +- **`ErrorBase`** — Abstract base class for all errors; declares `getMessage()` and `getNonRecursiveMessage()` pure virtual interfaces. +- **`Error`** — Concrete templated error class parameterized by an enum type. Stores an error code, an optional string message, and an optional chained `underlyingError_`. +- **`createError()`** — Factory functions (nodiscard) for constructing `Error` instances, with or without an underlying chained error. +- **`operator==`** — Overloads for comparing errors by error code (pointer, reference, and `ErrorBase` variants with safe `dynamic_cast`). +- **`operator<<` (stream)** — Streams the full recursive message of any `ErrorBase` to an output stream. +- **`operator<<` (append)** — Appends arbitrary values to an error's message using `ostringstream`. + +## Usage Example + +```cpp +// Define an error code enum +enum class FileError { NotFound, PermissionDenied }; +enum class AppError { IoFailure }; + +// Create a standalone error +auto err = osquery::createError(FileError::NotFound) << " path: /etc/config"; + +// Chain errors (wrap underlying cause) +auto app_err = osquery::createError(AppError::IoFailure, std::move(err)); + +// Retrieve full chained message +// e.g. "IoFailure <- FileError::NotFound (path: /etc/config)" +std::cout << app_err.getMessage() << "\n"; + +// Compare by error code +if (app_err == AppError::IoFailure) { + // handle it +} +``` + +> **Note:** `createError()` is marked `OSQUERY_NODISCARD` — the caller must handle or explicitly discard the returned error object to avoid silent error drops. \ No newline at end of file diff --git a/osquery/utils/error/tests/.error.md b/osquery/utils/error/tests/.error.md new file mode 100644 index 00000000000..d58f4d4b711 --- /dev/null +++ b/osquery/utils/error/tests/.error.md @@ -0,0 +1,44 @@ + +Unit tests for `osquery::Error` validating construction, chained error wrapping, message formatting, and the `createError` factory with streaming message composition. + +## Key Components + +| Test Case | Purpose | +|---|---| +| `ErrorTest.initialization` | Verifies basic `Error` construction, error code comparison, and message content | +| `ErrorTest.recursive` | Tests chained/nested errors via `hasUnderlyingError()`, short vs. full message scoping | +| `ErrorTest.createErrorSimple` | Validates `createError()` factory with special-character message content | +| `ErrorTest.createErrorFromOtherError` | Exercises error chaining by passing a prior error into `createError()` | +| `ErrorTest.createErrorAndStreamToIt` | Tests `operator<<` streaming multiple mixed types into a single error message | + +### Helper + +- `stringContains(where, what)` — thin wrapper over `boost::contains` used as a `EXPECT_PRED2` predicate + +## Usage Example + +```cpp +// Direct construction +auto error = osquery::Error(TestError::SomeError, "Something failed"); +error.getMessage(); // includes "TestError[1]" + message +error.getNonRecursiveMessage(); // only the top-level error, no chain + +// Factory + streaming +auto err = osquery::createError(TestError::AnotherError) + << "Detail: " << 42 << " bytes corrupt"; +err.getErrorCode(); // TestError::AnotherError +err.getMessage(); // "TestError[2] Detail: 42 bytes corrupt" + +// Chained errors +auto root = osquery::createError(TestError::SomeError) << "root cause"; +auto wrapped = osquery::createError(TestError::AnotherError, std::move(root)) + << "higher-level context"; +wrapped.hasUnderlyingError(); // true +wrapped.getMessage(); // includes both messages +``` + +## Notes + +- Error codes are rendered as `TypeName[numeric_value]` (e.g., `TestError[1]`) +- `getNonRecursiveMessage()` returns only the outermost error; `getMessage()` recurses through the full chain +- `operator<<` supports any streamable type, enabling fluent, composable error descriptions \ No newline at end of file diff --git a/osquery/utils/expected/.expected.md b/osquery/utils/expected/.expected.md new file mode 100644 index 00000000000..bd93215a9bd --- /dev/null +++ b/osquery/utils/expected/.expected.md @@ -0,0 +1,57 @@ + +Defines the `Expected` utility template class for osquery, enabling functions to return either a success value or a typed error without exceptions. + +## Key Components + +| Component | Description | +|---|---| +| `Expected` | Core template class holding either a value or an `Error` | +| `ExpectedShared` | Alias for `Expected, E>` | +| `ExpectedUnique` | Alias for `Expected, E>` | +| `ExpectedSuccess` | Alias for `Expected` for void-like success | +| `Success` | Type alias for `boost::blank`, used as a no-value success marker | + +**Key methods:** + +- `isError()` / `isValue()` / `operator bool()` — check result state +- `get()` / `take()` / `operator*()` / `operator->()` — access the value (lvalue-only) +- `takeOr(default)` — return value or fallback if error +- `getError()` / `takeError()` / `getErrorCode()` — access the error (lvalue-only) +- `ignoreResult()` — explicitly suppress the "unchecked" assertion +- `success()` / `failure()` — static factory helpers + +Rvalue-ref overloads are explicitly deleted to prevent unchecked access. A `debug_only::Var` flag enforces that callers check the result before destruction or reassignment. + +## Usage Example + +```cpp +enum class MyError { NotFound = 1, ParseFailed = 2 }; + +osquery::Expected fetchData(bool ok) { + if (ok) { + return std::string("hello"); + } + return osquery::createError(MyError::NotFound) << "resource not found"; +} + +// Consuming the result +auto result = fetchData(true); +if (result) { + std::cout << *result; // dereference value +} else { + switch (result.getErrorCode()) { + case MyError::NotFound: + // handle missing resource + break; + case MyError::ParseFailed: + // handle parse error + break; + } +} + +// Unique pointer variant +osquery::ExpectedUnique obj = makeObject(); +if (obj) { + obj->doSomething(); +} +``` \ No newline at end of file diff --git a/osquery/utils/expected/tests/.expected.md b/osquery/utils/expected/tests/.expected.md new file mode 100644 index 00000000000..b79c75b7816 --- /dev/null +++ b/osquery/utils/expected/tests/.expected.md @@ -0,0 +1,54 @@ + +Unit test suite for osquery's `Expected` type — a Rust-inspired result type that holds either a value or a typed error, enforcing checked access before destruction. + +## Key Components + +- **`TestError` enum** — Scoped error codes (`Some`, `Another`, `Semantic`, `Logical`, `Runtime`) used as the error type parameter throughout all tests +- **`stringContains` helper** — Predicate for `EXPECT_PRED2` to verify substrings in error messages +- **`testFunction()`** — Sample function returning `ExpectedUnique` to demonstrate unique-pointer wrapping + +## Test Coverage + +| Test Case | What It Validates | +|---|---| +| `success_constructor_initialization` | Value construction, `isValue()`, `get()` | +| `failure_error_constructor_initialization` | Error construction, `isError()`, `getErrorCode()` | +| `failure_error_str_constructor_initialization` | `Expected::failure(msg)`, error message content | +| `ExpectedSharedTestFunction` / `ExpectedUniqueTestFunction` | `ExpectedShared` and `ExpectedUnique` aliases | +| `createError_example` | Stream-operator error building with `createError()` | +| `ExpectedSuccess_example` | `ExpectedSuccess` / `Success{}` pattern | +| `nested_errors_example` | Chained errors via `takeError()` | +| `error_handling_example` | Switch-based error code dispatch | +| `expected_was_not_checked_before_destruction_failure` | Debug-mode death on unchecked destruction | +| `expected_was_not_checked_before_assigning_failure` | Debug-mode death on unchecked reassignment | +| `get/take_value_from_expected_with_error` | Death assertions when accessing value on error state | +| `get/take_error_from_expected_with_value` | Death assertions when accessing error on value state | +| `value__takeOr` / `error__takeOr` | Fallback retrieval via `takeOr()` | + +## Usage Example + +```cpp +// Returning a value or a typed error +template +using MyExpected = Expected; + +auto divide = [](int a, int b) -> MyExpected { + if (b == 0) { + return createError(TestError::Logical) << "Division by zero"; + } + return a / b; +}; + +auto result = divide(10, 0); +if (result.isError()) { + // result.getErrorCode() == TestError::Logical + // result.getError().getMessage() contains "Division by zero" +} else { + int value = result.take(); +} + +// Safe fallback with takeOr +int safe = divide(10, 0).takeOr(-1); // returns -1 +``` + +> **Note:** In debug builds (`!NDEBUG`), `Expected` will call `abort` if destroyed or reassigned without being checked — enforcing explicit error handling at every call site. \ No newline at end of file diff --git a/osquery/utils/info/.firmware.md b/osquery/utils/info/.firmware.md new file mode 100644 index 00000000000..ae25d2e8ae5 --- /dev/null +++ b/osquery/utils/info/.firmware.md @@ -0,0 +1,42 @@ + +Declares the firmware detection interface for osquery, providing types and functions to identify and describe the system's firmware type across platforms. + +## Key Components + +### `FirmwareKind` (enum class) + +Enumerates the supported firmware types: + +| Value | Description | +|---|---| +| `Bios` | Legacy BIOS firmware | +| `Uefi` | Unified Extensible Firmware Interface | +| `iBoot` | Apple iBoot bootloader (macOS/iOS) | +| `OpenFirmware` | Open Firmware (PowerPC-era Apple hardware) | + +### Functions + +- **`getFirmwareKind()`** — Detects and returns the current system's firmware type as a `boost::optional`. Returns empty if the firmware type cannot be determined. +- **`getFirmwareKindDescription()`** — Returns a human-readable `std::string` description for a given `FirmwareKind` value. + +## Usage Example + +```cpp +#include "firmware.h" +#include + +void printFirmwareInfo() { + auto kind = osquery::getFirmwareKind(); + + if (kind.has_value()) { + const std::string& description = + osquery::getFirmwareKindDescription(kind.value()); + + std::cout << "Firmware type: " << description << std::endl; + } else { + std::cout << "Firmware type could not be determined." << std::endl; + } +} +``` + +> **Note:** `getFirmwareKind()` returns a `boost::optional` — always check `has_value()` before accessing the result to safely handle systems where firmware detection is unsupported or unavailable. \ No newline at end of file diff --git a/osquery/utils/info/.platform_type.md b/osquery/utils/info/.platform_type.md new file mode 100644 index 00000000000..1c36552b97b --- /dev/null +++ b/osquery/utils/info/.platform_type.md @@ -0,0 +1,37 @@ + +Defines platform detection types and compile-time constants for identifying the target OS at both build time and runtime within the osquery framework. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `PlatformType` | `enum class` | Bitmask enum for platform identification (`TYPE_POSIX`, `TYPE_WINDOWS`, `TYPE_BSD`, `TYPE_LINUX`, `TYPE_OSX`, `TYPE_FREEBSD`) | +| `kPlatformType` | `constexpr` | Compile-time bitmask constant built from preprocessor defines (`POSIX`, `WINDOWS`, `BSD`, `LINUX`, `DARWIN`, `FREEBSD`) | +| `isPlatform()` | Function | Runtime check whether a given `PlatformType` flag is active | +| `operator\|` | Operator | Bitwise OR for combining `PlatformType` values | +| `kSDKPlatform` | `extern const std::string` | String identifier of the build platform, mirrors `OSQUERY_BUILD_PLATFORM` | +| `OSQUERY_PLATFORM` | `#define` | Macro alias for `OSQUERY_BUILD_PLATFORM` | + +> **Note:** `OSQUERY_BUILD_PLATFORM` and `OSQUERY_BUILD_DISTRO` **must** be defined by the build system — the header enforces this with `#error` directives. + +## Usage Example + +```c +#include + +// Runtime platform check +if (osquery::isPlatform(osquery::PlatformType::TYPE_LINUX)) { + // Linux-specific logic +} + +// Combining platform flags +osquery::PlatformType combined = + osquery::PlatformType::TYPE_POSIX | osquery::PlatformType::TYPE_LINUX; + +if (osquery::isPlatform(osquery::PlatformType::TYPE_POSIX, combined)) { + // Runs if combined mask includes POSIX +} + +// Accessing build-time platform string +std::cout << osquery::kSDKPlatform; // e.g. "linux", "darwin", "windows" +``` \ No newline at end of file diff --git a/osquery/utils/info/.tool_type.md b/osquery/utils/info/.tool_type.md new file mode 100644 index 00000000000..3e08fd43d30 --- /dev/null +++ b/osquery/utils/info/.tool_type.md @@ -0,0 +1,45 @@ + +Defines the `ToolType` enumeration and related utility functions for identifying the running osquery process type at runtime, enabling conditional behavior across daemons, shells, extensions, and tests. + +## Key Components + +### Enum: `ToolType` + +| Value | Description | +|---|---| +| `UNKNOWN` | Default/undetected tool type | +| `SHELL` | Interactive osquery shell | +| `DAEMON` | Background osquery daemon | +| `TEST` | Test harness process | +| `EXTENSION` | osquery extension process | +| `SHELL_DAEMON` | Combined shell/daemon mode | + +### Functions + +- **`setToolType(ToolType tool)`** — Sets the global tool type, typically called during initialization by the `Initializer` class. +- **`getToolType()`** — Returns the currently active `ToolType` for runtime branching. +- **`isDaemon()`** — Convenience check; returns `true` if the current tool type is `DAEMON`. +- **`isShell()`** — Convenience check; returns `true` if the current tool type is `SHELL`. + +## Usage Example + +```c +#include + +// Set tool type during startup +osquery::setToolType(osquery::ToolType::DAEMON); + +// Conditional behavior based on tool type +if (osquery::isDaemon()) { + // daemon-specific initialization +} else if (osquery::isShell()) { + // interactive shell setup +} + +// Direct enum comparison +if (osquery::getToolType() == osquery::ToolType::EXTENSION) { + // extension-specific logic +} +``` + +> **Note:** The `ToolType` is typically auto-detected by the `Initializer` class using the process name and compile-time flags — manual calls to `setToolType` are generally reserved for tests or special bootstrapping scenarios. \ No newline at end of file diff --git a/osquery/utils/info/.version.md b/osquery/utils/info/.version.md new file mode 100644 index 00000000000..f1e8e244994 --- /dev/null +++ b/osquery/utils/info/.version.md @@ -0,0 +1,40 @@ + +Defines version constants and comparison utilities for the osquery runtime and SDK, enforcing that required version macros are set at build time. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `kVersion` | `extern const std::string` | The current osquery runtime version string | +| `kSDKVersion` | `extern const std::string` | The osquery SDK version string | +| `OSQUERY_SDK_VERSION` | Macro | Stringified form of `OSQUERY_BUILD_SDK_VERSION` | +| `versionAtLeast()` | Function | Compares a version string against the SDK/core version | + +### Build-time Requirements + +| Macro | Behavior if missing | +|---|---| +| `OSQUERY_VERSION` | **Compile error** — must be defined | +| `OSQUERY_BUILD_SDK_VERSION` | **Compile error** — must be defined | +| `OSQUERY_BUILD_VERSION` | **Warning** — falls back to `1.0.0-unknown` | + +## Usage Example + +```c +#include + +// Check if a loaded extension meets the minimum version requirement +void checkExtensionCompatibility(const std::string& extensionVersion) { + if (!osquery::versionAtLeast(extensionVersion)) { + LOG(WARNING) << "Extension version " << extensionVersion + << " is older than core version " << osquery::kVersion; + } +} + +// Check against a specific version instead of kVersion +bool isAtLeast_5_0(const std::string& v) { + return osquery::versionAtLeast(v, "5.0.0"); +} +``` + +> Version strings follow `major.minor.patch-commit-hash` format. `versionAtLeast(v)` returns `true` if `v` is equal to or newer than the current osquery version. \ No newline at end of file diff --git a/osquery/utils/info/firmware/.common.md b/osquery/utils/info/firmware/.common.md new file mode 100644 index 00000000000..aad700b2839 --- /dev/null +++ b/osquery/utils/info/firmware/.common.md @@ -0,0 +1,34 @@ + +Provides a string description lookup for firmware type enumeration values within the osquery framework. + +## Key Components + +- **`getFirmwareKindDescription(const FirmwareKind&)`** — Returns a human-readable string for a given `FirmwareKind` enum value. Supported mappings: + +| Enum Value | String | +|---|---| +| `FirmwareKind::Bios` | `"bios"` | +| `FirmwareKind::Uefi` | `"uefi"` | +| `FirmwareKind::iBoot` | `"iboot"` | +| `FirmwareKind::OpenFirmware` | `"openfirmware"` | + +- **`kFirmwareKindNameMap`** — Static `unordered_map` used as a lookup table; initialized once and reused across calls. +- **`kUnknownFirmwareKind`** — Static fallback string `"unknown"` returned when no matching enum entry is found. + +## Usage Example + +```cpp +#include + +// Retrieve a string description for a detected firmware type +FirmwareKind kind = FirmwareKind::Uefi; +const std::string& description = osquery::getFirmwareKindDescription(kind); +// description == "uefi" + +// Unrecognized or uninitialized firmware kind +FirmwareKind unknownKind = static_cast(99); +const std::string& fallback = osquery::getFirmwareKindDescription(unknownKind); +// fallback == "unknown" +``` + +> **Note:** The function returns a `const std::string&` referencing either a static map entry or the static fallback. Callers must not store the reference beyond the lifetime of the static objects (i.e., program lifetime — safe in normal usage). \ No newline at end of file diff --git a/osquery/utils/info/firmware/.linux.md b/osquery/utils/info/firmware/.linux.md new file mode 100644 index 00000000000..cd67959c3d5 --- /dev/null +++ b/osquery/utils/info/firmware/.linux.md @@ -0,0 +1,31 @@ + +Detects the system firmware type on Linux by checking for the presence of the EFI sysfs directory. + +## Key Components + +- **`getFirmwareKind()`** — Returns a `boost::optional` indicating whether the system firmware is `FirmwareKind::Uefi` or `FirmwareKind::Bios`. +- **`kEfiDirectory`** — Internal constant (`/sys/firmware/efi`) used as the detection path for UEFI firmware. + +## Usage Example + +```cpp +#include + +auto kind = osquery::getFirmwareKind(); +if (kind) { + if (*kind == osquery::FirmwareKind::Uefi) { + // System is running UEFI firmware + } else { + // System is running legacy BIOS firmware + } +} +``` + +## Detection Logic + +| Condition | Result | +|---|---| +| `/sys/firmware/efi` exists as a directory | `FirmwareKind::Uefi` | +| Directory absent | `FirmwareKind::Bios` | + +The Linux kernel exposes `/sys/firmware/efi` only when booted in UEFI mode, making it a reliable detection heuristic. Falls back to `Bios` for any non-UEFI boot environment, including legacy BIOS and other firmware types. \ No newline at end of file diff --git a/osquery/utils/info/firmware/.macos.md b/osquery/utils/info/firmware/.macos.md new file mode 100644 index 00000000000..589400c7421 --- /dev/null +++ b/osquery/utils/info/firmware/.macos.md @@ -0,0 +1,42 @@ + +Detects the firmware type on macOS by querying the IOKit device tree registry for known firmware-specific paths. + +## Key Components + +- **`getFirmwareKind()`** — Queries the IOKit registry to identify the active firmware type. Returns a `boost::optional`, or `boost::none` if detection fails. +- **`kFirmwareToRegistryPath`** — Static lookup table mapping `FirmwareKind` enum values to their corresponding IOKit device tree paths: + - `FirmwareKind::Uefi` → `:/efi` + - `FirmwareKind::iBoot` → `:/chosen/iBoot` + - `FirmwareKind::OpenFirmware` → `:/openprom` + +## Usage Example + +```cpp +#include + +auto kind = osquery::getFirmwareKind(); + +if (kind.has_value()) { + switch (kind.value()) { + case FirmwareKind::Uefi: + // Apple Silicon or Intel Mac with EFI + break; + case FirmwareKind::iBoot: + // Apple Silicon iBoot path + break; + case FirmwareKind::OpenFirmware: + // Legacy PowerPC-era firmware + break; + } +} else { + // Could not obtain master port or no known firmware path matched +} +``` + +## Detection Logic + +1. Opens a connection to the IOKit master port via `IOMasterPort`. +2. Iterates over each firmware-to-path mapping. +3. Calls `IORegistryEntryFromPath` for each candidate path. +4. Returns the first `FirmwareKind` whose registry path resolves to a valid entry. +5. Releases the IOKit object reference before returning to avoid resource leaks. \ No newline at end of file diff --git a/osquery/utils/info/firmware/.windows.md b/osquery/utils/info/firmware/.windows.md new file mode 100644 index 00000000000..c485ff8ddc4 --- /dev/null +++ b/osquery/utils/info/firmware/.windows.md @@ -0,0 +1,52 @@ + +Windows-specific implementation of firmware type detection for the osquery `FirmwareKind` utility, using the Win32 `GetFirmwareType` API with a registry-based fallback. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `WindowsFirmwareType` | `enum class` | Typed wrapper for Win32 firmware type values (`Bios = 1`, `Uefi = 2`) | +| `locateGetFirmwareTypeProc()` | `static fn` | Dynamically resolves `GetFirmwareType` from `kernel32.dll` at runtime | +| `queryFirmwareKind()` | `static fn` | Calls the resolved Win32 proc and maps the result to `FirmwareKind` | +| `detectFirmwareTypeFromRegistry()` | `static fn` | Fallback: infers UEFI by checking existence of `HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot\State` | +| `getFirmwareKind()` | **public** | Entry point — tries Win32 API first, falls back to registry detection | + +## Detection Flow + +```mermaid +graph TD + A[getFirmwareKind] --> B{GetFirmwareType\nresolved?} + B -->|Yes| C{API call\nsucceeds?} + B -->|No| F[Registry fallback] + C -->|Yes| D[Map to FirmwareKind] + C -->|No| F + F --> G{SecureBoot\nkey exists?} + G -->|Yes| H[FirmwareKind::Uefi] + G -->|No| I[FirmwareKind::Bios] +``` + +## Usage Example + +```cpp +#include + +auto kind = osquery::getFirmwareKind(); +if (kind.has_value()) { + switch (kind.value()) { + case FirmwareKind::Uefi: + // System is UEFI + break; + case FirmwareKind::Bios: + // System is legacy BIOS + break; + } +} else { + // Detection failed entirely +} +``` + +## Notes + +- `GetFirmwareType` is resolved **dynamically** via `GetProcAddress` to avoid hard linking against APIs absent on older Windows versions (pre-8/Server 2012). +- The registry fallback checks the presence of `SecureBoot\State` as a UEFI indicator — it does not validate the key's value. +- Errors are surfaced via osquery's `LOG(ERROR)` / `VLOG(1)` logging macros. \ No newline at end of file diff --git a/osquery/utils/json/.json.md b/osquery/utils/json/.json.md new file mode 100644 index 00000000000..5b88e19c6f2 --- /dev/null +++ b/osquery/utils/json/.json.md @@ -0,0 +1,50 @@ + +A wrapper class around RapidJSON that provides safe, memory-leak-resistant construction and manipulation of JSON objects and arrays within the osquery framework. + +## Key Components + +### `JSON` class +A move-only (`only_movable`) wrapper around `rapidjson::Document` with the following groups of methods: + +| Method Group | Description | +|---|---| +| `newObject()` / `newArray()` / `newFromValue()` | Static factory constructors | +| `add(key, value, ...)` | Overloaded adders for `string`, `char*`, `int`, `long`, `long long`, `unsigned` variants, `double`, `bool`, `rapidjson::Value` | +| `addCopy()` / `addRef()` | String-specific adders: copy vs. reference semantics | +| `push(value, ...)` | Append values to JSON arrays | +| `pushCopy()` | Append string copies to arrays | +| `copyFrom()` | Deep-copy a JSON value into the document | +| `mergeObject()` / `mergeArray()` | Merge two JSON objects or arrays | +| `toString()` / `toPrettyString()` | Serialize document to string | +| `fromString()` | Parse JSON string (iterative or recursive mode) | +| `getObject()` / `getArray()` | Create new empty sub-documents | +| `doc()` | Direct access to the underlying `rapidjson::Document` | +| `valueToSize()` | Static helper to extract `uint64_t` from a value | + +### `ParseMode` enum +Controls RapidJSON parsing strategy: +- `Iterative` — stack-safe, avoids overflow on deep nesting +- `Recursive` — default RapidJSON behavior + +## Usage Example + +```c +// Create a JSON object and serialize it +osquery::JSON json = osquery::JSON::newObject(); +json.add("hostname", "server01"); +json.add("port", 8080); +json.add("active", true); + +std::string output; +auto status = json.toString(output); +if (status.ok()) { + // output == {"hostname":"server01","port":8080,"active":true} +} + +// Parse JSON from a string +osquery::JSON parsed; +auto s = parsed.fromString( + "{\"key\":\"value\"}", + osquery::JSON::ParseMode::Iterative +); +``` \ No newline at end of file diff --git a/osquery/utils/json/tests/.json.md b/osquery/utils/json/tests/.json.md new file mode 100644 index 00000000000..dc4d1bc0cbb --- /dev/null +++ b/osquery/utils/json/tests/.json.md @@ -0,0 +1,42 @@ + +Unit test suite for the `osquery` JSON utility class (`osquery/utils/json/json.h`), validating serialization, parsing, type conversions, and edge case handling. + +## Key Components + +| Test Case | Description | +|---|---| +| `test_json_array` | Validates array construction with mixed numeric types and both compact/pretty serialization (including custom indent counts) | +| `test_json_object` | Validates object construction, compact output, and pretty-print with configurable indentation | +| `test_json_from_string` | Validates round-trip parsing from JSON strings and rejects malformed input | +| `test_json_from_string_error` | Verifies parse error messages include offset information | +| `test_json_add_object` | Tests nested object/array copying via `add`, `newFromValue`, and `copyFrom` | +| `test_json_strings` | Verifies `addCopy` (value semantics) vs `addRef` (reference semantics) for string keys | +| `test_json_strings_array` | Validates pushing string values into arrays via `pushCopy` | +| `test_json_duplicate_keys` | Confirms last-write-wins behavior for duplicate object keys | +| `test_json_merge_object` | Validates `mergeObject` combines two JSON objects correctly | +| `test_json_size_like` | Tests `valueToSize` coercion from both string `"10"` and integer `10` | +| `test_json_bool_like` | Tests `valueToBool` with truthy (`"true"`, `"T"`, `1`) and falsy (`"false"`, `"F"`, `0`) inputs | +| `test_json_iterativeparsing` | Guards against stack overflow on deeply nested input by enforcing iterative RapidJSON parsing | +| `test_json_largeexp` | Confirms parsing tolerates extreme floating-point exponents without crashing | + +## Usage Example + +```cpp +// Construct and serialize a JSON object +auto doc = JSON::newObject(); +size_t value = 10; +doc.add("key", value); +doc.addRef("label", "hello"); + +std::string output; +doc.toString(output); +// output: {"key":10,"label":"hello"} + +// Parse from string +auto parsed = JSON::newObject(); +parsed.fromString("{\"x\":42}"); + +// Type coercion helpers +size_t n = JSON::valueToSize(parsed.doc()["x"]); // 42 +bool b = JSON::valueToBool(parsed.doc()["x"]); // true +``` \ No newline at end of file diff --git a/osquery/utils/linux/dpkg/.dpkgquery.md b/osquery/utils/linux/dpkg/.dpkgquery.md new file mode 100644 index 00000000000..43012c91fdf --- /dev/null +++ b/osquery/utils/linux/dpkg/.dpkgquery.md @@ -0,0 +1,43 @@ + +Concrete implementation of the `IDpkgQuery` interface that queries the dpkg package database on Linux systems to retrieve installed package information. + +## Key Components + +### Class: `DpkgQuery` + +A `final` class inheriting from `IDpkgQuery`, providing the concrete dpkg querying logic. + +| Member | Type | Description | +|---|---|---| +| `getPackageList()` | Public | Returns an `Expected` with all installed dpkg packages | +| `~DpkgQuery()` | Public | Destructor; cleans up dpkg library state | +| `DpkgQuery(admindir)` | Private | Constructor accepting a custom dpkg admin directory path | +| `validateAdminDir(path)` | Private | Validates the provided admin directory exists and is accessible | +| `packageIteratorCallback(...)` | Private Static | Callback invoked per-package during dpkg array iteration; populates `PackageList` | + +### Notable Macros + +- `LIBDPKG_VOLATILE_API` — Required define to acknowledge the unstable dpkg C API before including dpkg headers + +### Dependencies + +- `` — dpkg C library for package array iteration +- `osquery/utils/linux/idpkgquery.h` — Abstract interface defining `IDpkgQuery`, `PackageList`, and `ErrorCode` + +## Usage Example + +```cpp +// Instantiation is controlled via the IDpkgQuery factory (friend class) +auto query = IDpkgQuery::create("/var/lib/dpkg"); + +auto result = query->getPackageList(); +if (result.isValue()) { + for (const auto& pkg : result.get()) { + // Process each installed package entry + } +} else { + // Handle ErrorCode from result.getError() +} +``` + +> **Note:** `DpkgQuery` cannot be instantiated directly — the constructor is private and only accessible via the `IDpkgQuery` factory pattern (declared as `friend class IDpkgQuery`). \ No newline at end of file diff --git a/osquery/utils/linux/dpkg/.idpkgquery.md b/osquery/utils/linux/dpkg/.idpkgquery.md new file mode 100644 index 00000000000..bf8f83483dc --- /dev/null +++ b/osquery/utils/linux/dpkg/.idpkgquery.md @@ -0,0 +1,61 @@ + +Interface definition for querying Debian package (dpkg) information, providing an abstraction layer for accessing installed package metadata from a dpkg admin directory. + +## Key Components + +### `IDpkgQuery` (Abstract Class) +The primary interface for dpkg package queries within the `osquery` namespace. + +**`ErrorCode` Enum** +Represents failure states returned via `Expected<>`: + +| Code | Description | +|---|---| +| `MemoryAllocationFailure` | Failed to allocate required memory | +| `InvalidAdminDirPath` | Provided path is not valid | +| `NotADpkgAdminDir` | Path exists but is not a dpkg admin directory | +| `NoPackagesFound` | Admin dir is valid but contains no packages | +| `PermissionError` | Insufficient permissions to read the directory | + +**`Package` Struct** +Holds metadata for a single installed package: + +| Field | Type | +|---|---| +| `name`, `version`, `source` | `std::string` | +| `size`, `arch`, `revision` | `std::string` | +| `status`, `status_string` | `std::string` | +| `maintainer`, `section`, `priority` | `std::string` | + +**Key Methods** + +- `create(dpkg_admindir)` — Static factory returning `Expected` +- `getPackageList()` — Pure virtual; returns all installed packages +- `getErrorCodeDescription(error_code)` — Human-readable error message + +## Usage Example + +```cpp +#include "idpkgquery.h" + +auto result = osquery::IDpkgQuery::create("/var/lib/dpkg"); + +if (result.isError()) { + auto msg = osquery::IDpkgQuery::getErrorCodeDescription( + result.getError() + ); + LOG(ERROR) << "Failed to create dpkg query: " << msg; + return; +} + +auto query = std::move(result.get()); +auto packages = query->getPackageList(); + +if (!packages.isError()) { + for (const auto& pkg : packages.get()) { + LOG(INFO) << pkg.name << " @ " << pkg.version; + } +} +``` + +> Non-copyable by design — use `IDpkgQuery::Ptr` (`std::unique_ptr`) for ownership transfer. \ No newline at end of file diff --git a/osquery/utils/linux/dpkg/.modstatdb.md b/osquery/utils/linux/dpkg/.modstatdb.md new file mode 100644 index 00000000000..ba67051bba7 --- /dev/null +++ b/osquery/utils/linux/dpkg/.modstatdb.md @@ -0,0 +1,43 @@ + +RAII wrapper around the `libdpkg` package status database (`modstatdb`), providing safe, scoped access to the dpkg package database in read-only mode. + +## Key Components + +### `ModstatDB` (class) + +A non-copyable RAII class in the `osquery` namespace that manages the lifecycle of the dpkg `modstatdb` connection. + +| Member | Description | +|---|---| +| `static open()` | Factory method — creates and returns a `ModstatDB` instance | +| `~ModstatDB()` | Destructor — calls `modstatdb_shutdown()` to release the database | +| Copy constructor / assignment | Deleted — prevents double-shutdown on copy | +| `ModstatDB()` (private) | Opens the database with `msdbrw_readonly \| msdbrw_available_readonly` flags | + +## Usage Example + +```cpp +#include "modstatdb.h" + +void queryInstalledPackages() { + // Database opens on construction, closes automatically on scope exit + auto db = osquery::ModstatDB::open(); + + // Iterate dpkg package records using libdpkg APIs + struct pkg_hash_iter* iter = pkg_hash_iter_new(); + struct pkginfo* pkg = nullptr; + + while ((pkg = pkg_hash_iter_next_pkg(iter)) != nullptr) { + // Access pkg->set->name, pkg->installed.version, etc. + } + + pkg_hash_iter_free(iter); + // db goes out of scope here -> modstatdb_shutdown() called automatically +} +``` + +## Notes + +- `LIBDPKG_VOLATILE_API` must be defined before including `` — the header enforces this guard to acknowledge the unstable API surface. +- The `extern "C"` block ensures correct C linkage for the dpkg headers when compiled as C++. +- Database is always opened **read-only** (`msdbrw_readonly | msdbrw_available_readonly`), preventing any accidental writes to the dpkg status database. \ No newline at end of file diff --git a/osquery/utils/linux/dpkg/.pkgarray.md b/osquery/utils/linux/dpkg/.pkgarray.md new file mode 100644 index 00000000000..ff86ae3028b --- /dev/null +++ b/osquery/utils/linux/dpkg/.pkgarray.md @@ -0,0 +1,50 @@ + +RAII wrapper around the `libdpkg` `pkg_array` structure, providing safe initialization, sorting, and destruction of Debian package arrays within the osquery framework. + +## Key Components + +### `PkgArray` (class) + +A non-copyable RAII class that manages the lifecycle of a `pkg_array` from `libdpkg`. + +| Member | Description | +|---|---| +| `static create()` | Factory method — constructs and returns a `PkgArray` instance | +| `~PkgArray()` | Destructor — calls `pkg_array_destroy()` to release resources | +| `get()` | Returns a reference to the underlying `pkg_array` struct | +| Copy constructor | Deleted — prevents unsafe duplication | +| Copy assignment | Deleted — prevents unsafe duplication | + +### C Library Includes + +Wraps the following `libdpkg` headers inside `extern "C"` for C++ compatibility: + +- `dpkg/pkg-array.h` — core package array types and functions +- `dpkg/pkg-show.h` — package display/sorting utilities + +`LIBDPKG_VOLATILE_API` is defined before inclusion to satisfy the library's unstable-API acknowledgment requirement. + +## Usage Example + +```cpp +#include "pkgarray.h" + +namespace osquery { + +void listInstalledPackages() { + // Create and initialize a sorted package array + auto pkgs = PkgArray::create(); + + struct pkg_array& array = pkgs.get(); + + for (int i = 0; i < array.n_pkgs; ++i) { + struct pkginfo* pkg = array.pkgs[i]; + // Process each package... + } + // pkg_array_destroy() called automatically on scope exit +} + +} // namespace osquery +``` + +> **Note:** `PkgArray` is non-copyable by design. Always use `std::move` or pass by reference when transferring ownership across scopes. \ No newline at end of file diff --git a/osquery/utils/macros/.macros.md b/osquery/utils/macros/.macros.md new file mode 100644 index 00000000000..1083e9f3081 --- /dev/null +++ b/osquery/utils/macros/.macros.md @@ -0,0 +1,33 @@ + +Utility header providing portable preprocessor macros for stringification, string concatenation, and symbol visibility across Windows and POSIX platforms. + +## Key Components + +| Macro | Description | +|---|---| +| `STR(x)` / `STR_OF(x)` | Two-stage stringification — converts a token or macro expansion to a string literal | +| `STR_EX(x)` | Platform-safe token expansion; uses variadic `__VA_ARGS__` on Win32 to handle edge cases | +| `CONCAT(x, y)` | Stringifies and concatenates two tokens into a single string literal | +| `USED_SYMBOL` | Marks a symbol as used (`__attribute__((used))`) on GCC/Clang; no-op on Win32 | +| `EXPORT_FUNCTION` | Marks a function for DLL export (`__declspec(dllexport)`) on Win32; no-op elsewhere | + +## Usage Example + +```c +#include "macros.h" + +// Stringification +#define VERSION 42 +const char* ver = STR(VERSION); // → "42" + +// Concatenation +const char* path = CONCAT(/usr/local, /bin); // → "/usr/local/bin" + +// Symbol visibility — prevents linker from stripping an unreferenced symbol +USED_SYMBOL static void internal_hook(void) { /* ... */ } + +// DLL export on Windows, transparent on Linux/macOS +EXPORT_FUNCTION void my_plugin_init(void); +``` + +> **Note:** The two-stage `STR` / `STR_OF` pattern is required to force macro expansion before stringification. Using a single-stage `#x` directly would stringify the macro name rather than its value. \ No newline at end of file diff --git a/osquery/utils/pidfile/.pidfile.md b/osquery/utils/pidfile/.pidfile.md new file mode 100644 index 00000000000..d92e72d1534 --- /dev/null +++ b/osquery/utils/pidfile/.pidfile.md @@ -0,0 +1,55 @@ + +Provides the `Pidfile` class interface for creating, locking, reading, and managing PID files to ensure single-instance process execution within the osquery framework. + +## Key Components + +### `Pidfile` Class +A non-copyable, move-only RAII class managing the lifecycle of a PID file. + +### Error Enum +Defines failure states returned via `Expected`: + +| Value | Description | +|---|---| +| `Busy` | Another instance is already running | +| `NotRunning` | No active process found | +| `AccessDenied` | Insufficient file permissions | +| `MemoryAllocationFailure` | Heap allocation failed | +| `IOError` | Filesystem read/write failure | +| `InvalidProcessID` | PID value is malformed or invalid | +| `Unknown` | Unclassified error | + +### Static Methods + +| Method | Description | +|---|---| +| `create(path)` | Creates and locks a PID file, writing the current PID | +| `read(path)` | Reads and returns the PID stored in an existing file | +| `createFile(path)` | Opens/creates the underlying file handle | +| `lockFile(handle)` | Acquires an exclusive lock on the file | +| `readFile(handle)` | Reads raw content from the file handle | +| `writeFile(handle)` | Writes the current process ID to the file | +| `closeFile(handle)` | Releases the file handle | +| `destroyFile(handle, path)` | Closes and deletes the PID file | + +## Usage Example + +```cpp +#include + +// Create and lock a PID file for single-instance enforcement +auto pidfile = osquery::Pidfile::create("/var/run/osqueryd.pid"); +if (pidfile.isError()) { + if (pidfile.getError() == osquery::Pidfile::Error::Busy) { + std::cerr << "Another instance is already running.\n"; + } + return 1; +} + +// Read an existing PID file +auto pid = osquery::Pidfile::read("/var/run/osqueryd.pid"); +if (pid.isValue()) { + std::cout << "Running PID: " << pid.get() << "\n"; +} +// PID file is automatically released when `pidfile` goes out of scope +``` \ No newline at end of file diff --git a/osquery/utils/pidfile/.pidfile_posix.md b/osquery/utils/pidfile/.pidfile_posix.md new file mode 100644 index 00000000000..bc72fe8580b --- /dev/null +++ b/osquery/utils/pidfile/.pidfile_posix.md @@ -0,0 +1,44 @@ + +POSIX implementation of the `Pidfile` class, providing file-based process locking using POSIX system calls to create, lock, read, write, and destroy PID files on Unix-like systems. + +## Key Components + +| Function | Description | +|---|---| +| `createFile()` | Opens or creates a PID file at the given path with mode `0600`, retrying up to 5 times on `EINTR` | +| `lockFile()` | Acquires an exclusive, non-blocking `flock` lock; enforces `0600` permissions and current user ownership via `fchmod`/`fchown` | +| `writeFile()` | Truncates the file and writes the current process PID (from `getpid()`) | +| `readFile()` | Reads up to 32 bytes from the PID file using `fstat` for sizing | +| `closeFile()` | Closes the file descriptor via `close()` | +| `destroyFile()` | Unlinks the file path, releases the `flock`, and closes the descriptor | + +## Security Notes + +The file is always created and enforced with mode `0600` to prevent unprivileged users from acquiring the lock before the owning process. This guards against a race condition documented in [`flock(2)`](https://man7.org/linux/man-pages/man2/flock.2.html) where shared/exclusive locks can be placed regardless of the file's open mode. + +## Usage Example + +```cpp +// Typical lifecycle managed by the Pidfile class +auto file_handle = Pidfile::createFile("/var/run/osquery.pid"); +if (!file_handle) { + // Handle AccessDenied or Unknown error +} + +auto locked = Pidfile::lockFile(*file_handle); +if (!locked) { + // Handle Busy (another instance running) or IOError +} + +auto write_error = Pidfile::writeFile(*locked); +if (write_error) { + // Handle IOError +} + +// On shutdown: +Pidfile::destroyFile(*locked, "/var/run/osquery.pid"); +``` + +## Error Handling + +All operations return `Expected` or `boost::optional`, mapping `errno` values to typed errors: `AccessDenied`, `Busy`, `MemoryAllocationFailure`, `IOError`, and `Unknown`. \ No newline at end of file diff --git a/osquery/utils/pidfile/.pidfile_windows.md b/osquery/utils/pidfile/.pidfile_windows.md new file mode 100644 index 00000000000..a7538fdd476 --- /dev/null +++ b/osquery/utils/pidfile/.pidfile_windows.md @@ -0,0 +1,44 @@ + +Windows-specific implementation of the `Pidfile` class, providing PID file creation, locking, reading, writing, and cleanup using the Win32 API. + +## Key Components + +### Internal Helpers + +| Function | Description | +|---|---| +| `toNativeHandle()` | Converts a `Pidfile::FileHandle` to a Win32 `HANDLE` via `memcpy` | +| `fromNativeHandle()` | Converts a Win32 `HANDLE` back to a `Pidfile::FileHandle` | +| `getCurrentPID()` | Returns the current process ID as a string via `GetCurrentProcessId()` | + +### Public `Pidfile` Methods + +| Method | Description | +|---|---| +| `createFile()` | Opens or creates a PID file at the given path using `CreateFileW`; falls back to `OPEN_EXISTING` on collision; maps Win32 errors to `Pidfile::Error` | +| `lockFile()` | Upgrades the file handle via `ReOpenFile` to add write access and `FILE_FLAG_DELETE_ON_CLOSE`, restricting sharing to read-only; returns `Error::Busy` on failure | +| `writeFile()` | Writes the current PID string to the file, retrying up to 5 times for partial writes | +| `readFile()` | Reads up to 32 bytes from the PID file using `GetFileSizeEx` + `ReadFile`, retrying on partial reads | +| `closeFile()` | Closes the underlying Win32 handle via `CloseHandle` | +| `destroyFile()` | Delegates to `closeFile()`; the `FILE_FLAG_DELETE_ON_CLOSE` flag set during locking handles file deletion automatically | + +## Usage Example + +```cpp +// Create and lock a PID file on Windows +auto handle_result = Pidfile::createFile("C:\\ProgramData\\osquery\\osquery.pid"); +if (handle_result.isError()) { + // Handle Error::Busy, Error::AccessDenied, etc. +} + +auto locked = Pidfile::lockFile(handle_result.get()); +if (locked.isError()) { + // Another process holds the lock +} + +Pidfile::writeFile(locked.get()); // Writes current PID +// File is auto-deleted on CloseHandle due to FILE_FLAG_DELETE_ON_CLOSE +Pidfile::destroyFile(locked.get(), ""); +``` + +> **Note:** The `FILE_FLAG_DELETE_ON_CLOSE` flag set in `lockFile()` means the file is automatically removed when the last handle is closed, so `destroyFile()` simply calls `closeFile()` with no explicit path deletion needed. \ No newline at end of file diff --git a/osquery/utils/pidfile/tests/.pidfile.md b/osquery/utils/pidfile/tests/.pidfile.md new file mode 100644 index 00000000000..0520fedda2f --- /dev/null +++ b/osquery/utils/pidfile/tests/.pidfile.md @@ -0,0 +1,44 @@ + +Unit test file for the `Pidfile` class, validating PID file creation, locking, destruction, and content verification across platforms (Windows and POSIX). + +## Key Components + +- **`UniquePath`** — RAII helper that generates a temporary unique file path via `boost::filesystem` and auto-removes it on destruction. Non-copyable. +- **`getErrorMessage()`** — Overloaded utilities to stringify `Pidfile::Error` codes for readable test failure output. +- **`PidfileTests`** — GTest fixture (`testing::Test`) containing all pidfile test cases. +- **`TEST_F(PidfileTests, test_pidfile)`** — Primary test covering the full pidfile lifecycle. + +## Test Coverage + +The single test case validates the following scenarios in sequence: + +| Scenario | Expected Outcome | +|---|---| +| Create pidfile at a new path | Success | +| Re-create pidfile at same path (×5) | Fails with `Pidfile::Error::Busy` | +| Create pidfile at a different path | Success | +| Destroy first pidfile, re-create at its path | Success | +| Overwrite an existing non-pidfile with valid content | Success | +| Read PID from file; compare to current process PID | Matches `getpid()` / `GetCurrentProcessId()` | +| Read PID after releasing the lock | Fails with `Pidfile::Error::NotRunning` | + +## Usage Example + +```cpp +// Typical pattern mirrored by these tests +auto result = Pidfile::create("/var/run/osquery.pid"); +if (result.isError()) { + // Another instance is running + std::cerr << result.getErrorCode(); // e.g., Pidfile::Error::Busy + return; +} + +// Read back the stored PID +auto pid_exp = Pidfile::read("/var/run/osquery.pid"); +if (!pid_exp.isError()) { + int pid = pid_exp.take(); // equals getpid() +} + +// Releasing the Pidfile (result.take()) removes the lock +result.take(); +``` \ No newline at end of file diff --git a/osquery/utils/schemer/.schemer.md b/osquery/utils/schemer/.schemer.md new file mode 100644 index 00000000000..e589607c3d4 --- /dev/null +++ b/osquery/utils/schemer/.schemer.md @@ -0,0 +1,44 @@ + +Defines a lightweight, macro-free serialization/deserialization schema framework for C++ classes within the osquery project, enabling a single schema declaration to drive multiple formatters (JSON, TOML, binary, etc.). + +## Key Components + +| Symbol | Kind | Description | +|---|---|---| +| `schemer::record` | Function template | Registers a class member with the archive by delegating to `a.record(key, value)` | +| `impl::DummyArchive` | Internal class | Stub archive used exclusively for compile-time introspection via `has_schema` | +| `has_schema` | Type trait | Compile-time boolean (`::value`) that detects whether a type exposes a `discloseSchema` static method | + +## Usage Example + +Define a `discloseSchema` static method on any class to make it serializable: + +```cpp +#include "schemer.h" + +class Config { + public: + template + static inline void discloseSchema(Archive& a, ValueType& value) { + osquery::schemer::record(a, "Host", value.host_); + osquery::schemer::record(a, "Port", value.port_); + osquery::schemer::record(a, "Enabled", value.enabled_); + } + + private: + std::string host_ = "localhost"; + int port_ = 8080; + bool enabled_ = true; +}; + +// Compile-time schema check +static_assert(osquery::schemer::has_schema::value, + "Config must declare discloseSchema"); +``` + +A formatter (e.g., a JSON writer) accepts a `Config` instance and an archive object; `discloseSchema` drives field iteration automatically — no separate serializer and deserializer methods required. + +## Notes + +- The framework itself has **zero dependencies** — all format-specific logic lives in external formatter implementations. +- Field ordering defined in `discloseSchema` is preserved, enabling binary formatters to ignore field names and rely solely on position. \ No newline at end of file diff --git a/osquery/utils/schemer/json/.schemer_json.md b/osquery/utils/schemer/json/.schemer_json.md new file mode 100644 index 00000000000..df572f14acf --- /dev/null +++ b/osquery/utils/schemer/json/.schemer_json.md @@ -0,0 +1,51 @@ + +Provides JSON serialization and deserialization for C++ objects with defined schemas, using RapidJSON as the underlying JSON engine within the `osquery::schemer` namespace. + +## Key Components + +| Symbol | Kind | Description | +|---|---|---| +| `toJson(os, value)` | Template function | Serializes a schema-defined object to a RapidJSON output stream | +| `toJson(value)` | Template function | Serializes a schema-defined object directly to `std::string` | +| `fromJson(value, is)` | Template function | Deserializes a schema-defined object from a RapidJSON input stream | +| `fromJson(value, c_str)` | Template function | Deserializes a schema-defined object from a C-string (`const char*`) | +| `JsonError` | Error type | Error codes for JSON failures (`Syntax`, `IncorrectFormat`) | + +### Supported Member Types + +- `bool`, integral types (`int`, `short`, `long`, etc.) +- Floating-point (`float`, `double`) +- `std::string`, C-strings *(serialization only)* +- Nested types with a defined schema (`schemer::has_schema`) + +## Usage Example + +```cpp +// Define a schema-aware struct +struct Config { + std::string host; + int port; + bool enabled; + + template + static void discloseSchema(Writer& w, Config const& obj) { + schemer::writeKeyValue(w, "host", obj.host); + schemer::writeKeyValue(w, "port", obj.port); + schemer::writeKeyValue(w, "enabled", obj.enabled); + } +}; + +// Serialize to JSON string +Config cfg{"localhost", 8080, true}; +auto result = osquery::schemer::toJson(cfg); +if (result) { + std::string json = result.take(); // {"host":"localhost","port":8080,"enabled":true} +} + +// Deserialize from C-string +Config parsed{}; +auto status = osquery::schemer::fromJson(parsed, R"({"host":"db","port":5432,"enabled":false})"); +if (status.isError()) { + // handle JsonError::Syntax or JsonError::IncorrectFormat +} +``` \ No newline at end of file diff --git a/osquery/utils/schemer/json/.schemer_json_error.md b/osquery/utils/schemer/json/.schemer_json_error.md new file mode 100644 index 00000000000..6cec2bf19d8 --- /dev/null +++ b/osquery/utils/schemer/json/.schemer_json_error.md @@ -0,0 +1,45 @@ + +Defines a scoped enumeration of JSON parsing error codes used within the `osquery::schemer` namespace. + +## Key Components + +### `JsonError` (enum class) + +Strongly-typed error codes representing failure modes during JSON schema processing: + +| Enumerator | Value | Description | +|---|---|---| +| `Syntax` | `1` | Malformed JSON syntax | +| `TypeMismatch` | `2` | Value type does not match expected schema type | +| `MissedKey` | `3` | Required key is absent from the JSON object | +| `IncorrectFormat` | `4` | JSON structure does not conform to expected format | + +## Usage Example + +```c +#include "schemer_json_error.h" + +osquery::schemer::JsonError handleParseResult() { + // Simulate a missing required field + return osquery::schemer::JsonError::MissedKey; +} + +void checkError(osquery::schemer::JsonError err) { + switch (err) { + case osquery::schemer::JsonError::Syntax: + // handle malformed JSON + break; + case osquery::schemer::JsonError::TypeMismatch: + // handle wrong value type + break; + case osquery::schemer::JsonError::MissedKey: + // handle absent required key + break; + case osquery::schemer::JsonError::IncorrectFormat: + // handle structural mismatch + break; + } +} +``` + +> Using an `enum class` (scoped enum) prevents implicit integer conversions and name collisions, making error handling safer and more explicit compared to a plain `enum`. \ No newline at end of file diff --git a/osquery/utils/schemer/json/.schemer_json_impl.md b/osquery/utils/schemer/json/.schemer_json_impl.md new file mode 100644 index 00000000000..1969bd2f0a2 --- /dev/null +++ b/osquery/utils/schemer/json/.schemer_json_impl.md @@ -0,0 +1,51 @@ + +Internal implementation header providing template-based JSON serialization and deserialization primitives for osquery's schemer framework, mapping C++ types to RapidJSON reader/writer operations. + +## Key Components + +### `writeValue<>` (template overloads) + +A family of SFINAE-dispatched free functions that forward C++ primitive values to the appropriate RapidJSON writer method: + +| C++ Type | RapidJSON Method | +|---|---| +| `bool` | `Bool()` | +| `int8/16/32_t` | `Int()` | +| `int64_t` | `Int64()` | +| `uint8/16/32_t` | `Uint()` | +| `uint64_t` | `Uint64()` | +| Floating point | `Double()` | +| `std::string` | `String()` | +| Schema-enabled types | Recursive `JsonWriter` | + +### `JsonWriter` + +A RAII wrapper around a RapidJSON writer that calls `StartObject()` on construction and `EndObject()` on destruction. Exposes a `record(key, value)` method that writes a key-value pair, dispatching value serialization through `writeValue`. + +### `JsonReader` + +A stateful reader wrapping a `rapidjson::Value` object. Exposes `record(key, value)` to extract fields by key with type checking. Errors are accumulated in the public `status` member (`ExpectedSuccess`). Supports `std::string`, `double`, integral types, and recursively schema-enabled types via `copyValueFromJValue` overloads. + +## Usage Example + +```cpp +// Serialization (via JsonWriter) +rapidjson::StringBuffer buf; +rapidjson::Writer raw_writer(buf); +auto writer = osquery::schemer::impl::JsonWriter(raw_writer); +writer.record("hostname", std::string{"myhost"}); +writer.record("port", std::int32_t{8080}); +// Destructor closes the JSON object automatically + +// Deserialization (via JsonReader) +rapidjson::Document doc; +doc.Parse(R"({"hostname":"myhost","port":8080})"); +auto reader = osquery::schemer::impl::JsonReader(doc); +std::string hostname; +std::int32_t port{}; +reader.record("hostname", hostname); +reader.record("port", port); +if (reader.status.isError()) { + // handle JsonError::TypeMismatch or JsonError::MissedKey +} +``` \ No newline at end of file diff --git a/osquery/utils/schemer/json/tests/.schemer_json.md b/osquery/utils/schemer/json/tests/.schemer_json.md new file mode 100644 index 00000000000..069f68fdec5 --- /dev/null +++ b/osquery/utils/schemer/json/tests/.schemer_json.md @@ -0,0 +1,52 @@ + +Unit test suite for the `schemer` JSON serialization/deserialization utilities in osquery, validating `schemer::toJson` and `schemer::fromJson` across flat and nested schema-disclosed C++ types. + +## Key Components + +- **`TestClass`** — Simple schema-disclosed class with `bool`, `int`, and `std::string` fields used to test basic serialization. +- **`NestedTestClass`** — Wraps `TestClass` to test recursive/nested JSON serialization and deserialization. +- **`SecondTestClass`** — Private-member class used to validate deserialization with typed field accessors. +- **`ThirdTestClass`** — Tests round-trip fidelity with edge-case values: escape characters, `unsigned` max, `float` max, and `int64_t` min. + +## Test Coverage + +| Test | Description | +|---|---| +| `writer_to_stream` | Serializes to a `rapidjson::StringBuffer` | +| `writer_to_string` | Serializes to `std::string` via `toJson` | +| `writer_nested_to_string` | Serializes nested schema objects | +| `read_from_stream` | Deserializes valid JSON from a stream | +| `read_from_stream_syntax_error` | Catches malformed JSON (`{{`) → `JsonError::Syntax` | +| `read_from_stream_object_type_error` | Catches JSON arrays where objects expected → `JsonError::IncorrectFormat` | +| `read_from_stream_member_type_error` | Catches wrong field types → `JsonError::IncorrectFormat` | +| `read_from_stream_missed_key` | Catches missing required keys → `JsonError::IncorrectFormat` | +| `read_write` | Full round-trip with boundary/escape values | +| `read_nested_from_string` | Deserializes nested JSON structures | +| `read_nested_*_fails_*` | Validates nested error cases (wrong type, incomplete JSON) | + +## Usage Example + +```cpp +// Define a schema-disclosed type +class MyConfig { + public: + template + static void discloseSchema(Archive& a, ValueType& value) { + schemer::record(a, "host", value.host_); + schemer::record(a, "port", value.port_); + } + std::string host_ = "localhost"; + int port_ = 8080; +}; + +// Serialize +auto const result = schemer::toJson(MyConfig{}); +if (result.isValue()) { + std::cout << result.get(); // {"host":"localhost","port":8080} +} + +// Deserialize +MyConfig cfg; +auto const status = schemer::fromJson(cfg, R"({"host":"remote","port":443})"); +if (status.isValue()) { /* use cfg */ } +``` \ No newline at end of file diff --git a/osquery/utils/schemer/tests/.schemer.md b/osquery/utils/schemer/tests/.schemer.md new file mode 100644 index 00000000000..810e5cf8d40 --- /dev/null +++ b/osquery/utils/schemer/tests/.schemer.md @@ -0,0 +1,47 @@ + +Unit tests for the `osquery::schemer` serialization framework, verifying schema-driven serialization, deserialization, and compile-time schema detection. + +## Key Components + +| Component | Description | +|---|---| +| `TestArchiveReader` | Mock archive that injects fixed values (`1234` for `int`, `"water under the bridge"` for `std::string`) during deserialization | +| `TestArchiveWriter` | Mock archive that serializes key-value pairs into a formatted string (e.g., `"Alpha:712 Bravo:Richard "`) | +| `Alpha` | Test struct with mixed private fields (`int`, `std::string`) exposing a `discloseSchema` method | +| `Beta` | Minimal struct with an empty `discloseSchema` — confirms schema detection works for no-op schemas | +| `Gama` | Plain struct with no schema — used to verify negative `has_schema` detection | + +## Test Cases + +| Test | What it validates | +|---|---| +| `serializing` | `TestArchiveWriter` correctly serializes all `Alpha` fields via `discloseSchema` | +| `deserializing` | `TestArchiveReader` correctly populates private `Alpha` fields, including additive logic for `Charlie` | +| `has_schema_static_go` | `schemer::has_schema::value` is `true` for `Alpha` and `Beta` | +| `has_schema_static_no_go` | `schemer::has_schema::value` is `false` for `Gama`, `int`, `unsigned`, `float`, `char const*`, `std::string` | + +## Usage Example + +```cpp +// Define a schema-aware class +class MyData { + public: + template + static void discloseSchema(Archive& a, ValueType& value) { + schemer::record(a, "id", value.id_); + schemer::record(a, "name", value.name_); + } + private: + int id_ = 0; + std::string name_ = "default"; +}; + +// Serialize +auto writer = TestArchiveWriter{}; +MyData::discloseSchema(writer, myObj); +// writer.text == "id:0 name:default " + +// Compile-time schema check +static_assert(schemer::has_schema::value); // true +static_assert(!schemer::has_schema::value); // true +``` \ No newline at end of file diff --git a/osquery/utils/status/.status.md b/osquery/utils/status/.status.md new file mode 100644 index 00000000000..e754c3118db --- /dev/null +++ b/osquery/utils/status/.status.md @@ -0,0 +1,47 @@ + +Defines the `osquery::Status` class, a lightweight utility for expressing operation outcomes with a numeric code and descriptive message — analogous to a simple result type used throughout the osquery codebase. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `Status` | Class | Core result type wrapping an integer code and string message | +| `kSuccessCode` | `static constexpr int` | Success sentinel value (`0`) | +| `ok()` | Method | Returns `true` if code equals `kSuccessCode` | +| `getCode()` | Method | Returns the raw integer status code | +| `getMessage()` / `toString()` / `what()` | Methods | Returns the descriptive message string | +| `Status::success()` | Static factory | Creates a success `Status` | +| `Status::failure(...)` | Static factory | Creates a failure `Status` with optional code and message | +| `to(Expected)` | Template function | Converts an `Expected` to a `Status` | + +## Usage Example + +```cpp +#include +#include + +// Return status from a function +osquery::Status doWork() { + if (someCondition()) { + return osquery::Status::success(); + } + return osquery::Status::failure("Something went wrong"); +} + +// Check result with ok() +auto s = doWork(); +if (s.ok()) { + LOG(INFO) << "Success"; +} else { + LOG(ERROR) << s.getMessage(); // or s.toString() / s.what() +} + +// Implicit bool conversion +if (doWork()) { + LOG(INFO) << "Also works!"; +} + +// Convert from Expected<> to Status +Expected result = getExpectedValue(); +osquery::Status status = osquery::to(result); +``` \ No newline at end of file diff --git a/osquery/utils/status/tests/.status.md b/osquery/utils/status/tests/.status.md new file mode 100644 index 00000000000..9365145315f --- /dev/null +++ b/osquery/utils/status/tests/.status.md @@ -0,0 +1,45 @@ + +Brief unit test suite for the `Status` utility class in osquery, validating construction, success/failure states, string conversion, and `Expected`-to-`Status` conversion. + +## Key Components + +- **`StatusTests`** — GTest fixture class containing all test cases for the `Status` type +- **`TestError`** — Local enum used to exercise `Expected` → `Status` conversion paths +- **`stringContains`** — Helper predicate wrapping `boost::contains` for use with `EXPECT_PRED2` + +## Test Coverage + +| Test | Validates | +|---|---| +| `test_constructor` | Code and message stored correctly via `Status(int, string)` | +| `test_constructor_2` | Default arg produces code `0` and message `"OK"` | +| `test_ok` | `ok()` returns `true` only when code is `0` | +| `test_to_string` | `toString()` returns the stored message | +| `test_default_constructor` | `Status{}` is implicitly successful | +| `test_success_code` | `Status(kSuccessCode)` passes `ok()` | +| `test_success` | `Status::success()` factory produces a passing status | +| `test_failure_single_arg` | `Status::failure(msg)` sets error message and fails `ok()` | +| `test_failure_double_arg` | `Status::failure(code, msg)` accepts a custom error code | +| `test_failure_with_success_code` | Debug-mode assertion fires when `kSuccessCode` is passed to `failure()` | +| `test_expected_to_status_failure` | `to(expected)` propagates error reason from a failed `Expected` | +| `test_expected_to_status_success` | `to(expected)` returns `Status::success()` from a valid `Expected` | + +## Usage Example + +```cpp +// Typical Status usage patterns exercised by these tests +auto ok = Status::success(); +assert(ok.ok()); + +auto err = Status::failure(42, "disk not found"); +assert(!err.ok()); +assert(err.getCode() == 42); +assert(err.toString() == "disk not found"); + +// Converting an Expected result to a Status +Expected result = doSomething(); +Status s = to(result); +if (!s.ok()) { + LOG(ERROR) << s.toString(); +} +``` \ No newline at end of file diff --git a/osquery/utils/system/.boottime.md b/osquery/utils/system/.boottime.md new file mode 100644 index 00000000000..7e3410b3f57 --- /dev/null +++ b/osquery/utils/system/.boottime.md @@ -0,0 +1,25 @@ + +Provides a utility interface for retrieving the system boot time as a Unix timestamp within the osquery framework. + +## Key Components + +- **`getBootTime()`** — Returns the system boot time as a `uint64_t` Unix epoch timestamp (seconds since January 1, 1970). + +## Usage Example + +```c +#include + +// Retrieve the system boot time +std::uint64_t bootTime = osquery::getBootTime(); + +// Calculate system uptime in seconds +std::uint64_t now = std::time(nullptr); +std::uint64_t uptime = now - bootTime; +``` + +## Notes + +- Declared within the `osquery` namespace. +- Depends on `osquery/filesystem/filesystem.h` for platform-level filesystem access used internally by the implementation. +- Typically used in system info tables (e.g., `system_info`, `uptime`) to expose boot time data to osquery SQL queries. \ No newline at end of file diff --git a/osquery/utils/system/.env.md b/osquery/utils/system/.env.md new file mode 100644 index 00000000000..7efcc02bf3d --- /dev/null +++ b/osquery/utils/system/.env.md @@ -0,0 +1,40 @@ + +Utility header for environment variable management within the `osquery` namespace, providing cross-platform helpers to get, set, and unset environment variables, with additional Windows-specific utilities for string expansion and argument parsing. + +## Key Components + +| Function | Description | +|---|---| +| `setEnvVar(name, value)` | Sets an environment variable; returns `true` on success | +| `unsetEnvVar(name)` | Removes an environment variable; returns `true` on success | +| `getEnvVar(name)` | Retrieves an environment variable value; returns `boost::none` if not found | +| `expandEnvString(input)` *(Windows only)* | Expands embedded environment variable references in a string | +| `splitArgs(args)` *(Windows only)* | Splits a command-line string into individual arguments per system rules | + +## Usage Example + +```c +#include "env.h" + +// Set an environment variable +bool ok = osquery::setEnvVar("MY_VAR", "hello"); + +// Retrieve a variable safely +auto val = osquery::getEnvVar("MY_VAR"); +if (val) { + std::cout << *val; // "hello" +} + +// Unset the variable +osquery::unsetEnvVar("MY_VAR"); + +#ifdef WINDOWS +// Expand %USERPROFILE%\config on Windows +auto expanded = osquery::expandEnvString("%USERPROFILE%\\config"); + +// Parse a command-line string into tokens +auto args = osquery::splitArgs("myapp.exe --flag value"); +#endif +``` + +> All functions return `boost::optional` or `bool` to allow safe, exception-free error handling without throwing on missing or invalid variables. \ No newline at end of file diff --git a/osquery/utils/system/.errno.md b/osquery/utils/system/.errno.md new file mode 100644 index 00000000000..dc44e797fde --- /dev/null +++ b/osquery/utils/system/.errno.md @@ -0,0 +1,40 @@ + +Cross-platform error handling utility header providing string descriptions for system error codes within the osquery framework. + +## Key Components + +| Symbol | Platform | Description | +|---|---|---| +| `platformStrerr(int errnum)` | All | Converts a POSIX `errno` integer to a human-readable `std::string` | +| `getWindowsErrorDescription(...)` | Windows only | Converts a Windows `DWORD` error code (from `GetLastError()` / `winerror.h`) to a `std::wstring`, returning an osquery `Status` | + +## Usage Example + +```c +#include +#include + +// POSIX-style error lookup (all platforms) +int result = some_syscall(); +if (result < 0) { + std::string msg = osquery::platformStrerr(errno); + LOG(ERROR) << "Syscall failed: " << msg; +} + +#ifdef WIN32 +// Windows-specific error lookup +std::wstring winErrorMsg; +DWORD lastErr = GetLastError(); +osquery::Status s = osquery::getWindowsErrorDescription(winErrorMsg, lastErr); +if (s.ok()) { + // use winErrorMsg +} +#endif +``` + +## Notes + +- Uses `#pragma once` for include guard +- `WIN32_LEAN_AND_MEAN` is defined before `` to minimize Windows header bloat +- `getWindowsErrorDescription` is conditionally compiled — only available on `WIN32` builds +- Depends on `osquery/utils/status/status.h` for the `Status` return type on Windows \ No newline at end of file diff --git a/osquery/utils/system/.filepath.md b/osquery/utils/system/.filepath.md new file mode 100644 index 00000000000..448756d6a52 --- /dev/null +++ b/osquery/utils/system/.filepath.md @@ -0,0 +1,24 @@ + +Provides a cross-platform utility for resolving canonical (absolute, symlink-resolved) file paths within the osquery namespace. + +## Key Components + +- **`canonicalize_file_name(const char* name)`** — A safer wrapper around `realpath` (or platform equivalent) that resolves a file path to its canonical absolute form, handling symlinks and relative path components. + +## Usage Example + +```c +#include "filepath.h" + +// Resolve a potentially relative or symlinked path +const std::string canonical = osquery::canonicalize_file_name("/etc/../etc/passwd"); +// Result: "/etc/passwd" + +const std::string resolved = osquery::canonicalize_file_name("./relative/path/../file.txt"); +// Result: fully resolved absolute path +``` + +## Notes + +- Returns a `std::string` rather than a raw pointer, avoiding manual memory management concerns associated with the standard `realpath` function. +- Designed to be a drop-in safer alternative where `realpath` might return `nullptr` or require explicit `free()`. \ No newline at end of file diff --git a/osquery/utils/system/.system.md b/osquery/utils/system/.system.md new file mode 100644 index 00000000000..310433f8be5 --- /dev/null +++ b/osquery/utils/system/.system.md @@ -0,0 +1,38 @@ + +Cross-platform system utility header providing hostname resolution functions for the osquery framework, with platform-specific implementations for Windows and POSIX systems. + +## Key Components + +### Functions (in `osquery` namespace) + +| Function | Returns | Description | +|---|---|---| +| `getHostname()` | `std::string` | Returns the host's current hostname | +| `getFqdn()` | `std::string` | Returns the fully qualified domain name, or hostname if not domain-joined | + +### Platform Includes + +Conditionally includes the appropriate platform implementation: +- **Windows**: `osquery/utils/system/windows/system.h` +- **POSIX** (Linux/macOS): `osquery/utils/system/posix/system.h` + +## Usage Example + +```cpp +#include +#include + +void printHostInfo() { + // Get short hostname (e.g., "my-server") + std::string hostname = osquery::getHostname(); + + // Get fully qualified domain name (e.g., "my-server.corp.example.com") + // Falls back to hostname if host is not domain-joined + std::string fqdn = osquery::getFqdn(); + + std::cout << "Hostname : " << hostname << "\n"; + std::cout << "FQDN : " << fqdn << "\n"; +} +``` + +> **Note:** When the host is not joined to a domain, `getFqdn()` returns the same value as `getHostname()`. \ No newline at end of file diff --git a/osquery/utils/system/.time.md b/osquery/utils/system/.time.md new file mode 100644 index 00000000000..814e95af8ee --- /dev/null +++ b/osquery/utils/system/.time.md @@ -0,0 +1,38 @@ + +Cross-platform time utility header providing UNIX epoch and human-readable time conversion functions within the `osquery` namespace. + +## Key Components + +| Function | Description | +|---|---| +| `platformAsctime()` | Converts a `struct tm*` to a C++ `std::string` using the platform's `asctime` equivalent | +| `toUnixTime()` | Converts a `struct tm*` to a `uint64_t` UNIX epoch timestamp | +| `getUnixTime()` | Returns the current time as a `uint64_t` UNIX epoch timestamp | +| `toAsciiTime()` | Converts a UTC `struct tm*` to a human-readable string (`"Wed Sep 21 10:27:52 2011"`) | +| `toAsciiTimeUTC()` | Converts a local `struct tm*` to a UTC human-readable string via epoch + `gmtime()` | +| `getAsciiTime()` | Returns the current date/time as a human-readable string | + +## Usage Example + +```cpp +#include +#include + +// Get current time as UNIX epoch +uint64_t epoch = osquery::getUnixTime(); +std::cout << "Epoch: " << epoch << std::endl; + +// Get current time as readable string +std::string now = osquery::getAsciiTime(); +std::cout << "Now: " << now << std::endl; // "Wed Sep 21 10:27:52 2011" + +// Convert a struct tm to UTC ASCII +struct tm tm_time = {}; +gmtime_r(&some_time_t, &tm_time); +std::string utc = osquery::toAsciiTimeUTC(&tm_time); + +// Convert struct tm to epoch +uint64_t ts = osquery::toUnixTime(&tm_time); +``` + +> **Note:** `toAsciiTime()` expects the `struct tm` to already be in UTC. Use `toAsciiTimeUTC()` when starting from local time, as it internally normalizes via epoch conversion and `gmtime()`. \ No newline at end of file diff --git a/osquery/utils/system/.uptime.md b/osquery/utils/system/.uptime.md new file mode 100644 index 00000000000..79f63b62960 --- /dev/null +++ b/osquery/utils/system/.uptime.md @@ -0,0 +1,15 @@ + +Provides a utility interface for retrieving system uptime within the osquery framework. + +## Key Components + +- **`getUptime()`** — Returns the system uptime as a `long` value (typically in seconds) within the `osquery` namespace. + +## Usage Example + +```c +#include "uptime.h" + +// Retrieve current system uptime +long uptimeSeconds = osquery::getUptime(); +``` \ No newline at end of file diff --git a/osquery/utils/system/linux/.boottime.md b/osquery/utils/system/linux/.boottime.md new file mode 100644 index 00000000000..f89a693e244 --- /dev/null +++ b/osquery/utils/system/linux/.boottime.md @@ -0,0 +1,26 @@ + +Reads the system boot time on Linux by parsing the `btime` field from `/proc/stat`, returning the result as a Unix timestamp. + +## Key Components + +- **`getBootTime()`** — Reads `/proc/stat`, locates the `btime` entry, extracts and parses the boot timestamp as a `uint64_t`. Returns `0` on any failure (file unreadable, field missing, or parse error). + +## Usage Example + +```cpp +#include + +uint64_t bootTime = osquery::getBootTime(); + +if (bootTime == 0) { + std::cerr << "Failed to retrieve boot time." << std::endl; +} else { + std::cout << "System booted at Unix timestamp: " << bootTime << std::endl; +} +``` + +## Notes + +- **Linux-specific** — Relies on `/proc/stat`, which is only available on Linux systems. +- **Error handling** — All failure paths (file read error, missing `btime` field, integer conversion failure) silently return `0`; callers should treat `0` as an error sentinel. +- **Dependencies** — Uses `osquery::readFile` for file I/O and `osquery::tryTo` for safe string-to-integer conversion. \ No newline at end of file diff --git a/osquery/utils/system/linux/.cpu.md b/osquery/utils/system/linux/.cpu.md new file mode 100644 index 00000000000..0a76034b4da --- /dev/null +++ b/osquery/utils/system/linux/.cpu.md @@ -0,0 +1,46 @@ + +Provides CPU topology utilities for Linux systems, exposing sysfs-based CPU state information as bitmask representations. + +## Key Components + +**Types & Constants** +- `Error` — enum with `IOError` and `IncorrectRange` variants for error handling +- `Mask` — alias for `std::bitset<128>`, representing up to 128 CPU cores (aligned with Linux `NR_CPUS`) +- `kMaskSize` — constant defining the bitmask width (`128`) + +**Functions** + +| Function | Description | +|---|---| +| `decodeMaskFromString(encoded_str)` | Parses a sysfs CPU mask string into a `Mask` bitset | +| `getOfflineRaw()` / `getOffline()` | Returns hotplugged-off or kernel-limited CPUs | +| `getOnlineRaw()` / `getOnline()` | Returns CPUs currently online and scheduled | +| `getPossibleRaw()` / `getPossible()` | Returns CPUs allocated and bringable online | +| `getPresentRaw()` / `getPresent()` | Returns CPUs physically present in the system | + +Each query is available in two forms: raw string (`*Raw`) as read from sysfs, or decoded `Mask` bitset. + +## Usage Example + +```cpp +#include + +// Get online CPUs as a bitmask +auto result = osquery::cpu::getOnline(); +if (result) { + osquery::cpu::Mask onlineMask = result.get(); + for (std::size_t i = 0; i < osquery::cpu::kMaskSize; ++i) { + if (onlineMask.test(i)) { + // CPU i is online + } + } +} else { + auto err = result.getError(); + // handle Error::IOError or Error::IncorrectRange +} + +// Decode a mask string directly +auto mask = osquery::cpu::decodeMaskFromString("0000,0000003f"); +``` + +> All functions return `Expected`, following osquery's error-handling pattern — always check for errors before accessing the value. \ No newline at end of file diff --git a/osquery/utils/system/linux/proc/.proc.md b/osquery/utils/system/linux/proc/.proc.md new file mode 100644 index 00000000000..4e5d984393f --- /dev/null +++ b/osquery/utils/system/linux/proc/.proc.md @@ -0,0 +1,18 @@ + +Declares a utility function for retrieving the command-line string of a running process by PID on Linux/Unix systems. + +## Key Components + +- **`osquery::proc::cmdline(pid_t pid)`** — Reads and returns the full command-line string for the given process ID, typically sourced from `/proc//cmdline`. + +## Usage Example + +```c +#include "proc.h" +#include + +// Retrieve the command line for a known PID +pid_t target = 1234; +std::string cmd = osquery::proc::cmdline(target); +std::cout << "Command: " << cmd << std::endl; +``` \ No newline at end of file diff --git a/osquery/utils/system/linux/proc/tests/.empty.md b/osquery/utils/system/linux/proc/tests/.empty.md new file mode 100644 index 00000000000..f74550642a4 --- /dev/null +++ b/osquery/utils/system/linux/proc/tests/.empty.md @@ -0,0 +1,13 @@ + +A licensing header placeholder file containing no implementation code, used to satisfy build system requirements or reserve a compilation unit. + +## Key Components + +None — this file contains only the OSQuery copyright and license header (Apache-2.0 OR GPL-2.0-only). + +## Usage Example + +```cpp +// No symbols, functions, or classes are defined in this file. +// It serves purely as a placeholder within the build system. +``` \ No newline at end of file diff --git a/osquery/utils/system/linux/proc/tests/.proc.md b/osquery/utils/system/linux/proc/tests/.proc.md new file mode 100644 index 00000000000..23b35cb9391 --- /dev/null +++ b/osquery/utils/system/linux/proc/tests/.proc.md @@ -0,0 +1,31 @@ + +Unit test file for the Linux `/proc` filesystem utility, validating the `proc::cmdline()` function across multiple PID scenarios. + +## Key Components + +- **`LinuxProcTests`** — GTest fixture class for Linux `/proc` utility tests +- **`cmdline_1`** — Verifies that PID 1 (`init`/`systemd`) has a non-empty command line +- **`cmdline_self`** — Verifies that the current process's command line contains the expected test binary path +- **`cmdline_not_existing_pid`** — Verifies that invalid/non-existent PIDs (`-1`, `0`, `-12`) return an empty string + +## Usage Example + +```cpp +// Run all LinuxProcTests via GTest +// $ ./proc_tests + +// Underlying API being tested: +#include + +// Get command line for a specific PID +auto line = proc::cmdline(1); // Returns init/systemd cmdline +auto self = proc::cmdline(getpid()); // Returns current process cmdline +auto bad = proc::cmdline(-1); // Returns empty string (invalid PID) +``` + +## Notes + +- Depends on `osquery/utils/system/linux/proc/proc.h` for the `proc::cmdline()` implementation +- Uses `getpid()` to dynamically resolve the current process PID at runtime +- Invalid PIDs are expected to return an empty `std::string` — callers should check `.empty()` before use +- Linux-only; not applicable on macOS or Windows targets \ No newline at end of file diff --git a/osquery/utils/system/linux/tests/.cpu.md b/osquery/utils/system/linux/tests/.cpu.md new file mode 100644 index 00000000000..597024697d5 --- /dev/null +++ b/osquery/utils/system/linux/tests/.cpu.md @@ -0,0 +1,39 @@ + +Unit test file for the `cpu` utility module in osquery's Linux system layer, validating CPU mask decoding and CPU set query functions. + +## Key Components + +- **`SystemCpuTests`** — GTest fixture class housing all test cases for `osquery::cpu` utilities +- **`decodeMaskFromString` tests** — Validates correct bitset mask generation from Linux CPU list strings (e.g., `"0"`, `"2-4"`, `"0,2-5"`) +- **Error case tests** — Verifies `cpu::Error::IncorrectRange` is returned for malformed inputs (reversed ranges, trailing dashes, non-numeric chars) +- **CPU set query tests** — Validates `getOffline`, `getOnline`, `getPossible`, and `getPresent` (plus their `Raw` variants) return valid, decodable values + +## Usage Example + +```cpp +// Decoding a CPU affinity mask string +auto exp = cpu::decodeMaskFromString("0,2-5"); +if (exp.isValue()) { + cpu::Mask mask = exp.get(); // Bitset: "111101" +} + +// Querying online CPUs +auto online = cpu::getOnline(); +if (online.isValue()) { + // Process the online CPU mask +} + +// Handling decode errors +auto bad = cpu::decodeMaskFromString("1-0"); // Reversed range +if (bad.isError()) { + assert(bad.getErrorCode() == cpu::Error::IncorrectRange); +} +``` + +## Test Coverage Summary + +| Category | Cases | +|---|---| +| Valid mask decoding | Empty string, single CPU, ranges, mixed | +| Invalid mask decoding | 6 error cases (reversed, malformed, non-numeric) | +| CPU set queries | `Offline`, `Online`, `Possible`, `Present` (raw + parsed) | \ No newline at end of file diff --git a/osquery/utils/system/posix/.env.md b/osquery/utils/system/posix/.env.md new file mode 100644 index 00000000000..0571233f710 --- /dev/null +++ b/osquery/utils/system/posix/.env.md @@ -0,0 +1,43 @@ + +Provides cross-platform utility functions for reading, writing, and removing environment variables within the `osquery` namespace. + +## Key Components + +| Function | Signature | Description | +|---|---|---| +| `setEnvVar` | `(const std::string& name, const std::string& value) → bool` | Sets an environment variable, overwriting if it exists. Returns `true` on success. | +| `unsetEnvVar` | `(const std::string& name) → bool` | Removes an environment variable. Returns `true` on success. | +| `getEnvVar` | `(const std::string& name) → boost::optional` | Retrieves an environment variable's value, or `boost::none` if not set. | + +## Usage Example + +```cpp +#include +#include + +using namespace osquery; + +// Set an environment variable +if (setEnvVar("MY_VAR", "hello")) { + std::cout << "Variable set successfully\n"; +} + +// Read it back +auto value = getEnvVar("MY_VAR"); +if (value) { + std::cout << "MY_VAR = " << *value << "\n"; // Output: MY_VAR = hello +} + +// Unset the variable +if (unsetEnvVar("MY_VAR")) { + std::cout << "Variable removed\n"; +} + +// Safe handling of missing variables +auto missing = getEnvVar("DOES_NOT_EXIST"); +if (!missing) { + std::cout << "Variable not found\n"; // boost::none returned +} +``` + +> **Note:** Internally wraps POSIX `setenv`, `unsetenv`, and `getenv`. The `getEnvVar` return type uses `boost::optional` to safely distinguish between an unset variable and an empty string value. \ No newline at end of file diff --git a/osquery/utils/system/posix/.errno.md b/osquery/utils/system/posix/.errno.md new file mode 100644 index 00000000000..675483fc3a4 --- /dev/null +++ b/osquery/utils/system/posix/.errno.md @@ -0,0 +1,36 @@ + +Provides a type-safe C++ wrapper around POSIX `errno` values within the `osquery` namespace, mapping raw integer error codes to a strongly-typed enum. + +## Key Components + +- **`PosixError` (enum class)** — Strongly-typed enumeration of standard POSIX error codes (e.g., `PERM`, `NOENT`, `NOMEM`), mapped directly from system `errno` macros. Includes an `Unknown = 0` sentinel for unrecognized errors. + +- **`impl::toPosixSystemError(int)`** — Internal implementation function that converts a raw `errno` integer to its corresponding `PosixError` enum value. + +- **`to(int from_errno)`** — Template conversion function, enabled only when `ToType` is `PosixError` (via `std::enable_if`). Provides a clean, generic interface for converting raw errno integers to `PosixError`. + +## Usage Example + +```cpp +#include + +// Direct conversion from a raw errno value +int result = open("/nonexistent/path", O_RDONLY); +if (result < 0) { + PosixError err = osquery::to(errno); + + switch (err) { + case osquery::PosixError::NOENT: + // Handle file not found + break; + case osquery::PosixError::ACCES: + // Handle permission denied + break; + default: + // Handle unknown/other errors + break; + } +} +``` + +> **Note:** The `to()` template is SFINAE-constrained, so calling it with a non-`PosixError` type will result in a compile-time error, preventing accidental misuse. \ No newline at end of file diff --git a/osquery/utils/system/posix/.filepath.md b/osquery/utils/system/posix/.filepath.md new file mode 100644 index 00000000000..350efc1e165 --- /dev/null +++ b/osquery/utils/system/posix/.filepath.md @@ -0,0 +1,33 @@ + +Provides cross-platform canonical file path resolution within the `osquery` namespace, wrapping `realpath()` with safe memory handling for both `PATH_MAX`-defined and undefined platforms. + +## Key Components + +### `canonicalize_file_name(const char* name) → std::string` +Resolves a file path to its canonical absolute form (resolving symlinks, `.`, `..`, etc.). + +- **`PATH_MAX` defined** (Linux, macOS): Passes `nullptr` as the buffer, letting libc allocate memory via `realpath()`. Falls back to the raw input string if resolution fails. +- **`PATH_MAX` undefined**: Queries `pathconf()` for `_PC_PATH_MAX`; falls back to an 8KB buffer if that also fails. + +In both branches, allocated memory is properly `free()`d before returning a `std::string`. + +## Usage Example + +```cpp +#include +#include + +int main() { + const std::string canonical = osquery::canonicalize_file_name("../../etc/passwd"); + std::cout << "Resolved path: " << canonical << std::endl; + + // If resolution fails (e.g. path does not exist), + // the original input string is returned unchanged. + const std::string fallback = osquery::canonicalize_file_name("/nonexistent/../path"); + std::cout << "Fallback path: " << fallback << std::endl; + + return 0; +} +``` + +> **Note:** If `realpath()` fails (e.g., path does not exist), the function returns the original `name` string rather than an empty string or throwing an exception, ensuring callers always receive a usable value. \ No newline at end of file diff --git a/osquery/utils/system/posix/.system.md b/osquery/utils/system/posix/.system.md new file mode 100644 index 00000000000..76ea3ea991a --- /dev/null +++ b/osquery/utils/system/posix/.system.md @@ -0,0 +1,49 @@ + +Provides privilege dropping and restoration utilities for osquery, enabling temporary reduction of process permissions to a specified user, group, or path owner. + +## Key Components + +### Types +- **`PlatformPidType`** — Platform-agnostic alias for `pid_t`, representing a process identifier. + +### Class: `DropPrivileges` +A non-copyable RAII-style class that temporarily lowers effective process privileges and automatically restores them on destruction. + +| Member | Description | +|---|---| +| `static get()` | Factory method returning a `DropPrivilegesRef` (`shared_ptr`) instance | +| `dropToParent(path)` | Drops privileges to the owner of the parent directory of the given path | +| `dropTo(uid, gid)` | Drops to explicit numeric UID/GID | +| `dropTo(uid_str, gid_str)` | Drops to string-specified UID/GID | +| `dropTo(user)` | Drops to a named user's UID/GID | +| `dropped()` | Returns `true` if effective privileges differ from real privileges | +| `~DropPrivileges()` | Destructor automatically restores original effective permissions | + +## Usage Example + +```cpp +#include +#include + +// Drop privileges to the owner of a file's parent directory +{ + auto dropper = osquery::DropPrivileges::get(); + if (dropper->dropToParent("/etc/osquery/osquery.conf")) { + // Executing with reduced privileges here + readSensitiveFile(); + } + // Privileges automatically restored when 'dropper' goes out of scope +} + +// Drop to a specific user +{ + auto dropper = osquery::DropPrivileges::get(); + dropper->dropTo("nobody"); + + if (dropper->dropped()) { + // Confirms effective UID/GID differ from real + } +} +``` + +> **Note:** Only one active privilege drop should exist at a time. Attempting a second drop while one is still active will return `false`. The destructor guarantees privilege restoration, making this safe for use in scoped blocks. \ No newline at end of file diff --git a/osquery/utils/system/posix/.time.md b/osquery/utils/system/posix/.time.md new file mode 100644 index 00000000000..1777729499b --- /dev/null +++ b/osquery/utils/system/posix/.time.md @@ -0,0 +1,31 @@ + +Cross-platform wrapper for system time formatting utilities within the osquery framework. + +## Key Components + +- **`platformAsctime(const struct tm* timeptr)`** — Converts a `tm` struct to a human-readable ASCII time string using the thread-safe `asctime_r` variant. Returns an empty string if the input pointer is null. + +## Usage Example + +```cpp +#include +#include + +// Get current time and convert to readable string +std::time_t now = std::time(nullptr); +struct tm* localTime = std::localtime(&now); + +std::string timeStr = osquery::platformAsctime(localTime); +// e.g. "Mon Jan 6 15:30:00 2025\n" + +// Safe null handling +std::string empty = osquery::platformAsctime(nullptr); +// Returns "" +``` + +## Notes + +- Uses a fixed 32-byte stack buffer (POSIX recommends minimum 26 bytes for `asctime_r` output) +- Wraps `asctime_r` for thread safety, as opposed to the non-reentrant `asctime` +- Null-pointer guard prevents undefined behavior on invalid input +- The returned string includes a trailing newline (`\n`), consistent with standard `asctime` behavior \ No newline at end of file diff --git a/osquery/utils/system/posix/tests/.errno.md b/osquery/utils/system/posix/tests/.errno.md new file mode 100644 index 00000000000..9d4b07b15d1 --- /dev/null +++ b/osquery/utils/system/posix/tests/.errno.md @@ -0,0 +1,28 @@ + +Unit test file for the `to()` conversion utility, validating correct mapping of POSIX `errno` integer values to the `PosixError` enum type. + +## Key Components + +- **`PosixErrnoTests`** — GTest fixture class for grouping POSIX errno conversion tests. +- **`TEST_F(PosixErrnoTests, to)`** — Test case covering: + - Invalid/unmapped integers (`0`, `-1`, `98765`, `987654`) resolve to `PosixError::Unknown` + - Known POSIX errno constants map correctly to their enum counterparts (`EPIPE`, `EDOM`, `ERANGE`, `E2BIG`) + +## Usage Example + +```cpp +#include + +// Convert a raw errno integer to a typed PosixError enum +int raw = EPIPE; +PosixError err = to(raw); + +if (err == PosixError::PIPE) { + // Handle broken pipe +} + +// Unknown or invalid errno values map to PosixError::Unknown +PosixError unknown = to(98765); // => PosixError::Unknown +``` + +The tests validate that the `to()` template function in `osquery/utils/system/posix/errno.h` handles both valid POSIX error codes and out-of-range values gracefully, ensuring safe enum conversion without undefined behavior. \ No newline at end of file diff --git a/osquery/utils/system/tests/.cpu.md b/osquery/utils/system/tests/.cpu.md new file mode 100644 index 00000000000..01985f3d30c --- /dev/null +++ b/osquery/utils/system/tests/.cpu.md @@ -0,0 +1,20 @@ + +Placeholder/stub source file for CPU-related functionality within the osquery framework. Currently contains only the standard license header with no implemented code. + +## Key Components + +None — the file is empty beyond the Apache-2.0/GPL-2.0 license header. + +## Usage Example + +```cpp +// No public API is defined in this file yet. +// Future CPU table implementations would likely follow this pattern: + +QueryData genCpuInfo(QueryContext& context) { + // Query CPU hardware info (model, cores, speed, etc.) + return results; +} +``` + +> **Note:** This file is likely a scaffold for an upcoming or platform-specific CPU information table (e.g., `cpu_info`). Refer to sibling files or the osquery table specs for the intended schema. \ No newline at end of file diff --git a/osquery/utils/system/tests/.errno.md b/osquery/utils/system/tests/.errno.md new file mode 100644 index 00000000000..774a9ee7938 --- /dev/null +++ b/osquery/utils/system/tests/.errno.md @@ -0,0 +1,25 @@ + +Placeholder/stub source file for errno-related error handling utilities within the osquery framework. + +## Key Components + +No exports, classes, or functions are currently defined in this file. + +## Usage Example + +```cpp +// No implementation present; file serves as a placeholder. +// errno handling in osquery typically follows this pattern: + +#include +#include + +void checkError() { + if (errno != 0) { + // strerror(errno) retrieves human-readable error description + std::string error = std::strerror(errno); + } +} +``` + +> **Note:** This file currently contains only the license header. Any errno-specific utilities intended for this module have not yet been implemented. \ No newline at end of file diff --git a/osquery/utils/system/tests/.time.md b/osquery/utils/system/tests/.time.md new file mode 100644 index 00000000000..886552003ae --- /dev/null +++ b/osquery/utils/system/tests/.time.md @@ -0,0 +1,26 @@ + +Unit tests for osquery's time utility functions, verifying correct ASCII-formatted UTC time string output from `toAsciiTime` and `toAsciiTimeUTC`. + +## Key Components + +- **`TimeTests`** — GTest fixture class for time-related unit tests +- **`test_asciitime`** — Validates `toAsciiTime()` converts a `std::gmtime` result to the expected UTC string format +- **`test_asciitimeutc`** — Validates `toAsciiTimeUTC()` converts a `std::localtime` result to the equivalent UTC string format + +## Usage Example + +```cpp +// Both tests validate against a fixed epoch timestamp +const std::time_t epoch = 1491518994; +const std::string expected = "Thu Apr 6 22:49:54 2017 UTC"; + +// test_asciitime: uses gmtime (already UTC) +auto const result = std::gmtime(&epoch); +EXPECT_EQ(expected, toAsciiTime(result)); + +// test_asciitimeutc: uses localtime, function normalizes to UTC +auto const result = std::localtime(&epoch); +EXPECT_EQ(expected, toAsciiTimeUTC(result)); +``` + +Both functions are expected to produce identical output (`"Thu Apr 6 22:49:54 2017 UTC"`), with `toAsciiTimeUTC` handling the local-to-UTC normalization internally. \ No newline at end of file diff --git a/osquery/utils/system/windows/.env.md b/osquery/utils/system/windows/.env.md new file mode 100644 index 00000000000..8d49985dc27 --- /dev/null +++ b/osquery/utils/system/windows/.env.md @@ -0,0 +1,48 @@ + +Windows-specific implementation of environment variable management and command-line argument parsing utilities for the osquery framework. + +## Key Components + +| Function | Description | +|---|---| +| `setEnvVar` | Sets a named environment variable using the Windows wide-character API | +| `unsetEnvVar` | Removes an environment variable by setting it to `nullptr` | +| `getEnvVar` | Retrieves an environment variable value, returning `boost::none` if not found | +| `expandEnvString` | Expands embedded environment variable references (e.g. `%APPDATA%`) in a string | +| `splitArgs` | Parses a command-line string into a vector of arguments using `CommandLineToArgvW` | + +**Constants:** +- `kInitialBufferSize` — 1024 WCHAR initial allocation for API calls +- `kEnvironmentExpansionMax` — 32767 character limit imposed by `ExpandEnvironmentStrings` + +## Usage Example + +```cpp +#include + +// Set and retrieve an environment variable +osquery::setEnvVar("MY_VAR", "hello"); +auto val = osquery::getEnvVar("MY_VAR"); +if (val) { + std::cout << *val; // "hello" +} + +// Expand a string containing environment variable references +auto expanded = osquery::expandEnvString("%APPDATA%\\config.json"); +if (expanded) { + std::cout << *expanded; // e.g. "C:\Users\user\AppData\Roaming\config.json" +} + +// Split a command-line string into arguments +auto args = osquery::splitArgs("myapp.exe --flag value"); +if (args) { + for (const auto& arg : *args) { + std::cout << arg << "\n"; + } +} + +// Unset an environment variable +osquery::unsetEnvVar("MY_VAR"); +``` + +> **Note:** All functions convert between UTF-8 `std::string` and UTF-16 `std::wstring` internally via `stringToWstring`/`wstringToString`. Input strings exceeding 32,767 characters will cause `expandEnvString` to return `boost::none`. \ No newline at end of file diff --git a/osquery/utils/system/windows/.errno.md b/osquery/utils/system/windows/.errno.md new file mode 100644 index 00000000000..2a3ee16e554 --- /dev/null +++ b/osquery/utils/system/windows/.errno.md @@ -0,0 +1,31 @@ + +Windows-specific error handling utilities for translating numeric error codes into human-readable strings, wrapping both POSIX-style `errno` and Windows system error APIs. + +## Key Components + +- **`platformStrerr(int errnum)`** — Thread-safe wrapper around `strerror_s` that converts a POSIX-style error number into a `std::string`. Returns an empty string on failure. +- **`getWindowsErrorDescription(std::wstring& error_message, DWORD error_id)`** — Uses `FormatMessageW` to retrieve a localized Windows system error description for a given `DWORD` error code. Returns an `osquery::Status` indicating success or failure. +- **`kWindowsLanguageId`** — Module-level constant using `MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)` for neutral language formatting in `FormatMessageW`. + +## Usage Example + +```cpp +#include + +// Convert a POSIX errno value to a string +int err = ENOENT; +std::string msg = osquery::platformStrerr(err); +// msg => "No such file or directory" + +// Retrieve a Windows system error description +std::wstring winMsg; +DWORD winErr = GetLastError(); +osquery::Status status = osquery::getWindowsErrorDescription(winMsg, winErr); +if (status.ok()) { + // winMsg contains the localized error description +} else { + // status.getMessage() explains why the lookup failed +} +``` + +> **Note:** This file is Windows-specific. `platformStrerr` uses `strerror_s` (the safe CRT variant), and `getWindowsErrorDescription` relies on `FormatMessageW` with automatic buffer allocation via `FORMAT_MESSAGE_ALLOCATE_BUFFER`, freeing the buffer with `LocalFree` after copying. \ No newline at end of file diff --git a/osquery/utils/system/windows/.etw_helpers.md b/osquery/utils/system/windows/.etw_helpers.md new file mode 100644 index 00000000000..38423f79748 --- /dev/null +++ b/osquery/utils/system/windows/.etw_helpers.md @@ -0,0 +1,38 @@ + +Windows ETW (Event Tracing for Windows) utility header providing helper functions for extracting metadata from ETW event records within the osquery framework. + +## Key Components + +### Functions + +| Function | Signature | Description | +|---|---|---| +| `sidStringFromEtwRecord` | `std::string(const EVENT_RECORD&)` | Extracts and returns the user SID string from the extended header of an ETW event record | +| `processImagePathFromProcessId` | `std::string(uint32_t processId)` | Resolves and returns the process image file path from a given process ID | + +## Usage Example + +```c +#include "etw_helpers.h" + +// Extract user SID from an ETW event record +VOID WINAPI MyEventCallback(PEVENT_RECORD pRecord) { + // Get the SID of the user associated with the event + std::string userSid = osquery::sidStringFromEtwRecord(*pRecord); + + // Resolve the image path of the process that generated the event + uint32_t pid = pRecord->EventHeader.ProcessId; + std::string imagePath = osquery::processImagePathFromProcessId(pid); + + // Use the extracted metadata + LOG(INFO) << "Event from PID " << pid + << " | SID: " << userSid + << " | Path: " << imagePath; +} +``` + +## Notes + +- **Windows-only**: Depends on `` and `osquery/utils/system/system.h`, scoped to Windows ETW infrastructure. +- All symbols are declared within the `osquery` namespace. +- SID extraction relies on the **extended data** section of the `EVENT_RECORD` structure; ensure the ETW session is configured to include user SID data. \ No newline at end of file diff --git a/osquery/utils/system/windows/.system.md b/osquery/utils/system/windows/.system.md new file mode 100644 index 00000000000..dea479f326f --- /dev/null +++ b/osquery/utils/system/windows/.system.md @@ -0,0 +1,39 @@ + +Windows platform compatibility header that provides POSIX-to-Windows shim definitions and type aliases for the osquery framework, ensuring consistent Windows API configuration across compilation units. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `SIGHUP` | Macro | Defined as `1`, mirroring POSIX convention | +| `SIGUSR1` | Macro | Mapped to `SIGILL` (Windows has no native `SIGUSR1`) | +| `SIGALRM` | Macro | Aliased to `SIGUSR1` | +| `GetObject` / `GetMessage` | `#undef` | Removes conflicting Win32 global macros that break RapidJSON | +| `gmtime_r` | Function | Thread-safe `gmtime` wrapper matching POSIX signature | +| `localtime_r` | Function | Thread-safe `localtime` wrapper matching POSIX signature | +| `alarm` | Function | No-op stub satisfying POSIX `alarm()` call sites | +| `pid_t` | Type alias | Defined as `unsigned long` (equivalent to Win32 `DWORD`) | +| `PlatformPidType` | Type alias | Defined as `void*` for Windows process handle abstraction | + +## Usage Example + +```c +#include "system.h" + +// Use POSIX-style time functions on Windows +time_t now = time(nullptr); +struct tm result; +osquery::gmtime_r(&now, &result); + +// Use portable pid type +osquery::pid_t pid = GetCurrentProcessId(); + +// Signal constants work cross-platform +int sig = SIGHUP; // = 1 +``` + +## Notes + +- Requires `NTDDI_VERSION` and `_WIN32_WINNT` to be defined at build time (enforced via `#error`); see `tools/build_defs/oss/osquery/cxx.bzl`. +- `WIN32_LEAN_AND_MEAN` and `_WIN32_DCOM` are set here to guarantee consistent Windows API surface across all translation units. +- Warning `C4067` is suppressed locally around `sddl.h` to avoid spurious preprocessor diagnostic noise from the Windows SDK. \ No newline at end of file diff --git a/osquery/utils/system/windows/.time.md b/osquery/utils/system/windows/.time.md new file mode 100644 index 00000000000..83ac5afff85 --- /dev/null +++ b/osquery/utils/system/windows/.time.md @@ -0,0 +1,28 @@ + +Provides a Windows-specific implementation of a thread-safe ASCII time string formatter within the osquery framework. + +## Key Components + +- **`platformAsctime(const struct tm* timeptr)`** — Converts a `tm` struct to a human-readable ASCII time string using the Windows-safe `asctime_s` function, stripping trailing newlines from the result. + +## Usage Example + +```cpp +#include +#include + +// Get current time and format it +std::time_t now = std::time(nullptr); +struct tm timeinfo = {}; +localtime_s(&timeinfo, &now); + +std::string formatted = osquery::platformAsctime(&timeinfo); +// Returns e.g. "Mon Jan 6 15:04:05 2025" +``` + +## Notes + +- Uses a fixed 256-byte stack buffer (`MAX_BUFFER_SIZE`) to hold the formatted output. +- Returns an empty string on formatting failure (non-zero `asctime_s` status). +- Replaces newline characters (`\n`) injected by `asctime_s` using Boost's `replace_all`. +- Windows-only counterpart to the POSIX `asctime_r` call; enables cross-platform time formatting via the `platformAsctime` abstraction. \ No newline at end of file diff --git a/osquery/utils/system/windows/.users_groups_helpers.md b/osquery/utils/system/windows/.users_groups_helpers.md new file mode 100644 index 00000000000..621b0330895 --- /dev/null +++ b/osquery/utils/system/windows/.users_groups_helpers.md @@ -0,0 +1,56 @@ + +Windows-specific utility header providing RAII wrappers and helper functions for Windows Net API objects, SID manipulation, and user/group account lookups within the osquery framework. + +## Key Components + +### `NetApiObjectPtr` +A move-only RAII smart pointer template that manages Windows Net API buffer lifetimes, automatically calling `NetApiBufferFree()` on destruction. + +| Method | Description | +|--------|-------------| +| `get_new_ptr()` | Returns a raw double pointer for use with Net API calls; frees existing buffer first | +| `get()` | Returns a const raw pointer to the managed buffer | +| `operator->()` | Direct member access to the managed object | + +### Type Aliases + +```c +user_info_0_ptr // NetApiObjectPtr +user_info_2_ptr // NetApiObjectPtr +user_info_3_ptr // NetApiObjectPtr +user_info_4_ptr // NetApiObjectPtr +localgroup_users_info_0_ptr // NetApiObjectPtr +localgroup_info_1_ptr // NetApiObjectPtr +``` + +### Free Functions + +| Function | Description | +|----------|-------------| +| `psidToString(PSID)` | Converts a binary SID struct to its string representation | +| `getSidFromAccountName(...)` | Looks up a SID by account name (wide string or `LPCWSTR`) | +| `getRidFromSid(PSID)` | Extracts the Relative Identifier (RID) from a SID | +| `getGidFromUserSid(PSID)` | Returns optional GID from a user's SID | +| `getGidFromUsername(LPCWSTR)` | Returns optional GID from a username | +| `getGroupSidFromUserSid(PSID)` | Returns group SID string from a user SID | +| `getGroupSidFromUsername(...)` | Returns group SID string from a username | +| `getUserHomeDir(string)` | Returns the home directory path for a given SID string | + +## Usage Example + +```c +// RAII-managed Net API buffer usage +osquery::user_info_4_ptr userInfo; +NetUserGetInfo(nullptr, L"john.doe", 4, + reinterpret_cast(userInfo.get_new_ptr())); + +// SID conversions +std::string sidStr = osquery::psidToString(userInfo->usri4_user_sid); +DWORD rid = osquery::getRidFromSid(userInfo->usri4_user_sid); + +// Group lookup +auto gid = osquery::getGidFromUsername(L"john.doe"); +if (gid.has_value()) { + // use gid.value() +} +``` \ No newline at end of file diff --git a/osquery/utils/tests/.base64.md b/osquery/utils/tests/.base64.md new file mode 100644 index 00000000000..6d07a4a6afb --- /dev/null +++ b/osquery/utils/tests/.base64.md @@ -0,0 +1,26 @@ + +Unit test file for the osquery `base64` utility, verifying encode/decode round-trip correctness using Google Test. + +## Key Components + +- **`Base64Tests`** — GTest fixture class inheriting from `testing::Test`, scoped under the `osquery` namespace +- **`test_base64`** — Single test case validating that a string encoded with `base64::encode` produces a non-empty result and is correctly restored by `base64::decode` + +## Usage Example + +```cpp +// Encoding a string +std::string unencoded = "HELLO"; +auto encoded = base64::encode(unencoded); +// encoded is a non-empty base64 string + +// Decoding back to original +auto decoded = base64::decode(encoded); +// decoded == "HELLO" +``` + +## Notes + +- Depends on `osquery/utils/base64.h` for the `base64::encode` and `base64::decode` functions +- Uses the Google Test (`gtest`) framework; run via the osquery test suite +- Only asserts non-empty output on encode and exact equality on decode — does not test edge cases (empty strings, binary data, invalid input) \ No newline at end of file diff --git a/osquery/utils/tests/.chars.md b/osquery/utils/tests/.chars.md new file mode 100644 index 00000000000..bb7922da75f --- /dev/null +++ b/osquery/utils/tests/.chars.md @@ -0,0 +1,44 @@ + +Unit test file for character and string utility functions from `osquery/utils/chars.h`, covering ASCII printability checks and Unicode escape sequence handling. + +## Key Components + +- **`ConversionsTests`** — GTest fixture class for character conversion and string utility tests +- **`test_ascii_true`** — Validates that `isPrintable()` returns `true` for standard ASCII strings +- **`test_ascii_false`** — Validates that `isPrintable()` returns `false` for non-ASCII (e.g., Japanese Unicode) strings +- **`test_unicode_unescape`** — Validates `unescapeUnicode()` against multiple edge cases including valid sequences, invalid sequences, plain strings, and Windows-style paths + +## Usage Example + +```cpp +#include + +// Check if a string contains only printable ASCII characters +std::string input = "HELLO"; +bool printable = isPrintable(input); // true + +std::string unicode = "こんにちは"; +bool notPrintable = isPrintable(unicode); // false + +// Unescape Unicode escape sequences in a string +std::string escaped = "\\u0025hi"; +std::string result = unescapeUnicode(escaped); // "%hi" + +// Invalid or incomplete sequences are left unchanged +std::string invalid = "\\uFFFFhi"; +std::string unchanged = unescapeUnicode(invalid); // "\\uFFFFhi" + +// Windows paths with double backslashes pass through unmodified +std::string path = "c:\\\\users\\\\file.txt"; +std::string samePath = unescapeUnicode(path); // "c:\\\\users\\\\file.txt" +``` + +## Key Behaviors Tested + +| Input | Function | Expected Output | +|---|---|---| +| `"HELLO"` | `isPrintable` | `true` | +| `"こんにちは"` | `isPrintable` | `false` | +| `"\\u0025hi"` | `unescapeUnicode` | `"%hi"` | +| `"\\uFFFFhi"` | `unescapeUnicode` | `"\\uFFFFhi"` (unchanged) | +| `"0000\\u"` | `unescapeUnicode` | `"0000\\u"` (unchanged) | \ No newline at end of file diff --git a/osquery/utils/tests/.map_take.md b/osquery/utils/tests/.map_take.md new file mode 100644 index 00000000000..96e00dbd60c --- /dev/null +++ b/osquery/utils/tests/.map_take.md @@ -0,0 +1,42 @@ + +Unit test file for the `map_take` utility, validating the `tryTake` and `tryTakeCopy` functions from `osquery/utils/map_take.h` against both `std::map` and `std::unordered_map` container types. + +## Key Components + +- **`testTryTake()`** — Template helper verifying that `tryTake` extracts and **removes** an entry from the map on success, returns the correct value, and returns a `MapTakeError::NoSuchKey` error (without mutating the map) when the key is absent. +- **`testTryTakeCopy()`** — Template helper verifying that `tryTakeCopy` returns a copy of the value **without removing** the entry, and likewise returns `MapTakeError::NoSuchKey` for missing keys. +- **`GetTests/tryTake_on_map`** — Runs `testTryTake` against `std::map`. +- **`GetTests/tryTake_on_unordered_map`** — Runs `testTryTake` against `std::unordered_map`. +- **`GetTests/tryTakeCopy_on_map`** — Runs `testTryTakeCopy` against `std::map`. +- **`GetTests/tryTakeCopy_on_unordered_map`** — Runs `testTryTakeCopy` against `std::unordered_map`. + +## Usage Example + +```cpp +#include + +std::map m = { + {"dev", "/dev/"}, + {"etc", "/etc/"}, +}; + +// Moves value out and erases key from map +auto result = tryTake(m, "dev"); +if (!result.isError()) { + std::string value = result.get(); // "/dev/" + // m no longer contains "dev" +} + +// Returns a copy; map is unchanged +auto copy = tryTakeCopy(m, "etc"); +if (copy.isValue()) { + std::string value = copy.get(); // "/etc/" + // m still contains "etc" +} + +// Missing key returns MapTakeError::NoSuchKey +auto missing = tryTake(m, "usr"); +if (missing.isError()) { + assert(missing.getErrorCode() == MapTakeError::NoSuchKey); +} +``` \ No newline at end of file diff --git a/osquery/utils/tests/.rot13.md b/osquery/utils/tests/.rot13.md new file mode 100644 index 00000000000..cc65a7042a0 --- /dev/null +++ b/osquery/utils/tests/.rot13.md @@ -0,0 +1,25 @@ + +Unit test file for the ROT13 decode utility in the osquery framework, verifying that the `rotDecode` function correctly transforms ROT13-encoded strings back to plaintext. + +## Key Components + +- **`Rot13Tests`** — GTest fixture class inheriting from `testing::Test`, scoped within the `osquery` namespace +- **`test_ro13`** — Single test case validating `rotDecode` against the classic pangram "The quick brown fox jumps over the lazy dog" + +## Usage Example + +```cpp +#include +#include + +// Decode a ROT13-encoded string +std::string encoded = "Gur dhvpx oebja sbk whzcf bire gur ynml qbt"; +std::string decoded = rotDecode(encoded); +// decoded == "The quick brown fox jumps over the lazy dog" +``` + +## Notes + +- The actual `rotDecode` implementation lives in `osquery/utils/rot13.h` +- ROT13 is its own inverse — encoding and decoding use the same operation +- This test uses a pangram (covers all 26 letters), providing broad alphabet coverage in a single assertion \ No newline at end of file diff --git a/osquery/utils/tests/.scope_guard.md b/osquery/utils/tests/.scope_guard.md new file mode 100644 index 00000000000..b25958c1024 --- /dev/null +++ b/osquery/utils/tests/.scope_guard.md @@ -0,0 +1,35 @@ + +Unit test file for the `scope_guard` utility, validating that cleanup callbacks execute correctly when a scope guard goes out of scope. + +## Key Components + +- **`ScopeGuardTests`** — GTest fixture class housing all scope guard test cases +- **`guard_is_called`** — Verifies the guard's callback is invoked exactly when its enclosing scope exits +- **`example_time_measurement`** — Demonstrates using a scope guard to measure elapsed time via `std::chrono::steady_clock` +- **`example_temporary_file`** — Demonstrates automatic resource cleanup by removing a temporary file on scope exit using `boost::filesystem` + +## Usage Example + +```cpp +// Basic callback invocation +auto guard_has_been_called = false; +{ + auto guard = scope_guard::create( + [&guard_has_been_called]() { guard_has_been_called = true; }); + // guard_has_been_called is still false here +} +// guard_has_been_called is now true + +// Automatic file cleanup pattern +{ + const auto guard = scope_guard::create( + [&file_path]() { fs::remove(file_path); }); + // work with the file... +} // file is deleted here automatically +``` + +## Notes + +- Tests rely on `osquery/utils/scope_guard.h` for the `scope_guard::create()` factory +- The `example_time_measurement` test uses a 1ms sleep as a minimum duration floor, asserting `duration >= 1ms` +- The temporary file test uses `boost::filesystem::unique_path` to avoid naming collisions across parallel test runs \ No newline at end of file diff --git a/osquery/utils/tests/windows/.env.md b/osquery/utils/tests/windows/.env.md new file mode 100644 index 00000000000..cb45372e736 --- /dev/null +++ b/osquery/utils/tests/windows/.env.md @@ -0,0 +1,38 @@ + +Unit tests for Windows environment utility functions, validating `expandEnvString` and `splitArgs` from `osquery/utils/system/env.h`. + +## Key Components + +- **`WindowsEnvTests`** — GTest fixture class housing all Windows environment tests +- **`test_expandEnvString`** — Validates environment variable expansion behavior: + - Returns `boost::none` for strings exceeding the 32K Windows API limit + - Correctly expands `%WINDIR%` to its actual value + - Handles multiple expansions within a single string +- **`test_splitArgs`** — Validates command-line argument splitting: + - Parses quoted paths with spaces (e.g., `"C:\Program Files\Foo"`) + - Splits unquoted tokens correctly + - Returns the expected argument count and values + +## Usage Example + +```cpp +#include + +// Expand a Windows environment variable +auto result = expandEnvString("%WINDIR%"); +if (result) { + std::cout << "WINDIR: " << *result << std::endl; +} + +// Returns boost::none for oversized strings (>32K) +auto big = std::string(32769, 'A'); +assert(expandEnvString(big) == boost::none); + +// Split a command-line string into arguments +auto args = splitArgs("\"C:\\Program Files\\Foo\" bar baz"); +if (args) { + // args.get() == {"C:\\Program Files\\Foo", "bar", "baz"} +} +``` + +> **Note:** These tests are Windows-specific. `expandEnvString` relies on the Windows API and cannot process environment strings larger than 32,767 characters. \ No newline at end of file diff --git a/osquery/utils/tests/windows/.filetime.md b/osquery/utils/tests/windows/.filetime.md new file mode 100644 index 00000000000..3fe25695626 --- /dev/null +++ b/osquery/utils/tests/windows/.filetime.md @@ -0,0 +1,26 @@ + +Unit test file for validating the `littleEndianToUnixTime` Windows FILETIME conversion utility in the osquery framework. + +## Key Components + +- **`FiletimeTests`** — GTest fixture class inheriting from `testing::Test` +- **`test_filetime`** — Single test case that verifies correct conversion of a little-endian Windows FILETIME hex string to a Unix timestamp + +## Usage Example + +```cpp +#include + +// Convert a little-endian Windows FILETIME hex string to Unix epoch time +std::string filetime_hex = "00c0bdd640c6cf01"; +long long unix_time = littleEndianToUnixTime(filetime_hex); +// unix_time == 1409616000 (2014-09-02 00:00:00 UTC) +``` + +## Test Coverage + +| Input (Little-Endian FILETIME) | Expected Unix Timestamp | Date (UTC) | +|---|---|---| +| `00c0bdd640c6cf01` | `1409616000` | 2014-09-02 | + +The test exercises `littleEndianToUnixTime()` from the Windows-specific conversions utility, ensuring that 64-bit FILETIME values (100-nanosecond intervals since January 1, 1601) stored as little-endian hex strings are correctly normalized to Unix epoch seconds. \ No newline at end of file diff --git a/osquery/utils/tests/windows/.shellitems.md b/osquery/utils/tests/windows/.shellitems.md new file mode 100644 index 00000000000..1d2e0e56e5c --- /dev/null +++ b/osquery/utils/tests/windows/.shellitems.md @@ -0,0 +1,46 @@ + +Unit test file for the `shellitem` utility in osquery, validating parsing of Windows Shell Item binary data structures across various item types. + +## Key Components + +The test fixture `ShellitemTests` covers the following parser functions from `osquery/utils/windows/shellitem.h`: + +| Function | Tested Shell Item Type | +|---|---| +| `fileEntry()` | File system entry (name, MFT metadata) | +| `ftpItem()` | FTP server hostname and timestamp | +| `zipContentItem()` | ZIP archive content name | +| `mtpDevice()` | MTP device display name | +| `mtpFolder()` | MTP folder name | +| `mtpRoot()` | MTP root device name | +| `rootFolderItem()` | Root folder GUID | +| `driveLetterItem()` | Drive letter path | +| `controlPanelCategoryItem()` | Control Panel category name | +| `controlPanelItem()` | Control Panel item GUID | +| `variableFtp()` | Variable-length FTP path segment | +| `variableGuid()` | Variable GUID item | +| `propertyViewDrive()` | Property view drive letter | +| `propertyStore()` | Property store key-value extraction | +| `networkShareItem()` | UNC network share path | + +## Usage Example + +```cpp +// Each test follows the same hex-data-in, parsed-value-out pattern: +std::string raw_hex = + "19002F433A5C000000000000000000000000000000000000000000"; + +auto name = driveLetterItem(raw_hex); +ASSERT_TRUE(name == "C:\\"); + +// Property store requires pre-locating "31535053" (1SPS) markers: +std::vector wps_list; +size_t wps = data.find("31535053"); +while (wps != std::string::npos) { + wps_list.push_back(wps); + wps = data.find("31535053", wps + 1); +} +auto value = propertyStore(data, wps_list); +``` + +All inputs are hex-encoded binary blobs representing raw Windows Shell Item structures extracted from LNK files or the Windows registry. \ No newline at end of file diff --git a/osquery/utils/windows/.lzxpress.md b/osquery/utils/windows/.lzxpress.md new file mode 100644 index 00000000000..bcc5e25a6b0 --- /dev/null +++ b/osquery/utils/windows/.lzxpress.md @@ -0,0 +1,34 @@ + +Header file exposing a Windows LZ Xpress Huffman decompression utility within the osquery framework. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `ExpectedDecompressData` | Type alias | `Expected, ConversionError>` — wraps decompressed bytes or a conversion error | +| `decompressLZxpress` | Function | Decompresses LZ Xpress Huffman-encoded data, returning a byte vector or error | + +## Usage Example + +```c +#include + +std::vector compressed = /* raw compressed bytes from registry/EVTX/etc. */; +unsigned long uncompressed_size = 4096; // known original size + +auto result = osquery::decompressLZxpress(compressed, uncompressed_size); + +if (result.isError()) { + // Handle ConversionError + LOG(ERROR) << "Decompression failed: " << result.getError().getMessage(); +} else { + std::vector data = result.take(); + // Process decompressed bytes... +} +``` + +## Notes + +- **Windows-only**: Uses `UCHAR` from ``, which maps to Windows platform types. +- **Caller-provided size**: The `size` parameter must reflect the **total uncompressed output size** in bytes — typically sourced from metadata (e.g., registry hive headers or event log records). +- **Error handling**: Returns an `Expected` type (similar to `std::expected`) — always check `isError()` before consuming the result to avoid undefined behavior. \ No newline at end of file diff --git a/osquery/utils/windows/.shellitem.md b/osquery/utils/windows/.shellitem.md new file mode 100644 index 00000000000..3a9de17da96 --- /dev/null +++ b/osquery/utils/windows/.shellitem.md @@ -0,0 +1,56 @@ + +Windows Shell Item parsing utilities for osquery, providing helper functions to extract structured data from various Windows Shell Item binary formats (LNK files, jump lists, shellbags, etc.). + +## Key Components + +### `ShellFileEntryData` Struct +Holds parsed file entry metadata extracted from shell items: + +| Field | Type | Description | +|---|---|---| +| `path` | `string` | File system path | +| `dos_created/accessed/modified` | `long long` | DOS timestamps | +| `mft_entry` / `mft_sequence` | `long long` / `int` | NTFS MFT reference | +| `extension_sig` / `identifier` | `string` | Shell extension data | +| `version` / `string_size` | `int` | Format metadata | + +### Parsing Functions + +| Function | Returns | +|---|---| +| `fileEntry()` | `ShellFileEntryData` struct with full file metadata | +| `propertyStore()` | Windows Property List GUID name or value | +| `networkShareItem()` | Network share UNC name | +| `zipContentItem()` | Zip archive entry name | +| `rootFolderItem()` | Root folder (e.g., Desktop, My Computer) name | +| `driveLetterItem()` | Drive letter string | +| `controlPanelItem()` / `controlPanelCategoryItem()` | Control panel entry name | +| `ftpItem()` | Vector of FTP hostnames/paths | +| `guidParse()` | Properly-ordered GUID string from little-endian bytes | +| `propertyViewDrive()` | Drive name from property view data | +| `variableGuid()` / `variableFtp()` | GUID name or FTP string from variable-length items | +| `mtpDevice()` / `mtpFolder()` / `mtpRoot()` | MTP (Media Transfer Protocol) device/folder/root names | + +## Usage Example + +```c +#include "shellitem.h" + +// Parse a raw shell item binary blob +std::string raw_shell_data = /* binary data from shellbag registry key */; + +// Extract file entry metadata +osquery::ShellFileEntryData entry = osquery::fileEntry(raw_shell_data); +std::cout << "Path: " << entry.path << "\n"; +std::cout << "Created: " << entry.dos_created << "\n"; +std::cout << "MFT Entry: " << entry.mft_entry << "\n"; + +// Parse a GUID from little-endian bytes +std::string guid = osquery::guidParse(raw_shell_data); + +// Resolve a network share item +std::string share = osquery::networkShareItem(raw_shell_data); + +// Enumerate FTP entries +std::vector ftp_paths = osquery::ftpItem(raw_shell_data); +``` \ No newline at end of file diff --git a/osquery/utils/ycloud/.ycloud_util.md b/osquery/utils/ycloud/.ycloud_util.md new file mode 100644 index 00000000000..fd87fea4287 --- /dev/null +++ b/osquery/utils/ycloud/.ycloud_util.md @@ -0,0 +1,36 @@ + +Utility header for retrieving Yandex Cloud (YCloud) instance metadata within the osquery framework. + +## Key Components + +| Function | Description | +|---|---| +| `getYCloudKey` | Extracts a named key from a YCloud metadata JSON document | +| `getVendorKey` | Extracts a vendor-specific key from a YCloud metadata JSON document | +| `getYCloudSshKey` | Retrieves the SSH public key from a YCloud metadata document | +| `getSerialPortEnabled` | Reads the serial port enabled flag from a YCloud metadata document | +| `getZoneId` | Parses and returns the zone identifier from a raw zone string | +| `fetchYCloudMetadata` | Fetches YCloud instance metadata from a given endpoint and populates a JSON document | + +All functions reside in the `osquery` namespace and operate on `JSON` document objects (`osquery::JSON`). + +## Usage Example + +```c +#include "ycloud_util.h" + +osquery::JSON doc; +osquery::Status status = osquery::fetchYCloudMetadata( + doc, "http://169.254.169.254/latest/meta-data/" +); + +if (status.ok()) { + std::string instanceId = osquery::getYCloudKey(doc, "id"); + std::string vendor = osquery::getVendorKey(doc, "vendor"); + std::string sshKey = osquery::getYCloudSshKey(doc); + std::string serialPort = osquery::getSerialPortEnabled(doc); + std::string zone = osquery::getZoneId("ru-central1-a"); +} +``` + +> **Note:** `fetchYCloudMetadata` returns an `osquery::Status` object — always check `status.ok()` before consuming the populated `doc` to handle network or parse failures gracefully. \ No newline at end of file diff --git a/osquery/utils/ycloud/tests/.ycloud.md b/osquery/utils/ycloud/tests/.ycloud.md new file mode 100644 index 00000000000..bf01d370ffa --- /dev/null +++ b/osquery/utils/ycloud/tests/.ycloud.md @@ -0,0 +1,32 @@ + +Unit tests for the `ycloud_util` library, specifically validating the `getZoneId()` function that parses Yandex Cloud zone identifiers from zone path strings. + +## Key Components + +- **`YCloudUtilsTests`** — Test fixture class inheriting from `testing::Test`, grouping all Yandex Cloud utility unit tests +- **`getZoneId(zone)`** — The function under test (declared in `osquery/utils/ycloud/ycloud_util.h`), extracts the zone ID from a fully-qualified Yandex Cloud zone path + +## Test Cases + +| Test | Input | Expected Output | +|------|-------|----------------| +| `pass` | Valid path: `projects/.../zones/ru-central1-a` | `"ru-central1-a"` | +| `fail` | Malformed path: `projects/.../zones-ru-central1-a` | `""` (empty string) | + +## Usage Example + +```cpp +#include + +// Valid zone path — extracts zone ID after "/zones/" +const std::string zone = "projects/b1g1slgali4fssudpn26/zones/ru-central1-a"; +std::string zone_id = getZoneId(zone); +// zone_id == "ru-central1-a" + +// Malformed path (missing "/" before zone segment) — returns empty +const std::string bad_zone = "projects/b1g1slgali4fssudpn26/zones-ru-central1-a"; +std::string zone_id_bad = getZoneId(bad_zone); +// zone_id_bad == "" +``` + +The tests confirm that `getZoneId()` correctly handles both the happy path (properly delimited `/zones/` segment) and the failure case where the delimiter is absent or malformed, returning an empty string on parse failure. \ No newline at end of file diff --git a/osquery/worker/ipc/.table_ipc_json_converter.md b/osquery/worker/ipc/.table_ipc_json_converter.md new file mode 100644 index 00000000000..09a49b32dfd --- /dev/null +++ b/osquery/worker/ipc/.table_ipc_json_converter.md @@ -0,0 +1,48 @@ + +Defines the `TableIPCJSONConverter` utility class for bidirectional serialization between osquery's internal data structures and JSON messages exchanged over IPC channels. + +## Key Components + +### Enum: `JSONMessageType` +Identifies the type of JSON message being transmitted over IPC: + +| Value | Description | +|---|---| +| `None` | Unrecognized or unset message type | +| `QueryData` | Contains SQL query result rows | +| `Log` | Contains a log message with priority and type | +| `Job` | Represents a job/task message | + +### Class: `TableIPCJSONConverter` + +All methods are static — no instantiation required. + +| Method | Direction | Description | +|---|---|---| +| `JSONToQueryData` | JSON → QueryData | Deserializes JSON into osquery row results | +| `queryDataToJSON` | QueryData → JSON | Serializes query results into JSON | +| `JSONToLogMessage` | JSON → Log fields | Extracts priority, log type, and message string | +| `logMessageToJSON` | Log fields → JSON | Serializes a log entry into JSON | +| `JSONTypeToMessageType` | JSON → enum | Inspects a JSON message and resolves its `JSONMessageType` | + +## Usage Example + +```cpp +#include + +// Serialize query results for IPC transmission +QueryData results = getQueryResults(); +JSON json_helper; +Status s = TableIPCJSONConverter::queryDataToJSON(results, json_helper); +if (!s.ok()) { /* handle error */ } + +// Deserialize received JSON back into QueryData +JSON received_json = receiveFromIPC(); +JSONMessageType msg_type; +TableIPCJSONConverter::JSONTypeToMessageType(received_json, msg_type); + +if (msg_type == JSONMessageType::QueryData) { + QueryData data; + TableIPCJSONConverter::JSONToQueryData(received_json, data); +} +``` \ No newline at end of file diff --git a/osquery/worker/ipc/include/.platform_table_container_ipc.md b/osquery/worker/ipc/include/.platform_table_container_ipc.md new file mode 100644 index 00000000000..ff414cb0880 --- /dev/null +++ b/osquery/worker/ipc/include/.platform_table_container_ipc.md @@ -0,0 +1,28 @@ + +Provides platform-specific IPC stub implementations for table container namespace operations, serving as a no-op fallback for platforms that do not support namespace-based table generation. + +## Key Components + +- **`TableGeneratePtr`** — Function pointer type alias for table generation callbacks with signature `QueryData(QueryContext&, Logger&)` +- **`hasNamespaceConstraint()`** — Inline stub that always returns `false`, indicating no namespace constraint exists on this platform +- **`generateInNamespace()`** — Inline stub that throws `std::logic_error` when called, signaling that namespace-scoped table generation is unsupported on this platform + +## Usage Example + +```cpp +#include + +// Check if a namespace constraint applies before dispatching +if (osquery::hasNamespaceConstraint(query_context)) { + result = osquery::generateInNamespace( + query_context, + "target_namespace", + &myTableGenerateFunction + ); +} else { + // Fall through to direct table generation + result = myTableGenerateFunction(query_context, logger); +} +``` + +> **Note:** On platforms where this header is active, `generateInNamespace()` will always throw. The guarding call to `hasNamespaceConstraint()` (which always returns `false`) ensures the throw path is never reached in normal execution flow. Platform-specific implementations override these stubs on supported systems (e.g., Linux with namespace IPC support). \ No newline at end of file diff --git a/osquery/worker/ipc/include/.table_channel_base.md b/osquery/worker/ipc/include/.table_channel_base.md new file mode 100644 index 00000000000..b8b412641e1 --- /dev/null +++ b/osquery/worker/ipc/include/.table_channel_base.md @@ -0,0 +1,39 @@ + +Defines the `TableChannelBase` CRTP base class for osquery table communication channels, providing a move-only interface for sending and receiving string messages between table components. + +## Key Components + +- **`TableChannelBase`** — CRTP base class inheriting `only_movable`, enforcing move-only semantics on all channel implementations +- **`sendStringMessage()`** — Delegates to `Derived::sendStringMessageImpl()` via static dispatch, returns a `Status` +- **`recvStringMessage()`** — Delegates to `Derived::recvStringMessageImpl()` via static dispatch, returns a `Status` +- **`table_name_`** — Stores the associated table name, set at construction + +## Usage Example + +```cpp +// Define a concrete channel by inheriting from the base +class MyTableChannel : public TableChannelBase { + public: + MyTableChannel(const std::string& name) + : TableChannelBase(name) {} + + Status sendStringMessageImpl(const std::string& message) { + // Send logic here + return Status::success(); + } + + Status recvStringMessageImpl(std::string& message) { + // Receive logic here + message = "response"; + return Status::success(); + } +}; + +// Usage +MyTableChannel channel("my_table"); +Status s = channel.sendStringMessage("query_payload"); +std::string response; +Status r = channel.recvStringMessage(response); +``` + +> **Note:** The default constructor is deleted — a `table_name` string must always be provided. Channels are move-only; copying is disabled via `only_movable`. \ No newline at end of file diff --git a/osquery/worker/ipc/include/.table_channel_factory_base.md b/osquery/worker/ipc/include/.table_channel_factory_base.md new file mode 100644 index 00000000000..86541b04c0e --- /dev/null +++ b/osquery/worker/ipc/include/.table_channel_factory_base.md @@ -0,0 +1,53 @@ + +Base CRTP template for managing the lifecycle of table communication channels within the osquery framework. + +## Key Components + +### `GetChannelType` (struct) +A traits struct that must be specialized for each concrete factory type to expose the associated `Channel` type. + +### `TableChannelFactoryBase` (class) +A CRTP base class that provides common channel management logic. Enforces at compile time (via `static_assert`) that the resolved `TableChannel` type derives from `TableChannelBase`. + +**Methods:** + +| Method | Description | +|--------|-------------| +| `createChannel(table_name, args...)` | Delegates to the derived factory's `createChannelImpl`, stores the channel, and returns a reference | +| `getTableChannel(table_name)` | Returns a pointer to an existing channel, or `nullptr` if not found | +| `dropTableChannel(table_name)` | Removes a channel by name; throws `std::logic_error` if not found | +| `clear()` | Destroys all managed channels | + +**Storage:** Channels are owned via `std::unique_ptr` in an internal `std::unordered_map>`. + +## Usage Example + +```cpp +// 1. Define your concrete channel type +class MyTableChannel : public TableChannelBase { ... }; + +// 2. Specialize the traits struct +template <> +struct osquery::GetChannelType { + using Channel = MyTableChannel; +}; + +// 3. Implement the factory using CRTP +class MyTableChannelFactory + : public osquery::TableChannelFactoryBase { + friend class TableChannelFactoryBase; + + std::unique_ptr createChannelImpl( + const std::string& table_name) { + return std::make_unique(table_name); + } +}; + +// 4. Use the factory +MyTableChannelFactory factory; +auto& channel = factory.createChannel("processes"); +auto* ptr = factory.getTableChannel("processes"); // non-owning lookup +factory.dropTableChannel("processes"); // explicit teardown +``` + +> The private constructor and `friend TableChannelFactory` declaration enforce that only the derived class can instantiate this base, preventing misuse of the CRTP pattern. \ No newline at end of file diff --git a/osquery/worker/ipc/include/.table_ipc_base.md b/osquery/worker/ipc/include/.table_ipc_base.md new file mode 100644 index 00000000000..31e1aff53b9 --- /dev/null +++ b/osquery/worker/ipc/include/.table_ipc_base.md @@ -0,0 +1,48 @@ + +CRTP-based base class for osquery's worker IPC communication layer, providing serialization and dispatch of JSON messages between table worker processes. + +## Key Components + +### `TableIPCBase` (CRTP Template Class) + +Uses the Curiously Recurring Template Pattern to delegate transport-level operations (`sendJSONString`, `recvJSONString`) to derived classes while providing shared message logic. + +**Send Methods** +- `sendQueryData(const QueryData&)` — Serializes query results to JSON and sends them with type tag `"QueryData"` +- `sendLogMessage(int severity, GLOGLogType, const std::string&)` — Serializes a log entry with type tag `"Log"` +- `sendJob(const QueryContext&)` — Serializes a query context as a job request with type tag `"Job"` + +**Receive/Dispatch Methods** +- `recvJSONMessage(JSON&, JSONMessageType&)` — Receives a raw JSON string, parses it, and resolves its `"Type"` field into a `JSONMessageType` enum +- `processOneMessage(QueryData*, JSONMessageType&)` — Receives one message and dispatches to the derived class's `processLogMessage`, `processQueryDataMessage`, or `processJobMessage` handler + +## Usage Example + +```cpp +// Define a concrete transport by inheriting via CRTP +class MyTableIPC : public TableIPCBase { + public: + Status sendJSONString(const std::string& json) { + // write json to pipe/socket + } + Status recvJSONString(std::string& json) { + // read json from pipe/socket + } + Status processLogMessage(JSON& msg) { /* handle log */ } + Status processQueryDataMessage(JSON& msg, QueryData& out) { /* populate out */ } + Status processJobMessage(JSON& msg) { /* dispatch job */ } +}; + +MyTableIPC ipc; +ipc.sendJob(context); // worker side: dispatch a job +ipc.sendQueryData(results); // worker side: return results + +JSONMessageType type; +ipc.processOneMessage(&results, type); // orchestrator side: handle response +``` + +## Notes + +- All messages are JSON objects with a mandatory `"Type"` discriminator field +- Derived classes **must** implement `sendJSONString`, `recvJSONString`, and the three `process*` handler methods +- Transport is fully abstracted — the base class has zero knowledge of pipes, sockets, or shared memory \ No newline at end of file diff --git a/osquery/worker/ipc/include/.table_ipc_message_handler.md b/osquery/worker/ipc/include/.table_ipc_message_handler.md new file mode 100644 index 00000000000..2e31be7c5c5 --- /dev/null +++ b/osquery/worker/ipc/include/.table_ipc_message_handler.md @@ -0,0 +1,39 @@ + +Defines the abstract interface for handling IPC messages in osquery's worker table system, providing a contract for processing log entries and query jobs. + +## Key Components + +### `TableIPCMessageHandler` (Abstract Class) +A pure virtual base class in the `osquery` namespace that establishes the interface for IPC message handlers used in worker table communication. + +| Method | Signature | Description | +|--------|-----------|-------------| +| `handleLog` | `virtual Status handleLog(GLOGLogType, int, const std::string&)` | Handles incoming log messages with a GLOG type, priority level, and message string | +| `handleJob` | `virtual Status handleJob(QueryContext&)` | Processes a query job given a `QueryContext`, returning a `Status` result | + +## Usage Example + +```cpp +#include + +class MyTableHandler : public osquery::TableIPCMessageHandler { + public: + osquery::Status handleLog(osquery::GLOGLogType log_type, + int priority, + const std::string& message) override { + // Forward log to internal logger + return osquery::Status::success(); + } + + osquery::Status handleJob(osquery::QueryContext& context) override { + // Execute query logic using context + return osquery::Status::success(); + } +}; +``` + +## Notes + +- Both virtual methods **must** be overridden in concrete subclasses (pure virtual `= 0`) +- Returns `osquery::Status` for consistent error propagation across both handlers +- Depends on `QueryContext` from `osquery/core/tables.h` and `GLOGLogType` from the worker logging subsystem \ No newline at end of file diff --git a/osquery/worker/ipc/linux/.linux_table_container_ipc.md b/osquery/worker/ipc/linux/.linux_table_container_ipc.md new file mode 100644 index 00000000000..5dccd728bf4 --- /dev/null +++ b/osquery/worker/ipc/linux/.linux_table_container_ipc.md @@ -0,0 +1,53 @@ + +Manages IPC communication between the osquery host process and container worker processes on Linux, orchestrating connection lifecycle, query dispatch, and result retrieval across namespace boundaries. + +## Key Components + +### `LinuxTableContainerIPC` Class +Main class driving container worker lifecycle and query execution over pipe-based IPC. + +| Member | Description | +|---|---| +| `connectToContainer()` | Spawns/connects to a container worker process for a given table | +| `retrieveQueryDataFromContainer()` | Sends a `QueryContext` and collects `QueryData` results | +| `executeQueryJobs()` | Worker-side loop that processes incoming query jobs (no-return) | +| `stopContainerWorker()` | Terminates the container worker process and cleans up | +| `handleLog()` | Routes GLOG log messages received from the worker | +| `handleJob()` | Executes a table generate function inside the container namespace | + +### `CleanupWorkerOnError` (RAII Guard) +Inner class that automatically calls `stopContainerWorker()` on scope exit unless `dismiss()` is called — ensures cleanup on error paths. + +### Free Functions + +| Function | Description | +|---|---| +| `hasNamespaceConstraint()` | Returns `true` if `QueryContext` includes a `pid_with_namespace` constraint | +| `generateInNamespace()` | Entry point to run a table generator inside a container namespace via IPC | + +### Type Alias +```c +using TableGeneratePtr = QueryData (*)(QueryContext&, Logger&); +``` +Function pointer type for table generator callbacks passed into the container worker. + +## Usage Example + +```cpp +PipeChannelFactory factory; +LinuxTableContainerIPC container_ipc(factory); + +Status s = container_ipc.connectToContainer( + "process_open_files", /*keep_process_open=*/true, &myTableGenerate); + +if (s.ok()) { + QueryData results; + container_ipc.retrieveQueryDataFromContainer(context, results); +} +``` + +For namespace-aware table generation, prefer the free function: + +```cpp +QueryData rows = generateInNamespace(context, "processes", &generateProcesses); +``` \ No newline at end of file diff --git a/osquery/worker/ipc/linux/.linux_table_ipc.md b/osquery/worker/ipc/linux/.linux_table_ipc.md new file mode 100644 index 00000000000..b2371264836 --- /dev/null +++ b/osquery/worker/ipc/linux/.linux_table_ipc.md @@ -0,0 +1,44 @@ + +Inter-process communication layer for Linux table workers in osquery, providing JSON-over-pipe messaging between parent and child processes that handle table query logic. + +## Key Components + +### `LinuxTableIPC` +Extends `TableIPCBase` (CRTP) to manage blocking pipe-based IPC channels between worker processes. + +| Method | Description | +|---|---| +| `sendJSONString` / `recvJSONString` | Low-level JSON string transport over the active pipe channel | +| `processLogMessage` | Deserializes and handles an incoming log message | +| `processJobMessage` | Deserializes and dispatches an incoming query job | +| `processQueryDataMessage` | Deserializes query results into a `QueryData` output param | +| `createChannelTicket` | Allocates a new `PipeChannelTicket` via the factory | +| `connectToChild` / `connectToParent` | Establishes the active channel from either side of the fork | +| `setActiveChannelIfOpen` | Activates an existing channel by table name if available | +| `closeActiveChannel` | Tears down the current pipe connection | +| `getTableNameFromPid` | Reverse-lookup of table name by remote PID | +| `isChannelOpen` / `getRemotePid` / `getTableName` | Channel state inspection helpers | + +### `LinuxTableIPCLogger` +Implements the `Logger` interface, forwarding `log()` and `vlog()` calls as serialized log messages over the IPC channel. + +## Usage Example + +```cpp +PipeChannelFactory factory; +TableIPCMessageHandler handler; +LinuxTableIPC ipc(factory, handler); + +// Parent side: establish connection to a spawned child +auto ticket = ipc.createChannelTicket(); +ipc.connectToParent("some_table", ticket); + +// Send/receive JSON payloads +std::string response; +ipc.sendJSONString(R"({"action":"query"})"); +ipc.recvJSONString(response); + +// Attach logger to route GLOG output through the pipe +LinuxTableIPCLogger logger(ipc); +logger.log(google::INFO, "Worker ready"); +``` \ No newline at end of file diff --git a/osquery/worker/ipc/linux/.platform_table_container_ipc.md b/osquery/worker/ipc/linux/.platform_table_container_ipc.md new file mode 100644 index 00000000000..74852687993 --- /dev/null +++ b/osquery/worker/ipc/linux/.platform_table_container_ipc.md @@ -0,0 +1,18 @@ + +Provides a platform-specific header that aliases the Linux IPC implementation for table container inter-process communication within the osquery worker subsystem. + +## Key Components + +- **Platform alias header** — Re-exports `linux_table_container_ipc.h` as the canonical platform table container IPC interface, enabling platform-agnostic includes across the codebase. + +## Usage Example + +```c +// Instead of including the platform-specific path directly: +// #include + +// Use the platform-neutral include: +#include +``` + +> **Note:** This header acts as a thin indirection layer. On Linux, it forwards to the Linux-specific implementation. If support for additional platforms is added in the future, this file would conditionally include the appropriate platform backend without requiring changes in consuming code. \ No newline at end of file diff --git a/osquery/worker/ipc/linux/tests/.worker_table_container_tests.md b/osquery/worker/ipc/linux/tests/.worker_table_container_tests.md new file mode 100644 index 00000000000..3fc19837e9e --- /dev/null +++ b/osquery/worker/ipc/linux/tests/.worker_table_container_tests.md @@ -0,0 +1,37 @@ + +Unit test file for the `LinuxTableContainerIPC` worker table container, verifying IPC connectivity and query data retrieval between the main osquery process and a Linux container worker via pipe channels. + +## Key Components + +- **`WorkerTableContainerTests`** — GTest fixture that initializes the osquery platform, registry, and verbose logging before each test case. +- **`genTest1`** — Stub table generator function returning a single row `{"test": "Hello"}`, used as the container-side query handler. +- **`test_ipc_container_connect`** — Primary test case covering the full IPC lifecycle: container connection, constrained query execution, result retrieval, and worker teardown. + +## Usage Example + +```cpp +// The test simulates a container worker IPC round-trip: + +PipeChannelFactory factory; +LinuxTableContainerIPC container_ipc(factory); + +// 1. Connect to container, registering genTest1 as the query handler +auto status = container_ipc.connectToContainer("test", false, genTest1); +ASSERT_TRUE(status.ok()); + +// 2. Build a QueryContext constrained to the current PID's namespace +QueryContext context; +context.constraints["pid_with_namespace"].add( + Constraint(ConstraintOperator::EQUALS, std::to_string(getpid()))); + +// 3. Retrieve results from the container worker +QueryData results; +status = container_ipc.retrieveQueryDataFromContainer(context, results); +ASSERT_TRUE(status.ok()); + +// 4. Assert expected row data and clean up +EXPECT_EQ(results[0]["test"], "Hello"); +container_ipc.stopContainerWorker(); +``` + +> **Note:** This test is Linux-specific and depends on `LinuxTableContainerIPC` with `PipeChannelFactory`. It requires `pid_with_namespace` constraint support and runs with verbose GLog output enabled for debugging IPC behavior. \ No newline at end of file diff --git a/osquery/worker/ipc/posix/.pipe_channel.md b/osquery/worker/ipc/posix/.pipe_channel.md new file mode 100644 index 00000000000..75073f9eae1 --- /dev/null +++ b/osquery/worker/ipc/posix/.pipe_channel.md @@ -0,0 +1,36 @@ + +Implements a pipe-based IPC channel for inter-process communication between osquery worker processes, using file descriptors for bidirectional message passing. + +## Key Components + +- **`PipeChannel`** — Concrete channel class inheriting from `TableChannelBase` (CRTP pattern) that wraps a pair of Unix pipe file descriptors for sending and receiving string messages between processes. + +| Member | Description | +|---|---| +| `PipeChannel(table_name, read_pipe_fd, write_pipe_fd, remote_pid)` | Constructs a channel with dedicated read/write pipe FDs and the remote process PID | +| `~PipeChannel()` | Automatically closes both pipe FDs on destruction | +| `getRemotePid()` | Returns the PID of the remote process on the other end of the pipe | +| `sendStringMessageImpl()` | CRTP-dispatched implementation for writing a string message to the write pipe | +| `recvStringMessageImpl()` | CRTP-dispatched implementation for reading a string message from the read pipe | +| `blockSIGPIPE()` / `restoreSIGPIPE()` | Signal mask helpers that suppress `SIGPIPE` during writes to detect broken pipes gracefully via return codes | + +- **`PipeChannelFactory`** — Forward-declared factory (defined elsewhere) responsible for constructing `PipeChannel` instances. + +## Usage Example + +```cpp +// Typically constructed via PipeChannelFactory, not directly. +// Conceptual usage after channel setup: +PipeChannel channel("processes_table", read_fd, write_fd, child_pid); + +std::string response; +Status recv_status = channel.recvStringMessage(response); + +if (!recv_status.ok()) { + LOG(ERROR) << "IPC recv failed: " << recv_status.getMessage(); +} + +// Channel FDs are closed automatically when channel goes out of scope +``` + +> **Note:** `PipeChannel` is non-default-constructible by design. All instances must be created with explicit pipe FDs and a remote PID, enforcing valid channel state at construction time. \ No newline at end of file diff --git a/osquery/worker/ipc/posix/.pipe_channel_factory.md b/osquery/worker/ipc/posix/.pipe_channel_factory.md new file mode 100644 index 00000000000..a827d7dccdf --- /dev/null +++ b/osquery/worker/ipc/posix/.pipe_channel_factory.md @@ -0,0 +1,46 @@ + +Manages creation and lifecycle of POSIX pipe-based IPC channels used for inter-process communication between parent and child worker processes in osquery's table extension system. + +## Key Components + +### `PipeChannelTicket` +A move-only RAII wrapper that holds pre-allocated pipe file descriptors before a channel is fully constructed. Ensures proper cleanup of unused file descriptors and prevents double-use via a `used_` flag. + +| Method | Description | +|---|---| +| `getAndUseReadFd(index)` | Transfers ownership of a read-end fd (marks it consumed) | +| `getAndUseWriteFd(index)` | Transfers ownership of a write-end fd (marks it consumed) | +| `~PipeChannelTicket()` | Closes any unclaimed file descriptors | + +### `PipeChannelFactory` +Concrete factory extending `TableChannelFactoryBase` that creates `PipeChannel` instances for parent/child process pairs. + +| Method | Description | +|---|---| +| `createChannelTicket()` | Allocates pipe fd pairs; returns a `PipeChannelTicket` | +| `createChildChannel(name, ticket)` | Builds the child-side `PipeChannel` from a ticket | +| `createParentChannel(name, ticket, pid)` | Builds the parent-side `PipeChannel`, tracking child PID | +| `getTableNameFromPid(pid)` | Looks up the table name associated with a child PID | + +### `GetChannelType` (template specialization) +Maps `PipeChannelFactory` → `PipeChannel` for compile-time type resolution in the factory base. + +## Usage Example + +```cpp +PipeChannelFactory factory; + +// Before fork: create the ticket (pre-allocates pipe fds) +auto ticket = factory.createChannelTicket(); + +pid_t child_pid = fork(); +if (child_pid == 0) { + // Child process + factory.createChildChannel("processes", std::move(ticket)); +} else { + // Parent process + factory.createParentChannel("processes", std::move(ticket), child_pid); +} +``` + +> **Note:** `PipeChannelTicket` is move-only and cannot be reused after channel creation. Attempting to move a `used_` ticket throws `std::logic_error`. \ No newline at end of file diff --git a/osquery/worker/ipc/posix/tests/.worker_ipc_channels_test.md b/osquery/worker/ipc/posix/tests/.worker_ipc_channels_test.md new file mode 100644 index 00000000000..2304cf9a003 --- /dev/null +++ b/osquery/worker/ipc/posix/tests/.worker_ipc_channels_test.md @@ -0,0 +1,45 @@ + +Unit tests for the osquery worker IPC pipe channel subsystem, verifying correct behavior of POSIX pipe-based inter-process communication including message delivery, error handling, and file descriptor leak prevention. + +## Key Components + +### `WorkerIPCChannelsTest` (Test Fixture) +- **`getFdsOpen()`** — Returns the current number of open file descriptors for the process; uses `/proc` on Linux and `/dev/fd` enumeration on macOS. +- **`setRelativeFDLimit(int fd_num)`** — Sets the process `RLIMIT_NOFILE` soft/hard limit to `currentFds + fd_num + 1`, enabling precise fd exhaustion scenarios in tests. + +### Test Cases + +| Test | Description | +|---|---| +| `test_pipe_read_after_exit` | Verifies a parent can still `recvStringMessage` after the child process exits (pipe buffer preserved) | +| `test_pipe_sigpipe` | Confirms that sending to a pipe whose reader has exited returns a failed status with `EPIPE` error code | +| `test_pipe_close_while_reading` | Asserts that `recvStringMessage` returns a failed status when the write end closes before any data is sent | +| `test_pipe_ticket_leak` | Validates no fd leaks occur when `createChannelTicket` fails due to fd exhaustion, and confirms exactly 4 fds are allocated on success | + +## Usage Example + +```bash +# Run all IPC channel tests +./osquery_tests --gtest_filter="WorkerIPCChannelsTest.*" + +# Run a specific test case +./osquery_tests --gtest_filter="WorkerIPCChannelsTest.test_pipe_ticket_leak" +``` + +```cpp +// Typical pattern exercised by these tests +PipeChannelFactory factory; +auto ticket = factory.createChannelTicket(); + +if (fork() == 0) { + auto& child = factory.createChildChannel("name", std::move(ticket)); + child.sendStringMessage("ping"); + std::exit(0); +} else { + auto& parent = factory.createParentChannel("name", std::move(ticket), pid); + std::string msg; + parent.recvStringMessage(msg); // "ping" +} +``` + +> **Platform note:** `getFdsOpen()` uses `procDescriptors` on Linux and `boost::filesystem` directory iteration on macOS (`DARWIN`). Tests requiring fd counting will behave correctly on both platforms. \ No newline at end of file diff --git a/osquery/worker/ipc/tests/.worker_json_conversions_test.md b/osquery/worker/ipc/tests/.worker_json_conversions_test.md new file mode 100644 index 00000000000..6f8a4694d35 --- /dev/null +++ b/osquery/worker/ipc/tests/.worker_json_conversions_test.md @@ -0,0 +1,40 @@ + +Unit tests for JSON serialization and deserialization of IPC message types exchanged between osquery worker processes and table plugins via `TableIPCBase`. + +## Key Components + +### `TestTableIPC` +A concrete test double extending `TableIPCBase` that exposes an in-memory `JSON json_helper` buffer, implementing `sendJSONString` and `recvJSONString` to simulate IPC transport without actual inter-process communication. + +### `WorkerJSONConversionsTests` +The GTest fixture class providing a shared `verifyMessageType` helper that asserts the presence and correctness of the `"Type"` discriminator field in serialized JSON documents. + +### Test Cases + +| Test | Description | +|---|---| +| `test_querydata_and_json_conversions` | Serializes a two-row `QueryData` result set, validates the raw JSON structure, round-trips through `JSONToQueryData`, and asserts that parsing as a `Log` message fails | +| `test_log_message_and_json_conversions` | Serializes a `GLOGLogType::LOG` message with priority, validates JSON fields, round-trips through `JSONToLogMessage`, and asserts that parsing as `QueryData` fails | +| `test_job_and_json_conversions` | Serializes a `QueryContext` with constraints and `colsUsed` columns, validates the JSON structure, and round-trips through `deserializeQueryContextJSON` verifying all constraint operators are preserved | + +## Usage Example + +```cpp +// Typical pattern exercised by the tests +TestTableIPC ipc; + +// Serialize +QueryData data = {{"col1", "val1"}}; +Status s = ipc.sendQueryData(data); + +// Inspect raw JSON +auto& doc = ipc.json_helper.doc(); + +// Deserialize +JSONMessageType type; +JSON helper; +ipc.recvJSONMessage(helper, type); // type == JSONMessageType::QueryData + +QueryData result; +TableIPCJSONConverter::JSONToQueryData(helper, result); +``` \ No newline at end of file diff --git a/osquery/worker/logging/glog/.glog_logger.md b/osquery/worker/logging/glog/.glog_logger.md new file mode 100644 index 00000000000..b0533b1b301 --- /dev/null +++ b/osquery/worker/logging/glog/.glog_logger.md @@ -0,0 +1,33 @@ + +Defines `GLOGLogger`, a singleton logger implementation that bridges osquery's worker logging interface to the GLOG (Google Logging Library) backend. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `GLOGLogger` | `class` | Final singleton class implementing the `Logger` interface using GLOG | +| `instance()` | `static method` | Returns the singleton `GLOGLogger` instance | +| `log()` | `method` | Logs a message at the specified severity level via GLOG | +| `vlog()` | `method` | Logs a verbose message at the specified severity level via GLOG | + +## Usage Example + +```c +#include + +// Retrieve the singleton instance and log a message +osquery::GLOGLogger& logger = osquery::GLOGLogger::instance(); + +// Standard severity log (e.g., INFO=0, WARNING=1, ERROR=2) +logger.log(0, "Worker process started successfully"); + +// Verbose log for debug-level output +logger.vlog(1, "Processing query batch"); +``` + +## Notes + +- Inherits from `osquery::Logger` (defined in `osquery/worker/logging/logger.h`) +- Marked `final` — not intended for further subclassing +- Singleton pattern ensures a single shared GLOG logging instance across worker processes +- Severity integers map to standard GLOG severity levels \ No newline at end of file diff --git a/osquery/worker/logging/include/.glog_logger_types.md b/osquery/worker/logging/include/.glog_logger_types.md new file mode 100644 index 00000000000..081ca20c35a --- /dev/null +++ b/osquery/worker/logging/include/.glog_logger_types.md @@ -0,0 +1,22 @@ + +Defines the `GLOGLogType` enumeration used to differentiate between standard and verbose GLOG logging modes within the osquery framework. + +## Key Components + +- **`GLOGLogType` enum class** — Scoped enumeration with two variants: + - `LOG` — Standard GLOG log output + - `VLOG` — Verbose GLOG log output + +## Usage Example + +```c +#include "glog_logger_types.h" + +void handleLogMessage(osquery::GLOGLogType type) { + if (type == osquery::GLOGLogType::LOG) { + // Handle standard log message + } else if (type == osquery::GLOGLogType::VLOG) { + // Handle verbose log message + } +} +``` \ No newline at end of file diff --git a/osquery/worker/logging/include/.logger.md b/osquery/worker/logging/include/.logger.md new file mode 100644 index 00000000000..9bfdf06bee3 --- /dev/null +++ b/osquery/worker/logging/include/.logger.md @@ -0,0 +1,40 @@ + +Abstract base class defining the logging interface for the osquery framework, providing a contract for severity-based message logging implementations. + +## Key Components + +- **`Logger` (abstract class)** — Pure virtual base class within the `osquery` namespace that all concrete logger implementations must inherit from. + - `log(int severity, const std::string& message)` — Pure virtual method for standard log output. + - `vlog(int severity, const std::string& message)` — Pure virtual method for verbose log output. + - Virtual destructor ensures proper cleanup of derived class instances. + +## Usage Example + +```cpp +#include "logger.h" + +namespace osquery { + +// Concrete implementation of the Logger interface +class ConsoleLogger : public Logger { + public: + void log(int severity, const std::string& message) override { + std::cout << "[" << severity << "] " << message << std::endl; + } + + void vlog(int severity, const std::string& message) override { + if (severity >= VERBOSE_LEVEL) { + std::cout << "[VERBOSE:" << severity << "] " << message << std::endl; + } + } +}; + +} // namespace osquery + +// Usage via base pointer (polymorphism) +std::unique_ptr logger = std::make_unique(); +logger->log(1, "Service started successfully."); +logger->vlog(2, "Detailed diagnostic information."); +``` + +> **Note:** The distinction between `log` and `vlog` allows implementations to separately control standard vs. verbose logging behavior, enabling fine-grained verbosity filtering without modifying call sites. \ No newline at end of file diff --git a/osquery/worker/system/.memory.md b/osquery/worker/system/.memory.md new file mode 100644 index 00000000000..25703a79a14 --- /dev/null +++ b/osquery/worker/system/.memory.md @@ -0,0 +1,25 @@ + +Provides a memory management utility for osquery on Linux, exposing a function to release retained process memory when usage exceeds a defined threshold. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `releaseRetainedMemory()` | Function | Attempts to free retained memory if the current process exceeds a memory usage threshold. Linux-only (`OSQUERY_LINUX`). | + +## Usage Example + +```c +#include "memory.h" + +// Periodically call to release memory on Linux builds +#ifdef OSQUERY_LINUX +osquery::releaseRetainedMemory(); +#endif +``` + +## Notes + +- Only available on Linux platforms via the `OSQUERY_LINUX` preprocessor guard. +- Intended for periodic invocation to prevent unbounded memory growth in long-running osquery processes. +- No-op on non-Linux platforms; callers should wrap usage in `#ifdef OSQUERY_LINUX` guards or rely on the namespace-level guard already present. \ No newline at end of file diff --git a/osquery/worker/system/linux/.memory.md b/osquery/worker/system/linux/.memory.md new file mode 100644 index 00000000000..cac9d73f2bb --- /dev/null +++ b/osquery/worker/system/linux/.memory.md @@ -0,0 +1,62 @@ + +Implements memory management for the osquery Linux daemon by monitoring and trimming heap memory retention when usage exceeds a configurable threshold. + +## Key Components + +### `releaseRetainedMemory()` *(Linux only)* +The sole exported function. Determines a memory threshold (in MB) and calls `malloc_trim(0)` if current RSS exceeds it. + +**Threshold resolution logic:** + +```mermaid +graph TD + A[Check malloc_trim_threshold flag] --> B{Flag found?} + B -->|No| C[Default: 200MB] + B -->|Yes| D{Is default value?} + D -->|Yes| E{Watchdog enabled?} + E -->|Yes| F["80% of watchdog memory limit"] + E -->|No| C + D -->|No| G{Parse user value} + G -->|Error| C + G -->|Value == 0| H[Trimming disabled - return] + G -->|Value > 0| I[Use user-supplied MB value] + F --> J[Compare RSS vs threshold] + C --> J + I --> J + J -->|RSS exceeds threshold| K[malloc_trim 0] + J -->|Within limit| L[No action] +``` + +## Usage Example + +```cpp +#include "memory.h" + +// Called periodically by the osquery watchdog or scheduler +// to prevent unbounded heap growth on Linux hosts. +void periodicCleanup() { +#ifdef OSQUERY_LINUX + osquery::releaseRetainedMemory(); +#endif +} +``` + +**Flag control (at startup):** + +```bash +# Disable trimming entirely +osqueryd --malloc_trim_threshold=0 + +# Set explicit 512MB threshold +osqueryd --malloc_trim_threshold=512 + +# Default: 80% of watchdog limit, or 200MB if watchdog is off +osqueryd +``` + +## Notes + +- Linux-only (`#ifdef OSQUERY_LINUX`); no-op on other platforms. +- RSS is read from `/proc/self/status` via `getProcRSS("self")`. +- Threshold unit is **megabytes**; RSS is converted from bytes before comparison. +- Failures to read `/proc` or parse the flag are non-fatal and fall back to 200 MB. \ No newline at end of file diff --git a/plugins/config/.filesystem_config.md b/plugins/config/.filesystem_config.md new file mode 100644 index 00000000000..b2073a5d284 --- /dev/null +++ b/plugins/config/.filesystem_config.md @@ -0,0 +1,34 @@ + +Filesystem-based configuration plugin for osquery that loads JSON configuration from local files on disk. + +## Key Components + +### `FilesystemConfigPlugin` (extends `ConfigPlugin`) +Registered as the `"filesystem"` config provider. Handles two core operations: + +- **`genConfig(config)`** — Reads the primary config file (`FLAGS_config_path`) and any supplemental `.conf` files found in the adjacent `.d/` drop-in directory. Files are sorted and merged into the config map by path. + +- **`genPack(name, value, pack)`** — Loads a query pack from disk. Supports two modes: + - **Single pack**: Reads the file at `value` directly. + - **Multi-pack** (when `name == "*"`): Resolves a glob pattern, reads each matching file, strips comments, parses JSON, and assembles a combined property tree keyed by filename stem. + +### CLI Flag +- **`--config_path`** — Path to the primary JSON config file. Defaults to `OSQUERY_HOME/osquery.conf`. + +## Usage Example + +```cpp +// Registered automatically via the macro — no manual instantiation needed. +// osquery will invoke genConfig at startup using the registered plugin: +REGISTER(FilesystemConfigPlugin, "config", "filesystem"); + +// Drop-in config fragments are auto-discovered alongside the main config: +// /etc/osquery/osquery.conf ← primary config +// /etc/osquery/osquery.conf.d/01-packs.conf ← merged automatically +// /etc/osquery/osquery.conf.d/02-custom.conf ← merged automatically + +// Multi-pack glob in your config JSON: +// { "packs": { "*": "/etc/osquery/packs/*.conf" } } +``` + +> **Note:** Drop-in `.conf` files are loaded in lexicographic order before the primary config file is appended. Invalid or unreadable files are skipped with a `LOG(WARNING)`. \ No newline at end of file diff --git a/plugins/config/.tls_config.md b/plugins/config/.tls_config.md new file mode 100644 index 00000000000..a6805e17c60 --- /dev/null +++ b/plugins/config/.tls_config.md @@ -0,0 +1,39 @@ + +Declares the `TLSConfigPlugin` class, which retrieves osquery configuration over TLS from a remote endpoint. + +## Key Components + +### `TLSConfigPlugin` +Inherits from `ConfigPlugin` and `std::enable_shared_from_this`. Implements TLS-based remote config fetching. + +| Member | Type | Description | +|--------|------|-------------| +| `setUp()` | `Status` | Initializes the plugin, validates and caches the remote TLS config URL | +| `genConfig()` | `Status` | Fetches and populates the config map from the remote TLS endpoint | +| `uri_` | `std::string` (protected) | Cached URL computed once during setup | + +## Usage Example + +```cpp +// Register and use TLSConfigPlugin via the osquery plugin system +auto plugin = std::make_shared(); + +Status s = plugin->setUp(); +if (!s.ok()) { + LOG(ERROR) << "TLS config setup failed: " << s.getMessage(); +} + +std::map config; +Status result = plugin->genConfig(config); +if (result.ok()) { + for (const auto& [source, json] : config) { + // Process each config source JSON blob + } +} +``` + +## Notes + +- The `uri_` field is computed once in `setUp()` and reused across `genConfig()` calls to avoid redundant URL resolution. +- `TLSConfigTests` is declared a `friend` class for unit test access to internals. +- Typically registered via osquery's plugin registry rather than instantiated directly. \ No newline at end of file diff --git a/plugins/config/.update.md b/plugins/config/.update.md new file mode 100644 index 00000000000..86a0210b6ee --- /dev/null +++ b/plugins/config/.update.md @@ -0,0 +1,23 @@ + +Implements a special-purpose config plugin that enables asynchronous configuration updates from plugins back to the osquery core process. + +## Key Components + +- **`UpdateConfigPlugin`** — A `ConfigPlugin` subclass registered under the `"config/update"` route. Acts as a relay endpoint for plugin-initiated config changes, routing update requests from extensions back to core. +- **`genConfig()`** — Stub implementation (returns `Status(0, "Unused")`); this plugin's role is routing, not config generation. +- **`REGISTER` macro** — Registers the plugin as `"update"` in the `"config"` registry category. + +## Usage Example + +Extension plugins that need to push config changes asynchronously should call `Config::update` directly rather than invoking this plugin explicitly: + +```cpp +// Inside an extension plugin performing an async config update: +Config::get().update({{"my_plugin", R"({"queries": {...}})"}}); + +// osquery's Config::update internally checks if running as an extension. +// If external, it routes via the registry to "config/update" in core, +// which this plugin handles transparently. +``` + +> **Note:** Plugin authors do not need to implement call-chaining manually. The registry automatically forwards `"config/update"` requests from external (extension) processes to core when the route is missing locally — this plugin provides that missing route on the core side. \ No newline at end of file diff --git a/plugins/config/parsers/.auto_constructed_tables.md b/plugins/config/parsers/.auto_constructed_tables.md new file mode 100644 index 00000000000..b338c64b10c --- /dev/null +++ b/plugins/config/parsers/.auto_constructed_tables.md @@ -0,0 +1,45 @@ + +Defines the plugin classes for osquery's Auto Table Construction (ATC) system, which dynamically generates queryable SQL tables from user-defined configuration. + +## Key Components + +### `ATCPlugin` (extends `TablePlugin`) +Represents a dynamically constructed table backed by a SQLite query against an external database path. + +| Member | Description | +|---|---| +| `ATCPlugin(path, tc_columns, sqlite_query)` | Constructor; initializes table in `PENDING` state | +| `generate(context)` | Executes the configured SQLite query and returns rows | +| `setActive()` | Transitions the table out of `PENDING` state, marking it ready for queries | +| `attributes()` | Returns current `TableAttributes` (e.g., `PENDING` vs active) | + +> **Note:** The `PENDING` state guards against race conditions when the SQL database is reinitialized between table registration and attachment. + +### `ATCConfigParserPlugin` (extends `ConfigParserPlugin`) +Parses the `auto_table_construction` section of the osquery config, manages registration and teardown of ATC tables. + +| Member | Description | +|---|---| +| `keys()` | Returns `{"auto_table_construction"}` as the config key | +| `setUp()` | Initializes the parser and restores previously registered ATC tables | +| `update(source, config)` | Processes config changes — registers new tables and removes stale ones | +| `removeATCTables(tables)` | Deregisters a set of ATC tables | +| `registeredATCTables()` | Returns the set of currently active ATC table names from the database | + +## Usage Example + +ATC tables are declared in the osquery config: + +```json +{ + "auto_table_construction": { + "my_custom_table": { + "query": "SELECT * FROM my_db_table", + "path": "/path/to/database.db", + "columns": ["col1", "col2"] + } + } +} +``` + +At runtime, `ATCConfigParserPlugin` parses this config and instantiates an `ATCPlugin` per entry, which is then registered and attached as a queryable osquery table. \ No newline at end of file diff --git a/plugins/config/parsers/.decorators.md b/plugins/config/parsers/.decorators.md new file mode 100644 index 00000000000..e6398d8ce3b --- /dev/null +++ b/plugins/config/parsers/.decorators.md @@ -0,0 +1,46 @@ + +Defines the decorator subsystem for osquery, which enriches log items with additional key-value metadata before they are dispatched to downstream logging APIs. + +## Key Components + +### `DecorationPoint` Enum +Specifies when decorators should execute: + +| Value | Description | +|---|---| +| `DECORATE_LOAD` | Run once on configuration load | +| `DECORATE_ALWAYS` | Run on every log event | +| `DECORATE_INTERVAL` | Run at a specified time interval | + +### `kDecorationPointKeys` +A constant map linking each `DecorationPoint` enum value to its corresponding configuration key string. + +### Functions + +| Function | Description | +|---|---| +| `runDecorators(point, time, source)` | Iterates and executes all discovered decorators for the given `DecorationPoint`. Accepts an optional timestamp (for interval-based points) and an optional source filter. | +| `getDecorations(results)` | Populates `results` with the current set of accumulated decoration key-value pairs. | +| `clearDecorations(source)` | Removes all stored decorations associated with a given config source, typically called when that source updates. | + +## Usage Example + +```c +#include + +// Execute all "always" decorators +osquery::runDecorators(osquery::DECORATE_ALWAYS); + +// Execute interval decorators at a specific unix timestamp +osquery::runDecorators(osquery::DECORATE_INTERVAL, 1700000000ULL); + +// Retrieve accumulated decorations and apply to a log record +std::map decorations; +osquery::getDecorations(decorations); +for (const auto& kv : decorations) { + logRecord[kv.first] = kv.second; +} + +// Clear decorations when a config source is refreshed +osquery::clearDecorations("filesystem"); +``` \ No newline at end of file diff --git a/plugins/config/parsers/.events_parser.md b/plugins/config/parsers/.events_parser.md new file mode 100644 index 00000000000..cf3914fde9f --- /dev/null +++ b/plugins/config/parsers/.events_parser.md @@ -0,0 +1,24 @@ + +Parses the `"events"` dictionary key from osquery configuration, storing event-related settings via the plugin system. + +## Key Components + +- **`EventsConfigParserPlugin`** — A `ConfigParserPlugin` implementation that handles the `"events"` top-level config key. + - **`keys()`** — Declares `"events"` as the only config key this parser owns. + - **`setUp()`** — Initializes `data_` with an empty `"events"` JSON object. + - **`update()`** — Copies the `"events"` subtree from the incoming `ParserConfig` into the plugin's internal `data_` store. +- **`REGISTER_INTERNAL`** — Registers the plugin under the `"config_parser"` registry with the name `"events"`. + +## Usage Example + +```cpp +// Retrieve parsed events config from the registry +auto parser = Config::getParser("events"); +if (parser != nullptr) { + const auto& data = parser->getData(); + // Access the "events" subtree + auto events = data.doc()["events"].GetObject(); +} +``` + +> The plugin is registered internally and activated automatically by the osquery config subsystem — no manual instantiation is required. The parsed data is accessible via `Config::getParser("events")` wherever event configuration values (e.g., schedules or subscriber settings) need to be consumed. \ No newline at end of file diff --git a/plugins/config/parsers/.feature_vectors.md b/plugins/config/parsers/.feature_vectors.md new file mode 100644 index 00000000000..c3dcda207a4 --- /dev/null +++ b/plugins/config/parsers/.feature_vectors.md @@ -0,0 +1,27 @@ + +A `ConfigParserPlugin` implementation that parses and stores feature vector configuration keys from the osquery config system. + +## Key Components + +- **`kFeatureVectorsRootKey`** — External constant defining the root key used to locate feature vector data within the config tree. +- **`FeatureVectorsConfigParserPlugin`** — A `ConfigParserPlugin` subclass responsible for extracting feature vector dictionary keys from the osquery configuration. + - **`keys()`** — Returns the list of top-level config keys this parser handles. + - **`update()`** — Called when the configuration is loaded or refreshed; processes the parsed config data for the given source. + +## Usage Example + +```cpp +// Typically registered automatically via osquery's plugin system. +// Access parsed feature vector data through the Config interface: + +auto parser = Config::getParser("feature_vectors"); +if (parser != nullptr) { + const auto& doc = parser->getData(); + auto it = doc.doc().FindMember(kFeatureVectorsRootKey.c_str()); + if (it != doc.doc().MemberEnd()) { + // Iterate over feature vector keys + } +} +``` + +> **Note:** This parser is registered as part of osquery's config subsystem and is invoked automatically during config load/refresh cycles. Direct instantiation is not typically required — rely on `Config::getParser()` to retrieve the active instance. \ No newline at end of file diff --git a/plugins/config/parsers/.file_paths.md b/plugins/config/parsers/.file_paths.md new file mode 100644 index 00000000000..b042c792b06 --- /dev/null +++ b/plugins/config/parsers/.file_paths.md @@ -0,0 +1,42 @@ + +Implements a `ConfigParserPlugin` for osquery that parses and manages file path monitoring configuration, handling static paths, SQL-queried paths, file access categories, and exclusion patterns from the osquery config. + +## Key Components + +- **`FilePathsConfigParserPlugin`** — Main plugin class extending `ConfigParserPlugin` that handles four top-level config keys: + - `file_paths` — Static glob patterns mapped to named categories + - `file_paths_query` — SQL queries whose results dynamically resolve monitored paths + - `file_accesses` — Categories of files to track for access events + - `exclude_paths` — Glob patterns to exclude from monitoring +- **`setUp()`** — Initializes internal JSON structures and clears the access map +- **`update()`** — Entry point called on config change; dispatches to per-key update methods and clears stale source data +- **`access_map_`** — Internal `map>` binding config sources to their access categories +- **`REGISTER_INTERNAL`** — Registers the plugin under the `"config_parser"` registry with key `"file_paths"` + +## Usage Example + +```cpp +// Typical osquery config JSON consumed by this parser: +{ + "file_paths": { + "homes": ["/root/.ssh/%%", "/home/%/.ssh/%%"], + "etc": ["/etc/%%"] + }, + "file_paths_query": { + "dynamic": ["SELECT path FROM custom_paths_table"] + }, + "file_accesses": ["homes", "etc"], + "exclude_paths": { + "homes": ["/home/vagrant/.ssh/%%"] + } +} +``` + +```cpp +// Plugin is registered automatically; retrieved via the registry: +auto plugin = Config::get().getParser("file_paths"); +// Paths are registered into Config via Config::get().addFile(source, name, pattern) +// after glob wildcards are normalized with replaceGlobWildcards() +``` + +> **Note:** `file_paths_query` execution is disabled at compile time when `OSQUERY_IS_FUZZING` is defined to prevent SQL side-effects during fuzz testing. \ No newline at end of file diff --git a/plugins/config/parsers/.kafka_topics.md b/plugins/config/parsers/.kafka_topics.md new file mode 100644 index 00000000000..f5fc4d378e7 --- /dev/null +++ b/plugins/config/parsers/.kafka_topics.md @@ -0,0 +1,34 @@ + +Declares the `KafkaTopicsConfigParserPlugin` class and associated constants for parsing Kafka topic configurations from osquery's configuration system. + +## Key Components + +- **`kKafkaTopicParserRootKey`** — Extern constant string defining the root key used to retrieve Kafka topic configurations from the osquery config. +- **`KafkaTopicsConfigParserPlugin`** — A `ConfigParserPlugin` subclass responsible for extracting and updating Kafka topic configurations. + - `keys()` — Returns the list of configuration keys this parser handles. + - `update(source, config)` — Processes and applies updated Kafka topic configuration from a given source. + +## Usage Example + +```cpp +#include "kafka_topics.h" + +namespace osquery { + +// Access the root key for Kafka topic config lookup +std::cout << kKafkaTopicParserRootKey << std::endl; + +// Plugin is registered via osquery's config plugin system +// and invoked automatically on config updates. +// To retrieve parsed config data: +auto& config = Config::get(); +auto parser = config.getParser(kKafkaTopicParserRootKey); +if (parser != nullptr) { + const auto& doc = parser->getData(); + // Access parsed Kafka topic entries from doc +} + +} // namespace osquery +``` + +> **Note:** `KafkaTopicsConfigParserPlugin` is intended to be registered with osquery's plugin registry. Direct instantiation is typically handled by the framework during config initialization. \ No newline at end of file diff --git a/plugins/config/parsers/.logger.md b/plugins/config/parsers/.logger.md new file mode 100644 index 00000000000..b60609a6265 --- /dev/null +++ b/plugins/config/parsers/.logger.md @@ -0,0 +1,29 @@ + +Config parser plugin for osquery logger settings, exposing a `ConfigParserPlugin` implementation that reads and updates logger-related configuration keys. + +## Key Components + +- **`LoggerConfigParserPlugin`** — Extends `ConfigParserPlugin` to handle the logger configuration section. + - **`keys()`** — Returns the list of config keys this parser handles (driven by `kLoggerKey`). + - **`update()`** — Called when the configuration source changes; applies updated logger settings. + - **`kLoggerKey`** *(private static)* — The string key identifying the logger section in the osquery config. + +## Usage Example + +```c +// The plugin is registered with osquery's config system and invoked automatically. +// When a config update is received containing the logger key, update() is triggered: + +Status LoggerConfigParserPlugin::update(const std::string& source, + const ParserConfig& config) { + // Access logger config section via kLoggerKey + auto logger_config = config.find(kLoggerKey); + if (logger_config != config.end()) { + // Apply logger settings (e.g., log paths, verbosity) + data_[kLoggerKey] = logger_config->second; + } + return Status::success(); +} +``` + +> **Note:** This parser is automatically discovered and invoked by osquery's `Config` subsystem — no manual instantiation is required. Register it via the standard plugin registration macros if extending. \ No newline at end of file diff --git a/plugins/config/parsers/.options.md b/plugins/config/parsers/.options.md new file mode 100644 index 00000000000..5bd060e0744 --- /dev/null +++ b/plugins/config/parsers/.options.md @@ -0,0 +1,38 @@ + +Parses the `"options"` key from osquery configuration, applying flag values at runtime and reconfiguring logger verbosity when relevant options change. + +## Key Components + +- **`kVerboseOptions`** — A set of flag names (`verbose`, `minloglevel`, `logger_stderr`, etc.) that trigger a verbosity reconfiguration when updated via config. +- **`OptionsConfigParserPlugin`** — A `ConfigParserPlugin` subclass registered internally as `"options"`. Implements: + - `keys()` — Declares interest in the `"options"` config key. + - `update()` — Merges incoming option values into internal state and applies them as osquery flags. +- **`REGISTER_INTERNAL`** — Registers the plugin under `"config_parser"` with the key `"options"`. + +## Usage Example + +Options are consumed automatically from the osquery config (e.g., `osquery.conf`): + +```json +{ + "options": { + "verbose": true, + "logger_min_status": 1, + "custom_my_setting": "value" + } +} +``` + +At runtime, `update()` handles the following: + +```cpp +// Internally called by the config subsystem on config load/refresh. +// For each option in the "options" block: +// - Custom flags (prefix "custom_") are always accepted. +// - Unknown/invalid flags emit a WARNING and are skipped. +// - CLI-only flags emit a WARNING and are skipped. +// - Valid flags are updated via Flag::updateValue(name, value). +// - Verbosity flags additionally trigger setVerboseLevel(). +``` + +> **Note:** CLI-only flags set via the config file are silently ignored with a warning. Use a flagfile or pass them at process startup instead. \ No newline at end of file diff --git a/plugins/config/parsers/.prometheus_targets.md b/plugins/config/parsers/.prometheus_targets.md new file mode 100644 index 00000000000..098a50d8bb8 --- /dev/null +++ b/plugins/config/parsers/.prometheus_targets.md @@ -0,0 +1,29 @@ + +Defines the `PrometheusMetricsConfigParserPlugin` class and associated constants for parsing Prometheus target configurations within the osquery config system. + +## Key Components + +- **`kPrometheusParserRootKey`** — External constant string defining the root configuration key used to identify Prometheus metrics entries in the osquery config. +- **`PrometheusMetricsConfigParserPlugin`** — A `ConfigParserPlugin` subclass responsible for extracting and updating Prometheus scrape target configuration from osquery's config sources. + - **`keys()`** — Returns the list of config keys this parser handles (typically wrapping `kPrometheusParserRootKey`). + - **`update()`** — Called by the config subsystem when a config source changes; processes the parsed Prometheus target data and stores it for use by the Prometheus metrics table. + +## Usage Example + +```cpp +// Plugin is registered automatically via osquery's plugin system. +// Access parsed Prometheus targets through the config after registration: + +auto& config = Config::get(); +config.parsers(); // triggers update() on all registered ConfigParserPlugins + +// Retrieve parsed Prometheus config data using the root key: +auto parser = Config::getParser(kPrometheusParserRootKey); +if (parser != nullptr) { + const auto& data = parser->getData(); + // data contains the parsed Prometheus scrape targets + auto targets = data.get(kPrometheusParserRootKey, {}); +} +``` + +> **Note:** This header is part of osquery's Prometheus metrics integration. The plugin is typically auto-registered at startup and interacts with the config subsystem rather than being instantiated directly by consumers. \ No newline at end of file diff --git a/plugins/config/parsers/.views.md b/plugins/config/parsers/.views.md new file mode 100644 index 00000000000..6d43d1f4dc6 --- /dev/null +++ b/plugins/config/parsers/.views.md @@ -0,0 +1,41 @@ + +Implements the `ViewsConfigParserPlugin`, an osquery config parser that manages SQL virtual views defined in the osquery configuration file, handling creation, updates, and teardown of views against the SQLite query engine. + +## Key Components + +- **`ViewsConfigParserPlugin`** — A `ConfigParserPlugin` subclass registered under the `"views"` config key. Parses the `views` dictionary from osquery config and reconciles it with the current database state. +- **`update()`** — Core reconciliation method that: + - Parses the incoming `views` JSON object from config + - Scans the database for previously registered views (prefixed with `config_views.`) + - Creates or updates views via `CREATE VIEW ... AS ` + - Drops stale views no longer present in config via `DROP VIEW` + - Persists view queries to the database for change detection across reloads +- **`kConfigViews`** — Database key prefix (`"config_views."`) used to namespace stored view queries +- **`first_time_`** — Atomic flag that forces view recreation on first load (startup), even if the query is unchanged in the database + +## Usage Example + +Views are defined in the osquery config JSON and automatically managed by this plugin: + +```json +{ + "views": { + "my_custom_view": "SELECT pid, name FROM processes WHERE uid = 0" + } +} +``` + +The plugin translates this into: + +```sql +DROP VIEW my_custom_view; +CREATE VIEW my_custom_view AS SELECT pid, name FROM processes WHERE uid = 0; +``` + +Once registered, the view is queryable like any osquery table: + +```sql +SELECT * FROM my_custom_view; +``` + +> **Note:** When `OSQUERY_IS_FUZZING` is defined, all SQL execution is skipped — only database bookkeeping operations are performed, preventing side effects during fuzz testing. \ No newline at end of file diff --git a/plugins/config/parsers/tests/.decorators_tests.md b/plugins/config/parsers/tests/.decorators_tests.md new file mode 100644 index 00000000000..7131777d821 --- /dev/null +++ b/plugins/config/parsers/tests/.decorators_tests.md @@ -0,0 +1,37 @@ + +Unit tests for the `DecoratorsConfigParserPlugin`, validating loading, interval-based execution, top-level decoration promotion, and invalid input handling for osquery's decorator system. + +## Key Components + +### `DecoratorsConfigParserPluginTests` (Test Fixture) +- Inherits from `testing::Test` +- **`SetUp()`** — Initializes the platform, registry, and database; loads a test config file; disables decorators by default and backs up the flag state +- **`TearDown()`** — Resets config and restores the `FLAGS_disable_decorators` flag + +### Test Cases + +| Test | Description | +|---|---| +| `test_decorators_list` | Verifies decorations are empty when `FLAGS_disable_decorators` is `true` | +| `test_decorators_run_load` | Confirms load-time decorators execute on config update and produce 3 decoration entries | +| `test_decorators_run_interval` | Validates interval-based decorators (`DECORATE_INTERVAL`) fire correctly at 60s and 3600s, and serializes a `QueryLogItem` to expected JSON | +| `test_decorators_run_load_top_level` | Verifies `FLAGS_decorations_top_level` promotes decoration keys to the top level of the serialized JSON document | +| `test_invalid_decorators` | Ensures `Config::update()` rejects non-object decorator formats (array, string, number) | + +## Usage Example + +```cpp +// Run all decorator tests via gtest +./osquery_tests --gtest_filter="DecoratorsConfigParserPluginTests.*" + +// Run a specific test case +./osquery_tests --gtest_filter="DecoratorsConfigParserPluginTests.test_decorators_run_interval" +``` + +**Key flags exercised:** + +```cpp +FLAGS_disable_decorators = false; // Enable decorator execution +FLAGS_decorations_top_level = true; // Promote decorations to JSON top level +FLAGS_logger_numerics; // Affects JSON serialization output +``` \ No newline at end of file diff --git a/plugins/config/parsers/tests/.events_parser_tests.md b/plugins/config/parsers/tests/.events_parser_tests.md new file mode 100644 index 00000000000..90aa8d5f096 --- /dev/null +++ b/plugins/config/parsers/tests/.events_parser_tests.md @@ -0,0 +1,35 @@ + +Unit tests for the `EventsConfigParserPlugin`, validating that the events configuration parser correctly reads and exposes event-related config data (e.g., `environment_variables`) from a parsed config file. + +## Key Components + +- **`EventsConfigParserPluginTests`** — GTest fixture that initializes the platform, registry/plugin system, and an in-memory test database before each test. +- **`test_get_event`** — Core test case covering: + - Config reset and reload from a synthetic test config file (`test_parse_items.conf`) + - Config update via `Config::get().update()` + - Retrieval of the `"events"` parser via `Config::get().getParser("events")` + - JSON structure assertions confirming `events.environment_variables` is a valid array containing the expected values (`"foo"` or `"bar"`) + +## Usage Example + +```cpp +// The test validates this parser retrieval and data access pattern: +auto plugin = Config::get().getParser("events"); +EXPECT_TRUE(plugin != nullptr); + +const auto& data = plugin->getData(); + +// Confirm the expected JSON structure exists +ASSERT_TRUE(data.doc().HasMember("events")); +ASSERT_TRUE(data.doc()["events"].HasMember("environment_variables")); +ASSERT_TRUE(data.doc()["events"]["environment_variables"].IsArray()); + +// Iterate over parsed environment variable entries +for (const auto& var : + data.doc()["events"]["environment_variables"].GetArray()) { + std::string value = var.GetString(); + EXPECT_TRUE(value == "foo" || value == "bar"); +} +``` + +> **Note:** Tests depend on `test_parse_items.conf` located in the osquery test config directory. The config is reset before and after the test to prevent state leakage between test runs. \ No newline at end of file diff --git a/plugins/config/parsers/tests/.file_paths_tests.md b/plugins/config/parsers/tests/.file_paths_tests.md new file mode 100644 index 00000000000..8d37a3504f3 --- /dev/null +++ b/plugins/config/parsers/tests/.file_paths_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the `FilePathsConfigParserPlugin`, validating that osquery correctly parses and manages file path configurations including monitored paths, file accesses, exclude paths, and source removal behavior. + +## Key Components + +### Test Fixture: `FilePathsConfigParserPluginTests` + +- **`SetUp()`** — Initializes the platform, registry, and database; loads `test_parse_items.conf` into a config map keyed by `"awesome"` +- **`TearDown()`** — Resets the global `Config` singleton between tests +- **`numFiles()`** — Helper that counts distinct file categories registered in `Config` + +### Test Cases + +| Test | Description | +|---|---| +| `test_get_files` | Verifies parsed file path categories (`config_files`, `logs`, `config_files_query`) and expected path values including `/dev/urandom` from `file_paths_query` | +| `test_get_file_accesses` | Asserts the `file_accesses` JSON array exists and contains exactly 2 entries | +| `test_get_exclude_paths` | Asserts `exclude_paths` is a JSON object containing an `"example"` array with 1 entry | +| `test_remove_source` | Confirms that `removeFiles()` retains pack-sourced paths, and that updating with empty content clears all file entries | + +## Usage Example + +```bash +# Run only file path parser tests via ctest or directly +./osquery_tests --gtest_filter="FilePathsConfigParserPluginTests.*" +``` + +```cpp +// Typical pattern validated by these tests: +Config::get().update(config_data_); +Config::get().files([](const std::string& category, + const std::vector& files) { + // Iterate monitored file categories and their resolved paths +}); + +auto parser = Config::getParser("file_paths"); +const auto& doc = parser->getData(); // Access raw parsed JSON +``` + +> **Note:** Tests depend on `test_parse_items.conf` in the osquery test config directory and use a live database plugin initialized via `initDatabasePluginForTesting()`. \ No newline at end of file diff --git a/plugins/config/parsers/tests/.options_tests.md b/plugins/config/parsers/tests/.options_tests.md new file mode 100644 index 00000000000..55aba63725f --- /dev/null +++ b/plugins/config/parsers/tests/.options_tests.md @@ -0,0 +1,38 @@ + +Unit tests for the `OptionsConfigParserPlugin`, validating how osquery parses, applies, and handles edge cases in configuration options. + +## Key Components + +| Component | Description | +|---|---| +| `OptionsConfigParserPluginTests` | GTest fixture that initializes the platform, registry, and test database before each test | +| `TestOptionsRaceParser` | Mock `ConfigParserPlugin` that listens on `before_options` key and verifies flags are set before other parsers run | +| `FLAG(test_options_race_parser)` | Boolean test flag used to verify ordering guarantees during config update | + +## Test Cases + +| Test | Description | +|---|---| +| `test_get_option` | Loads a real config file and asserts the `options` key is not exposed as a raw member in the parser document | +| `test_get_option_first` | Registers a parser that depends on a flag being set by `options`, then updates config — verifies options are applied before other parsers execute | +| `test_unknown_option` | Validates flag filtering rules: unknown flags are ignored, `custom_` prefix enables custom options, and improper prefixes (e.g. `fake_custom_fake`) are rejected | +| `test_json_option` | Confirms nested JSON values under `custom_` prefixed keys are serialized to string flags correctly | + +## Usage Example + +```cpp +// Simulate how a custom_ prefixed option is stored and retrieved +Config c; +std::map update; +update["source"] = R"({"options": {"custom_my_setting": 42}})"; +c.update(update); + +// Retrieve via Flag API +std::string val = Flag::getValue("custom_my_setting"); // "42" + +// Retrieve via parser document +const auto& doc = c.getParser("options")->getData().doc()["options"]; +doc["custom_my_setting"].GetUint(); // 42 +``` + +> **Note:** Only flags prefixed with `custom_` or pre-registered via `FLAG(...)` macros are accepted as valid options. Unknown or improperly prefixed keys are stored in the document but not applied as flags. \ No newline at end of file diff --git a/plugins/config/parsers/tests/.views_tests.md b/plugins/config/parsers/tests/.views_tests.md new file mode 100644 index 00000000000..51efb695809 --- /dev/null +++ b/plugins/config/parsers/tests/.views_tests.md @@ -0,0 +1,36 @@ + +Unit tests for the `ViewsConfigParserPlugin`, validating how osquery's configuration system handles SQL view lifecycle operations (add, swap, and update). + +## Key Components + +### Test Fixture: `ViewsConfigParserPluginTests` +- Inherits from `testing::Test` +- `SetUp()` initializes the platform, registry/plugins, and database exactly once via a static guard + +### Test Cases + +| Test | Description | +|---|---| +| `test_add_view` | Loads `test_parse_items.conf` and verifies exactly one view is stored under the `config_views.*` database prefix | +| `test_swap_view` | Loads `view_test.conf` after a prior state and confirms the old view is replaced, with the new key `config_views.kernel_hashes_new` | +| `test_update_view` | Loads `view_test2.conf` and validates both the view count and the exact SQL query string stored for `config_views.kernel_hashes_new` | + +## Usage Example + +```cpp +// Run all ViewsConfigParserPlugin tests via gtest +// Each test follows this pattern: + +Config c; +auto s = c.update(getTestConfigMap("view_test.conf")); +EXPECT_TRUE(s.ok()); + +std::vector views; +scanDatabaseKeys(kQueries, views, "config_views."); +EXPECT_EQ(views.size(), 1U); +EXPECT_EQ(views[0], "config_views.kernel_hashes_new"); + +c.reset(); // Clean up config state between tests +``` + +> **Note:** `c.reset()` is called at the end of each test to clear config state, since the database plugin is initialized only once and persists across all tests in the fixture. \ No newline at end of file diff --git a/plugins/config/tests/.tls_config_tests.md b/plugins/config/tests/.tls_config_tests.md new file mode 100644 index 00000000000..2c05f53a59d --- /dev/null +++ b/plugins/config/tests/.tls_config_tests.md @@ -0,0 +1,32 @@ + +Integration test suite for the `TLSConfigPlugin`, validating TLS-based configuration retrieval, scheduler interaction, and enrollment behavior against a live TLS test server. + +## Key Components + +| Component | Description | +|---|---| +| `TLSConfigTests` | GTest fixture that spins up a `TLSServerRunner`, saves/restores flag state, and disables the config refresh thread during tests | +| `test_retrieve_config` | Verifies POST-based config retrieval hashes correctly and that the node API returns the expected response via GET | +| `test_runner_and_scheduler` | Validates that the `SchedulerRunner` integrates with the config system without deadlock or state corruption | +| `test_setup` | Tests `TLSConfigPlugin::setUp()` enrollment logic — cached key reuse, `enroll_always` forced re-enrollment, and remote request auditing | + +## Usage Example + +```cpp +// Run all TLS config tests via GTest +// From the osquery build directory: +``` + +```bash +# Run the full suite +ctest -R TLSConfigTests --verbose + +# Run a single test case +./osquery_tests --gtest_filter="TLSConfigTests.test_setup" +``` + +Key test behaviors to note: + +- **Cached key path**: Sets `nodeKey` + `nodeKeyTime` in the persistent database, then asserts `setUp()` makes **zero** TLS requests. +- **`enroll_always` path**: Sets `FLAGS_enroll_always = true`, then confirms a fresh enrollment replaces the cached key and exactly **one** `"enroll"` command is posted to the server. +- **Config hash check**: After `Config::load()`, the schedule hash is asserted against a known SHA1 (`1c70dc4608ed9f8d8e24d23359a46e8739a93558`) to detect payload regressions. \ No newline at end of file diff --git a/plugins/database/.rocksdb.md b/plugins/database/.rocksdb.md new file mode 100644 index 00000000000..54d3288da3d --- /dev/null +++ b/plugins/database/.rocksdb.md @@ -0,0 +1,59 @@ + +RocksDB database plugin implementation for osquery, providing persistent key-value storage backed by RocksDB with column family support, corruption detection, and integrated logging via Glog. + +## Key Components + +### `GlogRocksDBLogger` +Extends `rocksdb::Logger` to intercept internal RocksDB log events, forwarding warnings and errors to Glog rather than letting them fall through to stderr. + +### `RocksDBDatabasePlugin` +Implements the `DatabasePlugin` interface using RocksDB as the storage backend. Key method groups: + +| Category | Methods | +|---|---| +| **Lifecycle** | `setUp()`, `tearDown()`, `close()`, `flush()` | +| **Read** | `get()` (string & int overloads), `scan()` | +| **Write** | `put()` (string & int overloads), `putBatch()` | +| **Delete** | `remove()`, `removeRange()` | +| **Maintenance** | `compactFiles()`, `repairDB()` | +| **Corruption** | `setCorrupted()`, `isCorrupted()` | + +**Private members of note:** +- `db_` — raw RocksDB database handle +- `handles_` — column family handle pointers per domain +- `close_mutex_` — guards teardown against concurrent access +- `options_` — RocksDB connection/tuning options + +## Usage Example + +```cpp +#include "rocksdb.h" + +osquery::RocksDBDatabasePlugin plugin; + +// Initialize the database +auto status = plugin.setUp(); +if (!status.ok()) { + LOG(ERROR) << "DB init failed: " << status.getMessage(); +} + +// Store a value +plugin.put("config", "refresh_interval", "60"); + +// Retrieve a value +std::string value; +plugin.get("config", "refresh_interval", value); + +// Scan keys with a prefix +std::vector keys; +plugin.scan("config", keys, "refresh_", 100); + +// Batch write +osquery::DatabaseStringValueList batch = {{"key1", "val1"}, {"key2", "val2"}}; +plugin.putBatch("config", batch); + +// Cleanup +plugin.tearDown(); +``` + +> **Note:** Corruption is tracked via a global indicator (`setCorrupted`/`isCorrupted`). If detected, `repairDB()` is invoked automatically during `setUp()` as a best-effort recovery step. \ No newline at end of file diff --git a/plugins/database/.sqlite.md b/plugins/database/.sqlite.md new file mode 100644 index 00000000000..50a8a9c00c3 --- /dev/null +++ b/plugins/database/.sqlite.md @@ -0,0 +1,50 @@ + +Defines the `SQLiteDatabasePlugin` class, providing a SQLite-backed implementation of osquery's `DatabasePlugin` interface for persistent key-value storage. + +## Key Components + +### `SQLiteDatabasePlugin` +Concrete plugin class inheriting from `DatabasePlugin` that wraps a long-lived `sqlite3*` handle. Registered internally as the `"sqlite"` database provider. + +| Method | Description | +|---|---| +| `get()` | Retrieves a `string` or `int` value by domain/key | +| `put()` | Stores a `string` or `int` value by domain/key | +| `putBatch()` | Bulk-inserts a list of string key-value pairs | +| `remove()` | Deletes a single key from a domain | +| `removeRange()` | Deletes all keys within a lexicographic range | +| `scan()` | Lists keys in a domain, optionally filtered by prefix and count | +| `setUp()` | Opens and initializes the SQLite database file | +| `tearDown()` / `~SQLiteDatabasePlugin()` | Closes the database safely | + +### Private Members +- `db_` — raw `sqlite3*` handle to the open database +- `close_mutex_` — `Mutex` guarding safe concurrent teardown +- `close()` — internal cleanup helper called by both destructor and `tearDown()` + +### Registration +```cpp +REGISTER_INTERNAL(SQLiteDatabasePlugin, "database", "sqlite"); +``` +Registers the plugin as the internal `"sqlite"` database backend, activated via the `database_path` flag. + +## Usage Example + +```cpp +// Instantiated and managed by the osquery plugin registry. +// Direct interaction is via the DatabasePlugin interface: + +std::string value; +auto s = db->get("config", "my_key", value); +if (s.ok()) { + // use value +} + +db->put("config", "my_key", "my_value"); + +std::vector keys; +db->scan("config", keys, "prefix_", 100); + +db->remove("config", "my_key"); +db->removeRange("config", "a", "z"); +``` \ No newline at end of file diff --git a/plugins/database/tests/.rocksdb.md b/plugins/database/tests/.rocksdb.md new file mode 100644 index 00000000000..5a1e058cc7f --- /dev/null +++ b/plugins/database/tests/.rocksdb.md @@ -0,0 +1,38 @@ + +RocksDB database plugin test suite for osquery, validating core database behaviors including log level suppression, corruption recovery, and column family rollback handling. + +## Key Components + +| Component | Description | +|---|---| +| `RocksDBDatabasePluginTests` | Test fixture extending `DatabasePluginTests`; manages DB directory lifecycle via `TearDown` cleanup | +| `CREATE_DATABASE_TESTS` | Macro that registers the standard database plugin operation test suite against the RocksDB backend | +| `test_rocksdb_loglevel` | Verifies RocksDB does not write a `LOG` file; confirms logs are intercepted and forwarded to GLog | +| `test_corruption` | Validates that marking the DB as corrupted triggers a `.backup` copy on reload, and subsequent clean reloads do not re-create backups | +| `test_column_families_rollback` | Ensures the plugin can open an existing RocksDB instance containing unknown column families without crashing | + +## Usage Example + +```cpp +// Run all RocksDB plugin tests via standard gtest runner +// Tests are registered through the CREATE_DATABASE_TESTS macro + +// Simulate corruption recovery manually in test context: +RocksDBDatabasePlugin::setCorrupted(); +resetDatabase(); +// Expects path_.backup to exist after reset + +// Verify unknown column families are tolerated on re-open: +auto db = RocksDBDatabasePlugin(); +FLAGS_database_path = "/tmp/osquery.test.db"; +auto s = db.setUp(); +assert(s.ok()); +// Create unknown CF, tear down, re-open — must succeed without error +db.tearDown(); +``` + +## Notes + +- `db_dirs_` accumulates temporary database paths created during tests; all are removed in `TearDown` via `removePath` +- RocksDB log interception (no `LOG` file) is a deliberate design choice — output is routed through GLog for unified osquery logging +- The column families rollback test uses `boost::filesystem::unique_path` to guarantee a collision-free temp directory per run \ No newline at end of file diff --git a/plugins/database/tests/.sqlite.md b/plugins/database/tests/.sqlite.md new file mode 100644 index 00000000000..0b5968c30b7 --- /dev/null +++ b/plugins/database/tests/.sqlite.md @@ -0,0 +1,34 @@ + +Defines and registers the SQLite database plugin test suite for osquery by instantiating the shared `DatabasePluginTests` base class with the `"sqlite"` plugin identifier. + +## Key Components + +- **`SQLiteDatabasePluginTests`** — Concrete test fixture that extends `DatabasePluginTests`, targeting the SQLite database backend via the `name()` override returning `"sqlite"`. +- **`CREATE_DATABASE_TESTS`** — Macro that expands the standard set of database plugin operation tests (read, write, delete, etc.) against the `SQLiteDatabasePluginTests` fixture. + +## Usage Example + +```cpp +// The test suite is auto-registered via the macro. +// No manual instantiation is needed; run via the standard test runner: + +// BUILD & RUN (from osquery build directory): +// cmake --build . --target osquery_database_tests +// ./osquery_database_tests --gtest_filter="SQLiteDatabasePluginTests.*" + +// Adding a custom SQLite-specific test case alongside the shared suite: +class SQLiteDatabasePluginTests : public DatabasePluginTests { + protected: + std::string name() override { + return "sqlite"; + } + + void customSQLiteTest() { + // Plugin-specific test logic here + } +}; + +CREATE_DATABASE_TESTS(SQLiteDatabasePluginTests); +``` + +> **Note:** The shared test suite is defined in `osquery/database/tests/test_utils.h`. To add support for a new database backend, create a similar file pointing `name()` to the new plugin identifier and apply the same macro. \ No newline at end of file diff --git a/plugins/distributed/.tls_distributed.md b/plugins/distributed/.tls_distributed.md new file mode 100644 index 00000000000..8f8c4de9b8c --- /dev/null +++ b/plugins/distributed/.tls_distributed.md @@ -0,0 +1,44 @@ + +Implements the TLS-based distributed query plugin for osquery, enabling retrieval of distributed queries and submission of query results over HTTPS/TLS endpoints. + +## Key Components + +### `TLSDistributedPlugin` +A `DistributedPlugin` subclass registered under the `"distributed"` registry as `"tls"`. Handles the full lifecycle of distributed query communication over TLS. + +| Method | Description | +|--------|-------------| +| `setUp()` | Initializes read/write URIs from flag values using `TLSRequestHelper::makeURI` | +| `getQueries(json)` | POSTs to the read endpoint to fetch pending distributed queries | +| `writeResults(json)` | POSTs serialized query results to the write endpoint | + +### Configuration Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `distributed_tls_read_endpoint` | `""` | HTTPS endpoint for fetching queries | +| `distributed_tls_write_endpoint` | `""` | HTTPS endpoint for submitting results | +| `distributed_tls_max_attempts` | `3` | Retry count per request | + +## Usage Example + +Configure the plugin via osquery flags, then the plugin is invoked automatically by the distributed framework: + +```cpp +// Launch osquery with TLS distributed plugin enabled +// osqueryd --distributed_plugin=tls \ +// --distributed_tls_read_endpoint=/api/v1/distributed/read \ +// --distributed_tls_write_endpoint=/api/v1/distributed/write \ +// --distributed_tls_max_attempts=3 + +// The plugin is registered automatically at startup: +REGISTER(TLSDistributedPlugin, "distributed", "tls"); + +// getQueries POSTs to read_uri_ and returns JSON query payload +Status s = plugin->getQueries(json_output); + +// writeResults serializes and POSTs results to write_uri_ +Status s = plugin->writeResults(json_results); +``` + +> **Note:** Both endpoints use `JSONSerializer` via `TLSRequestHelper::go()`. The response from `writeResults` is intentionally discarded — only the status of the HTTP request is checked. \ No newline at end of file diff --git a/plugins/logger/.aws_firehose.md b/plugins/logger/.aws_firehose.md new file mode 100644 index 00000000000..75d5cf30a80 --- /dev/null +++ b/plugins/logger/.aws_firehose.md @@ -0,0 +1,49 @@ + +Header file defining the AWS Kinesis Firehose logger plugin and log forwarder for osquery, enabling buffered log streaming to AWS Firehose delivery streams. + +## Key Components + +### Type Alias: `IFirehoseLogForwarder` +Template instantiation of `AwsLogForwarder` parameterized with Firehose-specific AWS SDK types (`Record`, `FirehoseClient`, `PutRecordBatchOutcome`, `PutRecordBatchResponseEntry`). + +### Class: `FirehoseLogForwarder` +Concrete implementation of `IFirehoseLogForwarder` that handles the low-level Firehose batching and transmission logic. + +| Method | Purpose | +|---|---| +| `internalSetup()` | Initializes the Firehose client connection | +| `internalSend(batch)` | Transmits a batch of records to Firehose | +| `initializeRecord()` | Populates a Firehose `Record` from a byte buffer | +| `getMax*()` methods | Returns Firehose-specific limits (batch size, record count, retry config) | +| `appendNewlineSeparators()` | Controls newline insertion between log entries | +| `getFailedRecordCount()` / `getResult()` | Parses batch response for failures | + +### Class: `FirehoseLoggerPlugin` +osquery `LoggerPlugin` integration layer that owns a `FirehoseLogForwarder` instance and bridges osquery's logging API to Firehose. + +| Method | Purpose | +|---|---| +| `setUp()` | Configures the plugin and forwarder | +| `logString()` | Forwards string log entries | +| `logStatus()` | Forwards structured status log lines | +| `usesLogStatus()` | Declares status log support (`true`) | + +### Flag: `aws_firehose_period` +Controls the flush interval (in seconds) for the log forwarder background thread. + +## Usage Example + +```cpp +// Registration is handled by osquery's plugin registry. +// Configure via osquery flags at startup: +// +// --logger_plugin=aws_firehose +// --aws_firehose_stream=my-delivery-stream +// --aws_firehose_period=10 +// --aws_region=us-east-1 + +// The plugin is instantiated internally; direct usage follows the LoggerPlugin interface: +auto plugin = std::make_shared(); +plugin->setUp(); +plugin->logString("{\"name\":\"osquery\",\"result\":{...}}"); +``` \ No newline at end of file diff --git a/plugins/logger/.aws_kinesis.md b/plugins/logger/.aws_kinesis.md new file mode 100644 index 00000000000..ecaf9dcd578 --- /dev/null +++ b/plugins/logger/.aws_kinesis.md @@ -0,0 +1,47 @@ + +Header file defining the AWS Kinesis log forwarding integration for osquery, providing classes to batch and send log records to an Amazon Kinesis stream. + +## Key Components + +### Type Alias +- **`IKinesisLogForwarder`** — Specialization of the generic `AwsLogForwarder` template, configured with Kinesis-specific types (`PutRecordsRequestEntry`, `KinesisClient`, `PutRecordsOutcome`, `PutRecordsResultEntry`) + +### `KinesisLogForwarder` +Concrete forwarder class that implements batched log delivery to Kinesis. Key overrides: + +| Method | Purpose | +|---|---| +| `internalSetup()` | Initializes the Kinesis client and stream configuration | +| `internalSend(batch)` | Sends a batch of records via `PutRecords` API | +| `initializeRecord()` | Populates a `PutRecordsRequestEntry` with a byte buffer | +| `getMax*()` methods | Enforce Kinesis service limits (batch size, record size, retry policy) | +| `getFailedRecordCount()` | Parses partial failures from the outcome | +| `appendNewlineSeparators()` | Controls newline formatting between records | + +**Private member:** `partition_key_` — used for Kinesis stream sharding unless `aws_kinesis_random_partition_key` flag is set. + +### `KinesisLoggerPlugin` +osquery `LoggerPlugin` implementation that owns a `KinesisLogForwarder` and integrates with the plugin system. + +| Method | Purpose | +|---|---| +| `setUp()` | Bootstraps the forwarder | +| `logString()` | Forwards a raw string log entry | +| `logStatus()` | Forwards ERROR/WARNING/INFO status lines | +| `usesLogStatus()` | Declares status log support | + +### Flag +- **`aws_kinesis_period`** — `uint64` gflag controlling the forwarding interval + +## Usage Example + +```cpp +// Instantiated internally by the plugin system via KinesisLoggerPlugin::setUp() +auto forwarder = std::make_shared( + "kinesis-forwarder", + /*log_period=*/10, + /*max_lines=*/500, + /*endpoint_override=*/"", + region +); +``` \ No newline at end of file diff --git a/plugins/logger/.aws_log_forwarder.md b/plugins/logger/.aws_log_forwarder.md new file mode 100644 index 00000000000..728a4cef09c --- /dev/null +++ b/plugins/logger/.aws_log_forwarder.md @@ -0,0 +1,81 @@ + +Generic template base class for forwarding buffered osquery log records to AWS services (e.g., Kinesis, Firehose), handling batching, retries, and error reporting. + +## Key Components + +### Template Parameters + +| Parameter | Description | +|---|---| +| `RecordType` | AWS SDK record type (e.g., `Aws::Kinesis::Model::PutRecordsRequestEntry`) | +| `ClientType` | AWS service client type | +| `OutcomeType` | AWS SDK outcome type returned by the send operation | +| `ResultType` | Per-record result list type from the outcome | + +### Public Type Aliases + +- `Batch` — `std::vector`, a single sendable unit +- `BatchList` — `std::vector`, ordered collection of batches +- `Client`, `Record`, `Outcome`, `Result` — convenience aliases for template params + +### Key Methods + +| Method | Visibility | Description | +|---|---|---| +| `setUp()` | `public` | Initializes AWS client via `makeAWSClient`, then calls `internalSetup()` | +| `send()` | `protected` | Entry point: converts raw log strings into batches and dispatches them | +| `sendBatch()` | `protected` | Sends one batch with configurable retry loop and backoff delay | +| `consumeDataAndGenerateBatches()` | `private` | Splits log lines into protocol-compliant batches, discarding oversized records | + +### Pure Virtual Interface (must be implemented by subclasses) + +```cpp +virtual Status internalSetup() = 0; +virtual Outcome internalSend(const Batch& batch) = 0; +virtual void initializeRecord(Record&, Aws::Utils::ByteBuffer&) const = 0; +virtual size_t getMaxBytesPerRecord() const = 0; +virtual size_t getMaxRecordsPerBatch() const = 0; +virtual size_t getMaxBytesPerBatch() const = 0; +virtual size_t getMaxRetryCount() const = 0; +virtual size_t getInitialRetryDelay() const = 0; +virtual bool appendNewlineSeparators() const = 0; +virtual size_t getFailedRecordCount(Outcome&) const = 0; +virtual Result getResult(Outcome&) const = 0; +``` + +## Usage Example + +```cpp +class KinesisLogForwarder + : public AwsLogForwarder< + Aws::Kinesis::Model::PutRecordsRequestEntry, + Aws::Kinesis::KinesisClient, + Aws::Kinesis::Model::PutRecordsOutcome, + Aws::Kinesis::Model::PutRecordsResultEntry> { + + Status internalSetup() override { /* configure stream */ } + + Outcome internalSend(const Batch& batch) override { + Aws::Kinesis::Model::PutRecordsRequest req; + req.SetRecords(batch); + return client_->PutRecords(req); + } + + void initializeRecord(Record& record, + Aws::Utils::ByteBuffer& buffer) const override { + record.SetData(buffer); + record.SetPartitionKey("osquery"); + } + + size_t getMaxBytesPerRecord() const override { return 1024 * 1024; } + size_t getMaxRecordsPerBatch() const override { return 500; } + size_t getMaxBytesPerBatch() const override { return 5 * 1024 * 1024; } + size_t getMaxRetryCount() const override { return 3; } + size_t getInitialRetryDelay() const override { return 1000; } + bool appendNewlineSeparators() const override { return false; } + size_t getFailedRecordCount(Outcome& o) const override { /* ... */ } + Result getResult(Outcome& o) const override { /* ... */ } +}; +``` + +> **Retry behavior:** `sendBatch()` applies linear backoff — delay per retry is `base_delay + (retry_index × 1000ms)`. On shutdown (`interrupted()`), the send is aborted cleanly and the batch is preserved for the next osquery startup. \ No newline at end of file diff --git a/plugins/logger/.buffered.md b/plugins/logger/.buffered.md new file mode 100644 index 00000000000..b43f2776c88 --- /dev/null +++ b/plugins/logger/.buffered.md @@ -0,0 +1,56 @@ + +Defines the `BufferedLogForwarder` base class and `iterate` utility for osquery's buffered log forwarding system, providing reliable database-backed buffering of status and result logs with periodic flushing, backoff support, and utilization-aware iteration. + +## Key Components + +### `iterate()` + +A free function that iterates a `std::vector`, applying a predicate to each element. Automatically yields the thread (`20ms` sleep) every 100 iterations to prevent CPU thrash. + +### `BufferedLogForwarder` + +An abstract `InternalRunnable` subclass managing the full lifecycle of buffered log forwarding: + +| Member | Description | +|---|---| +| `start()` | Runnable entry point; waits and flushes on schedule | +| `setUp()` | Initializes buffer count — **must** be called by subclasses | +| `logString()` | Writes a result string to the backing store | +| `logStatus()` | Decorates and writes status lines to the backing store | +| `send()` | **Pure virtual** — subclasses implement actual log delivery | +| `check()` | Scans buffered logs, sorts by type, forwards via `send()`, then purges | +| `purge()` | Drops oldest entries when buffer exceeds `buffered_log_max` | +| `backoffTick()` | Reduces exponential backoff period on each successful send | + +**Key constants:** + +- `kLogPeriod` — Default flush interval (seconds) +- `kMaxLogLines` — Maximum lines flushed per `check()` cycle + +## Usage Example + +```cpp +class MyLogForwarder : public BufferedLogForwarder { + public: + MyLogForwarder() + : BufferedLogForwarder("my_service", "mylogger", + std::chrono::seconds(30), 500) {} + + // Must call base setUp() + Status setUp() override { + return BufferedLogForwarder::setUp(); + } + + protected: + // Implement actual delivery (e.g., HTTP, file, socket) + Status send(std::vector& log_data, + const std::string& log_type) override { + for (auto& line : log_data) { + sendToRemote(log_type, line); + } + return Status::success(); + } +}; +``` + +> **Note:** Subclasses overriding `setUp()` **must** call `BufferedLogForwarder::setUp()` to correctly initialize the internal buffer count. \ No newline at end of file diff --git a/plugins/logger/.filesystem_logger.md b/plugins/logger/.filesystem_logger.md new file mode 100644 index 00000000000..8ef7b1db21c --- /dev/null +++ b/plugins/logger/.filesystem_logger.md @@ -0,0 +1,37 @@ + +A logger plugin that writes osquery query results, snapshots, and status logs to the local filesystem using separate file paths for each log type. + +## Key Components + +| Component | Description | +|---|---| +| `FilesystemLoggerPlugin` | Main logger plugin class extending `LoggerPlugin` | +| `setUp()` | Initializes filesystem paths and prepares log destinations | +| `logString()` | Writes differential query results to a dedicated results file | +| `logSnapshot()` | Writes snapshot query results to a dedicated snapshot file | +| `logStatus()` | Forwards status/health logs to Glog | +| `init()` | Post-startup initialization; may return errors to allow Glog direct file writes | +| `logStringToFile()` | Internal helper that performs the actual file write operation | +| `pimpl_` | Pointer-to-implementation (PIMPL) pattern for private state encapsulation | + +## Usage Example + +```c +// Plugin is auto-registered via the REGISTER macro at load time: +// REGISTER(FilesystemLoggerPlugin, "logger", "filesystem"); + +// Configure via osquery flags (e.g., osquery.flags or CLI): +// --logger_plugin=filesystem +// --logger_path=/var/log/osquery + +// The plugin then writes to separate files: +// /var/log/osquery/osqueryd.results.log <- logString() +// /var/log/osquery/osqueryd.snapshots.log <- logSnapshot() +// /var/log/osquery/osqueryd.INFO <- logStatus() via Glog +``` + +## Notes + +- Registered globally under the `"logger"` registry with the key `"filesystem"` — no manual instantiation required. +- Uses the **PIMPL idiom** (`struct impl`) to hide implementation details and reduce compile-time dependencies. +- Unique among osquery loggers in that `init()` can intentionally return an error, delegating status log file management directly to Glog. \ No newline at end of file diff --git a/plugins/logger/.generated_wel.md b/plugins/logger/.generated_wel.md new file mode 100644 index 00000000000..12e93c47bd2 --- /dev/null +++ b/plugins/logger/.generated_wel.md @@ -0,0 +1,35 @@ + +Auto-generated Windows Message Compiler header defining the ETW (Event Tracing for Windows) provider, channel, and event descriptors used by osquery's Windows Event Log integration. + +## Key Components + +| Symbol | Type | Description | +|---|---|---| +| `OsqueryWindowsEventLogProvider` | `GUID` | ETW provider GUID identifying the osquery event source | +| `OsqueryWindowsEventLogChannel` | `#define` | Channel ID (`0x10`) for routing events | +| `_opcode_message` | `#define` | Opcode value (`0xa`) for message events | +| `WindowsEventLogMessage` | `#define` | Task ID (`0x1`) assigned to log message events | +| `_keyword_*_message` | `#define` | Bitmask keywords for filtering by severity (info, warning, error, fatal, debug) | +| `DebugMessage` / `InfoMessage` / `WarningMessage` / `ErrorMessage` / `FatalMessage` | `EVENT_DESCRIPTOR` | Structured descriptors for each severity-level event | +| `MSG_osquery_event_1..5_message` | `#define` | Message resource IDs for the 5 registered event templates | + +## Usage Example + +```c +#include "generated_wel.h" + +// Register the ETW provider handle (done once at startup) +REGHANDLE hProvider = 0; +EventRegister(&OsqueryWindowsEventLogProvider, NULL, NULL, &hProvider); + +// Write a warning-level event to the Windows Event Log +EventWrite(hProvider, &WarningMessage, 0, NULL); + +// Write an error-level event +EventWrite(hProvider, &ErrorMessage, 0, NULL); + +// Unregister on shutdown +EventUnregister(hProvider); +``` + +> **Note:** This file is machine-generated by the Windows Message Compiler (`mc.exe`) and should not be edited manually. Modifications belong in the source `.man` or `.mc` manifest file that produces this header. \ No newline at end of file diff --git a/plugins/logger/.kafka_producer.md b/plugins/logger/.kafka_producer.md new file mode 100644 index 00000000000..79718d53add --- /dev/null +++ b/plugins/logger/.kafka_producer.md @@ -0,0 +1,40 @@ + +Declares the `KafkaProducerPlugin` class, which integrates osquery's logging pipeline with Apache Kafka by implementing both `LoggerPlugin` and `InternalRunnable` interfaces via librdkafka. + +## Key Components + +| Symbol | Type | Description | +|--------|------|-------------| +| `kKafkaBaseTopic` | `extern const std::string` | Default Kafka topic used when a payload's `name` field has no configured mapping | +| `getMsgName()` | Free function | Extracts the `"name"` field from a JSON log payload string | +| `KafkaProducerPlugin` | Class | Core plugin — produces log messages to Kafka brokers, manages topic routing, and runs a background poll loop | +| `logString()` | Method | Serializes a log string and produces it to the appropriate Kafka topic | +| `init()` | Method | Configures librdkafka producer, registers the OS hostname as `client.id` | +| `start()` / `stop()` | Methods | `InternalRunnable` lifecycle — poll loop entry point and graceful flush/shutdown | +| `publishMsg()` | Protected virtual | Low-level publish to a specific `rd_kafka_topic_t*` | +| `flushMessages()` | Protected virtual | Mutex-guarded `rd_kafka_flush` with a 3-second timeout | +| `pollKafka()` | Protected virtual | Mutex-guarded `rd_kafka_poll` to drive delivery callbacks | +| `queryToTopics_` | `std::map` | Routes query names to their corresponding Kafka topic handles | + +## Usage Example + +```cpp +// Registration via osquery plugin system (typical CMake/registry wiring) +REGISTER(KafkaProducerPlugin, "logger", "kafka_producer"); + +// Retrieve and initialize the plugin (osquery runtime handles this internally) +auto plugin = std::make_shared(); +plugin->init("kafka_producer", {}); + +// Log a JSON payload — topic is resolved from the payload's "name" field; +// falls back to kKafkaBaseTopic if no matching topic is configured. +Status s = plugin->logString(R"({"name":"pack_query","result":"..."})"); +if (!s.ok()) { + LOG(ERROR) << "Kafka publish failed: " << s.getMessage(); +} + +// Background dispatcher thread calls start(); shutdown triggers stop() +// which flushes up to 3 seconds of buffered messages before exit. +``` + +> **Note:** `KafkaProducerPlugin` is non-copyable by design. Topic-to-query routing is configured via osquery flags consumed inside `configureTopics()` at `init()` time. \ No newline at end of file diff --git a/plugins/logger/.logrotate.md b/plugins/logger/.logrotate.md new file mode 100644 index 00000000000..187172d5e4f --- /dev/null +++ b/plugins/logger/.logrotate.md @@ -0,0 +1,48 @@ + +Header defining the `LogRotate` class, which manages log file rotation with compression support within the osquery framework. + +## Key Components + +### `LogRotate` Class + +| Member | Type | Description | +|--------|------|-------------| +| `kUncompressedCount` | `static const size_t` | Number of rotated log files kept uncompressed (default: `1`) | +| `LogRotate(path)` | Constructor | Initializes rotator with the target log file path | +| `shouldRotate()` | Public method | Returns `true` if the current file has exceeded size limits | +| `rotate(max_files)` | Public method | Performs rotation, keeping at most `max_files` rotated copies | + +### Protected Virtual Methods (testable seams) + +| Method | Description | +|--------|-------------| +| `fileSize(filepath)` | Returns file size in bytes | +| `pathExists(path)` | Checks if a path exists | +| `removeFile(path)` | Deletes a file | +| `moveFile(source, dest)` | Renames/moves a file | +| `compressFile(source, dest)` | Compresses a rotated log file | +| `getRotateSize()` | Returns the configured size threshold for rotation | + +### Private Helpers + +- `getRotateFile(offset)` — Generates consistent rotated file names by index +- `moveFiles(moves, max_files)` — Shifts a sequence of rotated files after a new rotation + +## Usage Example + +```cpp +#include + +// Create a rotator for an application log +osquery::LogRotate rotator("/var/log/osquery/osquery.log"); + +// Check if rotation is needed, then rotate keeping 5 old files +if (rotator.shouldRotate()) { + auto status = rotator.rotate(5); + if (!status.ok()) { + LOG(ERROR) << "Rotation failed: " << status.getMessage(); + } +} +``` + +> **Note:** The protected virtual interface allows subclassing in unit tests to inject mock filesystem operations without touching real files. \ No newline at end of file diff --git a/plugins/logger/.stdout.md b/plugins/logger/.stdout.md new file mode 100644 index 00000000000..428dc446142 --- /dev/null +++ b/plugins/logger/.stdout.md @@ -0,0 +1,40 @@ + +Defines the `StdoutLoggerPlugin` class, an osquery logger plugin that outputs log messages directly to standard output (stdout). + +## Key Components + +### `StdoutLoggerPlugin` +A concrete implementation of `LoggerPlugin` that writes log output to stdout. + +| Member | Type | Description | +|---|---|---| +| `usesLogStatus()` | `bool` | Returns `true`, enabling status log handling | +| `logString()` | `Status` | Writes a plain string message to stdout | +| `init()` | `void` | Initializes the plugin with a name and buffered startup log lines | +| `logStatus()` | `Status` | Writes a batch of structured `StatusLogLine` entries to stdout | + +### Plugin Registration +```c +REGISTER(StdoutLoggerPlugin, "logger", "stdout"); +``` +Registers the plugin under the `"logger"` registry with the key `"stdout"`, making it selectable via osquery configuration. + +## Usage Example + +Enable the stdout logger in osquery configuration or via the `--logger_plugin` flag: + +```bash +osqueryi --logger_plugin=stdout +``` + +Or in `osquery.conf`: + +```json +{ + "options": { + "logger_plugin": "stdout" + } +} +``` + +Log output will be streamed directly to stdout, useful for containerized environments or debugging where log forwarding is handled externally (e.g., Docker log drivers, systemd journal capture). \ No newline at end of file diff --git a/plugins/logger/.syslog_logger.md b/plugins/logger/.syslog_logger.md new file mode 100644 index 00000000000..9ccfb0a9dfd --- /dev/null +++ b/plugins/logger/.syslog_logger.md @@ -0,0 +1,40 @@ + +Header defining the `SyslogLoggerPlugin` class, which implements osquery's logger plugin interface to route log output to the system syslog facility. + +## Key Components + +### `SyslogLoggerPlugin` +Extends `LoggerPlugin` to provide syslog-based logging for osquery. + +| Method | Description | +|---|---| +| `usesLogStatus()` | Returns `true`, enabling status log routing through this plugin | +| `logString(const std::string& s)` | Writes a single log string entry to syslog | +| `init(const std::string& name, const std::vector& log)` | Initializes the plugin and flushes any buffered startup logs | +| `logStatus(const std::vector& log)` | Writes a batch of status log lines to syslog | + +### Plugin Registration +```c +REGISTER(SyslogLoggerPlugin, "logger", "syslog"); +``` +Registers the plugin under the `"logger"` category with the key `"syslog"`, making it selectable via osquery configuration. + +## Usage Example + +Enable the syslog logger by setting the `logger_plugin` flag in your osquery configuration: + +```bash +osqueryd --logger_plugin=syslog +``` + +Or in a config file: + +```json +{ + "options": { + "logger_plugin": "syslog" + } +} +``` + +Once active, osquery query results and status messages are forwarded to the system syslog daemon (typically viewable via `journalctl` or `/var/log/syslog`), using the standard `` interface for log level mapping. \ No newline at end of file diff --git a/plugins/logger/.tls_logger.md b/plugins/logger/.tls_logger.md new file mode 100644 index 00000000000..814e7188e70 --- /dev/null +++ b/plugins/logger/.tls_logger.md @@ -0,0 +1,51 @@ + +TLS logger plugin and forwarder declarations for buffering and transmitting osquery logs to a remote TLS endpoint via a background dispatcher thread. + +## Key Components + +### `TLSLogForwarder` +Extends `BufferedLogForwarder` to flush database-buffered result and status logs to a TLS endpoint. Runs as a `Dispatcher` service when an enrollment key is present. + +| Member | Description | +|---|---| +| `configuration_mutex` | Guards concurrent reads/writes to configuration state | +| `configuration_updated` | Flag signaling pending config changes | +| `updated_uri`, `updated_log_period`, etc. | Staged config values applied on next `applyNewConfiguration()` | +| `send()` | Transmits a batch of log strings to the TLS endpoint | +| `applyNewConfiguration()` | Applies staged configuration changes atomically | +| `uri_` | Active endpoint URI | + +### `TLSLoggerPlugin` +Implements `LoggerPlugin` to receive, buffer, and forward osquery logs over TLS. + +| Method | Description | +|---|---| +| `init()` | Buffers early status logs captured before logger initialization | +| `setUp()` | Validates node key and starts the forwarder dispatcher thread | +| `configure()` | Reacts to runtime configuration updates | +| `logString()` | Handles snapshot and event result logs | +| `logStatus()` | Handles `ERROR`/`WARNING`/`INFO` status logs | +| `usesLogStatus()` | Returns `true` — plugin consumes status log lines | + +## Usage Example + +```cpp +// Plugin is registered and managed by the osquery plugin system. +// Direct instantiation is typically handled by the registry: + +auto plugin = std::make_shared(); + +// Called by the framework at startup with early-buffered status logs: +plugin->init("tls", early_status_logs); + +// Called after node enrollment to start the forwarder thread: +Status s = plugin->setUp(); +if (!s.ok()) { + LOG(ERROR) << "TLS logger setup failed: " << s.getMessage(); +} + +// React to a config change (e.g., updated TLS endpoint URI): +plugin->configure(); +``` + +> **Note:** `TLSLogForwarder` configuration fields (`updated_uri`, `updated_log_period`, etc.) should be written under `configuration_mutex` before setting `configuration_updated = true` to safely stage changes for the next flush cycle. \ No newline at end of file diff --git a/plugins/logger/.windows_event_log.md b/plugins/logger/.windows_event_log.md new file mode 100644 index 00000000000..ab21a48c8ad --- /dev/null +++ b/plugins/logger/.windows_event_log.md @@ -0,0 +1,48 @@ + +Windows Event Log logger plugin interface for osquery, providing integration with the Windows Event Log system via ETW (Event Tracing for Windows). + +## Key Components + +### `WindowsEventLoggerPlugin` +Extends `LoggerPlugin` to route osquery logs to the Windows Event Log subsystem. + +| Member | Description | +|---|---| +| `usesLogStatus()` | Returns `true` — enables status log routing | +| `logString()` | Writes a plain string message to the event log | +| `init()` | Initializes the plugin with a name and initial log lines | +| `logStatus()` | Writes structured `StatusLogLine` entries to the event log | +| `acquireHandle()` | Registers the process as a Windows Event Log provider | +| `releaseHandle()` | Releases the ETW `REGHANDLE` provider registration | +| `emitLogRecord()` | Emits a single log record with severity, source file, and line number | + +**Private member:** `registration_handle_` — the `REGHANDLE` used for all ETW provider operations. + +## Usage Example + +```cpp +#include "windows_event_log.h" + +osquery::WindowsEventLoggerPlugin plugin; + +// Acquire an ETW provider handle +REGHANDLE handle; +auto status = osquery::WindowsEventLoggerPlugin::acquireHandle(handle); +if (!status.ok()) { + // handle error +} + +// Emit a log record manually +osquery::WindowsEventLoggerPlugin::emitLogRecord( + handle, + "osquery service started", + osquery::O_INFO, + __FILE__, + __LINE__ +); + +// Release the handle when done +osquery::WindowsEventLoggerPlugin::releaseHandle(handle); +``` + +> **Note:** This header is Windows-only and depends on `evntprov.h` (Windows SDK). The static `acquireHandle` / `releaseHandle` / `emitLogRecord` methods can be used independently of the plugin lifecycle for direct ETW emission. \ No newline at end of file diff --git a/plugins/logger/tests/.aws_kinesis_logger_tests.md b/plugins/logger/tests/.aws_kinesis_logger_tests.md new file mode 100644 index 00000000000..f59a4553fbe --- /dev/null +++ b/plugins/logger/tests/.aws_kinesis_logger_tests.md @@ -0,0 +1,42 @@ + +Unit test file for the AWS Kinesis logger plugin, validating batch splitting, record size limits, and JSON formatting behavior of the `AwsLogForwarder` abstraction. + +## Key Components + +### `AwsLoggerTests` +Base test fixture that initializes the osquery platform, registry, and database plugin before each test. + +### `DummyOutcome` +A minimal stub of `Aws::Kinesis::Model::PutRecordsOutcome` that always reports success, used to simulate Kinesis API responses without a live AWS connection. + +### `DummyLogForwarder` +A concrete test double implementing `AwsLogForwarder` with the following fixed constraints: + +| Parameter | Value | +|---|---| +| Max bytes per record | 80 bytes | +| Max records per batch | 3 | +| Max bytes per batch | 128 bytes | +| Max retry count | 1 | +| Newline separators | enabled | + +Captures all emitted batches into `emitted_batch_list_` for assertion. + +### `test_send` (Test Case) +The primary test covering: +- **Normal batching** — 3 small records fit into one batch +- **Discard behavior** — oversized records and non-JSON strings are silently dropped +- **Batch splitting** — records that collectively exceed `getMaxBytesPerBatch()` are split across multiple batches +- **Output format** — verifies `log_type` field injection and newline appending + +## Usage Example + +```cpp +// Run the test suite +./aws_kinesis_logger_tests + +// Expected batch structure after test_send: +// Batch 0: 3 records ({"batch1":"1"}, {"batch1":"2"}, {"batch1":"3"}) +// Batch 1: 1 record (batch2/item1, split due to byte limit) +// Batch 2: 2 records (batch2/item2, batch3/item1) +``` \ No newline at end of file diff --git a/plugins/logger/tests/.buffered_tests.md b/plugins/logger/tests/.buffered_tests.md new file mode 100644 index 00000000000..9e857f5809d --- /dev/null +++ b/plugins/logger/tests/.buffered_tests.md @@ -0,0 +1,36 @@ + +Unit test file for the `BufferedLogForwarder` class, validating buffered log forwarding behavior including retry logic, batching, purging, and asynchronous dispatch in osquery. + +## Key Components + +- **`MatchesStatus` (GMock Matcher)** — Custom matcher that deserializes a JSON string and compares it against an expected `StatusLogLine` by severity, filename, line, and message. +- **`BufferedLogForwarderTests`** — Base test fixture that initializes the platform, registry, and database plugin, and provides a `makeStatusLogLine` helper factory. +- **`MockBufferedLogForwarder`** — GMock-based mock subclass of `BufferedLogForwarder` with a mocked `send()` method and controlled `interrupted()` behavior for deterministic test execution. +- **`FakeLogForwarder`** — Concrete stub implementation that captures all sent log lines into an in-memory `logs` vector for assertion in decoration tests. + +## Test Cases + +| Test | Description | +|---|---| +| `test_index` | Validates result/status index generation format and classification helpers | +| `test_basic` | Verifies logs are sent once on `check()` and not re-sent on subsequent calls | +| `test_retry` | Ensures failed sends are retried on the next `check()` call | +| `test_multiple` | Confirms independent buffering and retry across two forwarder instances | +| `test_async` | Validates end-to-end async dispatch via the `Dispatcher` service runner | +| `test_split` | Checks that `max_log_lines` batching limit is respected per send call | +| `test_purge` | Tests the `purge()` function trims old entries while preserving recent logs | + +## Usage Example + +```cpp +// Typical pattern used throughout the test suite +StrictMock runner; +runner.logString("foo"); + +EXPECT_CALL(runner, send(ElementsAre("foo"), "result")) + .WillOnce(Return(Status(0))); +runner.check(); // Triggers send + +// Second check — no send expected since buffer is now empty +runner.check(); +``` \ No newline at end of file diff --git a/plugins/logger/tests/.filesystem_logger_tests.md b/plugins/logger/tests/.filesystem_logger_tests.md new file mode 100644 index 00000000000..0e295f9769a --- /dev/null +++ b/plugins/logger/tests/.filesystem_logger_tests.md @@ -0,0 +1,41 @@ + +Unit tests for the `FilesystemLoggerPlugin` in osquery, validating log string output, status log line counting, and snapshot query serialization to disk. + +## Key Components + +### `FilesystemLoggerTests` (Test Fixture) +- Inherits `testing::Test` +- **`SetUp()`** — Creates a temp directory, sets `FLAGS_logger_path`, activates the `filesystem` logger plugin, and asserts the results log file exists and is empty +- **`results_path_`** — Stores the expected path to `osqueryd.results.log` + +### `FilesystemTestLoggerPlugin` +A minimal stub `LoggerPlugin` used in `test_log_status` to verify multi-logger activation. Reports `usesLogStatus() = true` but discards all log data. + +### Test Cases + +| Test | What it validates | +|---|---| +| `test_log_string` | `logString()` writes a JSON line followed by `\n` to the results log | +| `test_log_status` | Status log line counts increment correctly with `LOG(WARNING)` calls; skipped on Windows | +| `test_log_snapshot` | `logSnapshotQuery()` writes correctly serialized JSON snapshot entries, newline-delimited | + +## Usage Example + +```bash +# Run only filesystem logger tests via the osquery test binary +osquery_tests --gtest_filter="FilesystemLoggerTests.*" +``` + +```cpp +// Core test pattern used throughout the fixture +std::string content; +EXPECT_TRUE(readFile(results_path_, content)); +EXPECT_EQ(content, "{\"json\": true}\n"); + +// Multi-logger activation pattern +auto& rf = RegistryFactory::get(); +rf.registry("logger")->add("filesystem_test", filesystem_test); +EXPECT_TRUE(rf.setActive("logger", "filesystem,filesystem_test").ok()); +``` + +> **Note:** `test_log_status` is skipped on Windows via `isPlatform(PlatformType::TYPE_WINDOWS)` due to non-deterministic status log behavior on that platform. \ No newline at end of file diff --git a/plugins/logger/tests/.kafka_producer_tests.md b/plugins/logger/tests/.kafka_producer_tests.md new file mode 100644 index 00000000000..baec7fe3caa --- /dev/null +++ b/plugins/logger/tests/.kafka_producer_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the `KafkaProducerPlugin` logger plugin in osquery, validating message name extraction from JSON payloads and message routing/publishing behavior. + +## Key Components + +### `MockKafkaProducerPlugin` +A test double extending `KafkaProducerPlugin` that overrides core Kafka operations to enable in-memory inspection without a real broker: + +- `publishMsg()` — captures published payloads into `publishedMsgs_` (keyed by topic pointer) +- `flushMessages()` — increments `timesFlushed_` counter +- `pollKafka()` — increments `timesPolled_` counter +- `setQueryToTopics()` — injects a mock topic-to-query mapping +- `publishedMsgs_` — map of `rd_kafka_topic_t*` → list of published strings + +### `KafkaProducerPluginTest` +GTest fixture that initializes the osquery platform, registry, and database plugin before each test case. + +### Test Cases + +| Test | Description | +|---|---| +| `getMsgName_tests` | Verifies `getMsgName()` correctly extracts the query name from various JSON formats: simple event, snapshot with/without top-level `"name"`, batch diff results with/without per-row `"name"` columns, and event-mode results | +| `logString_single_topic_happy_path` | Validates that a logged string is routed to the correct Kafka topic (truncated in source) | + +## Usage Example + +```cpp +// Instantiate and configure the mock +MockKafkaProducerPlugin mkpp; +rd_kafka_topic_t* fakeTopic = /* ... */; +mkpp.setQueryToTopics({{"my_query", fakeTopic}}); + +// Verify published messages after a logString call +EXPECT_EQ(mkpp.publishedMsgs_[fakeTopic].size(), 1u); +EXPECT_EQ(mkpp.timesFlushed_.load(), 1); + +// Test getMsgName extraction +EXPECT_EQ(getMsgName("{\"name\": \"foo\"}"), "foo"); +``` \ No newline at end of file diff --git a/plugins/logger/tests/.logrotate_tests.md b/plugins/logger/tests/.logrotate_tests.md new file mode 100644 index 00000000000..fc3c0cab4ad --- /dev/null +++ b/plugins/logger/tests/.logrotate_tests.md @@ -0,0 +1,39 @@ + +Unit tests for the `LogRotate` class, validating log file rotation logic including size-based triggering, file shifting, compression, and overflow/max-file-count enforcement using an in-memory fake filesystem. + +## Key Components + +### `FakeLogRotate` +A test double extending `LogRotate` that overrides filesystem operations with an in-memory `std::map` (path → size). Overrides include: +- `fileSize()` — looks up size from the in-memory map +- `pathExists()` — checks map membership +- `removeFile()` / `moveFile()` — mutates the map +- `compressFile()` — simulates compression by halving file size and appending `.zst` +- `insertFile()` / `setRotateSize()` — test setup helpers + +### Test Cases + +| Test | Description | +|---|---| +| `test_should_rotate` | Verifies `shouldRotate()` returns `false` below threshold and `true` at or above it | +| `test_rotate` | Confirms the active log is renamed to `.1` on first rotation | +| `test_rotate_missing_file` | Validates multi-step rotation with gap-skipping: older files get compressed (`.zst`), sizes shift correctly, and a failed rotate returns a non-OK `Status` | +| `test_rotate_overflow` | Ensures oldest files are deleted when `max_files` is reached and that reducing the max mid-run evicts previously tracked rotations | + +## Usage Example + +```cpp +// Construct a fake rotator pointed at a virtual log path +FakeLogRotate rotate("/doesnotexist/logdir"); + +// Seed the in-memory filesystem +rotate.insertFile("/doesnotexist/logdir", 100); +rotate.setRotateSize(100); + +// Trigger rotation with a max of 5 retained files +auto status = rotate.rotate(5); + +// Verify rotated file exists and active log is cleared +ASSERT_TRUE(rotate.pathExists("/doesnotexist/logdir.1")); +EXPECT_FALSE(rotate.pathExists("/doesnotexist/logdir")); +``` \ No newline at end of file diff --git a/plugins/logger/tests/.syslog_logger_tests.md b/plugins/logger/tests/.syslog_logger_tests.md new file mode 100644 index 00000000000..a04864fe90c --- /dev/null +++ b/plugins/logger/tests/.syslog_logger_tests.md @@ -0,0 +1,40 @@ + +Unit tests for the osquery syslog logger plugin, verifying that the syslog logger is properly registered and can be activated within the osquery plugin registry. + +## Key Components + +- **`SyslogLoggerTests`** — GTest fixture class that initializes the platform and plugin registry before each test via `SetUp()` +- **`test_syslog`** — Single test case validating syslog logger registration and lifecycle + +## Test Coverage + +| Test | Assertion | Purpose | +|---|---|---| +| `test_syslog` | `exists("logger", "syslog")` | Confirms syslog plugin is registered | +| `test_syslog` | `setActive("logger", "syslog")` | Confirms syslog can be set as active logger | +| `test_syslog` | `plugin(...)->setUp()` | Confirms syslog plugin initializes successfully | + +## Usage Example + +```cpp +// Run only syslog logger tests +// From the osquery build directory: +// ./osquery_tests --gtest_filter=SyslogLoggerTests.* + +TEST_F(SyslogLoggerTests, test_syslog) { + // Capture current active logger to restore after test + auto active = Registry::get().getActive("logger"); + + // Verify syslog plugin exists in registry + EXPECT_TRUE(Registry::get().exists("logger", "syslog")); + + // Activate and initialize the syslog logger plugin + EXPECT_TRUE(Registry::get().setActive("logger", "syslog")); + EXPECT_TRUE(Registry::get().plugin("logger", "syslog")->setUp()); + + // Restore original logger + Registry::get().setActive("logger", active); +} +``` + +> **Note:** The test preserves the previously active logger and restores it after the syslog assertions, ensuring no side effects on other tests in the suite. \ No newline at end of file diff --git a/plugins/logger/tests/.tls_logger_tests.md b/plugins/logger/tests/.tls_logger_tests.md new file mode 100644 index 00000000000..5fd8fdea9c5 --- /dev/null +++ b/plugins/logger/tests/.tls_logger_tests.md @@ -0,0 +1,51 @@ + +Unit tests for the `TLSLogForwarder` plugin, verifying that log entries (strings and status messages) are correctly persisted to the database and transmitted to a TLS-enabled remote server. + +## Key Components + +| Component | Description | +|---|---| +| `TLSLoggerTests` | GTest fixture that initializes the platform, registry, and test database before each test | +| `runCheck()` | Public helper that manually triggers `TLSLogForwarder::check()` to flush buffered logs | +| `test_database` | Verifies that logged strings and status lines are stored in the `kLogs` database with correct content | +| `test_send` | Verifies that bulk log entries (20 JSON strings) are successfully forwarded to a running TLS server | + +## Usage Example + +```cpp +// Instantiate the forwarder and log a JSON string +auto forwarder = std::make_shared(); +std::string expected = "{\"new_json\": true}"; +forwarder->logString(expected); + +// Log a status entry +StatusLogLine status{}; +status.message = "{\"status\": \"bar\"}"; +forwarder->logStatus({status}); + +// Verify entries were persisted to the kLogs database +std::vector indexes; +scanDatabaseKeys(kLogs, indexes); +EXPECT_EQ(2U, indexes.size()); + +// Manually trigger forwarding/flush to the remote TLS server +runner->check(); +``` + +## Test Flow + +```mermaid +sequenceDiagram + participant Test + participant TLSServerRunner + participant TLSLogForwarder + participant Database + + Test->>TLSServerRunner: start() + Test->>TLSLogForwarder: logString() / logStatus() + TLSLogForwarder->>Database: persist to kLogs + Test->>Database: scanDatabaseKeys / verify entries + Test->>TLSLogForwarder: check() (flush) + TLSLogForwarder->>TLSServerRunner: send buffered logs + Test->>TLSServerRunner: stop() +``` \ No newline at end of file diff --git a/plugins/numeric_monitoring/.filesystem.md b/plugins/numeric_monitoring/.filesystem.md new file mode 100644 index 00000000000..80f740c370c --- /dev/null +++ b/plugins/numeric_monitoring/.filesystem.md @@ -0,0 +1,45 @@ + +Filesystem-backed implementation of the `NumericMonitoringPlugin` interface that writes numeric monitoring data points as delimited text lines to a log file. + +## Key Components + +### `NumericMonitoringFilesystemPlugin` +Extends `NumericMonitoringPlugin` to persist monitoring metrics to disk. + +| Member | Description | +|---|---| +| `NumericMonitoringFilesystemPlugin()` | Default constructor using configured log file path | +| `NumericMonitoringFilesystemPlugin(path)` | Constructor with explicit log file path override | +| `call(request, response)` | Handles incoming plugin requests and writes formatted metric lines | +| `setUp()` | Initializes the output file stream; must be called before `call()` | +| `isSetUp()` | Returns whether the plugin has been successfully initialized | +| `formTheLine(line, request)` | Private helper that formats a metric entry using `line_format_` and `separator_` | + +**Private fields:** +- `line_format_` — ordered list of fields to include per line +- `separator_` — single character delimiter between fields +- `log_file_path_` — resolved path to the output log file +- `output_file_stream_` — open file stream for writing +- `output_file_mutex_` — mutex guarding concurrent writes + +## Usage Example + +```cpp +#include + +// Use explicit path (e.g., in tests or custom setups) +osquery::NumericMonitoringFilesystemPlugin plugin( + boost::filesystem::path("/var/log/osquery/numeric_monitoring.log")); + +auto status = plugin.setUp(); +if (!plugin.isSetUp()) { + // handle initialization failure +} + +// Plugin is invoked via the registry; direct call example: +osquery::PluginRequest req = {{"path", "cpu.usage"}, {"value", "42.5"}}; +osquery::PluginResponse resp; +plugin.call(req, resp); +``` + +> **Note:** `setUp()` must succeed before invoking `call()`. The plugin is thread-safe for concurrent metric writes via `output_file_mutex_`. \ No newline at end of file diff --git a/plugins/numeric_monitoring/tests/.filesystem.md b/plugins/numeric_monitoring/tests/.filesystem.md new file mode 100644 index 00000000000..78574ef58d4 --- /dev/null +++ b/plugins/numeric_monitoring/tests/.filesystem.md @@ -0,0 +1,47 @@ + +Unit test file for the `NumericMonitoringFilesystemPlugin`, validating the complete lifecycle and data serialization behavior of the filesystem-based numeric monitoring plugin. + +## Key Components + +- **`NumericMonitoringFilesystemPluginTests`** — GTest fixture that saves and restores `FLAGS_numeric_monitoring_filesystem_path` around each test to prevent flag pollution. +- **`simple_workflow`** — End-to-end test covering plugin setup, record writing, file creation, and tab-delimited output parsing. + +## Usage Example + +The test validates the following workflow: + +```cpp +// 1. Point the plugin at a temp log file +FLAGS_numeric_monitoring_filesystem_path = log_path.string(); + +// 2. Initialize the plugin +NumericMonitoringFilesystemPlugin plugin{}; +ASSERT_FALSE(plugin.isSetUp()); +ASSERT_TRUE(plugin.setUp().ok()); + +// 3. Submit a monitoring record +PluginRequest request{ + {monitoring::recordKeys().path, path}, + {monitoring::recordKeys().value, std::to_string(1.5)}, + {monitoring::recordKeys().timestamp, std::to_string(1051)}, + {monitoring::recordKeys().sync, "true"}, +}; +PluginResponse response{}; +EXPECT_TRUE(plugin.call(request, response).ok()); + +// 4. Verify tab-delimited output written to disk +auto first_line = split(line, "\t"); +EXPECT_EQ(first_line[0], path); +EXPECT_NEAR(std::stod(first_line[1]), 1.5, 0.00001); +``` + +## What Is Tested + +| Assertion | Purpose | +|---|---| +| `isSetUp()` before/after `setUp()` | Guards against premature use | +| File exists and is non-empty | Confirms disk write occurred | +| Tab-separated field count matches request size | Schema integrity | +| Path field round-trips special characters | Encoding correctness | +| Value parsed within float tolerance | Numeric precision | +| Timestamp and sync flag round-trip | Type serialization | \ No newline at end of file diff --git a/plugins/remote/enroll/.tls_enroll.md b/plugins/remote/enroll/.tls_enroll.md new file mode 100644 index 00000000000..8d8875c6c5a --- /dev/null +++ b/plugins/remote/enroll/.tls_enroll.md @@ -0,0 +1,31 @@ + +TLS enrollment plugin that extends the base `EnrollPlugin` to handle node key acquisition and caching via TLS endpoints. + +## Key Components + +- **`TLSEnrollPlugin`** — Concrete implementation of `EnrollPlugin` for TLS-based enrollment + - `enroll()` — Returns a cached node key if available, otherwise triggers a new key request + - `requestKey(uri, node_key)` — Contacts the configured TLS endpoint to obtain a new enrollment key + - `TLSEnrollTests` — Declared as a friend class to allow unit test access to private members + +## Usage Example + +```c +// Plugin is registered and invoked through the osquery enrollment system. +// Direct instantiation is managed by the plugin registry. + +// Enrollment is triggered automatically when the agent starts: +// 1. enroll() checks for a cached node_key +// 2. If none exists, requestKey() contacts the TLS endpoint +// 3. The returned key is cached for subsequent calls + +// Configured via osquery flags, e.g.: +// --enroll_tls_endpoint=/api/v1/enroll +// --tls_hostname=fleet.example.com +``` + +## Notes + +- Inherits enrollment caching logic from `EnrollPlugin` base class +- `requestKey()` returns a `Status` object — callers should check for success before using the populated `node_key` +- All members are private; interaction is through the plugin registry interface defined in `enroll.h` \ No newline at end of file diff --git a/plugins/remote/enroll/tests/.tls_enroll_tests.md b/plugins/remote/enroll/tests/.tls_enroll_tests.md new file mode 100644 index 00000000000..772d6fae0f7 --- /dev/null +++ b/plugins/remote/enroll/tests/.tls_enroll_tests.md @@ -0,0 +1,48 @@ + +Integration test suite for the TLS enrollment plugin, validating that osquery nodes can successfully enroll with a TLS-based management server and that enrollment requests contain correct host identification and detail payloads. + +## Key Components + +- **`TLSEnrollTests`** — GTest fixture that spins up a local `TLSServerRunner` before each test and tears it down after, ensuring an isolated TLS environment +- **`SetUp()`** — Initializes the platform, registry, database, starts the TLS test server, configures client TLS settings, and clears any existing node key +- **`TearDown()`** — Stops the TLS test server and reverts client config +- **`testReadRequests()`** — Helper that issues a `GET` to `/test_read_requests` via `TLSTransport` + `JSONSerializer` and returns the parsed JSON response tree +- **`test_tls_enroll`** — Core integration test asserting end-to-end enrollment behavior + +## Usage Example + +```cpp +// Run only the TLS enroll tests via gtest filter +// ./osquery_tests --gtest_filter=TLSEnrollTests.* + +TEST_F(TLSEnrollTests, test_tls_enroll) { + // Trigger enrollment against the local test TLS server + auto node_key = getNodeKey("tls"); + ASSERT_FALSE(node_key.empty()); // Node key must be issued + + JSON response; + auto status = testReadRequests(response); + ASSERT_TRUE(status.ok()); + + // Verify enrollment payload fields + auto const& obj = response.doc()[0]; + EXPECT_EQ(obj["command"].GetString(), "enroll"); + EXPECT_EQ(obj["host_identifier"].GetString(), getHostIdentifier()); + + // Confirm host details include osquery_info uuid + auto osquery_info = SQL::selectAllFrom("osquery_info"); + EXPECT_EQ( + obj["host_details"]["osquery_info"]["uuid"].GetString(), + osquery_info[0]["uuid"] + ); +} +``` + +## Test Coverage + +| Assertion | What It Validates | +|---|---| +| `node_key` non-empty | Enrollment returns a valid node key | +| `command == "enroll"` | Correct RPC command sent to server | +| `host_identifier` match | Host identity is accurately reported | +| `host_details.osquery_info.uuid` | Host details include live `osquery_info` data | \ No newline at end of file diff --git a/tests/.test_util.md b/tests/.test_util.md new file mode 100644 index 00000000000..f5eb91f435b --- /dev/null +++ b/tests/.test_util.md @@ -0,0 +1,48 @@ + +Utility header providing shared test infrastructure for osquery unit tests and benchmarks, including process exit codes, path variables, and helper functions used across the test suite. + +## Key Components + +### Constants & Exit Codes + +| Constant | Value | Purpose | +|---|---|---| +| `EXTENSION_SUCCESS_CODE` | `0x45` | Expected exit code for successful extension child process | +| `WORKER_SUCCESS_CODE` | `0x57` | Expected exit code for successful worker child process | +| `ERROR_COMPARE_ARGUMENT` | `-1` | Child process argument comparison failure | +| `ERROR_LAUNCHER_PROCESS` | `-2` | Launcher process error | +| `ERROR_LAUNCHER_MISMATCH` | `-5` | Launcher identity mismatch | + +### Global Path Variables + +- `kTestDataPath` — resolved path to test data (source or build directory) +- `kTestWorkingDirectory` — working directory for config, logs, and caching during tests +- `kFakeDirectory` — root of the fake filesystem tree (`fstree`) used in iterator tests +- `kProcessTestExecPath` — path to the currently executing test binary + +### Functions + +- `initTesting()` — initializes osquery runtime for tests and benchmarks +- `shutdownTesting()` — tears down the osquery runtime after tests +- `getOsqueryScheduledQuery()` — returns a sample `ScheduledQuery` for test use +- `genRows(EventSubscriberPlugin*)` — materializes all rows from a generator-based event subscriber table +- `initUsersAndGroupsServices()` / `deinitUsersAndGroupsServices()` *(Windows only)* — manages Windows user/group service lifecycle in tests + +## Usage Example + +```c +#include "test_util.h" + +int main() { + osquery::initTesting(); + + // Access resolved test data path + std::string dataPath = osquery::kTestDataPath; + + // Generate rows from an event subscriber + auto rows = osquery::genRows(mySubscriberPlugin); + + osquery::shutdownTesting(); + return 0; +} +``` \ No newline at end of file diff --git a/tests/integration/tables/.account_policy_data.md b/tests/integration/tables/.account_policy_data.md new file mode 100644 index 00000000000..7c86a74077f --- /dev/null +++ b/tests/integration/tables/.account_policy_data.md @@ -0,0 +1,35 @@ + +Integration test file for the `account_policy_data` osquery table on macOS (Darwin), performing a basic sanity check to verify the table can be queried without errors. + +## Key Components + +- **`accountPolicyData`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a `SELECT *` query against the `account_policy_data` table to confirm basic queryability. + +## Usage Example + +```cpp +// Run the sanity test against the account_policy_data table +TEST_F(accountPolicyData, test_sanity) { + auto const data = execute_query("select * from account_policy_data"); + + // Optional: enable row count assertions + // ASSERT_GE(data.size(), 0ul); + + // Optional: enable column type validation + // ValidationMap row_map = { + // {"uid", IntType}, + // {"creation_time", NormalType}, + // {"failed_login_count", IntType}, + // {"failed_login_timestamp", NormalType}, + // {"password_last_set_time", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Targets the Darwin spec at `specs/darwin/account_policy_data.table`. +- Row count assertions and `ValidationMap` checks are scaffolded but currently commented out — enable them to enforce stricter validation of result size and column types (`IntType`, `NormalType`). +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag definitions. \ No newline at end of file diff --git a/tests/integration/tables/.acpi_tables.md b/tests/integration/tables/.acpi_tables.md new file mode 100644 index 00000000000..08455e77ad6 --- /dev/null +++ b/tests/integration/tables/.acpi_tables.md @@ -0,0 +1,24 @@ + +Integration sanity test for the `acpi_tables` osquery table, verifying the table can be queried without errors on POSIX systems. + +## Key Components + +- **`acpiTables`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(acpiTables, test_sanity)`** — Basic integration test that executes a `SELECT * FROM acpi_tables` query to confirm the table is queryable. Row count assertions and schema validation are scaffolded but currently commented out. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test runner: +// ./osquery_integration_tests --gtest_filter=acpiTables.test_sanity + +// To enable full validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"name", NormalType}, + {"size", IntType}, + {"md5", NormalType} +}; +validate_rows(data, row_map); +``` + +> **Note:** The row count assertions (`ASSERT_GE`, `ASSERT_EQ`) and `validate_rows` call are intentionally commented out, leaving only the query execution check active. To extend coverage, uncomment and populate the `ValidationMap` matching the schema defined in `specs/posix/acpi_tables.table`. \ No newline at end of file diff --git a/tests/integration/tables/.ad_config.md b/tests/integration/tables/.ad_config.md new file mode 100644 index 00000000000..1596b7c3834 --- /dev/null +++ b/tests/integration/tables/.ad_config.md @@ -0,0 +1,27 @@ + +Integration test file for the `ad_config` osquery table, providing a sanity check to verify the table can be queried without errors on Darwin (macOS) systems. + +## Key Components + +- **`adConfig`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(adConfig, test_sanity)`** — Basic sanity test that executes a `SELECT * FROM ad_config` query and provides scaffolding for row validation. + +## Usage Example + +```cpp +// Run the integration test (from osquery build directory) +// The test queries the ad_config table and validates results + +auto const data = execute_query("select * from ad_config"); + +// Uncomment and extend validation as needed: +ValidationMap row_map = { + {"name", NormalType}, + {"domain", NormalType}, + {"option", NormalType}, + {"value", NormalType}, +}; +validate_rows(data, row_map); +``` + +> **Note:** The `ad_config` table is Darwin-specific (spec: `specs/darwin/ad_config.table`). Size assertions and row validation are stubbed out and can be enabled as the table matures or test requirements are defined. \ No newline at end of file diff --git a/tests/integration/tables/.alf.md b/tests/integration/tables/.alf.md new file mode 100644 index 00000000000..4f0b11dae72 --- /dev/null +++ b/tests/integration/tables/.alf.md @@ -0,0 +1,48 @@ + +Integration test for the `alf` osquery table, which exposes macOS Application Layer Firewall (ALF) configuration data. Validates that the table returns expected fields with correct types, including version-conditional behavior for macOS 15+. + +## Key Components + +- **`alf` test class** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(alf, test_sanity)`** — Core sanity test that queries the `alf` table and validates the result structure. + +## Usage Example + +```cpp +// Runs automatically via the osquery integration test suite. +// The test performs the following logic: + +// 1. Query the alf table and assert exactly one row is returned +auto const data = execute_query("select * from alf"); +ASSERT_EQ(data.size(), 1ul); + +// 2. Detect macOS version to handle schema differences +const auto& qd = SQL::selectAllFrom("os_version"); +const auto macOS15Plus = qd.front().at("major") >= "15"; + +// 3. Build a ValidationMap with required fields +ValidationMap row_map = { + {"global_state", IntType}, + {"logging_enabled", IntType}, + {"stealth_enabled", IntType}, + {"version", NormalType}, +}; + +// 4. Conditionally validate fields absent/empty on macOS 15+ +if (macOS15Plus) { + row_map["allow_signed_enabled"] = EmptyOk; + row_map["firewall_unload"] = EmptyOk; + row_map["logging_option"] = EmptyOk; +} else { + row_map["allow_signed_enabled"] = IntType; + row_map["firewall_unload"] = IntType; + row_map["logging_option"] = IntType; +} + +validate_rows(data, row_map); +``` + +## Notes + +- **macOS 15+ compatibility** — `allow_signed_enabled`, `firewall_unload`, and `logging_option` are marked `EmptyOk` on macOS 15+, reflecting upstream ALF schema changes. +- Spec file reference: `specs/darwin/alf.table` \ No newline at end of file diff --git a/tests/integration/tables/.alf_exceptions.md b/tests/integration/tables/.alf_exceptions.md new file mode 100644 index 00000000000..406c5a3e093 --- /dev/null +++ b/tests/integration/tables/.alf_exceptions.md @@ -0,0 +1,40 @@ + +Integration test for the `alf_exceptions` osquery table on macOS (Darwin), verifying that the table returns valid data with the expected schema. + +## Key Components + +- **`alfExceptions`** — Test fixture class extending `testing::Test` that initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a `SELECT *` query against the `alf_exceptions` table and validates the results. + +## Usage Example + +```cpp +// Run the sanity test against the alf_exceptions table +TEST_F(alfExceptions, test_sanity) { + auto const data = execute_query("select * from alf_exceptions"); + + // Ensure at least one row is returned + ASSERT_GE(data.size(), 1ul); + + // Define expected column types + ValidationMap row_map = { + {"path", NormalType}, // Path to the excepted application + {"state", IntType}, // ALF exception state (integer) + }; + + validate_rows(data, row_map); +} +``` + +## Validated Schema + +| Column | Type | Description | +|--------|------|-------------| +| `path` | `NormalType` | File path of the application with an ALF exception | +| `state` | `IntType` | Exception state value | + +## Notes + +- Targets **macOS only** — spec defined in `specs/darwin/alf_exceptions.table` +- Requires at least **1 row** to pass (`ASSERT_GE(data.size(), 1ul)`) +- Uses the shared osquery integration test helper (`osquery/tests/integration/tables/helper.h`) \ No newline at end of file diff --git a/tests/integration/tables/.alf_explicit_auths.md b/tests/integration/tables/.alf_explicit_auths.md new file mode 100644 index 00000000000..d35f97f1655 --- /dev/null +++ b/tests/integration/tables/.alf_explicit_auths.md @@ -0,0 +1,33 @@ + +Integration test file for the `alf_explicit_auths` osquery table on Darwin (macOS), verifying that the table returns valid data or handles macOS version-specific behavior correctly. + +## Key Components + +- **`alfExplicitAuths`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(alfExplicitAuths, test_sanity)`** — Sanity check that queries `alf_explicit_auths` and validates results based on the host macOS version. + +## Behavior + +The test applies version-conditional logic: + +- **macOS 15+**: ALF explicit auths are no longer exposed; the test asserts the result set is empty. +- **macOS < 15**: Validates that each returned row contains a `process` field matching `NormalType`. + +## Usage Example + +```cpp +// Runs automatically via the osquery integration test suite. +// To execute manually, build and run the integration test binary targeting Darwin: + +./osquery_integration_tests --gtest_filter="alfExplicitAuths.test_sanity" +``` + +```cpp +// ValidationMap defines expected column types for each row returned: +ValidationMap row_map = { + {"process", NormalType}, +}; +validate_rows(data, row_map); +``` + +> **Note:** This test targets Darwin only. The corresponding table spec is located at `specs/darwin/alf_explicit_auths.table`. macOS 15+ skips row validation entirely since the ALF (Application Layer Firewall) explicit authorizations are no longer surfaced through this table. \ No newline at end of file diff --git a/tests/integration/tables/.alf_services.md b/tests/integration/tables/.alf_services.md new file mode 100644 index 00000000000..ae4f13ec3f8 --- /dev/null +++ b/tests/integration/tables/.alf_services.md @@ -0,0 +1,46 @@ + +Integration test file for the `alf_services` osquery table on Darwin (macOS), validating that the Apple Application Layer Firewall (ALF) services table returns correctly typed data. + +## Key Components + +- **`alfServices`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that queries the `alf_services` table and validates the schema and row count. + +## Validated Columns + +All columns are validated as `IntType` (boolean-style integer flags): + +| Column | Description | +|---|---| +| `screen_sharing` | Screen Sharing service state | +| `file_sharing` | File Sharing service state | +| `printer_sharing` | Printer Sharing service state | +| `remote_login` | Remote Login (SSH) service state | +| `remote_management` | Remote Management service state | +| `remote_apple_events` | Remote Apple Events service state | +| `internet_sharing` | Internet Sharing service state | +| `bluetooth_sharing` | Bluetooth Sharing service state | +| `disc_sharing` | Disc Sharing service state | +| `content_caching` | Content Caching service state | + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +TEST_F(alfServices, test_sanity) { + auto const data = execute_query("select * from alf_services"); + + // Expects exactly one row (system-level singleton table) + ASSERT_EQ(data.size(), 1ul); + + // All service fields must be integer type + ValidationMap row_map = { + {"screen_sharing", IntType}, + {"file_sharing", IntType}, + // ... + }; + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets macOS only. The spec is defined in `specs/darwin/alf_services.table`. The table always returns exactly one row, representing the current ALF service configuration of the host. \ No newline at end of file diff --git a/tests/integration/tables/.app_schemes.md b/tests/integration/tables/.app_schemes.md new file mode 100644 index 00000000000..c2020394c3f --- /dev/null +++ b/tests/integration/tables/.app_schemes.md @@ -0,0 +1,31 @@ + +Integration test file for the `app_schemes` osquery table on Darwin (macOS), verifying basic query execution as a sanity check. + +## Key Components + +- **`appSchemes`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(appSchemes, test_sanity)`** — Executes a `SELECT *` query against the `app_schemes` table and provides a scaffolded structure for row validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery's integration test runner) +// The test queries app_schemes and can be extended with validation: + +ValidationMap row_map = { + {"scheme", NormalType}, + {"handler", NormalType}, + {"enabled", IntType}, + {"external", IntType}, + {"protected", IntType} +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Darwin `app_schemes` table (spec: `specs/darwin/app_schemes.table`), which exposes URL scheme handlers registered on macOS. +- Row count assertions and `ValidationMap` validation are stubbed out and can be uncommented to enforce stricter checks. +- Follows the standard osquery integration test pattern: **query → size check → schema validation**. +- Depends on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants (`NormalType`, `IntType`). \ No newline at end of file diff --git a/tests/integration/tables/.appcompat_shims.md b/tests/integration/tables/.appcompat_shims.md new file mode 100644 index 00000000000..7e27d8dfeff --- /dev/null +++ b/tests/integration/tables/.appcompat_shims.md @@ -0,0 +1,36 @@ + +Integration test sanity check for the `appcompat_shims` osquery table on Windows, verifying the table can be queried without errors. + +## Key Components + +- **`appcompatShims`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM appcompat_shims` and provides a scaffolded validation structure (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test against the appcompat_shims table +TEST_F(appcompatShims, test_sanity) { + auto const data = execute_query("select * from appcompat_shims"); + + // Uncomment to enable row count assertions: + // ASSERT_GE(data.size(), 0ul); + + // Uncomment to enable schema validation: + // ValidationMap row_map = { + // {"executable", NormalType}, + // {"path", NormalType}, + // {"description", NormalType}, + // {"install_time", IntType}, + // {"type", NormalType}, + // {"sdb_id", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Targets the Windows-specific `appcompat_shims` table (spec: `specs/windows/appcompat_shims.table`). +- Validation logic (row count assertions, schema checks via `ValidationMap`) is scaffolded but commented out — activate as needed for stricter test coverage. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type constants (`NormalType`, `IntType`). \ No newline at end of file diff --git a/tests/integration/tables/.apps.md b/tests/integration/tables/.apps.md new file mode 100644 index 00000000000..22a47e328a2 --- /dev/null +++ b/tests/integration/tables/.apps.md @@ -0,0 +1,42 @@ + +Integration test for the osquery `apps` table on Darwin (macOS), validating that the table returns correctly structured data with expected column types. + +## Key Components + +- **`apps` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(apps, test_sanity)`** — Primary test case that: + - Defines a `ValidationMap` specifying all expected columns and their types. + - Queries all rows from the `apps` table and asserts the result is non-empty. + - Validates column structure of all returned rows against the map. + - Performs a targeted query for `Preview.app` and asserts exactly one matching row is returned. + +## Validated Columns + +| Column | Type | +|---|---| +| `name`, `path`, `bundle_executable` | `NormalType` | +| `bundle_identifier`, `bundle_name` | `NormalType` | +| `bundle_short_version`, `bundle_version` | `NormalType` | +| `bundle_package_type`, `environment` | `NormalType` | +| `compiler`, `development_region` | `NormalType` | +| `display_name`, `info_string` | `NormalType` | +| `minimum_system_version`, `category` | `NormalType` | +| `applescript_enabled`, `copyright`, `last_opened_time` | `NormalType` | + +## Usage Example + +```cpp +// Run this specific test via osquery's test runner on macOS: +// $ ./osquery_tests --gtest_filter=apps.test_sanity + +// Internally, the test executes: +auto const data = execute_query("select * from apps"); +ASSERT_FALSE(data.empty()); +validate_rows(data, row_map); + +// Targeted single-row assertion: +auto const data1 = execute_query("select * from apps where name = 'Preview.app'"); +ASSERT_EQ(data1.size(), 1ul); +``` + +> **Note:** This test targets the Darwin (macOS) platform only. The spec is defined in `specs/darwin/apps.table`. CI environments must have `Preview.app` installed for the targeted assertion to pass. \ No newline at end of file diff --git a/tests/integration/tables/.apt_sources.md b/tests/integration/tables/.apt_sources.md new file mode 100644 index 00000000000..4dd5aac02f8 --- /dev/null +++ b/tests/integration/tables/.apt_sources.md @@ -0,0 +1,43 @@ + +Integration test that validates the `apt_sources` osquery table, ensuring returned rows conform to expected data types and constraints on Linux systems. + +## Key Components + +### `AptSourcesTest` +A Google Test fixture class that initializes the osquery test environment via `setUpEnvironment()` before each test case. + +### `TEST_F(AptSourcesTest, test_sanity)` +Executes `SELECT * FROM apt_sources` and validates each returned row against a `ValidationMap` with the following field constraints: + +| Column | Validation Rule | +|---|---| +| `name` | `NonEmptyString` | +| `source` | `FileOnDisk` | +| `base_uri` | `NonEmptyString` | +| `release` | `NormalType` | +| `version` | `NormalType` | +| `maintainer` | `NonEmptyString` | +| `components` | `NormalType` | +| `architectures` | `NonEmptyString` | + +> If the table returns no rows (e.g., no APT sources configured), the test logs a warning and skips validation rather than failing. + +## Usage Example + +```cpp +// Run this specific integration test via osquery test runner: +// (Requires a Linux environment with osquery built and APT present) + +// The test executes internally as: +QueryData data = execute_query("select * from apt_sources"); +validate_rows(data, row_map); +``` + +To run the test directly: + +```bash +# From the osquery build directory +./osquery_tests --gtest_filter="AptSourcesTest.test_sanity" +``` + +The test targets the spec defined in `specs/linux/apt_sources.table` and is part of the broader osquery integration test suite. It gracefully handles systems where no APT sources are present by warning instead of asserting. \ No newline at end of file diff --git a/tests/integration/tables/.arp_cache.md b/tests/integration/tables/.arp_cache.md new file mode 100644 index 00000000000..c15d8f04711 --- /dev/null +++ b/tests/integration/tables/.arp_cache.md @@ -0,0 +1,35 @@ + +Sanity check integration test for the `arp_cache` osquery table, validating that query results conform to expected data types and formats. + +## Key Components + +- **`ArpCacheTest`** — Test fixture class inheriting from `testing::Test`, initializes the osquery environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a full table scan and validates each row against a schema map. + +## Validation Schema + +| Column | Validator | +|---|---| +| `address` | `verifyIpAddress` | +| `mac` | `verifyMacAddress` | +| `interface` | `NonEmptyString` | +| `permanent` | `Bool` | + +## Usage Example + +```cpp +// Run via the osquery integration test suite: +// The test executes the following query internally: +QueryData data = execute_query("select * from arp_cache"); + +// Each returned row is validated against the expected schema: +auto const row_map = ValidationMap{ + {"address", verifyIpAddress}, + {"mac", verifyMacAddress}, + {"interface", NonEmptyString}, + {"permanent", Bool}, +}; +validate_rows(data, row_map); +``` + +> **Note:** This is a read-only integration test against the live ARP cache. It does not assert row count — an empty ARP cache is considered a passing result as long as any present rows are well-formed. \ No newline at end of file diff --git a/tests/integration/tables/.asl.md b/tests/integration/tables/.asl.md new file mode 100644 index 00000000000..31e65212aff --- /dev/null +++ b/tests/integration/tables/.asl.md @@ -0,0 +1,32 @@ + +Integration test for the `asl` (Apple System Log) osquery table on Darwin/macOS, validating that queried rows conform to the expected schema and data types. + +## Key Components + +- **`asl` test class** — Inherits from `testing::Test`; calls `setUpEnvironment()` in `SetUp()` to initialize the integration test environment. +- **`test_sanity` fixture** — Executes a live query against the `asl` table and validates the result schema using a `ValidationMap`. +- **`ValidationMap row_map`** — Defines expected column names and their type constraints: + +| Column | Type | +|---|---| +| `time`, `time_nano_sec`, `pid`, `gid`, `uid`, `level` | `IntType` | +| `host`, `sender`, `facility`, `message`, `ref_proc`, `extra` | `NormalType` | +| `ref_pid` | `IntOrEmpty` | + +## Usage Example + +This test is run as part of the osquery integration test suite targeting Darwin platforms: + +```bash +# Run only the asl integration test +osquery_integration_tests --gtest_filter="asl.test_sanity" +``` + +```cpp +// Internal flow executed by the test +auto const data = execute_query("select * from asl limit 1"); +ASSERT_FALSE(data.empty()); // Ensures at least one log entry exists +validate_rows(data, row_map); // Asserts each column matches its declared type +``` + +> **Note:** This test targets the `asl` table defined in `specs/darwin/asl.table`. It will only run on macOS environments where the Apple System Log subsystem is available. The `ref_pid` column uses `IntOrEmpty` to accommodate optional fields that may not be present in all log entries. \ No newline at end of file diff --git a/tests/integration/tables/.augeas.md b/tests/integration/tables/.augeas.md new file mode 100644 index 00000000000..7873e83677e --- /dev/null +++ b/tests/integration/tables/.augeas.md @@ -0,0 +1,29 @@ + +Integration sanity test for the `augeas` osquery table, verifying basic query execution against the Augeas configuration file parser on POSIX systems. + +## Key Components + +- **`augeas` test class** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(augeas, test_sanity)`** — Executes `SELECT * FROM augeas` and provides a scaffolded (currently commented-out) validation structure for row count assertions and field-level type checks. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner. +// The test queries the augeas virtual table and can be extended +// to validate specific columns using the ValidationMap pattern: + +ValidationMap row_map = { + {"node", NormalType}, + {"value", NormalType}, + {"label", NormalType}, + {"path", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the spec file `specs/posix/augeas.table`. +- Row count assertions and `validate_rows()` are intentionally commented out — enable them when stricter sanity checks are needed. +- Uses the shared `osquery/tests/integration/tables/helper.h` utilities (`execute_query`, `validate_rows`, `ValidationMap`). \ No newline at end of file diff --git a/tests/integration/tables/.authenticode.md b/tests/integration/tables/.authenticode.md new file mode 100644 index 00000000000..48ada27a0cd --- /dev/null +++ b/tests/integration/tables/.authenticode.md @@ -0,0 +1,33 @@ + +Integration test stub for the `authenticode` osquery table on Windows, verifying basic query execution against the Authenticode digital signature data source. + +## Key Components + +- **`authenticode` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(authenticode, test_sanity)`** — Executes a sanity-check query against the `authenticode` table with an empty `path` filter to validate the table can be queried without errors. + +## Usage Example + +```cpp +// Run the integration test (example using osquery test runner) +// The test queries the authenticode table and can be extended +// to validate specific columns: + +ValidationMap row_map = { + {"path", NormalType}, + {"original_program_name", NormalType}, + {"serial_number", NormalType}, + {"issuer_name", NormalType}, + {"subject_name", NormalType}, + {"result", NormalType}, +}; + +// Uncomment in test body to activate full validation: +validate_rows(data, row_map); +``` + +## Notes + +- The test currently only executes the query; **size assertions and row validation are commented out** and serve as scaffolding for future implementation. +- Corresponds to the table spec at `specs/windows/authenticode.table`. +- Relies on shared test utilities from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.authorization_mechanisms.md b/tests/integration/tables/.authorization_mechanisms.md new file mode 100644 index 00000000000..c69a68a8db6 --- /dev/null +++ b/tests/integration/tables/.authorization_mechanisms.md @@ -0,0 +1,32 @@ + +Integration test file for the `authorization_mechanisms` osquery table on Darwin (macOS), verifying basic query execution as a sanity check. + +## Key Components + +- **`authorizationMechanisms`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a `SELECT *` query against the `authorization_mechanisms` table to confirm it runs without errors. + +## Usage Example + +```cpp +// Run the sanity test (via osquery integration test runner) +// The test queries the authorization_mechanisms table and can be +// extended with row validation: + +ValidationMap row_map = { + {"label", NormalType}, + {"plugin", NormalType}, + {"mechanism", NormalType}, + {"privileged", NormalType}, + {"entry", NormalType}, +}; +validate_rows(data, row_map); +``` + +> **Note:** The size assertions and `validate_rows` call are scaffolded but commented out. To harden this test, uncomment and configure `ASSERT_GE`/`ASSERT_EQ` and `validate_rows` with the `ValidationMap` above. + +## Notes + +- Targets Darwin only — corresponds to `specs/darwin/authorization_mechanisms.table`. +- Follows the standard osquery integration test pattern from `osquery/tests/integration/tables/helper.h`. +- Currently only validates that the query executes without crashing; schema-level field validation is stubbed for future implementation. \ No newline at end of file diff --git a/tests/integration/tables/.authorizations.md b/tests/integration/tables/.authorizations.md new file mode 100644 index 00000000000..cbcd2aa49fe --- /dev/null +++ b/tests/integration/tables/.authorizations.md @@ -0,0 +1,38 @@ + +Integration test for the `authorizations` osquery table on Darwin (macOS), verifying basic query execution against the system authorization policies. + +## Key Components + +- **`authorizations` (test class)** — Inherits from `testing::Test`; sets up the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(authorizations, test_sanity)`** — Executes a `SELECT *` query against the `authorizations` table and provides a scaffolded structure for row-level validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test (executed via the osquery integration test runner) +// The test queries the authorizations table and can validate rows like: + +ValidationMap row_map = { + {"label", NormalType}, + {"modified", NormalType}, + {"allow_root", NormalType}, + {"timeout", NormalType}, + {"version", NormalType}, + {"tries", NormalType}, + {"authenticate_user", NormalType}, + {"shared", NormalType}, + {"comment", NormalType}, + {"created", NormalType}, + {"class", NormalType}, + {"session_owner", NormalType}, +}; + +// Uncomment to enable full validation: +// validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/darwin/authorizations.table` +- Row count assertions and `validate_rows()` are scaffolded but **commented out** — enable them to enforce stricter validation. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `ValidationMap`, and `NormalType` utilities. \ No newline at end of file diff --git a/tests/integration/tables/.authorized_keys.md b/tests/integration/tables/.authorized_keys.md new file mode 100644 index 00000000000..f525e08630e --- /dev/null +++ b/tests/integration/tables/.authorized_keys.md @@ -0,0 +1,31 @@ + +Integration test file for the `authorized_keys` osquery table, providing a sanity check to verify the table can be queried without errors on POSIX systems. + +## Key Components + +- **`authorizedKeys`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM authorized_keys` and provides scaffolding for row count assertions and column validation. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test executes the following query internally: +auto const data = execute_query("select * from authorized_keys"); + +// Optionally enable row validation by uncommenting and configuring: +ValidationMap row_map = { + {"uid", IntType}, + {"algorithm", NormalType}, + {"key", NormalType}, + {"key_file", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the `authorized_keys` table defined in `specs/posix/authorized_keys.table`. +- Size assertions and `ValidationMap` checks are scaffolded but commented out — enable them as the table implementation stabilizes. +- Follows the standard osquery integration test pattern: query → size check → schema validation. +- See `osquery/tests/integration/tables/helper.h` for available type flags (`IntType`, `NormalType`, etc.) used in `ValidationMap`. \ No newline at end of file diff --git a/tests/integration/tables/.autoexec.md b/tests/integration/tables/.autoexec.md new file mode 100644 index 00000000000..7a37251290b --- /dev/null +++ b/tests/integration/tables/.autoexec.md @@ -0,0 +1,30 @@ + +Integration test file for the `autoexec` osquery table on Windows, providing a sanity check that the table can be queried without errors. + +## Key Components + +- **`autoexec` test class** — Inherits from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Executes `SELECT * FROM autoexec` and provides a scaffolded structure for row count assertions and column validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test against the autoexec table +TEST_F(autoexec, test_sanity) { + auto const data = execute_query("select * from autoexec"); + + // Uncomment to validate row structure: + // ValidationMap row_map = { + // {"path", NormalType}, + // {"name", NormalType}, + // {"source", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Targets the Windows `autoexec` table (spec: `specs/windows/autoexec.table`), which surfaces auto-executing programs and their registry/file sources. +- Row count assertions (`ASSERT_GE`, `ASSERT_EQ`) and the `ValidationMap` are scaffolded but left commented out — activate them to enforce expected column types (`path`, `name`, `source`) during CI validation. +- Follows the standard osquery integration test pattern from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.azure_instance_metadata.md b/tests/integration/tables/.azure_instance_metadata.md new file mode 100644 index 00000000000..26d95cca014 --- /dev/null +++ b/tests/integration/tables/.azure_instance_metadata.md @@ -0,0 +1,35 @@ + +Integration test for the `azure_instance_metadata` osquery table, validating that query results conform to expected column types when running on Azure virtual machines. + +## Key Components + +- **`azureInstanceMetadata`** — Test fixture class extending `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Sanity test that queries the `azure_instance_metadata` table and validates each returned row against a `ValidationMap` of expected column types. + +### Validated Columns + +| Column | Type | +|---|---| +| `location`, `name`, `architecture` | `NormalType` | +| `offer`, `publisher`, `sku`, `version` | `NormalType` | +| `os_type`, `platform_update_domain`, `platform_fault_domain` | `NormalType` | +| `vm_id`, `vm_size`, `subscription_id` | `NormalType` | +| `resource_group_name`, `placement_group_id` | `NormalType` | +| `vm_scale_set_name`, `zone` | `NormalType` | + +## Usage Example + +```cpp +// This test runs automatically via the osquery integration test suite. +// It executes only when azure_instance_metadata returns data (i.e., on Azure VMs). + +TEST_F(azureInstanceMetadata, test_sanity) { + auto const data = execute_query("select * from azure_instance_metadata"); + if (!data.empty()) { + // Validation only runs when executing on an Azure VM instance + validate_rows(data, row_map); + } +} +``` + +> **Note:** The test is conditionally guarded — if the query returns no rows (non-Azure environment), validation is skipped gracefully. Also note a missing comma between `sku` and `version` entries in the `ValidationMap` is present in the source. \ No newline at end of file diff --git a/tests/integration/tables/.azure_instance_tags.md b/tests/integration/tables/.azure_instance_tags.md new file mode 100644 index 00000000000..2b568e12b19 --- /dev/null +++ b/tests/integration/tables/.azure_instance_tags.md @@ -0,0 +1,38 @@ + +Integration test file for the `azure_instance_tags` osquery table, validating query execution and result schema on Azure virtual machine environments. + +## Key Components + +- **`azureInstanceTags`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Sanity test that executes a `SELECT *` query against `azure_instance_tags` and validates the schema of any returned rows. + +## Validation Schema + +When results are present, each row is validated against: + +| Column | Type | +|--------|------| +| `vm_id` | `NormalType` | +| `key` | `NormalType` | +| `value` | `NormalType` | + +> Results are only validated if the query returns data — the test passes silently on non-Azure hosts where the table is empty. + +## Usage Example + +```cpp +// Run the integration test directly via ctest or osquery test runner: +// The test executes internally as: + +auto const data = execute_query("select * from azure_instance_tags"); +if (!data.empty()) { + ValidationMap row_map = { + {"vm_id", NormalType}, + {"key", NormalType}, + {"value", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +This test is part of the osquery integration test suite and is only meaningful when run on an Azure VM instance where instance metadata and tags are accessible. On non-Azure hosts the table returns no rows and the validation block is skipped, ensuring the test remains non-blocking across environments. \ No newline at end of file diff --git a/tests/integration/tables/.background_activities_moderator.md b/tests/integration/tables/.background_activities_moderator.md new file mode 100644 index 00000000000..6de784de3a2 --- /dev/null +++ b/tests/integration/tables/.background_activities_moderator.md @@ -0,0 +1,31 @@ + +Integration test for the `background_activities_moderator` osquery table, verifying that the table returns valid data with correctly typed fields on Windows systems. + +## Key Components + +- **`BamTest`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` +- **`test_sanity`** — Single test case that executes a `SELECT *` query against the `background_activities_moderator` table and validates the result schema + +## Validated Schema + +| Column | Validation Rule | +|---|---| +| `path` | `NonEmptyString` | +| `last_execution_time` | `NormalType` | +| `sid` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run via osquery integration test suite +// The test executes this query internally: +QueryData const rows = + execute_query("select * from background_activities_moderator"); + +// Expected output columns: +// path - Executable path tracked by BAM +// last_execution_time - Timestamp of last run +// sid - Security Identifier of the user +``` + +The test asserts that the result set is non-empty (`rows.size() > 0`) and that each row conforms to the defined `ValidationMap`. The BAM (Background Activity Moderator) table surfaces Windows registry data tracking recently executed programs per user, making non-empty results expected on any active Windows host. \ No newline at end of file diff --git a/tests/integration/tables/.battery.md b/tests/integration/tables/.battery.md new file mode 100644 index 00000000000..7e82fb08b63 --- /dev/null +++ b/tests/integration/tables/.battery.md @@ -0,0 +1,31 @@ + +Integration sanity test for the `battery` osquery table on Darwin (macOS), verifying that the table can be queried without errors. + +## Key Components + +- **`battery` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(battery, test_sanity)`** — Executes `SELECT * FROM battery` and provides a scaffolded (currently commented-out) validation structure for row count assertions and field type checks. + +## Usage Example + +```cpp +// Run the sanity test (executed via the osquery integration test runner) +// The test queries the battery table and can be extended with validation: + +ValidationMap row_map = { + {"manufacturer", NormalType}, + {"cycle_count", IntType}, + {"percent_remaining", IntType}, + {"charging", IntType}, + {"charged", IntType}, + {"designed_capacity", IntType}, + {"max_capacity", IntType}, + {"current_capacity", IntType}, + {"voltage", IntType}, + {"minutes_until_empty", IntType}, +}; + +validate_rows(data, row_map); +``` + +> **Note:** The validation map and row count assertions are commented out by default. Uncomment and configure them to enforce specific field types (`NormalType`, `IntType`) and expected row counts when extending this test. \ No newline at end of file diff --git a/tests/integration/tables/.bitlocker_info.md b/tests/integration/tables/.bitlocker_info.md new file mode 100644 index 00000000000..3a81909a295 --- /dev/null +++ b/tests/integration/tables/.bitlocker_info.md @@ -0,0 +1,34 @@ + +Integration test stub for the `bitlocker_info` osquery table on Windows, verifying basic query execution against BitLocker drive encryption data. + +## Key Components + +- **`bitlockerInfo`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(bitlockerInfo, test_sanity)`** — Sanity test that executes `SELECT * FROM bitlocker_info` and provides a scaffolded (commented-out) validation structure for future assertions. + +## Usage Example + +```cpp +// Run the integration test (from osquery build directory) +// The test queries the bitlocker_info virtual table and validates results + +// Columns available for validation (currently commented out): +ValidationMap row_map = { + {"device_id", NormalType}, + {"drive_letter", NormalType}, + {"persistent_volume_id", NormalType}, + {"conversion_status", IntType}, + {"protection_status", IntType}, + {"encryption_method", NormalType}, +}; + +// To enable full validation, uncomment and call: +validate_rows(data, row_map); +``` + +## Notes + +- The test currently only executes the query without asserting row counts or validating column values — all validation logic is scaffolded but commented out. +- Corresponds to the table spec at `specs/windows/bitlocker_info.table`. +- Requires a Windows environment with osquery's integration test helpers (`osquery/tests/integration/tables/helper.h`). +- To extend: uncomment the `ValidationMap` block and call `validate_rows()` to enforce column type constraints. \ No newline at end of file diff --git a/tests/integration/tables/.block_devices.md b/tests/integration/tables/.block_devices.md new file mode 100644 index 00000000000..b256c7675ac --- /dev/null +++ b/tests/integration/tables/.block_devices.md @@ -0,0 +1,36 @@ + +Integration test file for the `block_devices` osquery table, validating that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`blockDevices`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic sanity test that executes `SELECT * FROM block_devices` and provides a scaffolded validation structure for future assertions. + +## Usage Example + +```cpp +// Run the sanity test against the block_devices table +TEST_F(blockDevices, test_sanity) { + auto const data = execute_query("select * from block_devices"); + + // Example: enable row validation when ready + ValidationMap row_map = { + {"name", NormalType}, + {"parent", NormalType}, + {"vendor", NormalType}, + {"model", NormalType}, + {"size", IntType}, + {"block_size", IntType}, + {"uuid", NormalType}, + {"type", NormalType}, + {"label", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Spec source: `specs/posix/block_devices.table` +- Row count assertions and `validate_rows()` are currently commented out — uncomment and configure when stricter validation is needed. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.browser_plugins.md b/tests/integration/tables/.browser_plugins.md new file mode 100644 index 00000000000..c6baf00072c --- /dev/null +++ b/tests/integration/tables/.browser_plugins.md @@ -0,0 +1,38 @@ + +Integration test for the `browser_plugins` osquery table, validating schema correctness and macOS version-conditional data presence on Darwin systems. + +## Key Components + +- **`browserPlugins`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that validates the `browser_plugins` table schema and query behavior. +- **`row_map`** — `ValidationMap` defining expected column types for the table schema. + +### Validated Columns + +| Column | Type | +|---|---| +| `uid` | `IntType` | +| `name` | `NormalType` | +| `identifier` | `NormalType` | +| `version` | `NormalType` | +| `sdk` | `NormalType` | +| `description` | `NormalType` | +| `development_region` | `NormalType` | +| `native` | `IntType` | +| `path` | `NormalType` | +| `disabled` | `IntType` | + +## Usage Example + +```cpp +// Runs automatically via the osquery integration test suite. +// The test conditionally asserts data presence based on macOS version: +// - macOS < 10.15: browser_plugins table must return non-empty results +// - macOS >= 10.15: table must return empty (feature removed by Apple) + +auto data = execute_query("select * from browser_plugins"); +auto datauser = execute_query("select * from browser_plugins where uid = 0"); +validate_rows(data, row_map); +``` + +> **Note:** Browser plugins (NPAPI) were removed in macOS 10.15 (Catalina). The test accounts for this by asserting an empty result set on Catalina and later, and non-empty results on earlier versions. \ No newline at end of file diff --git a/tests/integration/tables/.carbon_black_info.md b/tests/integration/tables/.carbon_black_info.md new file mode 100644 index 00000000000..dadf46e94d9 --- /dev/null +++ b/tests/integration/tables/.carbon_black_info.md @@ -0,0 +1,32 @@ + +Integration test file for the `carbon_black_info` osquery table, providing a sanity check that verifies the table can be queried without errors. + +## Key Components + +- **`carbonBlackInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(carbonBlackInfo, test_sanity)`** — Executes a `SELECT *` query against the `carbon_black_info` table to validate basic query execution. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test executes the following query internally: +auto const data = execute_query("select * from carbon_black_info"); + +// Commented-out validation map (available for future activation): +// ValidationMap row_map = { +// {"sensor_id", IntType}, +// {"config_name", NormalType}, +// {"sensor_ip_addr", NormalType}, +// {"event_queue", IntType}, +// {"binary_queue", IntType}, +// // ... additional fields +// }; +// validate_rows(data, row_map); +``` + +## Notes + +- Full row validation is **commented out** — the test currently only confirms the query runs without crashing. +- The `ValidationMap` scaffold covers all expected columns, including sensor configuration flags (`collect_*` fields), quota settings, and connection details, and can be enabled when stricter validation is needed. +- Corresponds to the table spec at `specs/carbon_black_info.table`. \ No newline at end of file diff --git a/tests/integration/tables/.carves.md b/tests/integration/tables/.carves.md new file mode 100644 index 00000000000..b0ed9950f49 --- /dev/null +++ b/tests/integration/tables/.carves.md @@ -0,0 +1,34 @@ + +Integration test file for the `carves` osquery table, providing a sanity check scaffold to validate file carving query results. + +## Key Components + +- **`carves` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(carves, test_sanity)`** — Executes a basic query against the `carves` table filtered by an empty path, with commented-out scaffolding for size assertions and row validation. + +## Usage Example + +```cpp +// Extend the test by uncommenting and configuring the validation map: +ValidationMap row_map = { + {"time", IntType}, + {"sha256", NormalType}, + {"size", IntType}, + {"path", NormalType}, + {"status", NormalType}, + {"carve_guid", NormalType}, + {"carve", IntType} +}; + +// Assert expected row count +ASSERT_GE(data.size(), 0ul); + +// Run validation against schema +validate_rows(data, row_map); +``` + +## Notes + +- The query `select * from carves where path = ''` intentionally returns no rows; size and validation assertions are commented out pending real carve data. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. +- Spec reference: `specs/carves.table`. \ No newline at end of file diff --git a/tests/integration/tables/.certificates.md b/tests/integration/tables/.certificates.md new file mode 100644 index 00000000000..20dce20a6a5 --- /dev/null +++ b/tests/integration/tables/.certificates.md @@ -0,0 +1,33 @@ + +Integration test file for the `certificates` osquery table, validating that certificate data can be queried and contains expected fields on macOS and Windows platforms. + +## Key Components + +- **`certificates` test fixture** — Inherits from `testing::Test`; handles environment setup and, on Windows, initializes/tears down user and group services via the `Dispatcher`. +- **`test_sanity`** — Executes `SELECT * FROM certificates`, asserts at least one row is returned, and validates all standard certificate columns plus Windows-specific columns (`sid`, `store_location`, `store`, `username`, `store_id`) when running on Windows. +- **`test_certificate_common_name`** — Executes a targeted query for `common_name`, asserts at least one row exists, and verifies the field is non-empty. + +## Validated Columns + +| Column | Type | +|---|---| +| `common_name`, `subject`, `issuer` | `NormalType` | +| `ca`, `self_signed` | `IntType` | +| `not_valid_before`, `not_valid_after` | `NormalType` | +| `signing_algorithm`, `key_algorithm`, `key_strength` | `NormalType` | +| `sha1`, `path`, `serial` | `NormalType` | +| `sid`, `store`, `store_location`, `username`, `store_id` *(Windows only)* | `NormalType` | + +## Usage Example + +```cpp +// Run all certificates integration tests via osquery test runner: +// These tests execute automatically under the table_tests namespace. + +// Manually invoking the query being tested: +auto data = execute_query("SELECT * FROM certificates"); +validate_rows(data, row_map); + +// Windows-specific setup is handled automatically in SetUpTestSuite: +initUsersAndGroupsServices(true, false); +``` \ No newline at end of file diff --git a/tests/integration/tables/.chassis_info.md b/tests/integration/tables/.chassis_info.md new file mode 100644 index 00000000000..7abfc561875 --- /dev/null +++ b/tests/integration/tables/.chassis_info.md @@ -0,0 +1,42 @@ + +Integration test that validates the `chassis_info` osquery table returns correct data structure and column types on Windows systems. + +## Key Components + +- **`chassisTest`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()` +- **`test_sanity`** — Single test case that executes `SELECT * FROM chassis_info` and validates the result + +## Validated Columns + +| Column | Validation Type | +|---|---| +| `audible_alarm` | `NonEmptyString` | +| `breach_description` | `NormalType` | +| `chassis_types` | `NonEmptyString` | +| `description` | `NormalType` | +| `lock` | `NonEmptyString` | +| `manufacturer` | `NormalType` | +| `model` | `NormalType` | +| `security_breach` | `NonEmptyString` | +| `serial` | `NormalType` | +| `smbios_tag` | `NormalType` | +| `sku` | `NormalType` | +| `status` | `NormalType` | +| `visible_alarm` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test framework +TEST_F(chassisTest, test_sanity) { + // Expects exactly one row returned from the chassis_info table + const QueryData data = execute_query("select * from chassis_info"); + ASSERT_EQ(data.size(), 1ul); + + // Columns marked NonEmptyString must contain a non-empty string value + // Columns marked NormalType allow empty or null values + validate_rows(data, row_map); +} +``` + +The test asserts exactly **one row** is returned (reflecting a single physical chassis), and uses `NonEmptyString` for fields always expected to be populated (e.g., `audible_alarm`, `lock`, `chassis_types`) versus `NormalType` for optional/nullable fields like `manufacturer` or `serial`. \ No newline at end of file diff --git a/tests/integration/tables/.chocolatey_packages.md b/tests/integration/tables/.chocolatey_packages.md new file mode 100644 index 00000000000..77911f5e3f7 --- /dev/null +++ b/tests/integration/tables/.chocolatey_packages.md @@ -0,0 +1,37 @@ + +Integration test for the `chocolatey_packages` osquery table, validating that query results conform to the expected schema and field types on Windows systems. + +## Key Components + +- **`ChocolateyPackagesTest`** — Test fixture class inheriting from `testing::Test`, initializes the osquery environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Single test case that executes a full table query and validates each row against a defined schema map. +- **`ValidationMap`** — Defines expected field types for each column: + +| Column | Validation Type | +|--------|----------------| +| `name` | `NormalType` | +| `version` | `NormalType` | +| `summary` | `NormalType` | +| `author` | `NormalType` | +| `license` | `NormalType` | +| `path` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run via osquery's integration test harness (Windows only) +// The test queries the chocolatey_packages virtual table and +// validates all returned rows match the schema + +TEST_F(ChocolateyPackagesTest, test_sanity) { + auto const data = execute_query("select * from chocolatey_packages"); + + ValidationMap row_map = { + {"name", NormalType}, + {"path", NonEmptyString}, // path must be a non-empty string + }; + validate_rows(data, row_map); +} +``` + +> **Note:** `path` uses `NonEmptyString` while other fields use `NormalType`, enforcing that every Chocolatey package entry must have a valid installation path. This test corresponds to the spec at `specs/windows/chocolatey_packages.table`. \ No newline at end of file diff --git a/tests/integration/tables/.chrome_extension_content_scripts.md b/tests/integration/tables/.chrome_extension_content_scripts.md new file mode 100644 index 00000000000..889650257e3 --- /dev/null +++ b/tests/integration/tables/.chrome_extension_content_scripts.md @@ -0,0 +1,36 @@ + +Integration test for the `chrome_extension_content_scripts` osquery table, validating that query execution succeeds and returned rows conform to the expected schema. + +## Key Components + +- **`chromeExtensions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(chromeExtensions, test_sanity)`** — Sanity test that executes a `SELECT *` against the `chrome_extension_content_scripts` table and validates the result schema. +- **`ValidationMap`** — Defines the expected column types for each returned row: + +| Column | Type | +|---|---| +| `uid` | `IntType` | +| `identifier` | `NonEmptyString` | +| `version` | `NonEmptyString` | +| `script` | `NormalType` | +| `match` | `NormalType` | + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test harness +// The test executes the following query internally: +execute_query("select * from chrome_extension_content_scripts"); + +// Validation ensures all rows conform to the schema: +ValidationMap row_map = { + {"uid", IntType}, + {"identifier", NonEmptyString}, + {"version", NonEmptyString}, + {"script", NormalType}, + {"match", NormalType} +}; +validate_rows(data, row_map); +``` + +> **Note:** `ASSERT_GE(data.size(), 0ul)` confirms the query executes without error but does not require rows to be present — Chrome extensions may not be installed in all test environments. \ No newline at end of file diff --git a/tests/integration/tables/.chrome_extensions.md b/tests/integration/tables/.chrome_extensions.md new file mode 100644 index 00000000000..dbb8aeb2a07 --- /dev/null +++ b/tests/integration/tables/.chrome_extensions.md @@ -0,0 +1,31 @@ + +Integration test for the `chrome_extensions` osquery table, validating query execution and schema correctness across platforms. + +## Key Components + +- **`chromeExtensions`** — Test fixture class inheriting from `testing::Test`, with platform-specific setup/teardown for Windows user and group services initialization. +- **`TEST_F(chromeExtensions, test_sanity)`** — Sanity test that executes a `SELECT` query against the `chrome_extensions` table and validates each returned row against a defined schema map. +- **`ValidationMap row_map`** — Defines expected column types for all 26 fields returned by the table, including extended JSON fields (`permissions_json`, `optional_permissions_json`, `manifest_json`). + +## Validated Columns + +| Column | Type | +|---|---| +| `uid`, `persistent`, `referenced`, `install_timestamp` | `IntType` | +| `browser_type`, `name`, `profile`, `identifier`, `version`, `description`, `default_locale`, `current_locale`, `update_url`, `author`, `path`, `permissions`, `permissions_json`, `optional_permissions`, `optional_permissions_json`, `manifest_hash`, `from_webstore`, `state`, `install_time`, `manifest_json`, `profile_path`, `referenced_identifier` | `NormalType` | + +## Usage Example + +```cpp +// Run the integration test via osquery test harness +TEST_F(chromeExtensions, test_sanity) { + auto const data = execute_query( + "select *, permissions_json, optional_permissions_json, manifest_json " + "from chrome_extensions"); + + ASSERT_GE(data.size(), 0ul); + validate_rows(data, row_map); // Validates all 26 column types +} +``` + +> **Note:** On Windows, `SetUpTestSuite` and `TearDownTestSuite` initialize and cleanly shut down user/group dispatcher services required for profile enumeration. \ No newline at end of file diff --git a/tests/integration/tables/.connected_displays.md b/tests/integration/tables/.connected_displays.md new file mode 100644 index 00000000000..d7337afe3fc --- /dev/null +++ b/tests/integration/tables/.connected_displays.md @@ -0,0 +1,43 @@ + +Integration test file for the `connected_displays` osquery table, validating that query results conform to expected column types and structure. + +## Key Components + +### `connectedDisplays` (Test Fixture) +- Inherits from `testing::Test` +- `SetUp()` initializes the test environment via `setUpEnvironment()` + +### `TEST_F(connectedDisplays, test_sanity)` +Executes `SELECT * FROM connected_displays` and validates each returned row against the following schema: + +| Column | Type | +|---|---| +| `name` | `NormalType` | +| `product_id` | `NormalType` | +| `serial_number` | `NormalType` | +| `vendor_id` | `NormalType` | +| `manufactured_week` | `IntType` | +| `manufactured_year` | `IntType` | +| `display_id` | `NormalType` | +| `pixels` | `NormalType` | +| `resolution` | `NormalType` | +| `ambient_brightness_enabled` | `IntType` | +| `connection_type` | `NormalType` | +| `display_type` | `NormalType` | +| `main` | `IntType` | +| `mirror` | `IntType` | +| `online` | `IntType` | +| `rotation` | `IntType` | + +## Usage Example + +```cpp +// Run as part of the osquery integration test suite +// The test fixture is automatically discovered by Google Test + +// Internally executes: +auto const data = execute_query("select * from connected_displays"); +validate_rows(data, row_map); // Asserts all rows match expected types +``` + +> This test targets display hardware enumeration and is typically exercised on macOS or platforms exposing monitor metadata (EDID data, connection type, rotation state, etc.). \ No newline at end of file diff --git a/tests/integration/tables/.connectivity.md b/tests/integration/tables/.connectivity.md new file mode 100644 index 00000000000..ae70aac2968 --- /dev/null +++ b/tests/integration/tables/.connectivity.md @@ -0,0 +1,42 @@ + +Integration test that validates the `connectivity` osquery table for Windows, ensuring it returns exactly one row with the correct column types for network connectivity status fields. + +## Key Components + +- **`connectivity` test class** — Inherits from `testing::Test`; initializes the test environment via `SetUp()` +- **`test_sanity` test case** — Executes `select * from connectivity` and validates the result structure + +## Usage Example + +```cpp +// The test queries the connectivity table and validates its schema: +auto const data = execute_query("select * from connectivity"); + +// Asserts exactly one row is returned +ASSERT_EQ(data.size(), 1ul); + +// Validates all connectivity columns are integer types +ValidationMap row_map = { + {"disconnected", IntType}, + {"ipv4_no_traffic", IntType}, + {"ipv6_no_traffic", IntType}, + {"ipv4_subnet", IntType}, + {"ipv4_local_network", IntType}, + {"ipv4_internet", IntType}, + {"ipv6_subnet", IntType}, + {"ipv6_local_network", IntType}, + {"ipv6_internet", IntType}, +}; +validate_rows(data, row_map); +``` + +## Validated Columns + +| Column | Type | Description | +|---|---|---| +| `disconnected` | `IntType` | Network disconnected state | +| `ipv4_no_traffic` | `IntType` | IPv4 exists but no traffic | +| `ipv6_no_traffic` | `IntType` | IPv6 exists but no traffic | +| `ipv4_subnet` / `ipv6_subnet` | `IntType` | Subnet-level connectivity | +| `ipv4_local_network` / `ipv6_local_network` | `IntType` | LAN connectivity | +| `ipv4_internet` / `ipv6_internet` | `IntType` | Full internet connectivity | \ No newline at end of file diff --git a/tests/integration/tables/.cpu_info.md b/tests/integration/tables/.cpu_info.md new file mode 100644 index 00000000000..3d99a384bc7 --- /dev/null +++ b/tests/integration/tables/.cpu_info.md @@ -0,0 +1,35 @@ + +Integration test that validates the `cpu_info` osquery table returns well-formed CPU data across platforms (Linux, macOS x86/ARM, Windows). + +## Key Components + +- **`cpuInfo` (test fixture)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(cpuInfo, test_sanity)`** — Executes `SELECT * FROM cpu_info` and validates every returned row against a platform-aware `ValidationMap`. + +### Validation Fields + +| Field | Constraint | +|---|---| +| `device_id` | `NonEmptyString` | +| `model`, `manufacturer` | `NormalType` | +| `processor_type`, `number_of_cores`, `logical_processors`, `address_width`, `max_clock_speed` | `NonNegativeOrErrorInt` | +| `socket_designation`, `current_clock_speed`, `cpu_status` | Platform-dependent (see below) | +| `availability`, `load_percentage` | Windows only, `NonNegativeOrErrorInt` | + +### Platform-Specific Branching + +- **macOS ARM (`__aarch64__`)** — Adds `number_of_efficiency_cores` / `number_of_performance_cores` as `NonNegativeInt`; `socket_designation`, `current_clock_speed`, and `cpu_status` are `EmptyOk`. +- **macOS x86** — Efficiency/performance core fields are `EmptyOk`; remaining fields use standard non-negative constraints. +- **Windows** — Appends `availability` and `load_percentage` fields. + +## Usage Example + +```cpp +// Run via the osquery integration test harness: +// ctest -R cpu_info --output-on-failure + +// Internally the test executes: +const QueryData data = execute_query("select * from cpu_info"); +ASSERT_GE(data.size(), 1ul); +validate_rows(data, row_map); +``` \ No newline at end of file diff --git a/tests/integration/tables/.cpu_time.md b/tests/integration/tables/.cpu_time.md new file mode 100644 index 00000000000..2fdd39bdcef --- /dev/null +++ b/tests/integration/tables/.cpu_time.md @@ -0,0 +1,35 @@ + +Integration sanity test for the `cpu_time` osquery table, verifying basic query execution against the POSIX `cpu_time` table spec. + +## Key Components + +- **`cpuTime`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(cpuTime, test_sanity)`** — Executes `SELECT * FROM cpu_time` and provides a scaffolded (currently commented-out) validation block for row count assertions and column type checks. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test queries the cpu_time table and can validate columns such as: +// ValidationMap row_map = { +// {"core", IntType}, +// {"user", IntType}, +// {"nice", IntType}, +// {"system", IntType}, +// {"idle", IntType}, +// {"iowait", IntType}, +// {"irq", IntType}, +// {"softirq", IntType}, +// {"steal", IntType}, +// {"guest", IntType}, +// {"guest_nice", IntType}, +// }; +// validate_rows(data, row_map); +``` + +> **Note:** Validation logic (row count assertions and `validate_rows`) is scaffolded but commented out. To activate, uncomment the `ValidationMap` block and the appropriate `ASSERT_*` size check, then call `validate_rows(data, row_map)`. + +## Notes + +- Corresponds to the table spec at `specs/posix/cpu_time.table`. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants (`IntType`, etc.). \ No newline at end of file diff --git a/tests/integration/tables/.cpuid.md b/tests/integration/tables/.cpuid.md new file mode 100644 index 00000000000..5e77778f446 --- /dev/null +++ b/tests/integration/tables/.cpuid.md @@ -0,0 +1,37 @@ + +Integration test that validates the `cpuid` osquery table, ensuring it returns correctly typed data for CPU feature detection information. + +## Key Components + +- **`cpuid` (test class)** — inherits from `testing::Test`, sets up the integration test environment via `setUpEnvironment()` +- **`TEST_F(cpuid, test_sanity)`** — validates that the `cpuid` table returns non-empty results with correctly typed columns + +### Validated Columns + +| Column | Expected Type | +|---|---| +| `feature` | `NormalType` | +| `value` | `NormalType` | +| `output_register` | `NormalType` | +| `output_bit` | `IntType` | +| `input_eax` | `NormalType` | + +## Usage Example + +```cpp +// Run the sanity check directly via osquery SQL +auto const data = execute_query("select * from cpuid"); + +// Expected row structure +ValidationMap row_map = { + {"feature", NormalType}, // e.g., "aes" + {"value", NormalType}, // e.g., "true" + {"output_register", NormalType}, // e.g., "ecx" + {"output_bit", IntType}, // e.g., 25 + {"input_eax", NormalType}, // e.g., "0x00000001" +}; + +validate_rows(data, row_map); +``` + +> This test corresponds to the table spec defined in `specs/cpuid.table` and is executed as part of osquery's integration test suite. \ No newline at end of file diff --git a/tests/integration/tables/.crashes.md b/tests/integration/tables/.crashes.md new file mode 100644 index 00000000000..e37d607d1bf --- /dev/null +++ b/tests/integration/tables/.crashes.md @@ -0,0 +1,36 @@ + +Integration test for the `crashes` osquery table (Darwin/macOS), validating schema correctness and data integrity of crash report records. + +## Key Components + +- **`crashes` (test class)** — Inherits from `testing::Test`; sets up the test environment via `setUpEnvironment()`. +- **`TEST_F(crashes, test_sanity)`** — Sanity test that executes a cross-join query between `users` and `crashes` on `uid`, then validates returned rows against the expected schema. +- **`ValidationMap row_map`** — Defines the expected column types for all 16 crash record fields. + +### Validated Columns + +| Column | Type | +|---|---| +| `type`, `path`, `crash_path`, `identifier`, `version` | `NormalType` | +| `pid`, `parent`, `uid`, `crashed_thread` | `IntType` | +| `responsible`, `datetime`, `stack_trace` | `NormalType` | +| `exception_type`, `exception_codes`, `exception_notes`, `registers` | `NormalType` | + +## Usage Example + +```cpp +// The test executes this query internally: +auto const data = execute_query( + "select crashes.* from users CROSS JOIN crashes USING(uid)"); + +// Validation is skipped gracefully if no crash records exist on the host +if (!data.empty()) { + validate_rows(data, row_map); +} +``` + +## Notes + +- Targets **Darwin (macOS)** only — spec lives at `specs/darwin/crashes.table`. +- The test is intentionally non-failing when `data` is empty, since crash reports may not exist on every test host. +- Run as part of osquery's broader integration test suite using the `helper.h` framework. \ No newline at end of file diff --git a/tests/integration/tables/.crontab.md b/tests/integration/tables/.crontab.md new file mode 100644 index 00000000000..c20854fa229 --- /dev/null +++ b/tests/integration/tables/.crontab.md @@ -0,0 +1,31 @@ + +Integration test for the `crontab` osquery table, validating that queried crontab entries conform to expected field formats and value ranges on POSIX systems. + +## Key Components + +- **`Crontab`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(Crontab, test_sanity)`** — Primary test case that executes `SELECT * FROM crontab` and validates all returned rows against a `ValidationMap`. + +## Validation Rules + +| Field | Validation | +|---|---| +| `event` | One of `""`, `@reboot`, `@hourly`, `@daily`, `@weekly`, `@monthly`, `@annually`, `@yearly` | +| `minute` | Cron values in range `0–59` | +| `hour` | Cron values in range `0–23` | +| `day_of_month` | Cron values in range `1–31` | +| `month` | Cron values `1–31` or named months (`jan`–`dec`) | +| `day_of_week` | Cron values `0–7` or named days (`mon`–`sun`) | +| `command` | Non-empty string | +| `path` | Must be a valid file path on disk | + +## Usage Example + +```cpp +// Run via osquery integration test suite +// Executes internally as: +QueryData data = execute_query("select * from crontab"); +validate_rows(data, row_map); +``` + +The test uses `CronValuesCheck` to handle cron-specific syntax (ranges, wildcards, named values) and `SpecificValuesCheck` for the `event` field's fixed set of special scheduling strings. \ No newline at end of file diff --git a/tests/integration/tables/.cups_destinations.md b/tests/integration/tables/.cups_destinations.md new file mode 100644 index 00000000000..56f873edcd4 --- /dev/null +++ b/tests/integration/tables/.cups_destinations.md @@ -0,0 +1,28 @@ + +Integration test for the `cups_destinations` osquery table, validating that the CUPS (Common Unix Printing System) printer destinations table can be queried without errors on Darwin/macOS systems. + +## Key Components + +- **`cupsDestinations`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(cupsDestinations, test_sanity)`** — Sanity check that executes a `SELECT *` query against the `cups_destinations` table and verifies basic query execution succeeds. + +## Usage Example + +```cpp +// Run the sanity test directly via osquery test runner +// The test executes: SELECT * FROM cups_destinations + +// To enable full validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"name", NormalType}, + {"option_name", NormalType}, + {"option_value", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/darwin/cups_destinations.table`. +- Row count assertions and `ValidationMap` checks are stubbed out — the test currently only verifies the query executes without crashing. +- Extend by uncommenting `ASSERT_GE`/`ASSERT_EQ` size checks and the `ValidationMap` block to enforce stricter validation in environments where CUPS printers are known to exist. \ No newline at end of file diff --git a/tests/integration/tables/.cups_jobs.md b/tests/integration/tables/.cups_jobs.md new file mode 100644 index 00000000000..a7d837fb4bb --- /dev/null +++ b/tests/integration/tables/.cups_jobs.md @@ -0,0 +1,34 @@ + +Integration test file for the `cups_jobs` osquery table, validating that the CUPS (Common Unix Printing System) print job query executes without error on Darwin/macOS systems. + +## Key Components + +- **`cupsJobs`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(cupsJobs, test_sanity)`** — Sanity check that executes `SELECT * FROM cups_jobs` and confirms the query runs successfully. + +## Usage Example + +```cpp +// Run the integration test via osquery's test harness +// The test executes the following query internally: +auto const data = execute_query("select * from cups_jobs"); + +// Commented-out validation map shows expected schema: +// ValidationMap row_map = { +// {"title", NormalType}, +// {"destination", NormalType}, +// {"user", NormalType}, +// {"format", NormalType}, +// {"size", IntType}, +// {"completed_time", IntType}, +// {"processing_time", IntType}, +// {"creation_time", IntType}, +// }; +// validate_rows(data, row_map); +``` + +## Notes + +- The full row count assertions and schema validation (`validate_rows`) are commented out, making this a **minimal smoke test** — it only verifies the query executes without crashing. +- Corresponds to the table spec at `specs/darwin/cups_jobs.table`. +- To enable stricter validation, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)`. \ No newline at end of file diff --git a/tests/integration/tables/.curl.md b/tests/integration/tables/.curl.md new file mode 100644 index 00000000000..7d8f6bd82b9 --- /dev/null +++ b/tests/integration/tables/.curl.md @@ -0,0 +1,32 @@ + +Integration test stub for the `curl` osquery table, validating that the table can be queried without errors using the osquery integration testing framework. + +## Key Components + +- **`curl` (test class)** — Inherits from `testing::Test`; sets up the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(curl, test_sanity)`** — Executes a baseline query against the `curl` table with an empty URL and contains commented-out scaffolding for size assertions and row validation. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test queries the curl virtual table: +auto const data = execute_query("select * from curl where url = ''"); + +// Uncomment to assert expected row count: +// ASSERT_GE(data.size(), 0ul); + +// Uncomment to validate row schema: +// ValidationMap row_map = { +// {"url", NormalType}, +// {"method", NormalType}, +// {"user_agent", NormalType}, +// {"response_code", IntType}, +// {"round_trip_time", IntType}, +// {"bytes", IntType}, +// {"result", NormalType}, +// }; +// validate_rows(data, row_map); +``` + +> **Note:** Size assertions and `validate_rows()` are intentionally commented out. To enable full validation, uncomment the `ValidationMap` block and the appropriate `ASSERT_*` call, then pass both to `validate_rows()`. See `specs/curl.table` for the authoritative column definitions. \ No newline at end of file diff --git a/tests/integration/tables/.curl_certificate.md b/tests/integration/tables/.curl_certificate.md new file mode 100644 index 00000000000..21ac6915fd6 --- /dev/null +++ b/tests/integration/tables/.curl_certificate.md @@ -0,0 +1,41 @@ + +Integration test for the `curl_certificate` osquery table, validating that SSL/TLS certificate data can be retrieved for a given hostname. + +## Key Components + +- **`curlCertificate`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that queries the `curl_certificate` table for `www.github.com` and validates the result schema. + +## What It Tests + +Executes the query: + +```cpp +select * from curl_certificate where hostname = 'www.github.com' +``` + +Asserts exactly one row is returned, then validates all expected columns against a `ValidationMap`: + +| Column | Type | +|---|---| +| `hostname`, `common_name`, `organization`, `organization_unit` | `NormalType` | +| `serial_number`, `issuer_*`, `valid_from`, `valid_to` | `NormalType` | +| `sha256_fingerprint`, `sha1_fingerprint` | `NormalType` | +| `version`, `has_expired` | `IntType` | +| `signature_algorithm`, `signature`, `key_usage`, `extended_key_usage` | `NormalType` | +| `policies`, `*_alternative_names`, `*_access`, `policy_*` | `NormalType` | +| `basic_constraint`, `name_constraints`, `policy_constraints`, `pem` | `NormalType` | + +## Usage Example + +```cpp +// Run via osquery integration test harness +TEST_F(curlCertificate, test_sanity) { + auto const data = execute_query( + "select * from curl_certificate where hostname = 'www.github.com'"); + ASSERT_EQ(data.size(), 1ul); + validate_rows(data, row_map); +} +``` + +This test requires network access to reach `www.github.com` and is part of the broader osquery integration test suite found under `osquery/tests/integration/tables/`. \ No newline at end of file diff --git a/tests/integration/tables/.deb_packages.md b/tests/integration/tables/.deb_packages.md new file mode 100644 index 00000000000..8f76a924e0a --- /dev/null +++ b/tests/integration/tables/.deb_packages.md @@ -0,0 +1,44 @@ + +Integration test for the `deb_packages` osquery table, validating that Debian package data is correctly queried and structured on systems with `dpkg` installed. + +## Key Components + +- **`DebPackages`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(DebPackages, test_sanity)`** — Primary sanity check that: + - Executes `SELECT * FROM deb_packages` + - Validates row schema using a `ValidationMap` + - Asserts the `dpkg` package is present in results + - Runs container-row validation on Linux platforms + +## Validated Schema Fields + +| Field | Validation Rule | +|---|---| +| `name` | `NonEmptyString` | +| `version` | `NonEmptyString` | +| `source` | `NormalType` | +| `size` | `IntOrEmpty` | +| `arch` | `NonEmptyString` | +| `revision` | `NormalType` | +| `status` | `NonEmptyString` | +| `maintainer` | `NonEmptyString` | +| `section` | `NormalType` | +| `priority` | `NormalType` | +| `admindir` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run the test directly via osquery test runner +TEST_F(DebPackages, test_sanity) { + QueryData rows = execute_query("select * from deb_packages"); + + // Skips gracefully if dpkg is not installed + if (rows.size() > 0) { + validate_rows(rows, row_map); + ASSERT_EQ(all_packages.count("dpkg"), 1u); + } +} +``` + +> **Note:** If the query returns empty results, the test logs a warning and skips assertions — assuming `dpkg` is not present on the system rather than failing hard. \ No newline at end of file diff --git a/tests/integration/tables/.default_environment.md b/tests/integration/tables/.default_environment.md new file mode 100644 index 00000000000..c5dd32eefda --- /dev/null +++ b/tests/integration/tables/.default_environment.md @@ -0,0 +1,41 @@ + +Integration test for the `default_environment` osquery table, validating that Windows default environment variable data is correctly queried and conforms to the expected schema. + +## Key Components + +- **`defaultEnvironment`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single sanity test that executes a `SELECT *` against the `default_environment` table and validates all returned rows against a defined schema map. +- **`ValidationMap`** — Defines the expected column types: + | Column | Type | + |---|---| + | `variable` | `NonEmptyString` | + | `value` | `NormalType` | + | `expand` | `IntType` | + +## Usage Example + +```cpp +// Run the integration test via osquery test runner: +// Covers: specs/windows/default_environment.table + +TEST_F(defaultEnvironment, test_sanity) { + auto const data = execute_query("select * from default_environment"); + + // Table may return zero or more rows + ASSERT_GE(data.size(), 0ul); + + ValidationMap row_map = { + {"variable", NonEmptyString}, // e.g. "PATH", "TEMP" + {"value", NormalType}, // the variable's value string + {"expand", IntType}, // whether the value is expandable + }; + + validate_rows(data, row_map); +} +``` + +## Notes + +- **Windows only** — This test targets the `default_environment` table defined in `specs/windows/`. +- The `ASSERT_GE(data.size(), 0ul)` check permits an empty result set, making the test resilient on systems where no default environment entries are present. +- Part of osquery's standard integration test suite; run alongside other table tests using the osquery integration test harness. \ No newline at end of file diff --git a/tests/integration/tables/.device_file.md b/tests/integration/tables/.device_file.md new file mode 100644 index 00000000000..e8508aef7d0 --- /dev/null +++ b/tests/integration/tables/.device_file.md @@ -0,0 +1,42 @@ + +Sanity check integration test for the `device_file` osquery table, validating that queries against the table execute without errors when given empty device and partition parameters. + +## Key Components + +- **`deviceFile`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(deviceFile, test_sanity)`** — Basic integration test that executes a query against the `device_file` table with empty `device` and `partition` filter values. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test executes the following query internally: +auto const data = execute_query( + "select * from device_file where device = '' and partition = ''"); + +// Extend the test by uncommenting and configuring the validation map: +ValidationMap row_map = { + {"device", NormalType}, + {"partition", NormalType}, + {"path", NormalType}, + {"filename", NormalType}, + {"inode", IntType}, + {"uid", IntType}, + {"gid", IntType}, + {"mode", NormalType}, + {"size", IntType}, + {"block_size", IntType}, + {"atime", IntType}, + {"mtime", IntType}, + {"ctime", IntType}, + {"hard_links", IntType}, + {"type", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- The validation map and row count assertions are **commented out**, meaning the test currently only checks that the query runs without crashing — not that the returned data is valid. +- Corresponds to the table spec at `specs/sleuthkit/device_file.table`. +- Full validation can be enabled by uncommenting the `ValidationMap` block and calling `validate_rows()`. \ No newline at end of file diff --git a/tests/integration/tables/.device_firmware.md b/tests/integration/tables/.device_firmware.md new file mode 100644 index 00000000000..79afc906bc8 --- /dev/null +++ b/tests/integration/tables/.device_firmware.md @@ -0,0 +1,27 @@ + +Integration sanity test for the `device_firmware` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`deviceFirmware`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM device_firmware` and provides a scaffold for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test (typical osquery integration test invocation) +// The test queries the table and can be extended with validation: + +ValidationMap row_map = { + {"type", NormalType}, + {"device", NormalType}, + {"version", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets **Darwin only** — spec defined in `specs/darwin/device_firmware.table`. +- Row count assertions and schema validation are commented out, marking this as a minimal scaffold test. +- Extend by uncommenting `ASSERT_GE` / `ASSERT_EQ` calls and populating the `ValidationMap` to enforce expected column types and row counts. \ No newline at end of file diff --git a/tests/integration/tables/.device_hash.md b/tests/integration/tables/.device_hash.md new file mode 100644 index 00000000000..bb8b17b2454 --- /dev/null +++ b/tests/integration/tables/.device_hash.md @@ -0,0 +1,34 @@ + +Integration test sanity check for the `device_hash` osquery table, verifying basic query execution against the Sleuthkit-based table spec. + +## Key Components + +- **`deviceHash`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(deviceHash, test_sanity)`** — Executes a parameterized query against `device_hash` with empty `device`, `partition`, and `inode` filters to validate the table is queryable without errors. + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the device_hash table with empty parameters: +auto const data = execute_query( + "select * from device_hash where device = '' and partition = '' and " + "inode = ''"); + +// Validation scaffolding (currently commented out) would verify: +// ValidationMap row_map = { +// {"device", NormalType}, +// {"partition", NormalType}, +// {"inode", IntType}, +// {"md5", NormalType}, +// {"sha1", NormalType}, +// {"sha256", NormalType}, +// }; +// validate_rows(data, row_map); +``` + +## Notes + +- Validation logic (row count assertions and `validate_rows`) is **commented out**, meaning the test currently only confirms the query runs without crashing. +- Corresponds to the table spec at `specs/sleuthkit/device_hash.table`. +- Expected columns when fully validated: `device`, `partition`, `inode`, `md5`, `sha1`, `sha256`. \ No newline at end of file diff --git a/tests/integration/tables/.device_partitions.md b/tests/integration/tables/.device_partitions.md new file mode 100644 index 00000000000..1565ebda770 --- /dev/null +++ b/tests/integration/tables/.device_partitions.md @@ -0,0 +1,42 @@ + +Integration test file for the `device_partitions` osquery table, providing a sanity check that validates query execution against the table spec. + +## Key Components + +- **`devicePartitions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that executes a query against `device_partitions` with an empty device filter. Validation logic (row count assertions and schema checks) is scaffolded but commented out. + +## Commented-Out Validation Schema + +The file documents the expected `device_partitions` table schema for future validation: + +| Column | Type | +|---|---| +| `device` | NormalType | +| `partition` | IntType | +| `label` | NormalType | +| `type` | NormalType | +| `offset` | IntType | +| `blocks_size` | IntType | +| `blocks` | IntType | +| `inodes` | IntType | +| `flags` | IntType | + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries device_partitions with an empty device filter: +auto const data = + execute_query("select * from device_partitions where device = ''"); + +// To enable full validation, uncomment and configure: +ValidationMap row_map = { + {"device", NormalType}, + {"partition", IntType}, + {"flags", IntType} +}; +validate_rows(data, row_map); +``` + +> **Note:** The test currently only verifies that the query executes without error. Row count assertions and schema validation via `validate_rows` are scaffolded but disabled, making this a minimal smoke test for the `device_partitions` table integration. \ No newline at end of file diff --git a/tests/integration/tables/.deviceguard_status.md b/tests/integration/tables/.deviceguard_status.md new file mode 100644 index 00000000000..28613e9891b --- /dev/null +++ b/tests/integration/tables/.deviceguard_status.md @@ -0,0 +1,43 @@ + +Integration test for the `deviceguard_status` osquery table, validating that the Windows Device Guard status query returns well-formed rows with expected fields. + +## Key Components + +- **`DeviceGuardStatus`** — Test fixture inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(DeviceGuardStatus, test_sanity)`** — Sanity test that executes `SELECT * FROM deviceguard_status`, asserts at least one row is returned, and validates field types against a `ValidationMap`. + +### Validated Fields + +| Field | Constraint | +|---|---| +| `version` | `NonEmptyString` | +| `instance_identifier` | `NormalType` | +| `vbs_status` | `NonEmptyString` | +| `code_integrity_policy_enforcement_status` | `NonEmptyString` | +| `umci_policy_status` | `NonEmptyString` | +| `configured_security_services` | `NonEmptyString` | +| `running_security_services` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run this test via the osquery integration test harness (Windows only). +// Corresponds to: specs/windows/deviceguard_status.table + +TEST_F(DeviceGuardStatus, test_sanity) { + QueryData data = execute_query("select * from deviceguard_status"); + + // Expect at least one result row + ASSERT_GE(data.size(), 1ul); + + // Validate each row matches expected schema types + ValidationMap row_map = { + {"vbs_status", NonEmptyString}, + {"running_security_services", NonEmptyString}, + // ... + }; + validate_rows(data, row_map); +} +``` + +> **Platform:** Windows only. The corresponding table spec is located at `specs/windows/deviceguard_status.table`. \ No newline at end of file diff --git a/tests/integration/tables/.disk_encryption.md b/tests/integration/tables/.disk_encryption.md new file mode 100644 index 00000000000..f05b2e0fcde --- /dev/null +++ b/tests/integration/tables/.disk_encryption.md @@ -0,0 +1,34 @@ + +Integration test file for the `disk_encryption` osquery table, providing a sanity check to verify the table can be queried without errors on POSIX systems. + +## Key Components + +- **`diskEncryption`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM disk_encryption` and contains scaffolding for row count assertions and schema validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test against the disk_encryption table +TEST_F(diskEncryption, test_sanity) { + auto const data = execute_query("select * from disk_encryption"); + + // Uncomment to enable row validation against expected schema: + // ValidationMap row_map = { + // {"name", NormalType}, + // {"uuid", NormalType}, + // {"encrypted", IntType}, + // {"type", NormalType}, + // {"uid", NormalType}, + // {"user_uuid", NormalType}, + // {"encryption_status", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/disk_encryption.table`. +- Row count assertions and `validate_rows()` calls are scaffolded but commented out — enable them to enforce expected row counts and column types. +- See `osquery/tests/integration/tables/helper.h` for available validation flags (`NormalType`, `IntType`, etc.) and the `ValidationMap` API. \ No newline at end of file diff --git a/tests/integration/tables/.disk_events.md b/tests/integration/tables/.disk_events.md new file mode 100644 index 00000000000..90e0ca4b60e --- /dev/null +++ b/tests/integration/tables/.disk_events.md @@ -0,0 +1,36 @@ + +Integration test file for the `disk_events` osquery table on Darwin (macOS), verifying basic query execution as a sanity check. + +## Key Components + +- **`diskEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(diskEvents, test_sanity)`** — Executes a `SELECT *` query against the `disk_events` table and provides a scaffolded (currently inactive) validation structure. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test executes the following query internally: +auto const data = execute_query("select * from disk_events"); + +// To enable full row validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"action", NormalType}, + {"path", NormalType}, + {"device", NormalType}, + {"uuid", NormalType}, + {"size", IntType}, + {"ejectable", IntType}, + {"writable", IntType}, + {"filesystem", NormalType}, + {"time", IntType}, + {"eid", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets **Darwin only** (`specs/darwin/disk_events.table`). +- Row count assertions and `validate_rows()` calls are commented out — the test currently only confirms the query runs without error. +- Expected columns include: `action`, `path`, `name`, `device`, `uuid`, `size`, `ejectable`, `mountable`, `writable`, `content`, `media_name`, `vendor`, `filesystem`, `checksum`, `time`, and `eid`. \ No newline at end of file diff --git a/tests/integration/tables/.disk_info.md b/tests/integration/tables/.disk_info.md new file mode 100644 index 00000000000..a8ab15e6311 --- /dev/null +++ b/tests/integration/tables/.disk_info.md @@ -0,0 +1,43 @@ + +Integration test that validates the `disk_info` osquery table returns well-formed disk information on Windows systems. + +## Key Components + +- **`diskInfo`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that queries the `disk_info` table and validates row structure and value constraints. +- **`ValidationMap`** — Defines per-column validation rules applied to every returned row. + +### Validated Columns + +| Column | Constraint | +|---|---| +| `partitions` | Non-negative integer | +| `disk_index` | Non-negative integer | +| `type` | One of: `SCSI`, `HDC`, `IDE`, `USB`, `1394` | +| `id` | Non-empty string | +| `pnp_device_id` | Non-empty string | +| `disk_size` | Non-negative, non-zero integer | +| `manufacturer` | Normal type | +| `hardware_model` | Normal type | +| `name` | Normal type | +| `serial` | Normal type | +| `description` | Normal type | + +## Usage Example + +```cpp +// Run via osquery integration test harness (Windows only) +// Spec: specs/windows/disk_info.table + +TEST_F(diskInfo, test_sanity) { + auto const data = execute_query("select * from disk_info"); + + // At least one physical disk must be present + ASSERT_GE(data.size(), 1ul); + + // Validate all rows conform to expected types and values + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets Windows exclusively, as reflected by its spec path (`specs/windows/disk_info.table`). It asserts at least one disk is present (`size >= 1`) and that the `type` field is restricted to known Windows disk interface strings. \ No newline at end of file diff --git a/tests/integration/tables/.dns_lookup_events.md b/tests/integration/tables/.dns_lookup_events.md new file mode 100644 index 00000000000..36fd8d5004c --- /dev/null +++ b/tests/integration/tables/.dns_lookup_events.md @@ -0,0 +1,50 @@ + +Sanity check integration test for the `dns_lookup_events` osquery virtual table, verifying that DNS lookup activity is correctly captured and validated via the ETW (Event Tracing for Windows) eventing framework. + +## Key Components + +### `dnsLookupEvents` (Test Fixture) +- **`SetUp()`** — Initializes the test environment, enables the `FLAGS_enable_dns_lookup_events` flag, registers config parsers, and starts the eventing framework via `attachEvents()`. +- **`TearDown()`** — Gracefully stops and joins dispatcher services after each test. + +### `TEST_F(dnsLookupEvents, test_sanity)` +Core integration test that: +1. Triggers DNS lookups using `ping` against both an invalid hostname (`hostname.invalid`) and `localhost`. +2. Waits 4 seconds for events to propagate. +3. Queries the `dns_lookup_events` virtual table. +4. Validates all rows against a `ValidationMap` schema. +5. Performs specific assertions on failed A/AAAA records and successful `localhost` resolution. + +## Usage Example + +```cpp +// Run this integration test via the osquery test runner: +// (from build directory) +``` + +```bash +# Build and run the integration test +cmake --build . --target osquery_integration_tests +./osquery_integration_tests --gtest_filter="dnsLookupEvents.test_sanity" +``` + +## Validation Schema + +```text +Column Type / Constraint +───────────────────────────────────────────── +eid NonNegativeInt +time NonNegativeInt +time_windows NonNegativeInt +datetime NonNegativeInt +pid NonNegativeInt +path FileOnDisk | EmptyOk | NullOk +username NonEmptyString +name NonEmptyString +type NonEmptyString (A, AAAA, etc.) +type_id NonNegativeInt +status NonEmptyString (0 = success) +response NonEmptyString | EmptyOk | NullOk +``` + +> **Note:** Specific row assertions (failed lookups returning status `87`/`9003`, successful `localhost` returning status `0`) are guarded with `if` checks to accommodate CI environments where DNS resolution may be unreliable. \ No newline at end of file diff --git a/tests/integration/tables/.dns_resolvers.md b/tests/integration/tables/.dns_resolvers.md new file mode 100644 index 00000000000..bde2786a296 --- /dev/null +++ b/tests/integration/tables/.dns_resolvers.md @@ -0,0 +1,35 @@ + +Integration test file for the `dns_resolvers` osquery table, validating that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`dnsResolvers`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic sanity test that executes a `SELECT *` query against the `dns_resolvers` table to confirm it runs without crashing. + +## Usage Example + +```cpp +// Run the sanity test against the dns_resolvers table +TEST_F(dnsResolvers, test_sanity) { + auto const data = execute_query("select * from dns_resolvers"); + + // Optional: uncomment to enforce row count expectations + // ASSERT_GE(data.size(), 0ul); + + // Optional: uncomment to validate column types + // ValidationMap row_map = { + // {"id", IntType}, + // {"type", NormalType}, + // {"address", NormalType}, + // {"netmask", NormalType}, + // {"options", IntType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Spec file: `specs/posix/dns_resolvers.table` +- Row count assertions and column-level `ValidationMap` checks are scaffolded but commented out — enable them to strengthen coverage. +- Relies on the shared test helper at `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants (`IntType`, `NormalType`). \ No newline at end of file diff --git a/tests/integration/tables/.docker_container_envs.md b/tests/integration/tables/.docker_container_envs.md new file mode 100644 index 00000000000..96f2ea6fa81 --- /dev/null +++ b/tests/integration/tables/.docker_container_envs.md @@ -0,0 +1,28 @@ + +Integration sanity test for the `docker_container_envs` osquery table, verifying basic query execution against Docker container environment variables. + +## Key Components + +- **`dockerContainerEnvs`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes `SELECT * FROM docker_container_envs` and provides scaffolding for row count assertions and column validation. + +## Usage Example + +```cpp +// Run the sanity test via osquery integration test runner +// The test queries the docker_container_envs table and can be extended +// to validate expected columns: + +ValidationMap row_map = { + {"id", NormalType}, + {"key", NormalType}, + {"value", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/docker_container_envs.table`. +- Row count assertions and `validate_rows()` calls are intentionally commented out — enable them once a stable Docker environment is guaranteed in CI. +- Relies on `osquery/tests/integration/tables/helper.h` for query execution utilities (`execute_query`, `validate_rows`) and validation flag constants (`NormalType`). \ No newline at end of file diff --git a/tests/integration/tables/.docker_container_labels.md b/tests/integration/tables/.docker_container_labels.md new file mode 100644 index 00000000000..4a2ac11790b --- /dev/null +++ b/tests/integration/tables/.docker_container_labels.md @@ -0,0 +1,30 @@ + +Integration sanity test for the `docker_container_labels` osquery table, verifying the table can be queried without errors as part of the osquery test suite. + +## Key Components + +- **`dockerContainerLabels`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerContainerLabels, test_sanity)`** — Single test case that executes a `SELECT *` query against the `docker_container_labels` table and provides scaffolding for row validation. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test executes: +auto const data = execute_query("select * from docker_container_labels"); + +// To enable full row validation, uncomment and configure: +ValidationMap row_map = { + {"id", NormalType}, + {"key", NormalType}, + {"value", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/docker_container_labels.table`. +- Size assertions and `ValidationMap` checks are currently commented out — activate them to enforce expected row counts and column types. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and flag constants. +- Requires a live Docker environment with running containers to return non-empty results. \ No newline at end of file diff --git a/tests/integration/tables/.docker_container_mounts.md b/tests/integration/tables/.docker_container_mounts.md new file mode 100644 index 00000000000..eb2c1673455 --- /dev/null +++ b/tests/integration/tables/.docker_container_mounts.md @@ -0,0 +1,35 @@ + +Integration test file for the `docker_container_mounts` osquery table, providing a sanity check that validates query execution against the table schema. + +## Key Components + +- **`dockerContainerMounts`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that executes a `SELECT *` query against the `docker_container_mounts` table and provides a scaffolded validation structure. + +## Usage Example + +```cpp +// Run the sanity test (executes internally via osquery test runner) +// The test queries the docker_container_mounts table: +auto const data = execute_query("select * from docker_container_mounts"); + +// To enable full row validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"id", NormalType}, + {"type", NormalType}, + {"name", NormalType}, + {"source", NormalType}, + {"destination", NormalType}, + {"driver", NormalType}, + {"mode", NormalType}, + {"rw", IntType}, + {"propagation", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/docker_container_mounts.table`. +- Row count assertions and `validate_rows()` are intentionally commented out, allowing the test to pass even when no Docker containers are running. +- Validation helpers (`NormalType`, `IntType`, `validate_rows`) are sourced from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.docker_container_networks.md b/tests/integration/tables/.docker_container_networks.md new file mode 100644 index 00000000000..54dc20e67ec --- /dev/null +++ b/tests/integration/tables/.docker_container_networks.md @@ -0,0 +1,38 @@ + +Integration test file for the `docker_container_networks` osquery table, providing a sanity check that verifies the table can be queried without errors. + +## Key Components + +- **`dockerContainerNetworks`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` query against the `docker_container_networks` table to confirm it is queryable. + +## Usage Example + +```cpp +// Run the sanity test against the docker_container_networks table +TEST_F(dockerContainerNetworks, test_sanity) { + auto const data = execute_query("select * from docker_container_networks"); + + // Optionally validate row structure using a ValidationMap: + ValidationMap row_map = { + {"id", NormalType}, + {"name", NormalType}, + {"network_id", NormalType}, + {"endpoint_id", NormalType}, + {"gateway", NormalType}, + {"ip_address", NormalType}, + {"ip_prefix_len", IntType}, + {"ipv6_gateway", NormalType}, + {"ipv6_address", NormalType}, + {"ipv6_prefix_len", IntType}, + {"mac_address", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- The full row validation and size assertions are commented out, leaving only the query execution as the active check — typical for tables where Docker may not be present in all CI environments. +- Corresponds to the table spec at `specs/posix/docker_container_networks.table`. +- Relies on shared test utilities from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.docker_container_ports.md b/tests/integration/tables/.docker_container_ports.md new file mode 100644 index 00000000000..53cf9e8a5e3 --- /dev/null +++ b/tests/integration/tables/.docker_container_ports.md @@ -0,0 +1,32 @@ + +Integration sanity test for the `docker_container_ports` osquery table, verifying that the table can be queried without errors as part of the osquery test suite. + +## Key Components + +- **`dockerContainerPorts`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM docker_container_ports` and provides a scaffolded structure for row count assertions and column validation. + +## Usage Example + +```cpp +// Run the sanity test against a live Docker environment +TEST_F(dockerContainerPorts, test_sanity) { + auto const data = execute_query("select * from docker_container_ports"); + + // Uncomment and configure validation as needed: + ValidationMap row_map = { + {"id", NormalType}, + {"type", NormalType}, + {"port", IntType}, + {"host_ip", NormalType}, + {"host_port", IntType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/docker_container_ports.table`. +- Row count assertions and `validate_rows()` calls are intentionally commented out — enable them based on the target environment's Docker state. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants (`NormalType`, `IntType`). \ No newline at end of file diff --git a/tests/integration/tables/.docker_container_processes.md b/tests/integration/tables/.docker_container_processes.md new file mode 100644 index 00000000000..0ef8b9a2c1c --- /dev/null +++ b/tests/integration/tables/.docker_container_processes.md @@ -0,0 +1,36 @@ + +Integration test file for the `docker_container_processes` osquery table, validating that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`dockerContainerProcesses`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerContainerProcesses, test_sanity)`** — Sanity check that executes a query against `docker_container_processes` with an empty `id` filter, confirming the table is queryable without crashing. + +## Usage Example + +```cpp +// Run the sanity test (executed via the osquery integration test runner) +// Queries the table with a no-match filter to safely validate schema: +auto const data = + execute_query("select * from docker_container_processes where id = ''"); + +// Full validation (currently commented out) would assert column types: +// ValidationMap row_map = { +// {"id", NormalType}, +// {"pid", IntType}, +// {"name", NormalType}, +// {"cmdline", NormalType}, +// {"uid", IntType}, +// {"gid", IntType}, +// {"threads", IntType}, +// {"cpu", NormalType}, +// // ... additional columns +// }; +// validate_rows(data, row_map); +``` + +## Notes + +- The validation map and row/size assertions are **commented out**, meaning this test only verifies the query executes without errors — it does not assert result counts or column types. +- Targets the table spec at `specs/posix/docker_container_processes.table`. +- Depends on `osquery/tests/integration/tables/helper.h` for `execute_query` and `validate_rows` utilities. \ No newline at end of file diff --git a/tests/integration/tables/.docker_container_stats.md b/tests/integration/tables/.docker_container_stats.md new file mode 100644 index 00000000000..28e305cd1d6 --- /dev/null +++ b/tests/integration/tables/.docker_container_stats.md @@ -0,0 +1,41 @@ + +Integration sanity test for the `docker_container_stats` osquery table, verifying that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`dockerContainerStats`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerContainerStats, test_sanity)`** — Executes a basic query against `docker_container_stats` filtered by an empty `id`, confirming the table is queryable. Full row validation is scaffolded but commented out. + +## Validated Table Columns (scaffolded) + +The commented `ValidationMap` documents the expected schema: + +| Column | Type | +|---|---| +| `id`, `name` | `NormalType` | +| `pids`, `read`, `preread`, `interval` | `IntType` | +| `disk_read`, `disk_write`, `num_procs` | `IntType` | +| `cpu_total_usage`, `cpu_kernelmode_usage`, `cpu_usermode_usage` | `IntType` | +| `system_cpu_usage`, `online_cpus` | `IntType` | +| `memory_usage`, `memory_cached`, `memory_limit` | `IntType` | +| `network_rx_bytes`, `network_tx_bytes` | `IntType` | + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner: +// The test issues this query internally: +auto const data = + execute_query("select * from docker_container_stats where id = ''"); + +// To enable full validation, uncomment and populate: +ValidationMap row_map = { + {"id", NormalType}, + {"cpu_total_usage", IntType}, + {"memory_usage", IntType}, + // ... +}; +validate_rows(data, row_map); +``` + +> **Note:** Row count assertions and schema validation are intentionally commented out, making this a lightweight smoke test that confirms query execution without requiring a live Docker container. \ No newline at end of file diff --git a/tests/integration/tables/.docker_containers.md b/tests/integration/tables/.docker_containers.md new file mode 100644 index 00000000000..01d70012b74 --- /dev/null +++ b/tests/integration/tables/.docker_containers.md @@ -0,0 +1,32 @@ + +Integration test file for the `docker_containers` osquery table, providing a sanity check to verify the table can be queried without errors on POSIX systems. + +## Key Components + +- **`dockerContainers`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerContainers, test_sanity)`** — Executes a `SELECT *` query against the `docker_containers` virtual table and serves as a scaffold for row-level validation. + +## Usage Example + +```cpp +// Run the sanity test (queries the docker_containers table) +auto const data = execute_query("select * from docker_containers"); + +// To enable full row validation, uncomment and populate the ValidationMap: +ValidationMap row_map = { + {"id", NormalType}, + {"name", NormalType}, + {"image", NormalType}, + {"state", NormalType}, + {"pid", IntType}, + {"privileged", IntType}, + // ...additional columns +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/docker_containers.table`. +- Validation logic (size assertions and `validate_rows`) is commented out, making this a **non-failing smoke test** — useful for CI environments where Docker may not be present. +- Supported columns include container metadata (`id`, `name`, `image`), runtime state (`state`, `status`, `pid`), security settings (`privileged`, `security_options`, `readonly_rootfs`), and namespace isolation fields (`cgroup_namespace`, `net_namespace`, etc.). \ No newline at end of file diff --git a/tests/integration/tables/.docker_image_history.md b/tests/integration/tables/.docker_image_history.md new file mode 100644 index 00000000000..d35311b840d --- /dev/null +++ b/tests/integration/tables/.docker_image_history.md @@ -0,0 +1,36 @@ + +Integration test that validates the `docker_image_history` osquery table returns well-formed data when Docker is available and images are present. + +## Key Components + +- **`dockerImageHistoryTest`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` +- **`test_sanity`** — Core test case that: + 1. Skips gracefully if Docker is unavailable or no images exist (checks `docker_images` first) + 2. Queries `docker_image_history` and asserts at least one row is returned + 3. Validates each row against a `ValidationMap` + +### Validated Columns + +| Column | Validation Rule | +|---|---| +| `id` | `NonEmptyString` | +| `created` | `NonNegativeInt` | +| `size` | `NonNegativeInt` | +| `created_by` | `NormalType` | +| `tags` | `NormalType` | +| `comment` | `NormalType` | + +## Usage Example + +```bash +# Run this specific integration test +osquery_integration_tests --gtest_filter="dockerImageHistoryTest.test_sanity" +``` + +```cpp +// The test exercises these two queries internally: +execute_query("select * from docker_images"); // guard check +execute_query("select * from docker_image_history"); // subject under test +``` + +> **Note:** The test exits early without failure if `docker_images` returns no rows — covering cases where Docker is not installed, `dockerd` is unreachable, or no images are locally present. The spec for this table is defined in `specs/posix/docker_image_history.table`. \ No newline at end of file diff --git a/tests/integration/tables/.docker_image_labels.md b/tests/integration/tables/.docker_image_labels.md new file mode 100644 index 00000000000..6d0abcccb19 --- /dev/null +++ b/tests/integration/tables/.docker_image_labels.md @@ -0,0 +1,33 @@ + +Integration sanity test for the `docker_image_labels` osquery table, verifying basic query execution against the Docker image labels virtual table. + +## Key Components + +- **`dockerImageLabels`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerImageLabels, test_sanity)`** — Executes a `SELECT *` query against the `docker_image_labels` table and provides scaffolding for row count assertions and column validation. + +## Usage Example + +```cpp +// Run the integration test directly via osquery test runner +// Expected table schema being tested: +// docker_image_labels(id TEXT, key TEXT, value TEXT) + +// To enable full validation, uncomment and configure: +ValidationMap row_map = { + {"id", NormalType}, + {"key", NormalType}, + {"value", NormalType} +}; +validate_rows(data, row_map); + +// To assert expected row counts: +ASSERT_GE(data.size(), 0ul); // at least N rows +ASSERT_EQ(data.size(), 1ul); // exactly N rows +``` + +## Notes + +- Spec file reference: `specs/posix/docker_image_labels.table` +- Row count assertions and `validate_rows()` calls are intentionally commented out — activate them when Docker is available in the CI environment or specific label data is expected. +- See `osquery/tests/integration/tables/helper.h` for available validation flags and `DataCheck` utilities. \ No newline at end of file diff --git a/tests/integration/tables/.docker_image_layers.md b/tests/integration/tables/.docker_image_layers.md new file mode 100644 index 00000000000..7054e1751b8 --- /dev/null +++ b/tests/integration/tables/.docker_image_layers.md @@ -0,0 +1,43 @@ + +Integration test that validates the `docker_image_layers` osquery table returns correctly structured data when Docker is available and images are present. + +## Key Components + +- **`dockerImageLayersTest`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()` +- **`test_sanity`** — Single test case that performs a two-phase query validation + +## Test Logic + +```text +1. Query docker_images → if empty (no Docker/no images), skip gracefully +2. Query docker_image_layers → assert at least one row returned +3. Validate each row against the expected schema +``` + +## Validated Schema + +| Column | Validation Rule | +|---|---| +| `id` | Non-empty string | +| `layer_id` | Non-empty string | +| `layer_order` | Non-negative integer | + +## Usage Example + +```cpp +// Run as part of the osquery integration test suite: +// The test gracefully skips if Docker is not installed, +// dockerd is unreachable, or no images exist on the host. + +TEST_F(dockerImageLayersTest, test_sanity) { + QueryData data = execute_query("select * from docker_images"); + if (data.size() <= 0) { + return; // Skip — Docker unavailable or no images + } + // Proceed with layer validation... +} +``` + +> **Note:** This test depends on a live Docker daemon with at least one pulled image. It is safe to run in environments without Docker — it exits early rather than failing. + +**Spec file:** `specs/posix/docker_image_layers.table` \ No newline at end of file diff --git a/tests/integration/tables/.docker_images.md b/tests/integration/tables/.docker_images.md new file mode 100644 index 00000000000..cbc5ee0637b --- /dev/null +++ b/tests/integration/tables/.docker_images.md @@ -0,0 +1,36 @@ + +Integration test file for the `docker_images` osquery table, providing a sanity check to verify the table can be queried without errors. + +## Key Components + +- **`dockerImages`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM docker_images` and provides scaffolding for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test against a live Docker environment +// Extend the validation map to assert column types: + +ValidationMap row_map = { + {"id", NormalType}, + {"created", IntType}, + {"size_bytes", IntType}, + {"tags", NormalType} +}; + +validate_rows(data, row_map); +``` + +To enable stricter checks, uncomment the size assertions and validation map inside `test_sanity`: + +```cpp +ASSERT_GE(data.size(), 0ul); // At least 0 rows returned +validate_rows(data, row_map); // Validate column schema +``` + +## Notes + +- Spec source: `specs/posix/docker_images.table` +- Validation logic is fully stubbed out and commented — intended as a starting template for contributors adding schema enforcement. +- Requires a running Docker daemon on the host for meaningful row data; the test will still pass on systems without Docker since no minimum row count is enforced. \ No newline at end of file diff --git a/tests/integration/tables/.docker_info.md b/tests/integration/tables/.docker_info.md new file mode 100644 index 00000000000..0c02a2ced95 --- /dev/null +++ b/tests/integration/tables/.docker_info.md @@ -0,0 +1,37 @@ + +Integration test file for the `docker_info` osquery table, providing a sanity check that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`dockerInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes `SELECT * FROM docker_info` and provides a commented-out validation scaffold for all expected columns. + +## Commented-Out Validation Map + +The file includes a reference schema (currently disabled) covering all `docker_info` columns: + +| Column | Expected Type | +|---|---| +| `id`, `name`, `os`, `os_type`, `architecture` | `NormalType` | +| `containers`, `containers_running`, `containers_paused`, `containers_stopped` | `IntType` | +| `cpus`, `memory`, `memory_limit`, `swap_limit` | `IntType` | +| `logging_driver`, `cgroup_driver`, `kernel_version`, `storage_driver` | `NormalType` | +| `http_proxy`, `https_proxy`, `no_proxy`, `root_dir`, `server_version` | `NormalType` | + +## Usage Example + +```cpp +// Run this specific test via osquery test runner: +// ./osquery_tests --gtest_filter=dockerInfo.test_sanity + +// To enable full validation, uncomment and activate in test_sanity: +ValidationMap row_map = { + {"id", NormalType}, + {"containers", IntType}, + {"containers_running", IntType}, + // ... remaining columns +}; +validate_rows(data, row_map); +``` + +> **Note:** Full row validation is scaffolded but disabled. Enable it by uncommenting the `ValidationMap` block and the `validate_rows()` call once Docker availability can be guaranteed in the test environment. \ No newline at end of file diff --git a/tests/integration/tables/.docker_network_labels.md b/tests/integration/tables/.docker_network_labels.md new file mode 100644 index 00000000000..eeb72c46bde --- /dev/null +++ b/tests/integration/tables/.docker_network_labels.md @@ -0,0 +1,29 @@ + +Integration sanity test for the `docker_network_labels` osquery table, verifying basic query execution against the Docker network labels data source. + +## Key Components + +- **`dockerNetworkLabels`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerNetworkLabels, test_sanity)`** — Executes a `SELECT *` query against the `docker_network_labels` table to confirm the table is queryable without errors. + +## Usage Example + +```cpp +// Run the integration test (from osquery build directory) +// The test queries the docker_network_labels table and validates results + +// Full validation (currently scaffolded, uncomment to enable): +ValidationMap row_map = { + {"id", NormalType}, + {"key", NormalType}, + {"value", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets **POSIX** platforms; spec defined in `specs/posix/docker_network_labels.table`. +- Row size assertions and `validate_rows()` calls are **commented out** — this is a scaffold test confirming the query runs without crashing, not full schema validation. +- To extend, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)` with appropriate type flags from `helper.h`. +- Requires a running Docker daemon with accessible networks for meaningful data return. \ No newline at end of file diff --git a/tests/integration/tables/.docker_networks.md b/tests/integration/tables/.docker_networks.md new file mode 100644 index 00000000000..ee71c9a7e53 --- /dev/null +++ b/tests/integration/tables/.docker_networks.md @@ -0,0 +1,33 @@ + +Integration sanity test for the `docker_networks` osquery table, verifying basic query execution against the Docker networks data source. + +## Key Components + +- **`dockerNetworks`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerNetworks, test_sanity)`** — Executes `SELECT * FROM docker_networks` and provides a scaffolded validation structure (currently commented out). + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the docker_networks table and can be extended +// with row validation using the commented ValidationMap: + +ValidationMap row_map = { + {"id", NormalType}, + {"name", NormalType}, + {"driver", NormalType}, + {"created", IntType}, + {"enable_ipv6", IntType}, + {"subnet", NormalType}, + {"gateway", NormalType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/docker_networks.table`. +- Size assertions and `validate_rows()` calls are scaffolded but commented out — enable them to enforce row count and schema constraints in CI. +- Requires a running Docker daemon for meaningful data; the test will pass with zero rows if Docker is unavailable. \ No newline at end of file diff --git a/tests/integration/tables/.docker_version.md b/tests/integration/tables/.docker_version.md new file mode 100644 index 00000000000..532bed61db3 --- /dev/null +++ b/tests/integration/tables/.docker_version.md @@ -0,0 +1,36 @@ + +Integration sanity test for the `docker_version` osquery table, verifying that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`dockerVersion`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerVersion, test_sanity)`** — Executes `SELECT * FROM docker_version` and provides a scaffolded (currently commented-out) validation structure for row count assertions and field type checks. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test queries the docker_version table and can be extended +// by uncommenting and configuring the validation map: + +ValidationMap row_map = { + {"version", NormalType}, + {"api_version", NormalType}, + {"min_api_version", NormalType}, + {"git_commit", NormalType}, + {"go_version", NormalType}, + {"os", NormalType}, + {"arch", NormalType}, + {"kernel_version", NormalType}, + {"build_time", NormalType}, +}; +validate_rows(data, row_map); +``` + +> **Note:** The validation logic and row count assertions are commented out by default. Enable them to enforce expected schema and result set size when Docker is present in the test environment. + +## Notes + +- Spec file: `specs/posix/docker_version.table` +- Depends on `osquery/tests/integration/tables/helper.h` for test utilities (`execute_query`, `validate_rows`, `ValidationMap`) +- Docker must be running on the host for meaningful query results \ No newline at end of file diff --git a/tests/integration/tables/.docker_volume_labels.md b/tests/integration/tables/.docker_volume_labels.md new file mode 100644 index 00000000000..fe11f683ea0 --- /dev/null +++ b/tests/integration/tables/.docker_volume_labels.md @@ -0,0 +1,33 @@ + +Integration sanity test for the `docker_volume_labels` osquery table, verifying the table can be queried without errors as part of the osquery test suite. + +## Key Components + +- **`dockerVolumeLabels`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` against the `docker_volume_labels` table to confirm the query runs successfully. + +## Usage Example + +```cpp +// Run the sanity test against the docker_volume_labels table +TEST_F(dockerVolumeLabels, test_sanity) { + auto const data = execute_query("select * from docker_volume_labels"); + + // Example: enable row count assertions as needed + // ASSERT_GE(data.size(), 0ul); + + // Example: enable schema validation + // ValidationMap row_map = { + // {"name", NormalType}, + // {"key", NormalType}, + // {"value", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/docker_volume_labels.table`. +- Row count assertions and `ValidationMap` schema checks are stubbed out and can be uncommented to enforce stricter validation (e.g., expected column types for `name`, `key`, and `value`). +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and related test utilities. \ No newline at end of file diff --git a/tests/integration/tables/.docker_volumes.md b/tests/integration/tables/.docker_volumes.md new file mode 100644 index 00000000000..5df58c0c3d0 --- /dev/null +++ b/tests/integration/tables/.docker_volumes.md @@ -0,0 +1,28 @@ + +Integration sanity test for the `docker_volumes` osquery table, verifying basic query execution against the `docker_volumes` virtual table on POSIX systems. + +## Key Components + +- **`dockerVolumes`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(dockerVolumes, test_sanity)`** — Executes `SELECT * FROM docker_volumes` and provides scaffolding for row count assertions and schema validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// To enable full validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"name", NormalType}, + {"driver", NormalType}, + {"mount_point", NormalType}, + {"type", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/posix/docker_volumes.table` +- Validation logic (row count assertions and `validate_rows`) is scaffolded but commented out — activate by uncommenting and populating the `ValidationMap` as needed. +- Requires a running Docker daemon for meaningful result validation; the test passes even with zero rows returned. \ No newline at end of file diff --git a/tests/integration/tables/.drivers.md b/tests/integration/tables/.drivers.md new file mode 100644 index 00000000000..25e2728ddfd --- /dev/null +++ b/tests/integration/tables/.drivers.md @@ -0,0 +1,39 @@ + +Integration test file for the osquery `drivers` table on Windows, verifying that the table can be queried without errors. + +## Key Components + +- **`drivers` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(drivers, test_sanity)`** — Executes `SELECT * FROM drivers` and provides a scaffolded structure for size assertions and row validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test against the drivers table +auto const data = execute_query("select * from drivers"); + +// Uncomment and configure validation as needed: +ValidationMap row_map = { + {"device_id", NormalType}, + {"device_name", NormalType}, + {"image", NormalType}, + {"description", NormalType}, + {"service", NormalType}, + {"service_key", NormalType}, + {"version", NormalType}, + {"inf", NormalType}, + {"class", NormalType}, + {"provider", NormalType}, + {"manufacturer", NormalType}, + {"driver_key", NormalType}, + {"date", IntType}, + {"signed", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Windows `drivers` table (spec: `specs/windows/drivers.table`). +- Size assertions and row validation are scaffolded but **commented out** — enable them as the table implementation matures. +- Relies on helpers from `osquery/tests/integration/tables/helper.h` for query execution and validation utilities. \ No newline at end of file diff --git a/tests/integration/tables/.ec2_instance_metadata.md b/tests/integration/tables/.ec2_instance_metadata.md new file mode 100644 index 00000000000..72affccc030 --- /dev/null +++ b/tests/integration/tables/.ec2_instance_metadata.md @@ -0,0 +1,34 @@ + +Integration test file for the `ec2_instance_metadata` osquery table, validating that the table can be queried without errors on both EC2 and non-EC2 environments. + +## Key Components + +- **`ec2InstanceMetadata`** — Google Test fixture class that initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Sanity test that executes a `SELECT *` query against the `ec2_instance_metadata` table. Reduces IMDSv2 retry interval and attempts to 1 to avoid long timeouts when running outside of AWS EC2. + +## Usage Example + +```cpp +// Run the integration test via osquery's test harness +// Flags are tuned for fast execution outside EC2: +FLAGS_aws_imdsv2_request_interval = 1; +FLAGS_aws_imdsv2_request_attempts = 1; + +auto const data = execute_query("select * from ec2_instance_metadata"); + +// Optional row validation (currently commented out): +// ValidationMap row_map = { +// {"instance_id", NormalType}, +// {"instance_type", NormalType}, +// {"region", NormalType}, +// {"availability_zone", NormalType}, +// {"local_ipv4", NormalType}, +// }; +// validate_rows(data, row_map); +``` + +## Notes + +- Full row validation is scaffolded but commented out — the test currently only asserts that the query executes without crashing. +- Corresponds to the table spec at `specs/linux/ec2_instance_metadata.table`. +- The IMDSv2 flags (`aws_imdsv2_request_interval`, `aws_imdsv2_request_attempts`) are declared externally and overridden here purely for test performance. \ No newline at end of file diff --git a/tests/integration/tables/.ec2_instance_tags.md b/tests/integration/tables/.ec2_instance_tags.md new file mode 100644 index 00000000000..f48d2f8df67 --- /dev/null +++ b/tests/integration/tables/.ec2_instance_tags.md @@ -0,0 +1,29 @@ + +Integration sanity test for the `ec2_instance_tags` osquery table, verifying the table can be queried without errors on Linux EC2 instances. + +## Key Components + +- **`ec2InstanceTags`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` against the `ec2_instance_tags` table to confirm query execution succeeds. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test executes the following query internally: +auto const data = execute_query("select * from ec2_instance_tags"); + +// To enable full row validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"instance_id", NormalType}, + {"key", NormalType}, + {"value", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/linux/ec2_instance_tags.table`. +- Row count assertions and `ValidationMap` checks are scaffolded but commented out — activate them to enforce stricter data validation when running against a real EC2 environment. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and available flag constants (`NormalType`, etc.). \ No newline at end of file diff --git a/tests/integration/tables/.etc_hosts.md b/tests/integration/tables/.etc_hosts.md new file mode 100644 index 00000000000..35ef1cfa816 --- /dev/null +++ b/tests/integration/tables/.etc_hosts.md @@ -0,0 +1,29 @@ + +Integration test file for the `etc_hosts` osquery table, providing a sanity check to verify the table can be queried without errors. + +## Key Components + +- **`etcHosts`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM etc_hosts` and provides scaffolding for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test against the etc_hosts table +// Extends the test to validate specific columns: + +ValidationMap row_map = { + {"address", NormalType}, + {"hostnames", NormalType} +}; +validate_rows(data, row_map); + +// Optionally assert expected row counts: +ASSERT_GE(data.size(), 1ul); +``` + +## Notes + +- Spec file reference: `specs/etc_hosts.table` +- Row count assertions and `ValidationMap` checks are stubbed out and can be uncommented to enforce stricter validation. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.etc_protocols.md b/tests/integration/tables/.etc_protocols.md new file mode 100644 index 00000000000..3df21f920a6 --- /dev/null +++ b/tests/integration/tables/.etc_protocols.md @@ -0,0 +1,36 @@ + +Integration test for the `etc_protocols` osquery table, validating that protocol entries from the system's `/etc/protocols` file are correctly parsed and returned with expected data types and constraints. + +## Key Components + +- **`EtcProtocolsTest`** — Test fixture class extending `testing::Test`, initializes the osquery environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that queries the `etc_protocols` virtual table and validates all returned rows against a schema map. + +## Validation Schema + +| Column | Constraint | +|--------|-----------| +| `name` | `NonEmptyString` | +| `number` | `NonNegativeInt` | +| `alias` | `NonNull` | +| `comment` | `NormalType` | + +## Usage Example + +```cpp +// Runs as part of the osquery integration test suite +// Executes the following query internally: +auto const rows = execute_query("select * from etc_protocols"); + +// Expected output columns per row: +// name → e.g., "tcp" +// number → e.g., 6 +// alias → e.g., "TCP" +// comment → e.g., "transmission control protocol" +``` + +## Notes + +- Corresponds to the table spec at `specs/etc_protocols.table` +- Relies on shared integration test helpers from `osquery/tests/integration/tables/helper.h` +- The test is cross-platform but primarily targets Unix-like systems where `/etc/protocols` is present \ No newline at end of file diff --git a/tests/integration/tables/.etc_services.md b/tests/integration/tables/.etc_services.md new file mode 100644 index 00000000000..8b6b99d1d4f --- /dev/null +++ b/tests/integration/tables/.etc_services.md @@ -0,0 +1,27 @@ + +Integration test stub for the `etc_services` osquery table, verifying basic query execution against the system's `/etc/services` file data. + +## Key Components + +- **`etcServices`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes `SELECT * FROM etc_services` and provides a scaffolded structure for row count assertions and schema validation. + +## Usage Example + +```cpp +// Extend the test with full validation by uncommenting and completing: +ValidationMap row_map = { + {"name", NormalType}, + {"port", IntType}, + {"protocol", NormalType}, + {"aliases", NormalType}, + {"comment", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to spec file: `specs/etc_services.table` +- Row count assertions and `validate_rows()` calls are stubbed out — enable them to enforce expected schema and data presence +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h` \ No newline at end of file diff --git a/tests/integration/tables/.etw_process_events.md b/tests/integration/tables/.etw_process_events.md new file mode 100644 index 00000000000..eac14253b44 --- /dev/null +++ b/tests/integration/tables/.etw_process_events.md @@ -0,0 +1,38 @@ + +ETW (Event Tracing for Windows) integration test for the `process_etw_events` osquery table. Validates that process start/stop events are correctly captured and structured via the Windows ETW eventing framework. + +## Key Components + +- **`etwProcessEvents`** — Test fixture (`testing::Test`) that initializes the ETW eventing environment, enables the `enable_process_etw_events` flag, and cleanly tears down the dispatcher after each test. +- **`test_sanity`** — Single integration test that triggers real process events, queries the `process_etw_events` table, and validates all returned rows against a typed schema map. +- **`ValidationMap`** — Schema descriptor mapping each column to a validator (`NonEmptyString`, `NonNegativeInt`, `EmptyOk | NullOk`) to enforce data integrity. + +## Usage Example + +```cpp +// Run the integration test (requires Windows with ETW support) +// From the osquery build directory: + +// Build and run the test binary targeting this fixture: +// osquery_tests --gtest_filter="etwProcessEvents.test_sanity" + +// Internally, the test triggers ETW events via: +system("logman.exe query -ets > NUL"); +Sleep(4000); // Allow ETW pipeline to flush events + +// Then validates captured rows match expected schema: +ValidationMap row_map = { + {"type", NonEmptyString}, + {"pid", NonNegativeInt}, + {"exit_code", EmptyOk | NullOk}, + // ... additional columns +}; +validate_rows(data, row_map); +``` + +## Notes + +- Requires `FLAGS_enable_process_etw_events = true` at runtime. +- The 4-second `Sleep` accounts for ETW event pipeline latency before querying. +- `exit_code`, `cmdline`, and similar optional fields allow null/empty values since they may not be populated for all event types (e.g., `ProcessStart` vs `ProcessStop`). +- Windows-only; ETW is not available on POSIX platforms. \ No newline at end of file diff --git a/tests/integration/tables/.event_taps.md b/tests/integration/tables/.event_taps.md new file mode 100644 index 00000000000..7d669d53f94 --- /dev/null +++ b/tests/integration/tables/.event_taps.md @@ -0,0 +1,29 @@ + +Integration sanity test for the `event_taps` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`eventTaps`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(eventTaps, test_sanity)`** — Executes a `SELECT * FROM event_taps` query and provides a scaffolded (currently commented-out) validation map for expected column types. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// To enable full validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"enabled", IntType}, + {"event_tap_id", IntType}, + {"event_tapped", NormalType}, + {"process_being_tapped", IntType}, + {"tapping_process", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/darwin/event_taps.table` +- Size assertions and row validation are scaffolded but **commented out** — enable them when deterministic row counts or stricter validation are needed. +- Follows the standard osquery integration test pattern: query → size check → validate schema. \ No newline at end of file diff --git a/tests/integration/tables/.example.md b/tests/integration/tables/.example.md new file mode 100644 index 00000000000..b8b1eec4235 --- /dev/null +++ b/tests/integration/tables/.example.md @@ -0,0 +1,41 @@ + +Sanity check integration test for the `example` osquery virtual table, verifying basic query execution against the `example` table spec. + +## Key Components + +- **`example` (test class)** — Inherits from `testing::Test`; initializes the osquery test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(example, test_sanity)`** — Executes `SELECT * FROM example` and provides a scaffolded structure for size assertions and row validation (currently commented out). + +## Usage Example + +```cpp +// Extend the test with row validation: +TEST_F(example, test_sanity) { + auto const data = execute_query("select * from example"); + + ASSERT_GE(data.size(), 1ul); + + ValidationMap row_map = { + {"name", NormalType}, + {"points", IntType}, + {"size", IntType}, + {"action", NormalType}, + {"id", IntType}, + {"path", NormalType}, + }; + + validate_rows(data, row_map); +} +``` + +To run this test in isolation: + +```bash +# From the osquery build directory +./osquery_tests --gtest_filter=example.test_sanity +``` + +## Notes + +- The validation map and size assertions are commented out by default — uncomment and configure them to match the expected schema and row count of your `example` table implementation. +- Refer to `osquery/tests/integration/tables/helper.h` for available type flags (`NormalType`, `IntType`, etc.) and the `validate_rows` utility. \ No newline at end of file diff --git a/tests/integration/tables/.extended_attributes.md b/tests/integration/tables/.extended_attributes.md new file mode 100644 index 00000000000..b157db4f02b --- /dev/null +++ b/tests/integration/tables/.extended_attributes.md @@ -0,0 +1,36 @@ + +Integration test file for the `extended_attributes` osquery table (Darwin/macOS), performing a basic sanity check query against the table spec. + +## Key Components + +- **`extendedAttributes`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a query against the `extended_attributes` table filtered by an empty path. + +## Usage Example + +```cpp +// Run the sanity check test (via osquery test runner) +TEST_F(extendedAttributes, test_sanity) { + auto const data = + execute_query("select * from extended_attributes where path = ''"); + + // Optional: enable row count assertions + // ASSERT_GE(data.size(), 0ul); + + // Optional: enable schema validation + // ValidationMap row_map = { + // {"path", NormalType}, + // {"directory", NormalType}, + // {"key", NormalType}, + // {"value", NormalType}, + // {"base64", IntType} + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Targets the Darwin-specific table spec: `specs/darwin/extended_attributes.table`. +- Row count assertions and schema `ValidationMap` validation are stubbed out and can be enabled as needed. +- Expected columns: `path`, `directory`, `key`, `value`, `base64`. \ No newline at end of file diff --git a/tests/integration/tables/.fan_speed_sensors.md b/tests/integration/tables/.fan_speed_sensors.md new file mode 100644 index 00000000000..6e7f8ce2375 --- /dev/null +++ b/tests/integration/tables/.fan_speed_sensors.md @@ -0,0 +1,29 @@ + +Integration sanity test for the `fan_speed_sensors` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`fanSpeedSensors`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(fanSpeedSensors, test_sanity)`** — Executes a `SELECT *` against the `fan_speed_sensors` table and provides a scaffolded (commented-out) validation map for row-level checks. + +## Usage Example + +```cpp +// Run the sanity test (executed via the osquery test runner) +// The test queries the fan_speed_sensors table and can be extended +// by uncommenting the validation map below: + +ValidationMap row_map = { + {"fan", NormalType}, + {"name", NormalType}, + {"actual", IntType}, + {"min", IntType}, + {"max", IntType}, + {"target", IntType}, +}; +validate_rows(data, row_map); +``` + +To enable full validation, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)` within `test_sanity`. Size assertions (`ASSERT_GE`, `ASSERT_EQ`) can also be enabled depending on expected hardware availability. + +> **Note:** This test targets Darwin only. It will only return meaningful data on macOS systems with fan hardware (e.g., Intel-based Macs). Apple Silicon Macs may return an empty result set. \ No newline at end of file diff --git a/tests/integration/tables/.fbsd_kmods.md b/tests/integration/tables/.fbsd_kmods.md new file mode 100644 index 00000000000..0df303c391e --- /dev/null +++ b/tests/integration/tables/.fbsd_kmods.md @@ -0,0 +1,34 @@ + +Integration sanity test for the `fbsd_kmods` osquery table, verifying that the FreeBSD kernel modules table can be queried without errors. + +## Key Components + +- **`fbsdKmods`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(fbsdKmods, test_sanity)`** — Basic integration test that executes a `SELECT *` query against the `fbsd_kmods` table to confirm it runs without failure. + +## Usage Example + +```cpp +// Run the sanity test (executed by the integration test runner) +TEST_F(fbsdKmods, test_sanity) { + auto const data = execute_query("select * from fbsd_kmods"); + + // Optional: enable row count assertions + // ASSERT_GE(data.size(), 0ul); + + // Optional: enable schema validation + ValidationMap row_map = { + {"name", NormalType}, + {"size", IntType}, + {"refs", IntType}, + {"address", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Targets **FreeBSD** only; corresponds to `specs/freebsd/fbsd_kmods.table`. +- Row count assertions and schema validation (`ValidationMap`) are scaffolded but commented out — enable them to add stricter coverage. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.file.md b/tests/integration/tables/.file.md new file mode 100644 index 00000000000..fcbf23c8a11 --- /dev/null +++ b/tests/integration/tables/.file.md @@ -0,0 +1,31 @@ + +Integration test for the osquery `file` virtual table, verifying that file metadata is correctly queried and validated across platforms (Linux, macOS, Windows), including multi-byte character set (MBCS) filename support and Windows Shell Link (`.lnk`) shortcut parsing. + +## Key Components + +- **`kFileNameList`** — Test filenames including a Japanese Unicode string (`辞書.txt`) to validate MBCS/UTF-8 path handling. +- **`createShellLink()`** *(Windows-only)* — Creates a `.lnk` shortcut file using COM `IShellLink`/`IPersistFile` interfaces, used to test shortcut metadata columns. +- **`FileTests`** — Test fixture (`testing::Test`) that creates a temporary directory, writes test files, and optionally creates `.lnk` shortcuts on Windows. Cleaned up in `TearDown()`. +- **`getRowIndexForFileName()`** — Helper that locates a row in `QueryData` by matching the `filename` column. +- **`TEST_F(FileTests, test_sanity)`** — Core integration test that queries the `file` table with path glob constraints and validates all returned columns against a `ValidationMap`. + +## Usage Example + +```cpp +// Run via osquery integration test harness; not called directly. +// Example of the query exercised by the test: +execute_query( + "SELECT * FROM file " + "WHERE path LIKE '/tmp/test-integration-file-table.xxxx/%.txt' " + "OR path LIKE '/tmp/test-integration-file-table.xxxx/%.lnk'" +); + +// Windows-specific shortcut fields validated: +// shortcut_target_path, shortcut_target_type, +// shortcut_target_location, shortcut_start_in, +// shortcut_run, shortcut_comment + +// Platform-specific row count assertion: +// Windows: kFileNameList.size() * 2 (files + .lnk shortcuts) +// Other: kFileNameList.size() +``` \ No newline at end of file diff --git a/tests/integration/tables/.file_events.md b/tests/integration/tables/.file_events.md new file mode 100644 index 00000000000..874ca611f62 --- /dev/null +++ b/tests/integration/tables/.file_events.md @@ -0,0 +1,37 @@ + +Integration sanity test for the `file_events` osquery table, verifying the table can be queried without errors on POSIX systems. + +## Key Components + +- **`fileEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(fileEvents, test_sanity)`** — Executes `SELECT * FROM file_events` and provides a scaffolded (currently commented-out) validation structure for row-level assertions. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner: +// The test executes the query and can be extended with row validation: + +ValidationMap row_map = { + {"target_path", NormalType}, + {"category", NormalType}, + {"action", NormalType}, + {"inode", IntType}, + {"uid", IntType}, + {"gid", IntType}, + {"mode", NormalType}, + {"size", IntType}, + {"md5", NormalType}, + {"sha1", NormalType}, + {"sha256", NormalType}, + {"time", IntType}, + {"eid", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- The size assertions and `validate_rows` call are intentionally commented out — `file_events` is an event-driven table that may return zero rows in a static test environment. +- To extend coverage, uncomment and populate the `ValidationMap` with the expected column types from `helper.h`. +- Spec reference: `specs/posix/file_events.table`. \ No newline at end of file diff --git a/tests/integration/tables/.firefox_addons.md b/tests/integration/tables/.firefox_addons.md new file mode 100644 index 00000000000..612d5102a5d --- /dev/null +++ b/tests/integration/tables/.firefox_addons.md @@ -0,0 +1,34 @@ + +Integration sanity test for the `firefox_addons` osquery table, verifying that the table can be queried without errors across supported platforms. + +## Key Components + +- **`firefoxAddons`** — Test fixture class inheriting from `testing::Test` that initializes the test environment via `setUpEnvironment()`. On Windows, it additionally bootstraps and tears down user/group services using `initUsersAndGroupsServices` and `deinitUsersAndGroupsServices`. +- **`TEST_F(firefoxAddons, test_sanity)`** — Core sanity test that executes `SELECT * FROM firefox_addons` and provides a commented-out scaffold for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test executes the following query internally: +auto const data = execute_query("select * from firefox_addons"); + +// Uncomment and populate to enable full schema validation: +ValidationMap row_map = { + {"uid", IntType}, + {"name", NormalType}, + {"identifier", NormalType}, + {"version", NormalType}, + {"active", IntType}, + {"disabled", IntType}, + {"path", NormalType}, + // ... additional columns +}; +validate_rows(data, row_map); +``` + +## Notes + +- The validation map and size assertions are intentionally commented out, following the osquery integration test convention where `test_sanity` only confirms the query executes without crashing. +- Windows-specific setup (`SetUpTestSuite` / `TearDownTestSuite`) handles dispatcher lifecycle management required for user/group enumeration on that platform. +- Spec reference: `specs/firefox_addons.table` \ No newline at end of file diff --git a/tests/integration/tables/.gatekeeper.md b/tests/integration/tables/.gatekeeper.md new file mode 100644 index 00000000000..c0eaf651e82 --- /dev/null +++ b/tests/integration/tables/.gatekeeper.md @@ -0,0 +1,41 @@ + +Integration test file for the `gatekeeper` osquery table on Darwin (macOS), verifying that the Gatekeeper security policy table can be queried without errors. + +## Key Components + +- **`gatekeeper` test class** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity` (TEST_F)** — Executes `SELECT * FROM gatekeeper` and provides a scaffold for row validation against expected column types. + +## Expected Table Schema + +The commented-out `ValidationMap` documents the anticipated columns: + +| Column | Type | +|---|---| +| `assessments_enabled` | `IntType` | +| `dev_id_enabled` | `IntType` | +| `version` | `NormalType` | +| `opaque_version` | `NormalType` | + +## Usage Example + +```cpp +// Run the sanity test directly via the osquery test harness: +TEST_F(gatekeeper, test_sanity) { + auto const data = execute_query("select * from gatekeeper"); + + // Uncomment to enable row count assertions: + // ASSERT_EQ(data.size(), 1ul); + + // Uncomment to enable column type validation: + // ValidationMap row_map = { + // {"assessments_enabled", IntType}, + // {"dev_id_enabled", IntType}, + // {"version", NormalType}, + // {"opaque_version", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +> **Note:** Row count assertions and `validate_rows()` are intentionally commented out, leaving the test as a non-crashing query smoke test. Enable them when stricter validation is required. \ No newline at end of file diff --git a/tests/integration/tables/.gatekeeper_approved_apps.md b/tests/integration/tables/.gatekeeper_approved_apps.md new file mode 100644 index 00000000000..9ebdab84cdb --- /dev/null +++ b/tests/integration/tables/.gatekeeper_approved_apps.md @@ -0,0 +1,29 @@ + +Integration test file for the `gatekeeper_approved_apps` osquery table on macOS (Darwin), verifying basic query execution as a sanity check. + +## Key Components + +- **`gatekeeperApprovedApps`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(gatekeeperApprovedApps, test_sanity)`** — Executes a `SELECT *` query against the `gatekeeper_approved_apps` table and provides scaffolding for row validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery's integration test runner) +// The test queries the table and optionally validates row structure: + +ValidationMap row_map = { + {"path", NormalType}, + {"requirement", NormalType}, + {"ctime", NormalType}, + {"mtime", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets **Darwin only** — spec defined in `specs/darwin/gatekeeper_approved_apps.table`. +- Row count assertions and `validate_rows` calls are commented out, leaving only query execution active. Enable them to enforce expected row counts or column type constraints. +- Expected columns: `path`, `requirement`, `ctime`, `mtime`. +- Depends on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and `ValidationMap` utilities. \ No newline at end of file diff --git a/tests/integration/tables/.groups.md b/tests/integration/tables/.groups.md new file mode 100644 index 00000000000..3f2b20a4586 --- /dev/null +++ b/tests/integration/tables/.groups.md @@ -0,0 +1,36 @@ + +Sanity check integration test for the osquery `groups` table, verifying that queried rows conform to expected column types across platforms. + +## Key Components + +- **`groups` (test fixture)** — Inherits from `testing::Test`; initializes the test environment and, on Windows, manages the users/groups services lifecycle via `SetUpTestSuite`/`TearDownTestSuite`. +- **`TEST_F(groups, test_sanity)`** — Core test case that builds a `ValidationMap` and validates `groups` table output. + +## Usage Example + +```cpp +// The test executes two query patterns internally: + +// 1. Full table scan — asserts at least one row is returned +auto const rows = execute_query("select * from groups"); +ASSERT_GE(rows.size(), 1ul); +validate_rows(rows, row_map); + +// 2. Filtered query by gid — asserts the specific group is retrievable +auto test_gid = rows.front().at("gid"); +auto const rows_one = + execute_query(std::string("select * from groups where gid=") + test_gid); +ASSERT_GE(rows_one.size(), 1ul); +validate_rows(rows_one, row_map); +``` + +## Validated Columns + +| Column | Type | Platform | +|---|---|---| +| `gid` | `IntType` | All | +| `gid_signed` | `IntType` | All | +| `groupname` | `NormalType` | All | +| `is_hidden` | `IntType` | macOS only | +| `comment` | `NormalType` | Windows only | +| `group_sid` | `NormalType` | Windows only | \ No newline at end of file diff --git a/tests/integration/tables/.hardware_events.md b/tests/integration/tables/.hardware_events.md new file mode 100644 index 00000000000..e3d6eb38fe1 --- /dev/null +++ b/tests/integration/tables/.hardware_events.md @@ -0,0 +1,38 @@ + +Integration test file for the `hardware_events` osquery table, providing a sanity check to verify the table can be queried without errors on POSIX systems. + +## Key Components + +- **`hardwareEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(hardwareEvents, test_sanity)`** — Single test case that executes `SELECT * FROM hardware_events` and provides a scaffold for row validation (currently stubbed out). + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test executes the following query internally: +auto const data = execute_query("select * from hardware_events"); + +// Uncommenting the validation map enables column-level type checking: +ValidationMap row_map = { + {"action", NormalType}, + {"path", NormalType}, + {"type", NormalType}, + {"driver", NormalType}, + {"vendor", NormalType}, + {"vendor_id", NormalType}, + {"model", NormalType}, + {"model_id", NormalType}, + {"serial", NormalType}, + {"revision", NormalType}, + {"time", IntType}, + {"eid", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/hardware_events.table`. +- Row count assertions and `validate_rows()` are intentionally commented out — the test currently only verifies the query executes successfully. +- To enable full validation, uncomment the `ValidationMap` block and the `validate_rows()` call. \ No newline at end of file diff --git a/tests/integration/tables/.hash.md b/tests/integration/tables/.hash.md new file mode 100644 index 00000000000..8e70788decb --- /dev/null +++ b/tests/integration/tables/.hash.md @@ -0,0 +1,34 @@ + +Integration test for the osquery `hash` table that validates file hashing functionality by querying MD5, SHA1, and SHA256 checksums against a known file. + +## Key Components + +- **`Hash` (test fixture)** — Sets up a temporary file with known content ("Lorem ipsum...") and tears it down after each test +- **`test_sanity`** — Core test case that queries the `hash` table and validates returned hash values + +## Usage Example + +```cpp +// The test creates a temp file, then queries the hash table: +const std::string query = + "select * from hash where path = '" + path.string() + "'"; + +QueryData data = execute_query(query); + +// Validates exact hash values for known content +ASSERT_EQ(data[0]["md5"], "35899082e51edf667f14477ac000cbba"); +ASSERT_EQ(data[0]["sha1"], "e7505beb754bed863e3885f73e3bb6866bdd7f8c"); +ASSERT_EQ(data[0]["sha256"], "a58dd8680234c1f8cc2ef2b325a43733605a7f16f288e072de8eae81fd8d6433"); +``` + +## Validation Coverage + +| Field | Validation | +|---|---| +| `path` | Non-empty string, exact match | +| `directory` | Non-empty string, matches parent path | +| `md5` | Exact hash value | +| `sha1` | Exact hash value | +| `sha256` | Exact hash value | + +> On Linux, an additional `validate_container_rows` check is executed to verify the table behaves correctly within containerized environments. \ No newline at end of file diff --git a/tests/integration/tables/.helper.md b/tests/integration/tables/.helper.md new file mode 100644 index 00000000000..79409be36c4 --- /dev/null +++ b/tests/integration/tables/.helper.md @@ -0,0 +1,70 @@ + +Utility header for osquery table-level integration tests, providing validation primitives, type-flag constants, and helper functions used across SQL table test suites. + +## Key Components + +### Validator Classes + +| Class | Purpose | +|---|---| +| `IntMinMaxCheck` | Validates a string-encoded integer falls within `[min, max]` | +| `SpecificValuesCheck` | Validates a string belongs to a predefined allowlist | +| `CronValuesCheck` | Validates cron-style values — numeric range or special strings (e.g., `*`, `@reboot`) | + +### Standalone Validators + +- `verifyIpAddress` — checks IPv4/IPv6 format +- `verifyEmptyStringOrIpAddress` — allows empty string or valid IP +- `verifyMacAddress` — validates MAC address format +- `verifyUidGid` — validates UNIX UID/GID values +- `is_valid_hex` — checks hexadecimal string format + +### Type Flag Enum + +Bitmask flags for column-level validation rules: + +```text +NormalType · IntType · NonEmpty · NonNull · NonZero +FileOnDisk · DirectoryOnDisk · ValidUUID · MD5 · SHA256 +SHA1 · Bool · EmptyOk · NullOk +Composites: NonNegativeInt · NonNegativeOrErrorInt · NonEmptyString · IntOrEmpty +``` + +### Core Test Helpers + +- `execute_query(query)` — runs a raw SQL query against the osquery SQLite engine +- `validate_row(row, map)` — validates a single result row against a `ValidationMap` +- `validate_rows(rows, map)` — validates all rows in a result set +- `validate_value_using_flags(value, flags)` — applies bitmask flag rules to a single value +- `validate_container_rows(table, map, constraints)` — validates a full table query with optional SQL constraints +- `setUpEnvironment()` — initializes the test environment before test execution + +### Type Aliases + +```cpp +using CustomCheckerType = std::function; +using ValidationDataType = boost::variant; +using ValidationMap = std::unordered_map; +``` + +## Usage Example + +```cpp +#include "helper.h" + +using namespace osquery::table_tests; + +TEST_F(MyTableTest, ValidateColumns) { + ValidationMap row_map = { + {"pid", NonNegativeInt}, + {"name", NonEmptyString}, + {"path", FileOnDisk}, + {"checksum", SHA256}, + {"status", SpecificValuesCheck({"running", "stopped", "idle"})}, + {"port", IntMinMaxCheck(0, 65535)}, + }; + + auto rows = execute_query("SELECT * FROM my_table"); + validate_rows(rows, row_map); +} +``` \ No newline at end of file diff --git a/tests/integration/tables/.homebrew_packages.md b/tests/integration/tables/.homebrew_packages.md new file mode 100644 index 00000000000..7a3068e597c --- /dev/null +++ b/tests/integration/tables/.homebrew_packages.md @@ -0,0 +1,28 @@ + +Integration sanity test for the `homebrew_packages` osquery table, verifying that the table can be queried without errors on Darwin (macOS) systems. + +## Key Components + +- **`homebrewPackages`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(homebrewPackages, test_sanity)`** — Executes a `SELECT *` against the `homebrew_packages` virtual table and provides a scaffold for row-level validation. + +## Usage Example + +```cpp +// Run the integration test (from the osquery build directory) +// The test queries the homebrew_packages table and validates results. + +// To extend with full validation, uncomment and populate the ValidationMap: +ValidationMap row_map = { + {"name", NormalType}, + {"path", NormalType}, + {"version", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/darwin/homebrew_packages.table` +- Size assertions and `validate_rows` calls are stubbed out — activate them to enforce row count or schema constraints as needed. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, `ValidationMap`, and environment setup utilities. \ No newline at end of file diff --git a/tests/integration/tables/.ibridge.md b/tests/integration/tables/.ibridge.md new file mode 100644 index 00000000000..17bb696205f --- /dev/null +++ b/tests/integration/tables/.ibridge.md @@ -0,0 +1,41 @@ + +Integration test for the `ibridge_info` osquery table, validating data returned from Apple's iBridge coprocessor on Darwin systems. + +## Key Components + +- **`IBridgeTest`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that queries the `ibridge_info` table and validates the result set. + +## Usage Example + +```cpp +// The test executes the following osquery SQL internally: +// select * from ibridge_info + +// Expected result: exactly 1 row (or empty on non-iBridge hardware) +// Validated fields: +ValidationMap validation_map = { + {"boot_uuid", NormalType}, + {"coprocessor_version", NonEmptyString}, + {"firmware_version", NonEmptyString}, + {"unique_chip_id", NonEmptyString}, +}; +``` + +## Validation Behavior + +| Scenario | Behavior | +|---|---| +| No iBridge hardware detected | Logs a `VLOG(1)` message and skips assertions | +| iBridge present | Asserts exactly `1` row is returned and validates all fields | + +## Field Validation Rules + +| Field | Rule | +|---|---| +| `boot_uuid` | `NormalType` — any valid value | +| `coprocessor_version` | `NonEmptyString` | +| `firmware_version` | `NonEmptyString` | +| `unique_chip_id` | `NonEmptyString` | + +> This test is Darwin-only and corresponds to the spec file `specs/darwin/ibridge_info.table`. On machines without an iBridge chip (non-T2/Apple Silicon Macs), the table returns no rows and the test gracefully skips validation. \ No newline at end of file diff --git a/tests/integration/tables/.ie_extensions.md b/tests/integration/tables/.ie_extensions.md new file mode 100644 index 00000000000..862dfd525a7 --- /dev/null +++ b/tests/integration/tables/.ie_extensions.md @@ -0,0 +1,34 @@ + +Integration test file for the `ie_extensions` osquery table, providing a sanity check to verify the table can be queried without errors on Windows systems. + +## Key Components + +- **`ieExtensions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM ie_extensions` to validate the table is queryable. + +## Usage Example + +```cpp +// Run the sanity test against the ie_extensions table +TEST_F(ieExtensions, test_sanity) { + auto const data = execute_query("select * from ie_extensions"); + + // Optional: validate row count + // ASSERT_GE(data.size(), 0ul); + + // Optional: enable column validation + ValidationMap row_map = { + {"name", NormalType}, + {"registry_path", NormalType}, + {"version", NormalType}, + {"path", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/windows/ie_extensions.table`. +- Row count assertions and `ValidationMap` checks are stubbed out and can be uncommented to enforce stricter validation. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and validation flag constants. \ No newline at end of file diff --git a/tests/integration/tables/.intel_me_info.md b/tests/integration/tables/.intel_me_info.md new file mode 100644 index 00000000000..c10d14aa317 --- /dev/null +++ b/tests/integration/tables/.intel_me_info.md @@ -0,0 +1,29 @@ + +Integration test stub for the `intel_me_info` osquery table, providing a sanity check framework to validate queries against Intel Management Engine firmware version data on Linux/Windows. + +## Key Components + +- **`intelMeInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(intelMeInfo, test_sanity)`** — Basic sanity test that executes a `SELECT *` query against the `intel_me_info` table. Validation logic (row count assertions and column type checks) is scaffolded but commented out pending implementation. + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the intel_me_info table and validates results + +// Uncomment and configure validation as needed: +ValidationMap row_map = { + {"version", NormalType} +}; +validate_rows(data, row_map); +``` + +To extend the test, uncomment the size assertions and validation map matching the `intel_me_info` table spec (`specs/linwin/intel_me_info.table`): + +```cpp +ASSERT_EQ(data.size(), 1ul); // ME info typically returns a single row +validate_rows(data, row_map); +``` + +> **Note:** This test is a scaffold — active assertions are commented out. The `version` column is the primary field exposed by the `intel_me_info` table. Full validation should be enabled once platform availability is confirmed. \ No newline at end of file diff --git a/tests/integration/tables/.interface_addresses.md b/tests/integration/tables/.interface_addresses.md new file mode 100644 index 00000000000..1980f33e4ea --- /dev/null +++ b/tests/integration/tables/.interface_addresses.md @@ -0,0 +1,41 @@ + +Integration test for the `interface_addresses` osquery table, validating that network interface address data is correctly structured and unique across all returned rows. + +## Key Components + +### `InterfaceAddressesTest` +A `testing::Test` subclass that initializes the osquery test environment via `setUpEnvironment()` before each test case. + +### `TEST_F(InterfaceAddressesTest, test_sanity)` +Executes `SELECT * FROM interface_addresses` and validates: + +| Column | Validation Rule | +|---|---| +| `interface` | Non-empty string | +| `address` | Valid IP address | +| `mask` | Empty string or valid IP | +| `broadcast` | Empty string or valid IP | +| `point_to_point` | Empty string or valid IP | +| `type` | One of: `dhcp`, `manual`, `auto`, `other`, `unknown` | +| `friendly_name` | Normal type *(Windows only)* | + +Also asserts that every `address` value is **unique** across all rows — enforcing that each IP address is associated with at most one interface entry. + +## Usage Example + +```bash +# Run this specific integration test via ctest +ctest --test-dir build/ -R InterfaceAddressesTest --output-on-failure + +# Or directly via the test binary +./build/osquery/tests/integration/tables/osquery_integration_tests \ + --gtest_filter="InterfaceAddressesTest.test_sanity" +``` + +```cpp +// Corresponding osquery table query +SELECT interface, address, mask, broadcast, point_to_point, type +FROM interface_addresses; +``` + +> **Note:** The `friendly_name` column is only validated on Windows builds (`OSQUERY_WINDOWS` preprocessor flag). All other columns are cross-platform. \ No newline at end of file diff --git a/tests/integration/tables/.interface_details.md b/tests/integration/tables/.interface_details.md new file mode 100644 index 00000000000..2d91708debd --- /dev/null +++ b/tests/integration/tables/.interface_details.md @@ -0,0 +1,35 @@ + +Integration test for the `interface_details` osquery table, validating that queried network interface data returns correctly typed and non-negative values across Linux, macOS, and Windows platforms. + +## Key Components + +- **`InterfaceDetailsTest`** — GTest fixture that initializes the osquery test environment via `SetUp()` +- **`test_sanity`** — Primary test case that executes `SELECT * FROM interface_details` and validates all returned rows against a platform-aware `ValidationMap` + +### Validation Lambdas + +| Lambda | Purpose | +|---|---| +| `verify_non_negative_or_empty` | Accepts empty strings on Windows; otherwise asserts a non-negative `int64_t` | +| `verify_non_empty_string_or_empty_on_win` | Allows empty values on Windows, requires non-empty on other platforms | +| `verify_int_or_empty_on_win` *(Windows only)* | Validates castable integer or empty string | +| `verify_bool_or_empty_on_win` *(Windows only)* | Validates `"0"` / `"1"` or empty string | + +### Platform-Conditional Columns + +- **POSIX**: `link_speed` +- **Linux**: `pci_slot` +- **Windows**: `friendly_name`, `description`, `manufacturer`, `connection_id`, `dhcp_*`, `dns_*`, and others + +## Usage Example + +```cpp +// Run as part of the osquery integration test suite: +// bazel test //osquery/tests/integration/tables:interface_details_test + +// The test internally executes: +QueryData const rows = execute_query("select * from interface_details"); +validate_rows(rows, row_map); +``` + +> **Note:** On Windows, the test currently exits early with an informational log (`LOG(INFO)`) due to known instability, without failing the test run. \ No newline at end of file diff --git a/tests/integration/tables/.interface_ipv6.md b/tests/integration/tables/.interface_ipv6.md new file mode 100644 index 00000000000..bf3378140bf --- /dev/null +++ b/tests/integration/tables/.interface_ipv6.md @@ -0,0 +1,40 @@ + +Integration test for the `interface_ipv6` osquery table, validating that query results conform to expected data types and value constraints. + +## Key Components + +### `InterfaceIpv6Test` (class) +A Google Test fixture that initializes the osquery test environment via `setUpEnvironment()` before each test case. + +### `test_sanity` (test case) +Executes `SELECT * FROM interface_ipv6` and validates each returned row against a `ValidationMap` with the following field rules: + +| Column | Validation Rule | +|---|---| +| `interface` | `NonEmptyString` | +| `hop_limit` | `IntMinMaxCheck(0, 255)` | +| `forwarding_enabled` | `Bool` | +| `redirect_accept` | `Bool` | +| `rtadv_accept` | `Bool` | + +## Usage Example + +```cpp +// Run this test via the osquery integration test harness: +// The test queries the interface_ipv6 virtual table and validates +// that all rows match expected types and ranges. + +TEST_F(InterfaceIpv6Test, test_sanity) { + QueryData const rows = execute_query("select * from interface_ipv6"); + auto const row_map = ValidationMap{ + {"interface", NonEmptyString}, + {"hop_limit", IntMinMaxCheck(0, 255)}, // Valid TTL hop limit range + {"forwarding_enabled", Bool}, + {"redirect_accept", Bool}, + {"rtadv_accept", Bool}, + }; + validate_rows(rows, row_map); +} +``` + +> **Note:** This is a sanity/integration test only. It verifies structural correctness of the `interface_ipv6` table output rather than specific network configuration values. The spec is defined in `specs/interface_ipv6.table`. \ No newline at end of file diff --git a/tests/integration/tables/.iokit_devicetree.md b/tests/integration/tables/.iokit_devicetree.md new file mode 100644 index 00000000000..87f9cad82b9 --- /dev/null +++ b/tests/integration/tables/.iokit_devicetree.md @@ -0,0 +1,36 @@ + +Integration test stub for the `iokit_devicetree` osquery table on Darwin (macOS), verifying basic query execution against the IOKit device tree. + +## Key Components + +- **`iokitDevicetree`** — Test fixture class extending `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes `SELECT * FROM iokit_devicetree` and provides a scaffolded (commented-out) validation structure for future assertion coverage. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test harness +TEST_F(iokitDevicetree, test_sanity) { + auto const data = execute_query("select * from iokit_devicetree"); + + // Uncomment and configure to enable full validation: + ValidationMap row_map = { + {"name", NormalType}, + {"class", NormalType}, + {"id", IntType}, + {"parent", IntType}, + {"device_path", NormalType}, + {"service", IntType}, + {"busy_state", IntType}, + {"retain_count", IntType}, + {"depth", IntType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Validation logic (`validate_rows`, size assertions) is currently **commented out** — the test confirms the query runs without error but does not assert on result contents. +- Expected columns mirror the Darwin table spec at `specs/darwin/iokit_devicetree.table`. +- See `osquery/tests/integration/tables/helper.h` for available type flags (`NormalType`, `IntType`, etc.) and `DataCheck` for custom validators. \ No newline at end of file diff --git a/tests/integration/tables/.iokit_registry.md b/tests/integration/tables/.iokit_registry.md new file mode 100644 index 00000000000..787dd116ee9 --- /dev/null +++ b/tests/integration/tables/.iokit_registry.md @@ -0,0 +1,32 @@ + +Integration test file for the `iokit_registry` osquery table on Darwin (macOS), verifying basic query execution against the IOKit device registry. + +## Key Components + +- **`iokitRegistry`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Sanity check test that executes a `SELECT * FROM iokit_registry` query and provides scaffolding for row validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery integration test runner) +// To enable full validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"name", NormalType}, + {"class", NormalType}, + {"id", IntType}, + {"parent", IntType}, + {"busy_state", IntType}, + {"retain_count", IntType}, + {"depth", IntType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/darwin/iokit_registry.table`. +- Full row validation is stubbed out but not yet enabled — size assertions and `validate_rows()` calls are commented out pending implementation. +- Requires Darwin (macOS); the `iokit_registry` table surfaces IOKit registry entries including device name, class, hierarchy depth, and reference counts. \ No newline at end of file diff --git a/tests/integration/tables/.iptables.md b/tests/integration/tables/.iptables.md new file mode 100644 index 00000000000..3f468ea5e23 --- /dev/null +++ b/tests/integration/tables/.iptables.md @@ -0,0 +1,40 @@ + +Integration test for the `iptables` osquery table, validating that query results conform to expected data types and value constraints on Linux systems. + +## Key Components + +- **`iptables` test class** — Inherits from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity` test case** — Executes `SELECT * FROM iptables` and validates the result set against a defined schema map. +- **`ValidationMap row_map`** — Defines per-column validation rules covering all expected fields. + +## Validated Columns + +| Column | Validation Rule | +|---|---| +| `filter_name` | Non-empty string | +| `chain`, `policy`, `target` | Normal type | +| `protocol` | Integer | +| `src_port`, `dst_port` | Integer in range `[0, 65535]` | +| `src_ip`, `src_mask`, `dst_ip`, `dst_mask` | Empty string or valid IP address | +| `iniface`, `outiface` | Normal type | +| `iniface_mask`, `outiface_mask` | Empty string or valid IP address | +| `match` | Enum: `"yes"` or `"no"` | +| `packets`, `bytes` | Non-negative integer | + +## Usage Example + +```cpp +// Run the sanity test via GTest +// Validates iptables table schema and data integrity +TEST_F(iptables, test_sanity) { + auto const data = execute_query("select * from iptables"); + + // At minimum, the query must succeed (empty result is acceptable) + ASSERT_GE(data.size(), 0ul); + + // Each row must pass field-level validation + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets Linux only, matching the table spec at `specs/linux/iptables.table`. An empty result set is permitted (e.g., no active iptables rules), but any returned rows must fully conform to the validation map. \ No newline at end of file diff --git a/tests/integration/tables/.jetbrains_plugins.md b/tests/integration/tables/.jetbrains_plugins.md new file mode 100644 index 00000000000..3f2605d0e42 --- /dev/null +++ b/tests/integration/tables/.jetbrains_plugins.md @@ -0,0 +1,39 @@ + +Integration test file that validates the `jetbrains_plugins` osquery table, ensuring query execution and row schema correctness. + +## Key Components + +- **`jetbrainsPlugins`** — Test fixture class inheriting from `testing::Test`, with Windows-specific setup/teardown that initializes user and group services via the osquery dispatcher. +- **`TEST_F(jetbrainsPlugins, test_sanity)`** — Sanity test that executes `SELECT * FROM jetbrains_plugins` and validates each returned row against an expected schema. + +## Validated Schema + +| Column | Expected Type | +|---|---| +| `product_type` | `NormalType` | +| `uid` | `IntType` | +| `name` | `NormalType` | +| `version` | `NormalType` | +| `vendor` | `NormalType` | +| `path` | `NormalType` | + +## Usage Example + +```cpp +// Run this specific test via osquery's test runner +TEST_F(jetbrainsPlugins, test_sanity) { + ValidationMap row_map = { + {"product_type", NormalType}, + {"uid", IntType}, + {"name", NormalType}, + {"version", NormalType}, + {"vendor", NormalType}, + {"path", NormalType}, + }; + + auto const data = execute_query("select * from jetbrains_plugins"); + validate_rows(data, row_map); +} +``` + +The Windows-specific `SetUpTestSuite` / `TearDownTestSuite` lifecycle methods ensure proper initialization and cleanup of user/group services before and after the test suite runs, preventing resource leaks in the dispatcher. \ No newline at end of file diff --git a/tests/integration/tables/.kernel_extensions.md b/tests/integration/tables/.kernel_extensions.md new file mode 100644 index 00000000000..b90bd73ec76 --- /dev/null +++ b/tests/integration/tables/.kernel_extensions.md @@ -0,0 +1,33 @@ + +Integration sanity test for the `kernel_extensions` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`kernelExtensions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(kernelExtensions, test_sanity)`** — Executes `SELECT * FROM kernel_extensions` and provides a scaffold for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test queries the kernel_extensions table and can be extended +// to validate expected columns: + +ValidationMap row_map = { + {"idx", IntType}, + {"refs", IntType}, + {"size", IntType}, + {"name", NormalType}, + {"version", NormalType}, + {"linked_against", NormalType}, + {"path", NormalType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Targets **Darwin only** — spec defined in `specs/darwin/kernel_extensions.table`. +- Row count assertions and `validate_rows` calls are commented out, leaving a ready-to-enable scaffold. +- Follows the standard osquery integration test pattern from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.kernel_info.md b/tests/integration/tables/.kernel_info.md new file mode 100644 index 00000000000..0ecd80779f1 --- /dev/null +++ b/tests/integration/tables/.kernel_info.md @@ -0,0 +1,35 @@ + +Sanity check integration test for the `kernel_info` osquery table, verifying that the table returns valid, well-formed data at runtime. + +## Key Components + +- **`KernelInfo`** — Test fixture inheriting from `testing::Test`; initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Executes `SELECT * FROM kernel_info` and validates the result schema. + +## Validation Schema + +| Column | Rule | +|---|---| +| `version` | `NonEmptyString` | +| `arguments` | `NormalType` | +| `path` | `NormalType` | +| `device` | `NormalType` | + +## Usage Example + +```cpp +// Run via ctest or the osquery integration test binary: +// ./osquery_integration_tests --gtest_filter=KernelInfo.test_sanity + +TEST_F(KernelInfo, test_sanity) { + QueryData data = execute_query("select * from kernel_info"); + ValidationMap row_map = { + {"version", NonEmptyString}, + {"arguments", NormalType}, + {"path", NormalType}, + {"device", NormalType}}; + validate_rows(data, row_map); +} +``` + +> Spec reference: `specs/kernel_info.table`. This test only validates schema shape and non-emptiness — it does not assert specific kernel version values, making it portable across platforms. \ No newline at end of file diff --git a/tests/integration/tables/.kernel_keys.md b/tests/integration/tables/.kernel_keys.md new file mode 100644 index 00000000000..b1510ab2d53 --- /dev/null +++ b/tests/integration/tables/.kernel_keys.md @@ -0,0 +1,23 @@ + +Integration test file for the `kernel_keys` osquery table on Linux, validating that the table can be queried without errors. + +## Key Components + +- **`KernelKeys`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic sanity test that executes a `SELECT *` query against the `kernel_keys` table to verify it runs without crashing or throwing exceptions. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test executes the following query internally: +QueryData data = execute_query("select * from kernel_keys"); +``` + +To run this specific test: + +```bash +./osquery_tests --gtest_filter="KernelKeys.test_sanity" +``` + +> **Note:** This test targets Linux only. Refer to `specs/linux/kernel_keys.table` for the full table schema definition. The test does not assert on specific row contents — it only validates that the query executes successfully against the kernel keyring subsystem. \ No newline at end of file diff --git a/tests/integration/tables/.kernel_modules.md b/tests/integration/tables/.kernel_modules.md new file mode 100644 index 00000000000..38cc34f9661 --- /dev/null +++ b/tests/integration/tables/.kernel_modules.md @@ -0,0 +1,43 @@ + +Integration test that validates the `kernel_modules` osquery table returns well-formed data on Linux systems. + +## Key Components + +- **`KernelModules`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that queries `kernel_modules` and validates the result set structure and data integrity. + +## Validated Schema + +| Column | Validation Rule | +|--------|----------------| +| `name` | Non-empty string | +| `size` | Non-negative integer | +| `used_by` | Non-empty string | +| `status` | Non-empty string | +| `address` | Non-negative integer | + +## Usage Example + +```cpp +// Run via the osquery integration test harness (Linux only) +// Spec: specs/linux/kernel_modules.table + +TEST_F(KernelModules, test_sanity) { + QueryData data = execute_query("select * from kernel_modules"); + + // Ensures at least one loaded kernel module is returned + ASSERT_GT(data.size(), 0ul); + + // Validates each row matches the expected column types + ValidationMap row_map = { + {"name", NonEmptyString}, + {"size", NonNegativeInt}, + {"used_by", NonEmptyString}, + {"status", NonEmptyString}, + {"address", NonNegativeInt}, + }; + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets Linux only (per `specs/linux/kernel_modules.table`) and asserts the table is never empty, reflecting that a running Linux system always has loaded kernel modules. \ No newline at end of file diff --git a/tests/integration/tables/.kernel_panics.md b/tests/integration/tables/.kernel_panics.md new file mode 100644 index 00000000000..59106c46297 --- /dev/null +++ b/tests/integration/tables/.kernel_panics.md @@ -0,0 +1,39 @@ + +Integration sanity test for the `kernel_panics` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`kernelPanics`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM kernel_panics` and provides a scaffolded (commented-out) validation structure for row data. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner: +// The test executes the following query internally: +auto const data = execute_query("select * from kernel_panics"); + +// Uncomment and extend the validation map to assert column types: +ValidationMap row_map = { + {"path", NormalType}, + {"time", NormalType}, + {"registers", NormalType}, + {"frame_backtrace", NormalType}, + {"module_backtrace", NormalType}, + {"dependencies", NormalType}, + {"name", NormalType}, + {"os_version", NormalType}, + {"kernel_version", NormalType}, + {"system_model", NormalType}, + {"uptime", IntType}, + {"last_loaded", NormalType}, + {"last_unloaded", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets Darwin only — corresponds to `specs/darwin/kernel_panics.table`. +- Size assertions and `validate_rows()` are commented out, meaning the test currently only verifies the query executes without crashing. +- Extend validation by uncommenting the `ValidationMap` block and calling `validate_rows()` to enforce column type constraints. \ No newline at end of file diff --git a/tests/integration/tables/.keychain_acls.md b/tests/integration/tables/.keychain_acls.md new file mode 100644 index 00000000000..c6a409ebce2 --- /dev/null +++ b/tests/integration/tables/.keychain_acls.md @@ -0,0 +1,42 @@ + +Integration test stub for the `keychain_acls` osquery table on Darwin (macOS), providing a sanity check test fixture that validates the table's schema and query behavior. + +## Key Components + +- **`keychainAcls`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Placeholder test case for the `keychain_acls` table. Currently disabled due to performance and permission requirements that cause flakiness. + +## Expected Table Schema + +The commented-out `ValidationMap` documents the expected columns for the `keychain_acls` table: + +| Column | Type | +|---|---| +| `keychain_path` | NormalType | +| `authorizations` | NormalType | +| `path` | NormalType | +| `description` | NormalType | +| `label` | NormalType | + +## Usage Example + +To re-enable the full sanity test, uncomment the test body: + +```cpp +TEST_F(keychainAcls, test_sanity) { + auto const data = execute_query("select * from keychain_acls"); + ASSERT_GE(data.size(), 0ul); + + ValidationMap row_map = { + {"keychain_path", NormalType}, + {"authorizations", NormalType}, + {"path", NormalType}, + {"description", NormalType}, + {"label", NormalType}, + }; + + validate_rows(data, row_map); +} +``` + +> **Note:** This test is intentionally disabled. Running it may require elevated macOS permissions and can be slow, making it unsuitable for standard CI pipelines. Re-enable only in controlled environments with appropriate entitlements. \ No newline at end of file diff --git a/tests/integration/tables/.keychain_items.md b/tests/integration/tables/.keychain_items.md new file mode 100644 index 00000000000..1758a8b652f --- /dev/null +++ b/tests/integration/tables/.keychain_items.md @@ -0,0 +1,37 @@ + +Integration test for the `keychain_items` osquery table on Darwin (macOS), validating that query results conform to expected schema and value constraints. + +## Key Components + +### `KeychainItemsTest` (class) +A Google Test fixture inheriting from `testing::Test` that initializes the osquery test environment via `setUpEnvironment()` in its `SetUp()` override. + +### `test_sanity` (test case) +Executes `SELECT * FROM keychain_items` and validates every returned row against a `ValidationMap` enforcing: + +| Column | Constraint | +|---|---| +| `label`, `description`, `comment`, `account`, `created`, `modified`, `pk_hash` | `NormalType` (any valid type) | +| `type` | One of: `password`, `internet password`, `certificate`, `symmetric key`, `public key`, `private key` | +| `path` | `NonEmptyString` | + +The test also asserts the result set is non-empty (`ASSERT_FALSE(data.empty())`), ensuring the table returns at least one keychain entry on the host. + +## Usage Example + +```cpp +// Run this test as part of the osquery integration test suite on macOS: +// The test queries the keychain_items virtual table and validates schema conformance. + +TEST_F(KeychainItemsTest, test_sanity) { + auto const data = execute_query("select * from keychain_items"); + + // Must return at least one keychain item + ASSERT_FALSE(data.empty()); + + // Each row is validated against the expected column types and allowed values + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets Darwin (macOS) only, as defined in `specs/darwin/keychain_items.table`. It requires a real keychain to be present on the test host. Run via the osquery integration test harness — not as a standalone unit test. \ No newline at end of file diff --git a/tests/integration/tables/.known_hosts.md b/tests/integration/tables/.known_hosts.md new file mode 100644 index 00000000000..ce3c2105dea --- /dev/null +++ b/tests/integration/tables/.known_hosts.md @@ -0,0 +1,27 @@ + +Integration test for the `known_hosts` osquery table, validating that query results conform to expected schema and data types on POSIX systems. + +## Key Components + +- **`KnownHostsTest`** — Test fixture inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(KnownHostsTest, test_sanity)`** — Sanity check that executes `SELECT * FROM known_hosts` and validates returned rows against a schema map. +- **`ValidationMap`** — Defines expected column types: + - `uid` → `IntType` + - `key` → `NonEmptyString` + - `key_file` → `FileOnDisk` + +## Usage Example + +```cpp +// Runs automatically via the osquery integration test suite. +// To execute manually, build and run the integration test binary: +// ./osquery_integration_tests --gtest_filter="KnownHostsTest.test_sanity" +``` + +## Behavior Notes + +- If the `known_hosts` table returns **no rows** (e.g., no SSH known hosts files present on the system), the test logs a `WARNING` and skips validation rather than failing — this accounts for environments without SSH configuration. +- When rows are present, `validate_rows()` enforces that: + - `uid` is a valid integer (owning user ID) + - `key` is a non-empty string (the host public key) + - `key_file` resolves to an actual file path on disk \ No newline at end of file diff --git a/tests/integration/tables/.kva_speculative_info.md b/tests/integration/tables/.kva_speculative_info.md new file mode 100644 index 00000000000..ccae7507e71 --- /dev/null +++ b/tests/integration/tables/.kva_speculative_info.md @@ -0,0 +1,43 @@ + +Integration test file for the `kva_speculative_info` osquery table, validating Windows Kernel Virtual Address (KVA) shadowing and CPU speculative execution mitigation data. + +## Key Components + +- **`kvaSpeculativeInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(kvaSpeculativeInfo, test_sanity)`** — Sanity check that executes a `SELECT *` query against the `kva_speculative_info` table to verify it runs without error. + +## Expected Table Schema + +The commented-out `ValidationMap` documents the expected columns (all `IntType`): + +| Column | Description | +|---|---| +| `kva_shadow_enabled` | KVA shadow mitigation active | +| `kva_shadow_user_global` | User global KVA shadow | +| `kva_shadow_pcid` | PCID-based KVA shadow | +| `kva_shadow_inv_pcid` | Invalidating PCID shadow | +| `bp_mitigations` | Branch prediction mitigations | +| `bp_system_pol_disabled` | System policy BP disabled flag | +| `bp_microcode_disabled` | Microcode BP disabled flag | +| `cpu_spec_ctrl_supported` | SPEC_CTRL MSR support | +| `ibrs_support_enabled` | IBRS enabled | +| `stibp_support_enabled` | STIBP enabled | +| `cpu_pred_cmd_supported` | PRED_CMD MSR support | + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test harness +TEST_F(kvaSpeculativeInfo, test_sanity) { + auto const data = execute_query("select * from kva_speculative_info"); + + // Uncomment to enable full row validation: + // ValidationMap row_map = { + // {"kva_shadow_enabled", IntType}, + // {"ibrs_support_enabled", IntType}, + // }; + // validate_rows(data, row_map); +} +``` + +> **Note:** Row count assertions and `validate_rows()` calls are scaffolded but commented out, making this a minimal smoke test confirming the table is queryable on Windows without crashing. \ No newline at end of file diff --git a/tests/integration/tables/.last.md b/tests/integration/tables/.last.md new file mode 100644 index 00000000000..46537fbad94 --- /dev/null +++ b/tests/integration/tables/.last.md @@ -0,0 +1,44 @@ + +Integration test for the `last` osquery table, validating login session data from the `wtmp` log on POSIX systems. + +## Key Components + +- **`last` test fixture** — Inherits from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that queries the `last` table and validates returned rows against a schema map. + +## Validation Schema + +| Column | Validation Rule | +|---|---| +| `username` | `NormalType` | +| `tty` | `NormalType` | +| `pid` | `NonNegativeInt` | +| `type` | `IntMinMaxCheck(7, 8)` | +| `type_name` | `NormalType` | +| `time` | `NonNegativeInt` | +| `host` | `NormalType` | + +## Usage Example + +```cpp +// Run this test via the osquery integration test runner: +// ./osquery_integration_tests --gtest_filter="last.test_sanity" + +// Internally, the test executes: +auto const data = execute_query("select * from last"); + +// If wtmp has no entries, the test is skipped gracefully: +if (data.empty()) { + LOG(WARNING) << "No entries in wtmp, skipping test"; + return; +} + +// Rows are validated against the schema map +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the spec file at `specs/posix/last.table` — POSIX-only. +- Gracefully skips validation when `wtmp` contains no login records, avoiding false failures on minimal environments. +- `type` is constrained to values `7–8`, reflecting `utmp` record type constants for user processes and dead processes. \ No newline at end of file diff --git a/tests/integration/tables/.launchd.md b/tests/integration/tables/.launchd.md new file mode 100644 index 00000000000..e54239d8c6f --- /dev/null +++ b/tests/integration/tables/.launchd.md @@ -0,0 +1,37 @@ + +Integration test file for the `launchd` osquery table on Darwin (macOS), verifying that queries against the `launchd` table execute without errors. + +## Key Components + +- **`launchd` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(launchd, test_sanity)`** — Executes `SELECT * FROM launchd` and provides a scaffolded (currently commented-out) validation structure for row count assertions and column type checks. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test harness +// Build and execute with: +// cmake --build . --target osquery_integration_tests +// ./osquery_integration_tests --gtest_filter="launchd*" + +// To enable full validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"path", NormalType}, + {"name", NormalType}, + {"label", NormalType}, + {"program", NormalType}, + {"run_at_load", NormalType}, + {"keep_alive", NormalType}, + {"disabled", NormalType}, + {"username", NormalType}, + {"program_arguments", NormalType}, + {"process_type", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/darwin/launchd.table`. +- Row count assertions and `validate_rows()` calls are scaffolded but **commented out** — the test currently only confirms the query runs without crashing. +- To activate full validation, uncomment the `ValidationMap` block and the `validate_rows()` call, adjusting expected row counts as needed for the target environment. \ No newline at end of file diff --git a/tests/integration/tables/.launchd_overrides.md b/tests/integration/tables/.launchd_overrides.md new file mode 100644 index 00000000000..c631ec34207 --- /dev/null +++ b/tests/integration/tables/.launchd_overrides.md @@ -0,0 +1,30 @@ + +Integration sanity test for the `launchd_overrides` osquery table on Darwin (macOS), verifying that the table can be queried without errors. + +## Key Components + +- **`launchdOverrides`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(launchdOverrides, test_sanity)`** — Executes a `SELECT *` query against the `launchd_overrides` table and provides scaffolding for row validation. + +## Usage Example + +```cpp +// Run the integration test (typically via osquery's test runner) +// The test queries the launchd_overrides table and can be extended +// with a ValidationMap to assert column types: + +ValidationMap row_map = { + {"label", NormalType}, + {"key", NormalType}, + {"value", NormalType}, + {"uid", IntType}, + {"path", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Darwin platform only; corresponds to `specs/darwin/launchd_overrides.table`. +- Row count assertions and `validate_rows()` are commented out, leaving the test as a basic smoke test (query executes without crashing). +- To harden the test, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)` to enforce expected column types per row. \ No newline at end of file diff --git a/tests/integration/tables/.listening_ports.md b/tests/integration/tables/.listening_ports.md new file mode 100644 index 00000000000..0ffc0b8f00a --- /dev/null +++ b/tests/integration/tables/.listening_ports.md @@ -0,0 +1,35 @@ + +Integration sanity test for the `listening_ports` osquery table, verifying the table can be queried without errors as part of the osquery test suite. + +## Key Components + +- **`listeningPorts`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM listening_ports` and provides a scaffolded validation structure (currently passive — no assertions enforced). + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test runner. +// The test queries the listening_ports table and can be extended +// with row validation using the commented ValidationMap: + +ValidationMap row_map = { + {"pid", IntType}, + {"port", IntType}, + {"protocol", IntType}, + {"family", IntType}, + {"address", NormalType}, + {"fd", IntType}, + {"socket", IntType}, + {"path", NormalType}, + {"net_namespace", NormalType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- All assertions and the `ValidationMap` block are commented out, making this a **non-failing scaffold** ready for stricter validation. +- Corresponds to the table spec at `specs/listening_ports.table`. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.load_average.md b/tests/integration/tables/.load_average.md new file mode 100644 index 00000000000..484651ce362 --- /dev/null +++ b/tests/integration/tables/.load_average.md @@ -0,0 +1,26 @@ + +Integration test for the `load_average` osquery table, verifying basic query execution against the POSIX `load_average` table spec. + +## Key Components + +- **`loadAverage`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Sanity check that executes `SELECT * FROM load_average` and provides scaffolding for row count assertions and column validation. + +## Usage Example + +```cpp +// Run the sanity test (via osquery integration test runner) +// To enable full validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"period", NormalType}, + {"average", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the POSIX table spec: `specs/posix/load_average.table` +- Row count assertions and `validate_rows` are intentionally commented out — extend these to enforce specific result sizes or column constraints. +- Follows the standard osquery integration test pattern: query → size check → validation map → validate. \ No newline at end of file diff --git a/tests/integration/tables/.location_services.md b/tests/integration/tables/.location_services.md new file mode 100644 index 00000000000..2b17dcac068 --- /dev/null +++ b/tests/integration/tables/.location_services.md @@ -0,0 +1,33 @@ + +Integration test for the `location_services` osquery table on Darwin (macOS), verifying that the table returns valid data with the expected schema. + +## Key Components + +- **`locationServices`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a `SELECT *` query against the `location_services` table and validates the result. + +## Usage Example + +```cpp +// Validates the location_services table returns exactly one row +// with an integer "enabled" field +auto const data = execute_query("select * from location_services"); +ASSERT_EQ(data.size(), 1ul); + +ValidationMap row_map = { + {"enabled", IntType}, +}; +validate_rows(data, row_map); +``` + +## Validated Schema + +| Column | Type | Description | +|--------|------|-------------| +| `enabled` | `IntType` | Whether location services are enabled (`1`) or disabled (`0`) | + +## Notes + +- Targets **Darwin (macOS)** only — spec defined in `specs/darwin/location_services.table`. +- Expects **exactly one row** (`ASSERT_EQ(data.size(), 1ul)`), as location services is a system-wide singleton setting. +- Part of the osquery integration test suite; run via the standard osquery test harness, not as a standalone binary. \ No newline at end of file diff --git a/tests/integration/tables/.logged_in_users.md b/tests/integration/tables/.logged_in_users.md new file mode 100644 index 00000000000..73822fd065a --- /dev/null +++ b/tests/integration/tables/.logged_in_users.md @@ -0,0 +1,40 @@ + +Integration test for the `logged_in_users` osquery virtual table, validating query results against expected column types and constraints. + +## Key Components + +- **`LoggedInUsersTest`** — Test fixture inheriting from `testing::Test`, initializes the osquery environment via `setUpEnvironment()`. +- **`test_sanity`** — Executes `SELECT * FROM logged_in_users` and validates each returned row against a `ValidationMap`. + +### Validated Columns + +| Column | Validation Rule | +|---|---| +| `type` | `NonEmptyString` | +| `user` | `NormalType` | +| `tty` | `NormalType` | +| `host` | `NormalType` | +| `time` | `NonNegativeInt` | +| `pid` | `NonNegativeOrErrorInt` | +| `sid` *(Windows only)* | `NormalType` | +| `registry_hive` *(Windows only)* | `NormalType` | + +## Usage Example + +```cpp +// Run via the osquery integration test harness: +// The test fixture is auto-registered with Google Test + +TEST_F(LoggedInUsersTest, test_sanity) { + auto const rows = execute_query("select * from logged_in_users"); + auto const row_map = ValidationMap{ + {"type", NonEmptyString}, + {"user", NormalType}, + {"pid", NonNegativeOrErrorInt}, + // Windows-only columns conditionally included via OSQUERY_WINDOWS + }; + validate_rows(rows, row_map); +} +``` + +> **Note:** The `sid` and `registry_hive` columns are only validated on Windows builds, guarded by the `OSQUERY_WINDOWS` preprocessor macro. Spec reference: `specs/logged_in_users.table`. \ No newline at end of file diff --git a/tests/integration/tables/.logical_drives.md b/tests/integration/tables/.logical_drives.md new file mode 100644 index 00000000000..db4b3e4f0c1 --- /dev/null +++ b/tests/integration/tables/.logical_drives.md @@ -0,0 +1,42 @@ + +Integration test that validates the `logical_drives` osquery table on Windows, ensuring the table returns correct data types and at least one row. + +## Key Components + +- **`logicalDrives`** — Test fixture class inheriting from `testing::Test`, with environment setup via `setUpEnvironment()` +- **`test_sanity`** — Core test case that queries `logical_drives` and validates schema and row count + +### Validated Columns + +| Column | Type | +|---|---| +| `device_id` | `NormalType` | +| `type` | `NormalType` | +| `description` | `NormalType` | +| `free_space` | `IntType` | +| `size` | `IntType` | +| `file_system` | `NormalType` | +| `boot_partition` | `IntType` | + +## Usage Example + +```cpp +// Run as part of the osquery integration test suite: +// The test executes the following query internally: +auto const data = execute_query("select * from logical_drives"); + +// Validates at least one drive is returned +ASSERT_GE(data.size(), 1ul); + +// Validates each row matches the expected schema types +validate_rows(data, row_map); +``` + +To run this specific test: + +```bash +# From the osquery build directory +./osquery_tests --gtest_filter="logicalDrives.test_sanity" +``` + +> **Note:** This test targets Windows only, per the spec file at `specs/windows/logical_drives.table`. It will not run on non-Windows platforms. \ No newline at end of file diff --git a/tests/integration/tables/.logon_sessions.md b/tests/integration/tables/.logon_sessions.md new file mode 100644 index 00000000000..efc7cea6e8d --- /dev/null +++ b/tests/integration/tables/.logon_sessions.md @@ -0,0 +1,42 @@ + +Integration test file for the `logon_sessions` osquery table on Windows, providing a sanity check that the table can be queried without errors. + +## Key Components + +- **`logonSessions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(logonSessions, test_sanity)`** — Executes a `SELECT *` query against the `logon_sessions` table to verify basic query execution. + +## Usage Example + +```cpp +// Run the sanity test (executes internally via osquery test runner) +// The test queries the logon_sessions table and optionally validates rows: + +auto const data = execute_query("select * from logon_sessions"); + +// Optional row validation (currently commented out): +ValidationMap row_map = { + {"logon_id", IntType}, + {"user", NormalType}, + {"logon_domain", NormalType}, + {"authentication_package",NormalType}, + {"logon_type", NormalType}, + {"session_id", IntType}, + {"logon_sid", NormalType}, + {"logon_time", IntType}, + {"logon_server", NormalType}, + {"dns_domain_name", NormalType}, + {"upn", NormalType}, + {"logon_script", NormalType}, + {"profile_path", NormalType}, + {"home_directory", NormalType}, + {"home_directory_drive", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- **Windows-only** — Corresponds to `specs/windows/logon_sessions.table`. +- Row count assertions and `validate_rows()` are scaffolded but commented out, meaning the test currently only confirms the query executes without crashing. +- Extend by uncommenting the `ValidationMap` block and calling `validate_rows()` to enforce column type correctness. \ No newline at end of file diff --git a/tests/integration/tables/.magic.md b/tests/integration/tables/.magic.md new file mode 100644 index 00000000000..fdbbd6eb338 --- /dev/null +++ b/tests/integration/tables/.magic.md @@ -0,0 +1,30 @@ + +Integration test stub for the `magic` osquery table, verifying basic query execution against the `magic` virtual table on POSIX systems. + +## Key Components + +- **`magic` test class** — Inherits from `testing::Test`; initializes the osquery test environment via `SetUp()`. +- **`test_sanity`** — Executes a parameterized query (`select * from magic where path = ''`) to validate the table is queryable without errors. + +## Usage Example + +```cpp +// Run the sanity test using the osquery integration test harness +// The test queries the magic table with an empty path filter +auto const data = execute_query("select * from magic where path = ''"); + +// Uncomment and configure validation when ready to assert row structure: +ValidationMap row_map = { + {"path", NormalType}, + {"data", NormalType}, + {"mime_type", NormalType}, + {"mime_encoding", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/magic.table`. +- Row count assertions and `ValidationMap` checks are scaffolded but commented out — intended to be enabled once expected output is defined. +- Uses the shared `osquery/tests/integration/tables/helper.h` harness for environment setup and row validation utilities. \ No newline at end of file diff --git a/tests/integration/tables/.managed_policies.md b/tests/integration/tables/.managed_policies.md new file mode 100644 index 00000000000..0273148d05e --- /dev/null +++ b/tests/integration/tables/.managed_policies.md @@ -0,0 +1,39 @@ + +Integration test for the `managed_policies` osquery table on Darwin (macOS), validating query execution and row schema correctness. + +## Key Components + +- **`managedPolicies`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(managedPolicies, test_sanity)`** — Sanity test that queries the `managed_policies` table and validates the result schema. + +## Usage Example + +```cpp +// The test executes the following osquery SQL internally: +auto const data = execute_query("select * from managed_policies"); + +// Schema validated against each returned row: +ValidationMap row_map = { + {"domain", NormalType}, // Policy domain (e.g., com.apple.security) + {"uuid", NormalType}, // Unique policy identifier + {"name", NormalType}, // Policy key name + {"value", NormalType}, // Policy value + {"username", NormalType}, // Associated username (empty = device-wide) + {"manual", IntType}, // 1 if manually set, 0 if MDM-managed +}; + +validate_rows(data, row_map); +``` + +## Validated Columns + +| Column | Type | Description | +|---|---|---| +| `domain` | `NormalType` | Configuration profile domain | +| `uuid` | `NormalType` | Profile UUID | +| `name` | `NormalType` | Policy preference key | +| `value` | `NormalType` | Policy preference value | +| `username` | `NormalType` | Target user (`""` = all users) | +| `manual` | `IntType` | Manual override flag | + +> The test asserts row count ≥ 0, allowing empty results on systems with no active managed policies. Schema validation still runs when rows are present. \ No newline at end of file diff --git a/tests/integration/tables/.md_devices.md b/tests/integration/tables/.md_devices.md new file mode 100644 index 00000000000..156af9872d1 --- /dev/null +++ b/tests/integration/tables/.md_devices.md @@ -0,0 +1,35 @@ + +Integration sanity test for the `md_devices` osquery table on Linux, verifying that the table can be queried without errors. + +## Key Components + +- **`mdDevices`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(mdDevices, test_sanity)`** — Executes a `SELECT * FROM md_devices` query and provides a commented-out validation map skeleton covering all table columns. + +## Columns Covered (Validation Map) + +| Column | Type | +|---|---| +| `device_name`, `status`, `superblock_state/version` | `NormalType` | +| `raid_level`, `size`, `chunk_size`, `raid_disks`, `nr_raid_disks` | `IntType` | +| `working_disks`, `active_disks`, `failed_disks`, `spare_disks` | `IntType` | +| `recovery_*`, `resync_*`, `reshape_*`, `check_array_*` | `NormalType` | +| `bitmap_*`, `unused_devices`, `other` | `NormalType` | + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner: +// ctest -R md_devices + +// To enable full validation, uncomment and populate the map: +ValidationMap row_map = { + {"device_name", NormalType}, + {"raid_level", IntType}, + {"active_disks", IntType}, + // ... remaining columns +}; +validate_rows(data, row_map); +``` + +> **Note:** The size assertions and `validate_rows` call are intentionally commented out. To harden the test, uncomment the appropriate `ASSERT_*` macro and the `validate_rows` call once expected row counts and column constraints are known for the target environment. \ No newline at end of file diff --git a/tests/integration/tables/.md_drives.md b/tests/integration/tables/.md_drives.md new file mode 100644 index 00000000000..fb463c621fe --- /dev/null +++ b/tests/integration/tables/.md_drives.md @@ -0,0 +1,31 @@ + +Integration sanity test for the `md_drives` osquery table, verifying that the table can be queried without errors on Linux systems. + +## Key Components + +- **`mdDrives`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM md_drives` and provides scaffolding for row validation. + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test executes the following query internally: +auto const data = execute_query("select * from md_drives"); + +// Uncommenting the validation map enables column-level checks: +ValidationMap row_map = { + {"md_device_name", NormalType}, + {"drive_name", NormalType}, + {"slot", IntType}, + {"state", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/linux/md_drives.table` (Linux-only). +- The `md_drives` table exposes metadata about drives participating in Linux software RAID (MD) arrays. +- Size assertions and the `ValidationMap` block are intentionally commented out, following the standard osquery integration test scaffold pattern — uncomment to enforce stricter checks. +- See `osquery/tests/integration/tables/helper.h` for available validation flag types (`NormalType`, `IntType`, etc.). \ No newline at end of file diff --git a/tests/integration/tables/.md_personalities.md b/tests/integration/tables/.md_personalities.md new file mode 100644 index 00000000000..0af979ec66a --- /dev/null +++ b/tests/integration/tables/.md_personalities.md @@ -0,0 +1,26 @@ + +Integration sanity test for the `md_personalities` osquery table, verifying that the `md_personalities` virtual table can be queried without errors on Linux systems. + +## Key Components + +- **`mdPersonalities`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(mdPersonalities, test_sanity)`** — Executes `SELECT * FROM md_personalities` and provides a scaffold for row count assertions and schema validation (currently commented out). + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test executes the following query internally: +auto const data = execute_query("select * from md_personalities"); + +// Uncomment to add row count assertions: +// ASSERT_GE(data.size(), 0ul); + +// Uncomment to validate schema using a ValidationMap: +// ValidationMap row_map = { +// {"name", NormalType} +// }; +// validate_rows(data, row_map); +``` + +> **Note:** The `md_personalities` table exposes Linux MD (software RAID) personality/driver types (e.g., `raid0`, `raid1`, `linear`) sourced from `/proc/mdstat`. This test targets **Linux only** — see `specs/linux/md_personalities.table` for the full schema definition. \ No newline at end of file diff --git a/tests/integration/tables/.mdfind.md b/tests/integration/tables/.mdfind.md new file mode 100644 index 00000000000..04a4b88b826 --- /dev/null +++ b/tests/integration/tables/.mdfind.md @@ -0,0 +1,46 @@ + +Integration test for the `mdfind` osquery table on macOS, verifying that Spotlight metadata queries return valid results without crashing. + +## Key Components + +- **`Mdfind` (test fixture)** — GTest fixture class that initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(Mdfind, test_sanity)`** — Primary test case that validates the `mdfind` virtual table behavior end-to-end. + +### Test Logic Flow + +```mermaid +graph TD + A[Execute wildcard .app query LIMIT 10] --> B{mdutil: mdfind disabled?} + B -->|Yes| C[GTEST_SKIP] + B -->|No| D{Running on older macOS CI runner?} + D -->|Yes| E[GTEST_SKIP flakiness guard] + D -->|No| F[Assert 10 rows returned] + F --> G[Validate path and query columns are non-empty] + G --> H[Re-query by exact filename from first result] + H --> I[Assert all returned rows match that filename] +``` + +## Key Behaviors Tested + +| Check | Detail | +|---|---| +| No crash on disabled `mdfind` | Query runs even when Spotlight is off | +| Row count | Exactly 10 rows returned with `LIMIT 10` | +| Schema validation | `path` and `query` columns are non-empty strings | +| Exact filename lookup | Secondary query by filename returns only matching paths | + +## Usage Example + +```cpp +// Run this specific test via osquery test runner: +// ./osquery_tests --gtest_filter=Mdfind.test_sanity + +// The table under test accepts Spotlight query syntax: +execute_query( + "SELECT * FROM mdfind " + "WHERE query = 'kMDItemFSName = \"*.app\"' " + "LIMIT 10;" +); +``` + +> **Note:** The test automatically skips if Spotlight indexing is disabled (`mdutil -s / | grep disabled`) or when running on older macOS CI runners due to known flakiness. It maps to the table spec at `specs/darwin/mdfind.table`. \ No newline at end of file diff --git a/tests/integration/tables/.memory_array_mapped_addresses.md b/tests/integration/tables/.memory_array_mapped_addresses.md new file mode 100644 index 00000000000..c8dcb47734b --- /dev/null +++ b/tests/integration/tables/.memory_array_mapped_addresses.md @@ -0,0 +1,31 @@ + +Integration test stub for the `memory_array_mapped_addresses` osquery table, providing a sanity check that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`memoryArrayMappedAddresses`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` against the `memory_array_mapped_addresses` table to verify it runs without crashing. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test queries the table and optionally validates row structure: + +ValidationMap row_map = { + {"handle", NormalType}, + {"memory_array_handle", NormalType}, + {"starting_address", NormalType}, + {"ending_address", NormalType}, + {"partition_width", IntType}, +}; + +auto const data = execute_query("select * from memory_array_mapped_addresses"); +validate_rows(data, row_map); +``` + +## Notes + +- Validation logic (row count assertions and `validate_rows`) is commented out, meaning this test only confirms the query executes — it does not enforce schema or result constraints. +- Corresponds to the spec at `specs/posix/memory_array_mapped_addresses.table`. +- Extend by uncommenting and populating the `ValidationMap` to enforce column types when stricter coverage is needed. \ No newline at end of file diff --git a/tests/integration/tables/.memory_arrays.md b/tests/integration/tables/.memory_arrays.md new file mode 100644 index 00000000000..6d72f69c476 --- /dev/null +++ b/tests/integration/tables/.memory_arrays.md @@ -0,0 +1,32 @@ + +Integration sanity test for the `memory_arrays` osquery table, verifying that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`memoryArrays`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM memory_arrays` and provides a commented-out validation scaffold for row structure checks. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test queries the memory_arrays table and can be extended +// with row validation using the commented scaffold: + +ValidationMap row_map = { + {"handle", NormalType}, + {"location", NormalType}, + {"use", NormalType}, + {"memory_error_correction", NormalType}, + {"max_capacity", IntType}, + {"memory_error_info_handle", NormalType}, + {"number_memory_devices", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/memory_arrays.table`. +- Size assertions and row validation are commented out, making this a non-failing smoke test that confirms the query executes successfully. +- To enable stricter validation, uncomment the `ValidationMap` block and the `validate_rows` call, and add an appropriate `ASSERT_GE`/`ASSERT_EQ` size check. \ No newline at end of file diff --git a/tests/integration/tables/.memory_device_mapped_addresses.md b/tests/integration/tables/.memory_device_mapped_addresses.md new file mode 100644 index 00000000000..0fbbb4155da --- /dev/null +++ b/tests/integration/tables/.memory_device_mapped_addresses.md @@ -0,0 +1,33 @@ + +Integration test file for the `memory_device_mapped_addresses` osquery table, providing a sanity check to verify the table can be queried without errors on POSIX systems. + +## Key Components + +- **`memoryDeviceMappedAddresses`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` query against the `memory_device_mapped_addresses` table to confirm it runs without crashing. + +## Usage Example + +```cpp +// Run the sanity test against the table +auto const data = execute_query("select * from memory_device_mapped_addresses"); + +// Example validation map (currently commented out, ready to enable): +ValidationMap row_map = { + {"handle", NormalType}, + {"memory_device_handle", NormalType}, + {"memory_array_mapped_address_handle", NormalType}, + {"starting_address", NormalType}, + {"ending_address", NormalType}, + {"partition_row_position", IntType}, + {"interleave_position", IntType}, + {"interleave_data_depth", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec source: `specs/posix/memory_device_mapped_addresses.table` +- Row count assertions and full `ValidationMap` checks are scaffolded but commented out, allowing incremental test hardening as the table matures. +- Uses the shared `osquery/tests/integration/tables/helper.h` utilities (`execute_query`, `validate_rows`, type flags). \ No newline at end of file diff --git a/tests/integration/tables/.memory_devices.md b/tests/integration/tables/.memory_devices.md new file mode 100644 index 00000000000..63174a6a575 --- /dev/null +++ b/tests/integration/tables/.memory_devices.md @@ -0,0 +1,33 @@ + +Integration test for the `memory_devices` osquery table, validating that query results conform to expected column types and formats. + +## Key Components + +- **`memoryDevices`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(memoryDevices, test_sanity)`** — Primary test case that executes a `SELECT *` against the `memory_devices` table and validates each row's column types. +- **`ValidationMap row_map`** — Defines expected type constraints for all 20 columns returned by the table. + +## Validated Columns + +| Column | Type Constraint | +|---|---| +| `handle`, `array_handle`, `form_factor` | `NormalType` | +| `device_locator`, `bank_locator` | `NormalType` | +| `memory_type`, `memory_type_details` | `NormalType` | +| `manufacturer`, `serial_number`, `asset_tag`, `part_number` | `NormalType` | +| `total_width`, `data_width`, `size`, `set` | `IntOrEmpty` | +| `max_speed`, `configured_clock_speed` | `IntOrEmpty` | +| `min_voltage`, `max_voltage`, `configured_voltage` | `IntOrEmpty` | + +## Usage Example + +```cpp +// Run this test via the osquery integration test runner: +// ctest -R memory_devices + +// Internally, the test executes: +auto const data = execute_query("select * from memory_devices"); +validate_rows(data, row_map); +``` + +> **Note:** Row count assertions (`ASSERT_GT`, `ASSERT_EQ`) are intentionally commented out, as memory device availability varies by hardware configuration. The test validates schema shape only, not result count. \ No newline at end of file diff --git a/tests/integration/tables/.memory_error_info.md b/tests/integration/tables/.memory_error_info.md new file mode 100644 index 00000000000..0e7922f9b13 --- /dev/null +++ b/tests/integration/tables/.memory_error_info.md @@ -0,0 +1,34 @@ + +Integration test sanity check for the `memory_error_info` osquery table, verifying that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`memoryErrorInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM memory_error_info` and provides a scaffolded (commented-out) validation structure for future row-level assertions. + +## Usage Example + +```cpp +// Run the sanity test directly via osquery's integration test runner +// The test executes the following query internally: +auto const data = execute_query("select * from memory_error_info"); + +// To enable full row validation, uncomment and populate the ValidationMap: +ValidationMap row_map = { + {"handle", NormalType}, + {"error_type", NormalType}, + {"error_granularity", NormalType}, + {"error_operation", NormalType}, + {"vendor_syndrome", NormalType}, + {"memory_array_error_address", NormalType}, + {"device_error_address", NormalType}, + {"error_resolution", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec source: `specs/posix/memory_error_info.table` +- Row count and validation assertions are currently commented out — the test only confirms the query executes without failure. +- Full validation can be enabled by uncommenting the `ValidationMap` block and calling `validate_rows()`. \ No newline at end of file diff --git a/tests/integration/tables/.memory_info.md b/tests/integration/tables/.memory_info.md new file mode 100644 index 00000000000..266dea2fee5 --- /dev/null +++ b/tests/integration/tables/.memory_info.md @@ -0,0 +1,43 @@ + +Integration sanity test for the `memory_info` osquery table on Linux, verifying that the table can be queried without errors. + +## Key Components + +- **`memoryInfo`** — Test fixture class inheriting from `testing::Test`; calls `setUpEnvironment()` in `SetUp()` to initialize the test environment. +- **`TEST_F(memoryInfo, test_sanity)`** — Executes `SELECT * FROM memory_info` and provides a scaffolded (currently commented-out) validation structure for expected integer columns. + +## Columns Covered (spec reference) + +| Column | Type | +|---|---| +| `memory_total` | `IntType` | +| `memory_free` | `IntType` | +| `memory_available` | `IntType` | +| `buffers` | `IntType` | +| `cached` | `IntType` | +| `swap_cached` | `IntType` | +| `active` | `IntType` | +| `inactive` | `IntType` | +| `swap_total` | `IntType` | +| `swap_free` | `IntType` | + +## Usage Example + +```cpp +// To enable full validation, uncomment and populate the ValidationMap: +ValidationMap row_map = { + {"memory_total", IntType}, + {"memory_free", IntType}, + {"memory_available", IntType}, + {"buffers", IntType}, + {"cached", IntType}, + {"swap_cached", IntType}, + {"active", IntType}, + {"inactive", IntType}, + {"swap_total", IntType}, + {"swap_free", IntType}, +}; +validate_rows(data, row_map); +``` + +> **Note:** Validation logic is scaffolded but commented out. Activate row count assertions (`ASSERT_GE`, `ASSERT_EQ`) and `validate_rows()` once expected behavior is confirmed for the target Linux environment. \ No newline at end of file diff --git a/tests/integration/tables/.memory_map.md b/tests/integration/tables/.memory_map.md new file mode 100644 index 00000000000..a41dc52c0f9 --- /dev/null +++ b/tests/integration/tables/.memory_map.md @@ -0,0 +1,33 @@ + +Integration test for the `memory_map` osquery table, validating that system memory map entries are returned with correct structure and logically consistent address ranges. + +## Key Components + +- **`MemoryMapTest`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `SetUp()`. +- **`TEST_F(MemoryMapTest, test_sanity)`** — Primary test case that: + - Executes `SELECT * FROM memory_map` and asserts at least one row is returned. + - Validates row schema using a `ValidationMap` with: + - `name` — non-empty string + - `start` — non-negative integer (memory region start address) + - `end` — non-negative integer (memory region end address) + - Asserts each `start` and `end` value fits within `unsigned long long`. + - Asserts `start <= end` for every row to ensure address range integrity. + +## Usage Example + +```cpp +// Run this test via the osquery integration test harness: +// The test queries the memory_map virtual table and validates each row. + +QueryData data = execute_query("select * from memory_map"); + +// Expected row structure: +// { "name": "System RAM", "start": "0", "end": "134217727" } + +auto start = tryTo(row.at("start")); +auto end = tryTo(row.at("end")); + +ASSERT_LE(*start, *end); // start address must not exceed end address +``` + +> **Note:** This test targets Linux only, as defined by the corresponding spec at `specs/linux/memory_map.table`. It is a sanity check to ensure the table is populated and data is structurally valid at runtime. \ No newline at end of file diff --git a/tests/integration/tables/.mounts.md b/tests/integration/tables/.mounts.md new file mode 100644 index 00000000000..b74edbc750c --- /dev/null +++ b/tests/integration/tables/.mounts.md @@ -0,0 +1,39 @@ + +Integration test that validates the `mounts` osquery table returns well-formed data on POSIX systems. + +## Key Components + +- **`mounts` test class** — Inherits from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity` (TEST_F)** — Executes `SELECT * FROM mounts` and validates each returned row against a schema map. +- **`ValidationMap`** — Defines expected column types for all 11 columns of the `mounts` table. + +### Validated Columns + +| Column | Type | +|---|---| +| `device` | NormalType | +| `device_alias` | NormalType | +| `path` | NormalType | +| `type` | NormalType | +| `blocks_size` | IntType | +| `blocks` | IntType | +| `blocks_free` | IntType | +| `blocks_available` | IntType | +| `inodes` | IntType | +| `inodes_free` | IntType | +| `flags` | NormalType | + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// Spec: specs/posix/mounts.table + +TEST_F(mounts, test_sanity) { + auto const data = execute_query("select * from mounts"); + ASSERT_FALSE(data.empty()); // Table must return at least one row + validate_rows(data, row_map); // Each row must match the schema +} +``` + +The test asserts the result set is non-empty (at least one mount point must exist) and that every row conforms to the expected column types. Corresponds to the POSIX table spec at `specs/posix/mounts.table`. \ No newline at end of file diff --git a/tests/integration/tables/.msr.md b/tests/integration/tables/.msr.md new file mode 100644 index 00000000000..2828a78a7b3 --- /dev/null +++ b/tests/integration/tables/.msr.md @@ -0,0 +1,44 @@ + +Integration test file for the `msr` osquery table, validating that queries against the Linux Model-Specific Register (MSR) table execute without error. + +## Key Components + +- **`msr` test class** — Inherits from `testing::Test`; calls `setUpEnvironment()` in `SetUp()` to initialize the test environment. +- **`test_sanity`** — Executes `SELECT * FROM msr` and provides a commented scaffold for row count assertions and field-level validation. + +### Commented Validation Fields + +The validation map template covers these MSR columns: + +| Column | Type | +|---|---| +| `processor_number` | `IntType` | +| `turbo_disabled` | `IntType` | +| `turbo_ratio_limit` | `IntType` | +| `platform_info` | `IntType` | +| `perf_ctl` | `IntType` | +| `perf_status` | `IntType` | +| `feature_control` | `IntType` | +| `rapl_power_limit` | `IntType` | +| `rapl_energy_status` | `IntType` | +| `rapl_power_units` | `IntType` | + +## Usage Example + +To extend the sanity test with active validation, uncomment and configure the validation map: + +```cpp +TEST_F(msr, test_sanity) { + auto const data = execute_query("select * from msr"); + ASSERT_GE(data.size(), 0ul); + + ValidationMap row_map = { + {"processor_number", IntType}, + {"turbo_disabled", IntType}, + {"rapl_power_limit", IntType}, + }; + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets Linux only. Spec is defined in `specs/linux/msr.table`. Requires appropriate hardware and kernel permissions to read MSR registers at runtime. \ No newline at end of file diff --git a/tests/integration/tables/.nfs_shares.md b/tests/integration/tables/.nfs_shares.md new file mode 100644 index 00000000000..3f3265ef995 --- /dev/null +++ b/tests/integration/tables/.nfs_shares.md @@ -0,0 +1,29 @@ + +Integration test file for the `nfs_shares` osquery table on Darwin (macOS), providing a sanity check that verifies the table can be queried without errors. + +## Key Components + +- **`nfsShares`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(nfsShares, test_sanity)`** — Basic sanity test that executes a `SELECT * FROM nfs_shares` query and validates the result structure. + +## Usage Example + +```cpp +// Run the sanity test against the nfs_shares table +auto const data = execute_query("select * from nfs_shares"); + +// Uncomment and configure validation as needed: +ValidationMap row_map = { + {"share", NormalType}, + {"options", NormalType}, + {"readonly", IntType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- The validation map and row count assertions are stubbed out (commented) — enable them to enforce expected schema and result counts. +- Corresponds to the table spec at `specs/darwin/nfs_shares.table`. +- Expected columns: `share` (string), `options` (string), `readonly` (integer). +- Part of the osquery integration test suite; run via the standard osquery test harness rather than standalone. \ No newline at end of file diff --git a/tests/integration/tables/.npm_packages.md b/tests/integration/tables/.npm_packages.md new file mode 100644 index 00000000000..f9b4771f295 --- /dev/null +++ b/tests/integration/tables/.npm_packages.md @@ -0,0 +1,35 @@ + +Integration test file that validates the `npm_packages` osquery table returns correctly structured data with expected column types. + +## Key Components + +- **`NpmPackagesTest`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(NpmPackagesTest, test_sanity)`** — Primary test case that queries the `npm_packages` table and validates row structure. +- **`ValidationMap`** — Defines expected column constraints for each field returned by the table. + +## Validated Columns + +| Column | Constraint | +|---|---| +| `name` | `NonEmptyString` | +| `version` | `NonEmptyString` | +| `description` | `NormalType` | +| `homepage` | `NormalType` | +| `author` | `NormalType` | +| `license` | `NormalType` | +| `path` | `NonEmptyString` | +| `directory` | `NonEmptyString` | + +## Usage Example + +```cpp +// Test executes the following query internally: +auto data = execute_query("select * from npm_packages"); + +// On Linux, also validates container-scoped rows: +if (isPlatform(PlatformType::TYPE_LINUX)) { + validate_container_rows("npm_packages", row_map); +} +``` + +The container row validation (`validate_container_rows`) is Linux-only, reflecting that container-aware npm package discovery is scoped to Linux environments. The spec for this table lives at `specs/linux/npm_packages.table`. \ No newline at end of file diff --git a/tests/integration/tables/.ntdomains.md b/tests/integration/tables/.ntdomains.md new file mode 100644 index 00000000000..4ed7910bd5e --- /dev/null +++ b/tests/integration/tables/.ntdomains.md @@ -0,0 +1,40 @@ + +Integration test for the `ntdomains` osquery table, validating that the Windows NT domain information query returns well-formed results with expected fields. + +## Key Components + +- **`NtDomains`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(NtDomains, test_sanity)`** — Sanity check that executes `SELECT * FROM ntdomains` and validates the result set. +- **`ValidationMap`** — Defines per-column validation rules applied to every returned row. + +## Validated Columns + +| Column | Rule | +|---|---| +| `name` | `NonEmptyString` | +| `client_site_name` | `NormalType` | +| `dc_site_name` | `NormalType` | +| `dns_forest_name` | `NormalType` | +| `domain_controller_address` | `NormalType` | +| `domain_controller_name` | `NormalType` | +| `domain_name` | `NormalType` | +| `status` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run as part of the osquery integration test suite (Windows only) +// Spec: specs/windows/nt_info.table + +TEST_F(NtDomains, test_sanity) { + QueryData data = execute_query("select * from ntdomains"); + + // Expect at least one NT domain record + ASSERT_GE(data.size(), 1ul); + + // Validate all rows conform to expected schema + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets Windows only and requires at least one NT domain to be reachable. `name` and `status` must be non-empty strings; remaining fields accept any valid type (including null/empty). \ No newline at end of file diff --git a/tests/integration/tables/.ntfs_acl_permissions.md b/tests/integration/tables/.ntfs_acl_permissions.md new file mode 100644 index 00000000000..4a101edafa2 --- /dev/null +++ b/tests/integration/tables/.ntfs_acl_permissions.md @@ -0,0 +1,33 @@ + +Integration test for the `ntfs_acl_permissions` osquery table, validating that the table correctly returns NTFS Access Control List (ACL) entries for Windows file system paths. + +## Key Components + +- **`ntfsAclPermissions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that queries the `ntfs_acl_permissions` table for the `C:\` root path and validates the result structure. +- **`ValidationMap`** — Defines expected column schema with `NormalType` assertions for all five columns. + +## Validated Columns + +| Column | Type | +|---|---| +| `path` | NormalType | +| `type` | NormalType | +| `principal` | NormalType | +| `access` | NormalType | +| `inherited_from` | NormalType | + +## Usage Example + +```cpp +// Runs as part of the osquery integration test suite on Windows. +// The test executes the following query internally: +auto const data = + execute_query("select * from ntfs_acl_permissions where path = 'C:\\'"); + +// Asserts results are non-empty and all rows match the expected schema +ASSERT_FALSE(data.empty()); +validate_rows(data, row_map); +``` + +> **Note:** This is a Windows-only integration test. A `WHERE path = ...` clause is required when querying `ntfs_acl_permissions`, as the table does not support unfiltered scans. The test asserts that `C:\` always returns at least one ACL entry, ensuring the table is functional on any standard Windows installation. \ No newline at end of file diff --git a/tests/integration/tables/.nvram.md b/tests/integration/tables/.nvram.md new file mode 100644 index 00000000000..98c4e9ed04c --- /dev/null +++ b/tests/integration/tables/.nvram.md @@ -0,0 +1,39 @@ + +Integration test for the `nvram` osquery table on Darwin (macOS), validating that NVRAM variable queries return well-formed results. + +## Key Components + +- **`NvramTest`** — Test fixture inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Executes `SELECT * FROM nvram` and validates the result set against a schema map. + +## Validation Schema + +| Column | Rule | +|--------|------| +| `name` | `NonEmptyString` | +| `type` | `NonEmptyString` | +| `value` | `NormalType` | + +## Usage Example + +```cpp +// Run as part of the osquery integration test suite (Darwin only) +// Spec: specs/darwin/nvram.table + +TEST_F(NvramTest, test_sanity) { + auto const data = execute_query("select * from nvram"); + + // Ensure at least one NVRAM variable is returned + ASSERT_GT(data.size(), 0ul); + + // Validate each row conforms to the expected schema + ValidationMap row_map = { + {"name", NonEmptyString}, + {"type", NonEmptyString}, + {"value", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +> **Platform:** Darwin (macOS) only — targets NVRAM variables exposed via `IORegistryEntry`. The test asserts the table is non-empty, reflecting that real hardware will always have NVRAM entries populated. \ No newline at end of file diff --git a/tests/integration/tables/.oem_strings.md b/tests/integration/tables/.oem_strings.md new file mode 100644 index 00000000000..f976c129416 --- /dev/null +++ b/tests/integration/tables/.oem_strings.md @@ -0,0 +1,31 @@ + +Integration sanity test for the `oem_strings` osquery table, verifying basic query execution against the POSIX `oem_strings` virtual table. + +## Key Components + +- **`oemStrings`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(oemStrings, test_sanity)`** — Single test case that executes `SELECT * FROM oem_strings` and provides a scaffolded (but currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test queries the oem_strings table and can be extended +// with the following validation pattern: + +ValidationMap row_map = { + {"handle", NormalType}, + {"number", IntType}, + {"value", NormalType} +}; + +auto const data = execute_query("select * from oem_strings"); +validate_rows(data, row_map); +``` + +## Notes + +- All validation steps (row count assertions and `validate_rows`) are **commented out**, meaning the test currently only confirms the query executes without error. +- Corresponds to the table spec at `specs/posix/oem_strings.table`. +- To enable full validation, uncomment and configure the `ValidationMap` block along with the appropriate `ASSERT_GE`/`ASSERT_EQ` size checks based on expected SMBIOS OEM string entries on the target system. +- See `osquery/tests/integration/tables/helper.h` for available type flags (`NormalType`, `IntType`, etc.) and the `validate_rows` utility. \ No newline at end of file diff --git a/tests/integration/tables/.office_mru.md b/tests/integration/tables/.office_mru.md new file mode 100644 index 00000000000..9b08be67311 --- /dev/null +++ b/tests/integration/tables/.office_mru.md @@ -0,0 +1,34 @@ + +Integration test for the `office_mru` osquery table, validating that rows returned from the table conform to expected column types and constraints. + +## Key Components + +- **`OfficeMruTest`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(OfficeMruTest, test_sanity)`** — Sanity test that queries the `office_mru` table and validates returned row structure. + +## Validated Columns + +| Column | Validation Rule | +|---|---| +| `application` | `NonEmptyString` | +| `version` | `NonEmptyString` | +| `path` | `NonEmptyString` | +| `last_opened_time` | `NormalType` | +| `sid` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run the integration test directly via ctest or the osquery test binary: +// The test executes the following query internally: +QueryData const rows = execute_query("select * from office_mru"); + +// Validation only runs if rows are present (table may be empty +// on systems without Microsoft Office installed) +if (!rows.empty()) { + validate_rows(rows, row_map); + ASSERT_GT(rows.size(), 0ul); +} +``` + +> **Note:** The test conditionally validates rows only when results are present, accommodating systems where Microsoft Office is not installed. This ensures the test does not produce false failures in environments without Office data. \ No newline at end of file diff --git a/tests/integration/tables/.os_version.md b/tests/integration/tables/.os_version.md new file mode 100644 index 00000000000..ee76ac11c41 --- /dev/null +++ b/tests/integration/tables/.os_version.md @@ -0,0 +1,41 @@ + +Integration test for the `os_version` osquery table, validating that the table returns correct OS version data with proper field types across platforms (Linux, Windows, macOS). + +## Key Components + +- **`OsVersion`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` +- **`TEST_F(OsVersion, test_sanity)`** — Primary sanity test that queries and validates the `os_version` table + +## Validation Logic + +The test enforces the following field constraints: + +| Field | Constraint | Platform | +|---|---|---| +| `name`, `version` | `NonEmptyString` | All | +| `major`, `minor` | `NonNegativeInt` | All | +| `patch`, `build` | `NormalType` | All | +| `platform`, `platform_like` | `NonEmptyString` | All | +| `arch` | `NonEmptyString` | All | +| `codename` | `NormalType` | All | +| `install_date` | `NonEmptyString` | Windows only | +| `revision` | `NonNegativeInt` | Windows only | +| `extra` | `NormalType` | macOS only | + +## Usage Example + +```cpp +// Run this specific test via osquery's test runner +// The test executes the following query internally: +QueryData data = execute_query("select * from os_version"); + +// Expects exactly one row returned +ASSERT_EQ(data.size(), 1ul); + +// On Linux, also validates container-aware rows +if (isPlatform(PlatformType::TYPE_LINUX)) { + validate_container_rows("os_version", row_map); +} +``` + +The test asserts exactly **one row** is returned and uses `validate_container_rows` on Linux to account for container environments where OS version detection may differ from the host. \ No newline at end of file diff --git a/tests/integration/tables/.osquery_events.md b/tests/integration/tables/.osquery_events.md new file mode 100644 index 00000000000..3b2f6f99875 --- /dev/null +++ b/tests/integration/tables/.osquery_events.md @@ -0,0 +1,32 @@ + +Integration sanity test for the `osquery_events` virtual table, verifying that the table can be queried without errors as part of osquery's table integration test suite. + +## Key Components + +- **`osqueryEvents`** — Test fixture class inheriting from `testing::Test`; calls `setUpEnvironment()` in `SetUp()` to initialize the test environment. +- **`test_sanity`** — Single test case that executes `SELECT * FROM osquery_events` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test binary: +// $ osquery_integration_tests --gtest_filter=osqueryEvents.test_sanity + +// To enable full validation, uncomment and populate the ValidationMap: +ValidationMap row_map = { + {"name", NormalType}, + {"publisher", NormalType}, + {"type", NormalType}, + {"subscriptions", IntType}, + {"events", IntType}, + {"refreshes", IntType}, + {"active", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- The validation logic (row count assertions and `validate_rows`) is intentionally commented out, meaning the test currently only checks that the query executes without throwing. +- The `osquery_events` table schema is defined in `specs/utility/osquery_events.table`. +- Helper utilities (`execute_query`, `validate_rows`, type flags) are sourced from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.osquery_extensions.md b/tests/integration/tables/.osquery_extensions.md new file mode 100644 index 00000000000..8e2d41fb43a --- /dev/null +++ b/tests/integration/tables/.osquery_extensions.md @@ -0,0 +1,32 @@ + +Integration sanity test for the `osquery_extensions` virtual table, verifying the table can be queried without errors as part of osquery's integration test suite. + +## Key Components + +- **`osqueryExtensions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — A single test case that executes `SELECT * FROM osquery_extensions` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test queries the osquery_extensions table and can be +// extended with full row validation by uncommenting and +// configuring the validation map: + +ValidationMap row_map = { + {"uuid", IntType}, + {"name", NormalType}, + {"version", NormalType}, + {"sdk_version", NormalType}, + {"path", NormalType}, + {"type", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- The `osquery_extensions` table is defined in `specs/utility/osquery_extensions.table`. +- Size assertions and row validation are scaffolded but commented out — activate them to enforce stricter guarantees about loaded extensions. +- Depends on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants (`IntType`, `NormalType`, etc.). \ No newline at end of file diff --git a/tests/integration/tables/.osquery_flags.md b/tests/integration/tables/.osquery_flags.md new file mode 100644 index 00000000000..fa0cca47a83 --- /dev/null +++ b/tests/integration/tables/.osquery_flags.md @@ -0,0 +1,34 @@ + +Integration test file for the `osquery_flags` virtual table, verifying that the table can be queried without errors as a sanity check. + +## Key Components + +- **`osqueryFlags`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM osquery_flags` and provides a scaffold for row-level validation. + +## Usage Example + +```cpp +// Run the sanity test against the osquery_flags table +TEST_F(osqueryFlags, test_sanity) { + auto const data = execute_query("select * from osquery_flags"); + + // Optional: validate expected row structure + ValidationMap row_map = { + {"name", NormalType}, + {"type", NormalType}, + {"description", NormalType}, + {"default_value", NormalType}, + {"value", NormalType}, + {"shell_only", IntType} + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- The validation map and row count assertions are **commented out by default** — enable them to enforce stricter checks against the `osquery_flags` table schema. +- Expected columns: `name`, `type`, `description`, `default_value`, `value`, `shell_only`. +- Spec file reference: `specs/utility/osquery_flags.table`. +- Relies on helpers from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.osquery_info.md b/tests/integration/tables/.osquery_info.md new file mode 100644 index 00000000000..3ea514e57ca --- /dev/null +++ b/tests/integration/tables/.osquery_info.md @@ -0,0 +1,37 @@ + +Integration test file for the `osquery_info` virtual table, verifying that the table can be queried without errors as a basic sanity check. + +## Key Components + +- **`osqueryInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(osqueryInfo, test_sanity)`** — Single test case that executes `SELECT * FROM osquery_info` and provides a scaffolded (but currently inactive) validation structure. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test harness +// The test executes the following query internally: +auto const data = execute_query("select * from osquery_info"); + +// Commented-out validation map (for reference/future activation): +ValidationMap row_map = { + {"pid", IntType}, + {"uuid", NormalType}, + {"instance_id", NormalType}, + {"version", NormalType}, + {"config_hash", NormalType}, + {"config_valid", IntType}, + {"extensions", NormalType}, + {"build_platform", NormalType}, + {"build_distro", NormalType}, + {"start_time", IntType}, + {"watcher", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Row count assertions and `validate_rows()` are **commented out**, meaning the test currently only confirms the query executes without crashing. +- The validation map documents all expected columns and their types (`IntType`, `NormalType`) from the spec at `specs/utility/osquery_info.table`. +- Extend this test by uncommenting and configuring the `ValidationMap` and size assertions for stricter coverage. \ No newline at end of file diff --git a/tests/integration/tables/.osquery_packs.md b/tests/integration/tables/.osquery_packs.md new file mode 100644 index 00000000000..a3c067e277f --- /dev/null +++ b/tests/integration/tables/.osquery_packs.md @@ -0,0 +1,33 @@ + +Integration sanity test for the `osquery_packs` virtual table, verifying that the table can be queried without errors as part of osquery's automated test suite. + +## Key Components + +- **`osqueryPacks`** — Test fixture class inheriting from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(osqueryPacks, test_sanity)`** — Single test case that executes `SELECT * FROM osquery_packs` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test executes the following query internally: +auto const data = execute_query("select * from osquery_packs"); + +// Uncomment and configure to enable full row validation: +ValidationMap row_map = { + {"name", NormalType}, + {"platform", NormalType}, + {"version", NormalType}, + {"shard", IntType}, + {"discovery_cache_hits", IntType}, + {"discovery_executions", IntType}, + {"active", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/utility/osquery_packs.table` +- Row count assertions and `validate_rows()` are scaffolded but commented out — enable them to assert expected schema and result counts. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.osquery_registry.md b/tests/integration/tables/.osquery_registry.md new file mode 100644 index 00000000000..e8442f43348 --- /dev/null +++ b/tests/integration/tables/.osquery_registry.md @@ -0,0 +1,39 @@ + +Integration sanity test for the `osquery_registry` virtual table, verifying that the table can be queried without errors within the osquery testing framework. + +## Key Components + +- **`osqueryRegistry`** — Test fixture class inheriting from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(osqueryRegistry, test_sanity)`** — Executes `SELECT * FROM osquery_registry` and provides a scaffolded (commented-out) validation structure for row count assertions and column type checks. + +## Expected Table Schema + +The commented validation map documents the expected columns for `osquery_registry`: + +| Column | Type | +|---|---| +| `registry` | NormalType | +| `name` | NormalType | +| `owner_uuid` | IntType | +| `internal` | IntType | +| `active` | IntType | + +## Usage Example + +```cpp +// To enable full validation, uncomment and configure: +ValidationMap row_map = { + {"registry", NormalType}, + {"name", NormalType}, + {"owner_uuid", IntType}, + {"internal", IntType}, + {"active", IntType} +}; + +validate_rows(data, row_map); + +// Optionally assert row count: +ASSERT_GE(data.size(), 0ul); +``` + +> **Note:** The test currently only runs the query to confirm execution succeeds. Row count assertions and column validation are scaffolded but commented out — enable them to add stricter regression coverage. \ No newline at end of file diff --git a/tests/integration/tables/.osquery_schedule.md b/tests/integration/tables/.osquery_schedule.md new file mode 100644 index 00000000000..5fb928b9b1a --- /dev/null +++ b/tests/integration/tables/.osquery_schedule.md @@ -0,0 +1,43 @@ + +Integration test file for the `osquery_schedule` virtual table, providing a sanity check that verifies the table can be queried without errors. + +## Key Components + +- **`osquerySchedule`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM osquery_schedule` and provides a scaffolded validation structure (currently commented out). + +## Tested Table Schema + +The `osquery_schedule` table exposes scheduled query metadata with the following columns (from the commented validation map): + +| Column | Type | +|---|---| +| `name` | NormalType | +| `query` | NormalType | +| `interval` | IntType | +| `executions` | IntType | +| `last_executed` | IntType | +| `denylisted` | IntType | +| `output_size` | IntType | +| `wall_time` | IntType | +| `user_time` | IntType | +| `system_time` | IntType | +| `average_memory` | IntType | + +## Usage Example + +```cpp +// Run the integration test directly via osquery test runner: +// The test executes the following query internally: +auto const data = execute_query("select * from osquery_schedule"); + +// To enable full row validation, uncomment and configure: +ValidationMap row_map = { + {"name", NormalType}, + {"interval", IntType}, + {"executions", IntType} +}; +validate_rows(data, row_map); +``` + +> **Note:** Row count assertions and `validate_rows()` are scaffolded but commented out, meaning this test currently only validates that the query executes without crashing. \ No newline at end of file diff --git a/tests/integration/tables/.package_bom.md b/tests/integration/tables/.package_bom.md new file mode 100644 index 00000000000..8d433d131a6 --- /dev/null +++ b/tests/integration/tables/.package_bom.md @@ -0,0 +1,35 @@ + +Integration sanity test for the `package_bom` osquery table on Darwin (macOS), verifying basic query execution against the table spec defined in `specs/darwin/package_bom.table`. + +## Key Components + +- **`packageBom`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(packageBom, test_sanity)`** — Executes a basic `SELECT *` query against the `package_bom` table filtered by an empty path, serving as a smoke test to confirm the table is queryable without runtime errors. + +## Usage Example + +```cpp +// Run this specific integration test via osquery test runner: +// ./osquery_integration_tests --gtest_filter="packageBom.test_sanity" + +// The query under test: +auto const data = execute_query("select * from package_bom where path = ''"); + +// Full validation (currently scaffolded, uncomment to enable): +ValidationMap row_map = { + {"filepath", NormalType}, + {"uid", IntType}, + {"gid", IntType}, + {"mode", IntType}, + {"size", IntType}, + {"modified_time", IntType}, + {"path", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- The `path = ''` filter returns an empty result set by design — the test currently only validates that the query executes without crashing. +- Row count assertions and `validate_rows()` calls are scaffolded but commented out, making this a minimal existence/sanity check rather than a full data integrity test. +- Targets **Darwin only** — not applicable on Linux or Windows builds. \ No newline at end of file diff --git a/tests/integration/tables/.package_install_history.md b/tests/integration/tables/.package_install_history.md new file mode 100644 index 00000000000..cd39ce19be9 --- /dev/null +++ b/tests/integration/tables/.package_install_history.md @@ -0,0 +1,40 @@ + +Integration sanity test for the `package_install_history` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`packageInstallHistory`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(packageInstallHistory, test_sanity)`** — Executes a `SELECT *` against the `package_install_history` table and provides a scaffolded (commented-out) validation map for future row-level assertions. + +## Expected Table Schema + +The commented validation map documents the expected columns: + +| Column | Type | +|---|---| +| `package_id` | NormalType | +| `time` | IntType | +| `name` | NormalType | +| `version` | NormalType | +| `source` | NormalType | +| `content_type` | NormalType | + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// Uncomment and extend validation to assert row contents: + +ValidationMap row_map = { + {"package_id", NormalType}, + {"time", IntType}, + {"name", NormalType}, + {"version", NormalType}, + {"source", NormalType}, + {"content_type", NormalType}, +}; + +validate_rows(data, row_map); +``` + +> **Note:** Row count assertions and `validate_rows()` are currently commented out. To enable full validation, uncomment the `ValidationMap` block and the appropriate `ASSERT_GE`/`ASSERT_EQ` size check matching your expected dataset. \ No newline at end of file diff --git a/tests/integration/tables/.package_receipts.md b/tests/integration/tables/.package_receipts.md new file mode 100644 index 00000000000..e2ad8ef1ea8 --- /dev/null +++ b/tests/integration/tables/.package_receipts.md @@ -0,0 +1,32 @@ + +Integration sanity test for the `package_receipts` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`packageReceipts`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that executes `SELECT * FROM package_receipts` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test harness +// The test executes the query and can be extended with row validation: + +ValidationMap row_map = { + {"package_id", NormalType}, + {"package_filename", NormalType}, + {"version", NormalType}, + {"location", NormalType}, + {"install_time", NormalType}, + {"installer_name", NormalType}, + {"path", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets **Darwin (macOS)** only — spec defined in `specs/darwin/package_receipts.table`. +- Row count assertions and `validate_rows()` are scaffolded but commented out, making this a **non-failing smoke test** by default. +- Extend by uncommenting the `ValidationMap` and `validate_rows` call to enforce column type correctness. +- See `osquery/tests/integration/tables/helper.h` for available validation flags and `DataCheck` utilities. \ No newline at end of file diff --git a/tests/integration/tables/.patches.md b/tests/integration/tables/.patches.md new file mode 100644 index 00000000000..66883a2bc38 --- /dev/null +++ b/tests/integration/tables/.patches.md @@ -0,0 +1,33 @@ + +Integration test stub for the `patches` osquery table on Windows, verifying basic query execution against the `patches` table spec. + +## Key Components + +- **`patches` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(patches, test_sanity)`** — Executes `SELECT * FROM patches` and provides a scaffolded (commented-out) validation structure for future assertions. + +## Usage Example + +```cpp +// Run the sanity test (typically via ctest or the osquery test runner) +// The test queries the patches table and can be extended with row validation: + +ValidationMap row_map = { + {"csname", NormalType}, + {"hotfix_id", NormalType}, + {"caption", NormalType}, + {"description", NormalType}, + {"fix_comments", NormalType}, + {"installed_by", NormalType}, + {"install_date", NormalType}, + {"installed_on", NormalType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Targets **Windows only** (`specs/windows/patches.table`). +- Size assertions and `validate_rows` calls are commented out, making this a non-failing smoke test that confirms the query executes without error. +- To activate full validation, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)`. \ No newline at end of file diff --git a/tests/integration/tables/.pci_devices.md b/tests/integration/tables/.pci_devices.md new file mode 100644 index 00000000000..b67108739e3 --- /dev/null +++ b/tests/integration/tables/.pci_devices.md @@ -0,0 +1,40 @@ + +Integration sanity test for the `pci_devices` osquery table, verifying that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`pciDevices`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(pciDevices, test_sanity)`** — Executes a `SELECT *` query against the `pci_devices` table and provides a scaffolded (commented-out) structure for row count assertions and field-level validation. + +### Expected Table Columns (from commented validation map) + +| Column | Type | +|---|---| +| `pci_slot` | NormalType | +| `pci_class` | NormalType | +| `driver` | NormalType | +| `vendor` / `vendor_id` | NormalType | +| `model` / `model_id` | NormalType | +| `subsystem_vendor_id` / `subsystem_vendor` | NormalType | +| `subsystem_model_id` / `subsystem_model` | NormalType | + +## Usage Example + +```cpp +// Run the sanity test directly via osquery's test runner +// To enable full validation, uncomment and configure in the test body: + +ValidationMap row_map = { + {"pci_slot", NormalType}, + {"pci_class", NormalType}, + {"driver", NormalType}, + {"vendor", NormalType}, + {"vendor_id", NormalType}, + {"model", NormalType}, + {"model_id", NormalType}, +}; + +validate_rows(data, row_map); +``` + +The validation map and row count assertions are scaffolded but commented out, making this a lightweight smoke test confirming the query executes without failure. \ No newline at end of file diff --git a/tests/integration/tables/.physical_disk_performance.md b/tests/integration/tables/.physical_disk_performance.md new file mode 100644 index 00000000000..9c7786e6608 --- /dev/null +++ b/tests/integration/tables/.physical_disk_performance.md @@ -0,0 +1,39 @@ + +Integration test file for the `physical_disk_performance` osquery table on Windows, verifying the table can be queried without errors. + +## Key Components + +- **`physicalDiskPerformance`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic smoke test that executes `SELECT * FROM physical_disk_performance` and validates the result set. + +## Usage Example + +```cpp +// Run the sanity test against the physical_disk_performance table +TEST_F(physicalDiskPerformance, test_sanity) { + auto const data = execute_query("select * from physical_disk_performance"); + + // Example: enable full row validation by uncommenting and configuring: + ValidationMap row_map = { + {"name", NormalType}, + {"avg_disk_bytes_per_read", IntType}, + {"avg_disk_bytes_per_write", IntType}, + {"avg_disk_read_queue_length", IntType}, + {"avg_disk_write_queue_length", IntType}, + {"avg_disk_sec_per_read", IntType}, + {"avg_disk_sec_per_write", IntType}, + {"current_disk_queue_length", IntType}, + {"percent_disk_read_time", IntType}, + {"percent_disk_write_time", IntType}, + {"percent_disk_time", IntType}, + {"percent_idle_time", IntType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Targets **Windows only** (`specs/windows/physical_disk_performance.table`). +- Row count assertions and `validate_rows()` are currently commented out — the test only confirms the query executes without crashing. +- To enable full validation, uncomment the `ValidationMap` block and the `validate_rows()` call, and add an appropriate `ASSERT_GE`/`ASSERT_EQ` size check for the expected number of physical disks. \ No newline at end of file diff --git a/tests/integration/tables/.pipes.md b/tests/integration/tables/.pipes.md new file mode 100644 index 00000000000..083b6cc3514 --- /dev/null +++ b/tests/integration/tables/.pipes.md @@ -0,0 +1,28 @@ + +Integration test file for the `pipes` osquery table on Windows, verifying that the table can be queried without errors. + +## Key Components + +- **`pipes` (test class)** — Inherits from `testing::Test`; sets up the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(pipes, test_sanity)`** — Executes `SELECT * FROM pipes` and provides a scaffold for row count assertions and column-level validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via the osquery integration test runner) +// Uncomment and configure validation as needed: + +ValidationMap row_map = { + {"pid", IntType}, + {"name", NormalType}, + {"instances", IntType}, + {"max_instances", IntType}, + {"flags", NormalType} +}; + +auto const data = execute_query("select * from pipes"); +ASSERT_GE(data.size(), 0ul); +validate_rows(data, row_map); +``` + +> **Note:** The validation map and row count assertions are commented out by default. Enable them to enforce schema correctness and expected result counts for the Windows `pipes` table (spec: `specs/windows/pipes.table`). \ No newline at end of file diff --git a/tests/integration/tables/.pkg_packages.md b/tests/integration/tables/.pkg_packages.md new file mode 100644 index 00000000000..7091d45cc47 --- /dev/null +++ b/tests/integration/tables/.pkg_packages.md @@ -0,0 +1,32 @@ + +Integration test file for the `pkg_packages` osquery table, providing a sanity check that verifies the table can be queried on FreeBSD systems. + +## Key Components + +- **`pkgPackages`** - Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` +- **`test_sanity`** - Basic integration test that executes `SELECT * FROM pkg_packages` to verify the table is queryable + +## Usage Example + +```cpp +// Run the sanity test against the pkg_packages table +TEST_F(pkgPackages, test_sanity) { + auto const data = execute_query("select * from pkg_packages"); + + // Example: enable row validation with a ValidationMap + ValidationMap row_map = { + {"name", NormalType}, + {"version", NormalType}, + {"flatsize", IntType}, + {"arch", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Targets the FreeBSD `pkg_packages` table (spec: `specs/freebsd/pkg_packages.table`) +- Row count assertions and `ValidationMap` checks are scaffolded but commented out — enable them to enforce stricter constraints +- Follows the standard osquery integration test pattern: **query → size check → validate rows** +- Depends on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type constants (`NormalType`, `IntType`) \ No newline at end of file diff --git a/tests/integration/tables/.platform_info.md b/tests/integration/tables/.platform_info.md new file mode 100644 index 00000000000..625489cb14c --- /dev/null +++ b/tests/integration/tables/.platform_info.md @@ -0,0 +1,40 @@ + +Integration test that validates the `platform_info` osquery table returns correctly typed data across platforms. + +## Key Components + +- **`platformInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(platformInfo, test_sanity)`** — Executes `SELECT * FROM platform_info` and validates each row against a typed schema map. + +## Validation Schema + +| Column | Type | Platform | +|---|---|---| +| `vendor` | `NormalType` | All | +| `version` | `NormalType` | All | +| `extra` | `NormalType` | All | +| `date` | `NormalType` | All | +| `revision` | `NormalType` | All | +| `address` | `NormalType` | Non-Windows | +| `size` | `IntOrEmpty` | Non-Windows | +| `volume_size` | `NonNegativeInt` | Non-Windows | +| `firmware_type` | `NonEmptyString` | macOS & Windows | + +## Usage Example + +```cpp +// Run via osquery integration test harness: +// The test executes this query internally: +auto const data = execute_query("select * from platform_info"); + +// Validation ensures each row conforms to expected types, +// with platform-specific fields conditionally checked via preprocessor guards: +#ifndef OSQUERY_WINDOWS + {"address", NormalType}, +#endif +#if defined(__APPLE__) || defined(OSQUERY_WINDOWS) + {"firmware_type", NonEmptyString}, +#endif +``` + +The test uses `#ifdef` guards to conditionally include platform-specific columns, ensuring the sanity check is portable across Linux, macOS, and Windows builds. \ No newline at end of file diff --git a/tests/integration/tables/.plist.md b/tests/integration/tables/.plist.md new file mode 100644 index 00000000000..1cab8ff038b --- /dev/null +++ b/tests/integration/tables/.plist.md @@ -0,0 +1,32 @@ + +Integration test stub for the `plist` osquery table, providing a sanity check scaffold for querying macOS property list (`.plist`) data on Darwin systems. + +## Key Components + +- **`plist` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(plist, test_sanity)`** — Executes a basic query against the `plist` virtual table using an empty `path` filter, with validation logic stubbed out for future implementation. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The core query under test: +auto const data = execute_query("select * from plist where path = ''"); + +// To enable full validation, uncomment and configure: +ValidationMap row_map = { + {"key", NormalType}, + {"subkey", NormalType}, + {"value", NormalType}, + {"path", NormalType}, +}; +validate_rows(data, row_map); +``` + +> **Note:** The `path` filter is intentionally empty in the stub. Real tests should supply a valid `.plist` file path (e.g., `/Library/Preferences/com.apple.Finder.plist`) and uncomment the size assertions and `validate_rows` call to enforce expected row counts and column types. + +## Notes + +- Targets **Darwin (macOS)** only — spec defined in `specs/darwin/plist.table`. +- Validation and size assertions are commented out pending test case definition. +- See `osquery/tests/integration/tables/helper.h` for available `ValidationMap` flags and `DataCheck` objects. \ No newline at end of file diff --git a/tests/integration/tables/.portage_keywords.md b/tests/integration/tables/.portage_keywords.md new file mode 100644 index 00000000000..f894b3baa8a --- /dev/null +++ b/tests/integration/tables/.portage_keywords.md @@ -0,0 +1,40 @@ + +Integration test stub for the `portage_keywords` osquery table, verifying basic query execution against Gentoo Portage keyword/masking data on Linux systems. + +## Key Components + +- **`portageKeywords`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()` +- **`TEST_F(portageKeywords, test_sanity)`** — Sanity check that executes `SELECT * FROM portage_keywords` and provides a scaffolded (currently commented-out) validation structure + +## Expected Table Schema + +The commented-out `ValidationMap` documents the expected columns: + +| Column | Type | Description | +|--------|------|-------------| +| `package` | `NormalType` | Portage package name | +| `version` | `NormalType` | Package version string | +| `keyword` | `NormalType` | Arch keyword (e.g., `~amd64`) | +| `mask` | `IntType` | Whether the package is masked | +| `unmask` | `IntType` | Whether the package is unmasked | + +## Usage Example + +```cpp +// To enable full validation, uncomment and configure: +ValidationMap row_map = { + {"package", NormalType}, + {"version", NormalType}, + {"keyword", NormalType}, + {"mask", IntType}, + {"unmask", IntType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/linux/portage_keywords.table` +- Row count assertions and `validate_rows()` calls are scaffolded but disabled — enable them when the test environment guarantees Portage keyword data is present +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query()`, `validate_rows()`, and type flag constants \ No newline at end of file diff --git a/tests/integration/tables/.portage_packages.md b/tests/integration/tables/.portage_packages.md new file mode 100644 index 00000000000..0a5b43cd12b --- /dev/null +++ b/tests/integration/tables/.portage_packages.md @@ -0,0 +1,33 @@ + +Integration test for the `portage_packages` osquery table that performs a basic sanity check by querying all records from the table on Linux systems. + +## Key Components + +- **`portagePackages`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(portagePackages, test_sanity)`** — Core test case that executes `SELECT * FROM portage_packages` and provides scaffolding for row-level validation. + +## Usage Example + +```cpp +// Run the sanity test (queries portage_packages table) +// Extend with full validation by uncommenting and configuring: + +ValidationMap row_map = { + {"package", NormalType}, + {"version", NormalType}, + {"slot", NormalType}, + {"build_time", IntType}, + {"repository", NormalType}, + {"eapi", IntType}, + {"size", IntType}, + {"world", IntType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/linux/portage_packages.table` +- Size assertions and `validate_rows` are commented out, leaving only the query execution active. To enable full validation, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)`. +- Relies on `osquery/tests/integration/tables/helper.h` for test utilities (`execute_query`, `validate_rows`, `NormalType`, `IntType`). \ No newline at end of file diff --git a/tests/integration/tables/.portage_use.md b/tests/integration/tables/.portage_use.md new file mode 100644 index 00000000000..47b295f1ddb --- /dev/null +++ b/tests/integration/tables/.portage_use.md @@ -0,0 +1,28 @@ + +Integration test file for the `portage_use` osquery table, providing a sanity check to verify the table can be queried without errors on Linux systems running Portage (Gentoo's package manager). + +## Key Components + +- **`portageUse`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()` +- **`TEST_F(portageUse, test_sanity)`** — Basic sanity test that executes `SELECT * FROM portage_use` to confirm the table is queryable + +## Usage Example + +```cpp +// Run this specific test via osquery test runner: +// ./osquery_tests --gtest_filter=portageUse.test_sanity + +// To extend with full row validation, uncomment and configure: +ValidationMap row_map = { + {"package", NormalType}, + {"version", NormalType}, + {"use", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- The table spec is defined in `specs/linux/portage_use.table` +- Row count assertions and `ValidationMap` checks are scaffolded but commented out — activate them to enforce stricter validation +- Only relevant on Linux hosts with Portage installed; the query will return an empty result set on non-Gentoo systems, which the current test tolerates by not asserting a minimum row count \ No newline at end of file diff --git a/tests/integration/tables/.power_sensors.md b/tests/integration/tables/.power_sensors.md new file mode 100644 index 00000000000..bb42a2b23e6 --- /dev/null +++ b/tests/integration/tables/.power_sensors.md @@ -0,0 +1,28 @@ + +Integration test file for the `power_sensors` osquery table on Darwin (macOS), validating that the table can be queried without errors. + +## Key Components + +- **`powerSensors`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(powerSensors, test_sanity)`** — Sanity check that executes a `SELECT *` query against the `power_sensors` table and provides scaffolding for row validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery's integration test runner) +// The test queries the power_sensors table and can be extended with validation: + +ValidationMap row_map = { + {"key", NormalType}, + {"category", NormalType}, + {"name", NormalType}, + {"value", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Darwin platform; corresponds to `specs/darwin/power_sensors.table`. +- Row count assertions and full `ValidationMap` validation are currently commented out — extend these to enforce stricter correctness guarantees. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants. \ No newline at end of file diff --git a/tests/integration/tables/.powershell_events.md b/tests/integration/tables/.powershell_events.md new file mode 100644 index 00000000000..a8f2ef0f66b --- /dev/null +++ b/tests/integration/tables/.powershell_events.md @@ -0,0 +1,33 @@ + +Integration test sanity check for the `powershell_events` osquery table on Windows, verifying the table can be queried without errors. + +## Key Components + +- **`powershellEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM powershell_events` and provides a scaffolded (commented-out) validation map for future row-level assertions. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test executes the query and can be extended with row validation: + +ValidationMap row_map = { + {"time", IntType}, + {"datetime", NormalType}, + {"script_block_id", NormalType}, + {"script_block_count", IntType}, + {"script_text", NormalType}, + {"script_name", NormalType}, + {"script_path", NormalType}, + {"cosine_similarity", NormalType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/windows/powershell_events.table`. +- Row count assertions and `validate_rows()` calls are intentionally commented out — activate them as the table matures or test requirements solidify. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, `ValidationMap`, and type flag constants (`IntType`, `NormalType`). \ No newline at end of file diff --git a/tests/integration/tables/.preferences.md b/tests/integration/tables/.preferences.md new file mode 100644 index 00000000000..26be30a87cf --- /dev/null +++ b/tests/integration/tables/.preferences.md @@ -0,0 +1,38 @@ + +Integration test file for the osquery `preferences` table on Darwin (macOS), validating schema correctness and query behavior against the `preferences` virtual table. + +## Key Components + +- **`preferences` test class** — Inherits from `testing::Test`; initializes the test environment via `SetUp()`. +- **`test_sanity` test case** — Executes two queries and validates the returned rows against an expected schema. +- **`ValidationMap row_map`** — Defines the expected column types for the `preferences` table: + +| Column | Type | +|---|---| +| `domain` | `NormalType` | +| `key` | `NormalType` | +| `subkey` | `NormalType` | +| `value` | `NormalType` | +| `forced` | `IntType` | +| `username` | `NormalType` | +| `host` | `NormalType` | + +## Usage Example + +```cpp +// Two queries are validated in test_sanity: + +// 1. Full table scan — asserts rows are non-empty and schema-valid +auto const data = execute_query("select * from preferences"); +ASSERT_FALSE(data.empty()); +validate_rows(data, row_map); + +// 2. Cross join with users table — filters Apple domains per user +auto const datajoin = execute_query( + "select users.username, preferences.* from users CROSS JOIN preferences " + "USING(username) where preferences.domain LIKE 'com.apple.%';"); +ASSERT_FALSE(datajoin.empty()); +validate_rows(datajoin, row_map); +``` + +> **Note:** This test targets the Darwin platform only. See the spec at `specs/darwin/preferences.table` for the full table definition. \ No newline at end of file diff --git a/tests/integration/tables/.prefetch.md b/tests/integration/tables/.prefetch.md new file mode 100644 index 00000000000..22098ce52ab --- /dev/null +++ b/tests/integration/tables/.prefetch.md @@ -0,0 +1,44 @@ + +Integration test for the `prefetch` osquery table on Windows, validating that Prefetch file data is correctly parsed and returned with expected column types and values. + +## Key Components + +- **`PrefetchTest`** — Test fixture inheriting from `testing::Test`; calls `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(PrefetchTest, test_sanity)`** — Primary test case covering three query scenarios against the `prefetch` virtual table. + +## Validation Map + +Defines expected column types for all returned rows: + +| Column | Expected Type | +|---|---| +| `path` | `NonEmptyString` | +| `filename`, `hash`, `other_run_times`, `volume_serial`, `volume_creation`, `accessed_files`, `accessed_directories` | `NormalType` | +| `last_run_time`, `run_count`, `size`, `accessed_files_count`, `accessed_directories_count` | `IntType` | + +## Usage Example + +```cpp +// Test execution flow (three-phase): + +// Phase 1: Broad query against test fixture .pf files +std::string query = + "select * from prefetch where path like '\\windows\\prefetch\\%.pf'"; +QueryData rows = execute_query(query); +ASSERT_GT(rows.size(), 0ul); + +// Phase 2: Narrow query asserting specific parsed values +std::string specific_query = query + + " AND last_run_time = 1620953788 AND run_count = 3" + " AND accessed_files_count = 53"; +QueryData specific_rows = execute_query(specific_query); +ASSERT_EQ(specific_rows.size(), 1ul); + +// Phase 3: Optional live system query (skipped if no local Prefetch files) +QueryData default_rows = execute_query("select * from prefetch"); +``` + +## Notes + +- Requires the `TEST_CONF_FILES_DIR` environment variable pointing to a directory containing test `.pf` fixture files under `windows\prefetch\`. +- Phase 3 (live system query) is non-fatal — the test passes even if no local Prefetch files are found, making it safe to run in non-Windows CI environments. \ No newline at end of file diff --git a/tests/integration/tables/.process_envs.md b/tests/integration/tables/.process_envs.md new file mode 100644 index 00000000000..78eb0c3e9b4 --- /dev/null +++ b/tests/integration/tables/.process_envs.md @@ -0,0 +1,42 @@ + +Integration test for the `process_envs` osquery table, validating that process environment variable data is correctly queried and returned with proper structure. + +## Key Components + +### `ProcessEnvs` (Test Fixture) +- Inherits from `testing::Test` +- Initializes the test environment via `setUpEnvironment()` in `SetUp()` + +### `TEST_F(ProcessEnvs, test_sanity)` +Core sanity test that performs two levels of validation: + +1. **Schema Validation** — Executes `SELECT * FROM process_envs` and validates each row against a `ValidationMap`: + - `pid` → `NonNegativeInt` + - `key` → `NonEmptyString` + - `value` → `NormalType` + +2. **Linux-Specific Content Validation** — On Linux, retrieves the current process ID and checks that common environment variables (`USER`, `HOME`, `LANG`, `PATH`) appear in the query results with their expected values from the live process environment. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test framework +// From the osquery build directory: +// ./osquery_integration_tests --gtest_filter=ProcessEnvs.test_sanity + +// The test internally executes: +QueryData data = execute_query("select * from process_envs"); + +// And on Linux, cross-checks live env vars against query results: +Row r{ + {"pid", std::to_string(::getpid())}, + {"key", "HOME"}, + {"value", std::getenv("HOME")}, +}; +ASSERT_NE(std::find(data.begin(), data.end(), r), data.end()); +``` + +## Notes +- Spec source: `specs/posix/process_envs.table` +- Linux-only content assertions; other platforms only validate schema structure +- Uses `boost::io::quoted` for readable assertion failure messages \ No newline at end of file diff --git a/tests/integration/tables/.process_events.md b/tests/integration/tables/.process_events.md new file mode 100644 index 00000000000..96e56928188 --- /dev/null +++ b/tests/integration/tables/.process_events.md @@ -0,0 +1,34 @@ + +Integration test file for the `process_events` osquery table, validating that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`processEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic sanity test that executes a `SELECT *` query against the `process_events` table to verify it runs without crashing. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test framework +TEST_F(processEvents, test_sanity) { + auto const data = execute_query("select * from process_events"); + + // Optionally enable full row validation by uncommenting: + ValidationMap row_map = { + {"pid", IntType}, + {"path", NormalType}, + {"cmdline", NormalType}, + {"uid", IntType}, + {"gid", IntType}, + {"parent", IntType}, + {"time", IntType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/process_events.table`. +- The `ValidationMap` and row size assertions are stubbed out (commented) — extend these to enforce stricter schema checks. +- The `process_events` table is event-driven; results may be empty in environments without active process auditing, which is why size assertions are left disabled by default. \ No newline at end of file diff --git a/tests/integration/tables/.process_file_events.md b/tests/integration/tables/.process_file_events.md new file mode 100644 index 00000000000..1dd9aa1da29 --- /dev/null +++ b/tests/integration/tables/.process_file_events.md @@ -0,0 +1,40 @@ + +Sanity check integration test for the `process_file_events` osquery table, verifying the table can be queried without errors on Linux systems. + +## Key Components + +- **`processFileEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(processFileEvents, test_sanity)`** — Basic integration test that executes a `SELECT *` query against the `process_file_events` table. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery's integration test runner) +// The test queries the process_file_events table and can be extended +// with full row validation using the commented-out ValidationMap: + +ValidationMap row_map = { + {"operation", NormalType}, + {"pid", IntType}, + {"ppid", IntType}, + {"time", IntType}, + {"executable", NormalType}, + {"partial", NormalType}, + {"cwd", NormalType}, + {"path", NormalType}, + {"dest_path", NormalType}, + {"uid", NormalType}, + {"gid", NormalType}, + {"euid", NormalType}, + {"egid", NormalType}, + {"uptime", IntType}, + {"eid", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/linux/process_file_events.table`. +- Row count assertions and `validate_rows()` calls are intentionally commented out, as the table may return zero rows in environments where file event auditing is inactive. +- Full validation can be enabled by uncommenting the `ValidationMap` block and calling `validate_rows(data, row_map)`. \ No newline at end of file diff --git a/tests/integration/tables/.process_memory_map.md b/tests/integration/tables/.process_memory_map.md new file mode 100644 index 00000000000..effc99aff3f --- /dev/null +++ b/tests/integration/tables/.process_memory_map.md @@ -0,0 +1,50 @@ + +Integration sanity test for the `process_memory_map` osquery table, verifying that the table can be queried without errors. + +## Key Components + +- **`processMemoryMap`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` +- **`test_sanity`** — Single test case that executes `SELECT * FROM process_memory_map` and provides a scaffolded (commented-out) validation structure + +## Expected Table Schema + +```text +pid INTEGER +start TEXT +end TEXT +permissions TEXT +offset INTEGER +device TEXT +inode INTEGER +path TEXT +pseudo INTEGER +``` + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test harness +TEST_F(processMemoryMap, test_sanity) { + auto const data = execute_query("select * from process_memory_map"); + + // Uncomment to enable full row validation: + ValidationMap row_map = { + {"pid", IntType}, + {"start", NormalType}, + {"end", NormalType}, + {"permissions", NormalType}, + {"offset", IntType}, + {"device", NormalType}, + {"inode", IntType}, + {"path", NormalType}, + {"pseudo", IntType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Validation logic (`validate_rows`, size assertions) is scaffolded but commented out — the test currently only confirms the query executes without crashing +- Spec reference: `specs/process_memory_map.table` +- Helper utilities sourced from `osquery/tests/integration/tables/helper.h` \ No newline at end of file diff --git a/tests/integration/tables/.process_namespaces.md b/tests/integration/tables/.process_namespaces.md new file mode 100644 index 00000000000..771888d4fd9 --- /dev/null +++ b/tests/integration/tables/.process_namespaces.md @@ -0,0 +1,33 @@ + +Integration test file for the `process_namespaces` osquery table on Linux, providing a sanity check to verify the table can be queried without errors. + +## Key Components + +- **`processNamespaces`** — Test fixture class inheriting from `testing::Test`, sets up the test environment via `setUpEnvironment()`. +- **`TEST_F(processNamespaces, test_sanity)`** — Executes a `SELECT *` query against the `process_namespaces` table and provides a scaffolded structure for row validation (currently commented out). + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the process_namespaces table and validates results + +// Expected schema for validation (commented out in source, shown for reference): +ValidationMap row_map = { + {"pid", IntType}, + {"cgroup_namespace", NormalType}, + {"ipc_namespace", NormalType}, + {"mnt_namespace", NormalType}, + {"net_namespace", NormalType}, + {"pid_namespace", NormalType}, + {"user_namespace", NormalType}, + {"uts_namespace", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/linux/process_namespaces.table` +- Row count assertions and `validate_rows()` calls are scaffolded but commented out — extend these to enforce expected schema and result constraints when activating full validation. +- Follows the standard osquery integration test pattern: query → size check → validation map → validate. \ No newline at end of file diff --git a/tests/integration/tables/.process_open_files.md b/tests/integration/tables/.process_open_files.md new file mode 100644 index 00000000000..18ef72ccf1b --- /dev/null +++ b/tests/integration/tables/.process_open_files.md @@ -0,0 +1,40 @@ + +Integration test for the `process_open_files` osquery table, validating that open file descriptors for processes are correctly reported on POSIX systems. + +## Key Components + +### `ProcessOpenFilesTest` (Test Fixture) +A `testing::Test` subclass that manages test lifecycle: +- **`SetUp()`** — Creates a uniquely named temporary file in the system temp directory and writes to it, ensuring an open file descriptor exists during the test +- **`TearDown()`** — Closes and removes the temporary file for clean test isolation +- **`filepath`** — Public `boost::filesystem::path` to the temporary test file + +### `checkProcessOpenFilePath` (Validator) +A helper function used in row validation that accepts a file path string as valid if: +- It contains `"(deleted)"` (handles unlinked but still-open files on Linux) +- Or it is a non-empty, absolute path + +### `TEST_F(ProcessOpenFilesTest, test_sanity)` +The primary integration test that: +1. Executes `SELECT * FROM process_open_files` +2. Asserts the result set is non-empty +3. Validates each row has `pid` (non-negative int), `fd` (non-negative int), and `path` (absolute or deleted) +4. Confirms the current test process's own open file appears in the results by matching `pid` and canonical file path + +## Usage Example + +```cpp +// The test runs via the osquery integration test harness: +// Internally executes: +QueryData data = execute_query("select * from process_open_files"); + +// Row validation schema applied to every result row: +ValidationMap row_map = { + {"pid", NonNegativeInt}, + {"fd", NonNegativeInt}, + {"path", checkProcessOpenFilePath}, +}; +validate_rows(data, row_map); +``` + +> **Note:** This test only runs on POSIX platforms (Linux/macOS), as indicated by its spec path (`specs/posix/`) and use of `unistd.h`. \ No newline at end of file diff --git a/tests/integration/tables/.process_open_pipes.md b/tests/integration/tables/.process_open_pipes.md new file mode 100644 index 00000000000..b94cd6e2959 --- /dev/null +++ b/tests/integration/tables/.process_open_pipes.md @@ -0,0 +1,44 @@ + +Integration test for the `process_open_pipes` osquery table, validating that the table correctly enumerates open pipe file descriptors (both named and unnamed) for running processes on Linux. + +## Key Components + +### `ProcessOpenPipesTest` (Test Fixture) +A `testing::Test` subclass that orchestrates pipe creation, child process management, and query validation. + +**Private Members:** +- `pipe_path_`, `dir_path_` — Temp directory and FIFO path for named pipe tests +- `fd_[2]`, `fd_signal_[2]` — Unnamed pipe FDs and a synchronization pipe between parent/child +- `setup_writer()` / `setup_reader()` — Opens the appropriate pipe end (named or unnamed) for each child role +- `do_writer()` / `do_reader()` — Child process logic; reader signals parent after receiving data, both spin indefinitely so the parent can query them +- `create_child()` — Forks a writer or reader child process +- `wait_child_signal()` — Blocks until the reader child signals readiness +- `do_query()` — Executes `process_open_pipes` filtered by writer/reader PIDs and validates schema +- `kill_children()` — Sends `SIGKILL` and reaps both child processes + +**Public Members:** +- `test_named_pipe()` — Runs the test using a FIFO (`mkfifo`) +- `test_unnamed_pipe()` — Runs the test using an anonymous `pipe()` +- `test_result_` — Stores the row count returned by the query + +### `TEST_F(ProcessOpenPipesTest, test_sanity)` +Runs both named and unnamed pipe scenarios, asserting that at least one row is returned for each. + +## Usage Example + +```cpp +// Schema validated in do_query(): +ValidationMap row_map = { + {"pid", NonNegativeInt}, + {"fd", NonNegativeInt}, + {"mode", NonEmptyString}, + {"inode", NonNegativeInt}, + {"type", NonEmptyString}, + {"partner_pid", NonNegativeInt}, + {"partner_fd", NonNegativeInt}, + {"partner_mode", NonEmptyString}, +}; + +// Equivalent manual query: +// SELECT * FROM process_open_pipes WHERE pid = AND partner_pid = ; +``` \ No newline at end of file diff --git a/tests/integration/tables/.process_open_sockets.md b/tests/integration/tables/.process_open_sockets.md new file mode 100644 index 00000000000..ff99da72adc --- /dev/null +++ b/tests/integration/tables/.process_open_sockets.md @@ -0,0 +1,29 @@ + +Integration test for the `process_open_sockets` osquery table, validating that the table returns well-formed rows with correct column types across supported platforms. + +## Key Components + +- **`processOpenSockets`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that executes a full table query and validates each row against a `ValidationMap`. + +### Validated Columns + +| Column | Type | +|---|---| +| `pid`, `family`, `protocol`, `local_port`, `remote_port` | `IntType` | +| `fd`, `socket` | `IntOrEmpty` | +| `local_address`, `remote_address`, `path`, `state` | `NormalType` | +| `net_namespace` *(Linux only)* | `IntType` | + +## Usage Example + +```cpp +// Run this test via the osquery integration test suite: +// The test executes the equivalent of: + +auto const data = execute_query("select * from process_open_sockets"); +ASSERT_FALSE(data.empty()); +validate_rows(data, row_map); +``` + +> **Platform Note:** On Linux, the `net_namespace` column is conditionally added to the validation map using `isPlatform(PlatformType::TYPE_LINUX)`. This column is not validated on macOS or Windows. \ No newline at end of file diff --git a/tests/integration/tables/.processes.md b/tests/integration/tables/.processes.md new file mode 100644 index 00000000000..5d97e8a84b2 --- /dev/null +++ b/tests/integration/tables/.processes.md @@ -0,0 +1,35 @@ + +Integration test for the osquery `processes` table that validates query results contain well-formed, platform-appropriate data across Linux, macOS, and Windows. + +## Key Components + +- **`ProcessesTest`** — Test fixture inheriting from `testing::Test`, initializes the osquery environment via `setUpEnvironment()`. +- **`TEST_F(ProcessesTest, test_sanity)`** — Primary test case that: + - Executes `SELECT * FROM processes` and asserts at least 2 rows returned. + - Validates `start_time` values fall within a sane range (between boot time and now) using a `timeSanityCheck` lambda, with a Windows-specific bypass due to `getUptime()` API limitations. + - Builds a `ValidationMap` of expected column types covering common process fields (PID, name, path, UID/GID variants, memory/CPU metrics, etc.). + - Appends platform-specific columns to the validation map at runtime. + +### Platform-Specific Column Coverage + +| Platform | Extra Columns | +|---|---| +| **Windows** | `elevated_token`, `secure_process`, `protection_type`, `virtual_process`, `elapsed_time`, `handle_count`, `percent_processor_time` | +| **macOS** | `upid`, `uppid`, `cpu_type`, `cpu_subtype`, `translated` | +| **Linux** | `cgroup_path` | + +## Usage Example + +```cpp +// Run the test via the osquery integration test runner: +// The test fixture setup and teardown are handled automatically. + +// Core validation logic pattern used internally: +auto timeSanityCheck = [&now, &boot_time](auto value) { + auto start_time_exp = tryTo(value); + if (start_time_exp.isError()) return false; + auto const start_time = start_time_exp.take(); + if (start_time == -1) return true; // sentinel: unknown start time + return start_time <= now && boot_time <= start_time; +}; +``` \ No newline at end of file diff --git a/tests/integration/tables/.programs.md b/tests/integration/tables/.programs.md new file mode 100644 index 00000000000..59b46d1b580 --- /dev/null +++ b/tests/integration/tables/.programs.md @@ -0,0 +1,51 @@ + +Integration test for the Windows `programs` table, validating that the osquery `programs` virtual table returns well-formed rows with expected columns. + +## Key Components + +- **`ProgramsTest`** — Test fixture inheriting from `testing::Test`; calls `setUpEnvironment()` in `SetUp()` to initialize the osquery test environment. +- **`test_sanity`** — Core test case that: + - Executes `SELECT * FROM programs` + - Asserts at least one row is returned + - Validates all expected columns conform to `NormalType` + +## Validated Columns + +| Column | Type | +|---|---| +| `name` | NormalType | +| `version` | NormalType | +| `install_location` | NormalType | +| `install_source` | NormalType | +| `language` | NormalType | +| `publisher` | NormalType | +| `uninstall_string` | NormalType | +| `install_date` | NormalType | +| `identifying_number` | NormalType | +| `package_family_name` | NormalType | +| `upgrade_code` | NormalType | + +## Usage Example + +```cpp +// Run the sanity test directly via the osquery test harness +// From the build directory: +// ./osquery_tests --gtest_filter=ProgramsTest.test_sanity + +TEST_F(ProgramsTest, test_sanity) { + QueryData data = execute_query("select * from programs"); + + // Ensure installed programs exist on the system + ASSERT_GT(data.size(), 0ul); + + // Validate each row matches the expected schema + ValidationMap row_map = { + {"name", NormalType}, + {"publisher", NormalType}, + // ...additional columns + }; + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets **Windows only** (spec: `specs/windows/programs.table`) and queries installed programs from the Windows registry, equivalent to entries visible in *Add/Remove Programs*. \ No newline at end of file diff --git a/tests/integration/tables/.prometheus_metrics.md b/tests/integration/tables/.prometheus_metrics.md new file mode 100644 index 00000000000..299aebf7206 --- /dev/null +++ b/tests/integration/tables/.prometheus_metrics.md @@ -0,0 +1,29 @@ + +Integration sanity test for the `prometheus_metrics` osquery table, verifying the table can be queried without errors on POSIX systems. + +## Key Components + +- **`prometheusMetrics`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(prometheusMetrics, test_sanity)`** — Basic smoke test that executes `SELECT * FROM prometheus_metrics` and provides scaffolding for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test executes the query and can be extended with validation: + +ValidationMap row_map = { + {"target_name", NormalType}, + {"metric_name", NormalType}, + {"metric_value", NormalType}, + {"timestamp_ms", IntType} +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/posix/prometheus_metrics.table` +- Row count assertions and schema validation are currently commented out — extend as needed when a reliable Prometheus endpoint is available in the test environment. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.python_packages.md b/tests/integration/tables/.python_packages.md new file mode 100644 index 00000000000..885128f8744 --- /dev/null +++ b/tests/integration/tables/.python_packages.md @@ -0,0 +1,34 @@ + +Integration test for the `python_packages` osquery table, validating that the table returns correctly typed rows for installed Python packages. + +## Key Components + +- **`pythonPackages`** — Test fixture class inheriting from `testing::Test`, with environment setup and Windows-specific user/group service lifecycle management +- **`TEST_F(pythonPackages, test_sanity)`** — Sanity test that executes `SELECT * FROM python_packages` and validates result schema + +## Validated Schema + +| Column | Expected Type | +|---|---| +| `name` | `NormalType` | +| `uid` | `IntType` | +| `version` | `NormalType` | +| `summary` | `NormalType` | +| `author` | `NormalType` | +| `license` | `NormalType` | +| `path` | `NormalType` | +| `directory` | `NormalType` | + +## Usage Example + +```cpp +// Run the integration test directly via ctest or the osquery test binary: +// $ ./osquery_tests --gtest_filter=pythonPackages.test_sanity + +// The test executes the equivalent of: +auto const data = execute_query("select * from python_packages"); +ASSERT_FALSE(data.empty()); // Ensures at least one package is found +validate_rows(data, row_map); // Ensures all columns match expected types +``` + +> **Note:** On Windows, `SetUpTestSuite` and `TearDownTestSuite` initialize and teardown the `UsersAndGroups` dispatcher services required to resolve package ownership (`uid`). These lifecycle hooks are conditionally compiled via `OSQUERY_WINDOWS`. \ No newline at end of file diff --git a/tests/integration/tables/.quicklook_cache.md b/tests/integration/tables/.quicklook_cache.md new file mode 100644 index 00000000000..bab40d68ce5 --- /dev/null +++ b/tests/integration/tables/.quicklook_cache.md @@ -0,0 +1,36 @@ + +Integration sanity test for the `quicklook_cache` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`quicklookCache`** — Test fixture class extending `testing::Test`, initializes the osquery test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Single test case that executes `SELECT * FROM quicklook_cache` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test runner +// To enable full validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"path", NormalType}, + {"rowid", IntType}, + {"fs_id", NormalType}, + {"volume_id", IntType}, + {"inode", IntType}, + {"mtime", IntType}, + {"size", IntType}, + {"label", NormalType}, + {"last_hit_date", IntType}, + {"hit_count", NormalType}, + {"icon_mode", IntType}, + {"cache_path", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- **Platform**: Darwin (macOS) only — spec defined in `specs/darwin/quicklook_cache.table`. +- **Validation is scaffolded but disabled** — row count assertions and `validate_rows()` calls are commented out, making this a lightweight smoke test confirming the query executes successfully. +- To harden the test, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)` once the expected schema is confirmed stable. \ No newline at end of file diff --git a/tests/integration/tables/.registry.md b/tests/integration/tables/.registry.md new file mode 100644 index 00000000000..1aef874ee27 --- /dev/null +++ b/tests/integration/tables/.registry.md @@ -0,0 +1,37 @@ + +Integration test that validates the `registry` osquery table returns well-formed data on Windows systems. + +## Key Components + +- **`RegistryTest`** — Test fixture inheriting from `testing::Test`, calls `setUpEnvironment()` during setup +- **`test_sanity`** — Executes `SELECT * FROM registry` and validates the result set + +## Validation Rules + +| Column | Constraint | +|--------|------------| +| `key` | Non-empty string | +| `path` | Non-empty string | +| `name` | Non-empty string | +| `type` | Non-empty string | +| `data` | Normal type (can be empty) | +| `mtime` | Non-negative integer | + +## Usage Example + +```cpp +// Run via the osquery integration test suite (Windows only) +// Spec file: specs/windows/registry.table + +TEST_F(RegistryTest, test_sanity) { + QueryData const rows = execute_query("select * from registry"); + + // Asserts at least one registry entry is returned + ASSERT_GT(rows.size(), 0ul); + + // Validates each row conforms to the expected schema + validate_rows(rows, row_map); +} +``` + +> **Note:** This test targets the Windows registry table only. It will not run on non-Windows platforms. Ensure tests are executed in the correct environment where the registry is accessible. \ No newline at end of file diff --git a/tests/integration/tables/.routes.md b/tests/integration/tables/.routes.md new file mode 100644 index 00000000000..cbcf970adf6 --- /dev/null +++ b/tests/integration/tables/.routes.md @@ -0,0 +1,43 @@ + +Integration test for the `routes` osquery table, validating schema correctness and query behavior across platforms. + +## Key Components + +- **`RoutesTest`** — Test fixture inheriting from `testing::Test`, initializes the osquery environment via `setUpEnvironment()`. +- **`test_sanity`** — Primary test case that validates the structure and content of the `routes` table. +- **`row_map` (ValidationMap)** — Defines per-column validation rules for all returned rows. +- **Conditional `hopcount` field** — Only validated on POSIX systems (`OSQUERY_POSIX`). + +## Validated Columns + +| Column | Validation | +|---|---| +| `destination` | Valid IP address | +| `netmask` | Integer 0–128 | +| `gateway` | Any normal type | +| `source` | Empty string or valid IP | +| `flags` | Integer | +| `interface` | Any normal type | +| `mtu` | Integer | +| `metric` | Integer | +| `type` | One of: `anycast`, `broadcast`, `dynamic`, `gateway`, `local`, `other`, `remote`, `router`, `static` | +| `hopcount` *(POSIX only)* | Integer 0–255 | + +## Usage Example + +```cpp +// Run all route tests via the osquery test harness +// Executes two queries internally: + +// 1. Full table scan — must return at least one row +auto const data = execute_query("select * from routes"); +ASSERT_FALSE(data.empty()); +validate_rows(data, row_map); + +// 2. Filtered query — ensures 'local' type routes exist and are valid +auto const datatype = execute_query("select * from routes where type = 'local'"); +ASSERT_FALSE(datatype.empty()); +validate_rows(datatype, row_map); +``` + +The test asserts both that the table is non-empty and that all rows conform to the expected schema, covering both a full scan and a type-filtered query. \ No newline at end of file diff --git a/tests/integration/tables/.rpm_package_files.md b/tests/integration/tables/.rpm_package_files.md new file mode 100644 index 00000000000..7cfc5fc443f --- /dev/null +++ b/tests/integration/tables/.rpm_package_files.md @@ -0,0 +1,33 @@ + +Integration test file for the `rpm_package_files` osquery table, providing a sanity check that verifies the table can be queried without errors on Linux systems. + +## Key Components + +- **`rpmPackageFiles`** — Test fixture class inheriting from `testing::Test`, sets up the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` query against the `rpm_package_files` table to validate it is queryable. + +## Usage Example + +```cpp +// Run the sanity test (via osquery integration test runner) +// The test executes the following query internally: +auto const data = execute_query("select * from rpm_package_files"); + +// To extend with full row validation, uncomment and configure: +ValidationMap row_map = { + {"package", NormalType}, + {"path", NormalType}, + {"username", NormalType}, + {"groupname", NormalType}, + {"mode", NormalType}, + {"size", IntType}, + {"sha256", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/linux/rpm_package_files.table`. +- Row count assertions and `validate_rows()` are scaffolded but commented out — activate them to enforce stricter validation on systems where RPM packages are guaranteed to be installed. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, `ValidationMap`, and type constants (`NormalType`, `IntType`). \ No newline at end of file diff --git a/tests/integration/tables/.rpm_packages.md b/tests/integration/tables/.rpm_packages.md new file mode 100644 index 00000000000..0e390bf3501 --- /dev/null +++ b/tests/integration/tables/.rpm_packages.md @@ -0,0 +1,50 @@ + +Integration test that validates the `rpm_packages` osquery table returns correctly typed and non-empty fields on RPM-based Linux systems. + +## Key Components + +- **`rpmPackages` (test fixture)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(rpmPackages, test_sanity)`** — Core test case that: + - Executes `SELECT * FROM rpm_packages` + - Skips gracefully (with a warning) if no results are returned, assuming RPM is absent + - Validates each row against a typed `ValidationMap` + - Runs container-aware validation on Linux via `validate_container_rows` + +## Validated Fields + +| Column | Expected Type | +|---|---| +| `name` | `NonEmptyString` | +| `version` | `NormalType` | +| `release` | `NormalType` | +| `source` | `NormalType` | +| `size` | `IntType` | +| `sha1` | `NonEmptyString` | +| `arch` | `NonEmptyString` | +| `epoch` | `IntOrEmpty` | +| `install_time` | `IntType` | +| `vendor` | `NonEmptyString` | +| `package_group` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run via osquery integration test harness +TEST_F(rpmPackages, test_sanity) { + auto rows = execute_query("select * from rpm_packages"); + + // Graceful skip if RPM is not present on the system + if (rows.empty()) { + LOG(WARNING) << "No RPM found, skipping..."; + return; + } + + validate_rows(rows, row_map); + + if (isPlatform(PlatformType::TYPE_LINUX)) { + validate_container_rows("rpm_packages", row_map); + } +} +``` + +> **Note:** This test is non-fatal when `rpm_packages` returns no rows — it assumes RPM tooling is simply absent rather than treating empty results as a failure. \ No newline at end of file diff --git a/tests/integration/tables/.running_apps.md b/tests/integration/tables/.running_apps.md new file mode 100644 index 00000000000..f97af65c35d --- /dev/null +++ b/tests/integration/tables/.running_apps.md @@ -0,0 +1,30 @@ + +Integration test for the `running_apps` osquery table on Darwin (macOS), verifying that the table returns valid data with correctly typed columns. + +## Key Components + +- **`runningApps`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(runningApps, test_sanity)`** — Sanity check that executes `SELECT * FROM running_apps` and validates the result set. + +## Usage Example + +```cpp +// ValidationMap defines expected column types for the running_apps table +ValidationMap row_map = { + {"pid", IntType}, // Process ID — must be an integer + {"bundle_identifier", NormalType} // App bundle ID — must be a non-empty string +}; + +// Execute the table query +QueryData general_query_data = execute_query("select * from running_apps"); + +// Assert the table is not empty and all rows conform to the schema +ASSERT_FALSE(general_query_data.empty()); +validate_rows(general_query_data, row_map); +``` + +## Notes + +- Targets **Darwin (macOS) only** — corresponds to `specs/darwin/running_apps.table`. +- The test asserts the query returns **at least one row**, meaning at least one application must be running during test execution. +- Uses osquery's integration test helpers (`execute_query`, `validate_rows`) from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.safari_extensions.md b/tests/integration/tables/.safari_extensions.md new file mode 100644 index 00000000000..dff5279f273 --- /dev/null +++ b/tests/integration/tables/.safari_extensions.md @@ -0,0 +1,32 @@ + +Integration test sanity check for the `safari_extensions` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`safariExtensions`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM safari_extensions` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the sanity test against the safari_extensions table +TEST_F(safariExtensions, test_sanity) { + auto const data = execute_query("select * from safari_extensions"); + + // Uncomment and configure to validate returned rows: + ValidationMap row_map = { + {"uid", IntType}, + {"name", NormalType}, + {"identifier", NormalType}, + {"version", NormalType}, + {"path", NormalType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/darwin/safari_extensions.table`. +- Row count assertions and `ValidationMap` checks are scaffolded but commented out — enable them to enforce expected schema and result constraints. +- Relies on osquery's integration test helper (`osquery/tests/integration/tables/helper.h`) for `execute_query`, `validate_rows`, and type constants like `IntType`/`NormalType`. \ No newline at end of file diff --git a/tests/integration/tables/.sandboxes.md b/tests/integration/tables/.sandboxes.md new file mode 100644 index 00000000000..5648e240491 --- /dev/null +++ b/tests/integration/tables/.sandboxes.md @@ -0,0 +1,41 @@ + +Integration sanity test for the `sandboxes` osquery table (Darwin/macOS), verifying that returned rows conform to expected column types and constraints. + +## Key Components + +- **`Sandboxes`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(Sandboxes, test_sanity)`** — Executes `SELECT * FROM sandboxes` and validates row structure if results are returned. Emits a warning if the table returns no rows (e.g., no sandboxes active on the host). + +### Validated Columns + +| Column | Constraint | +|---|---| +| `label` | `NonEmptyString` | +| `user` | `NonEmptyString` | +| `enabled` | `Bool` | +| `build_id` | `NonEmptyString` | +| `bundle_path` | `NormalType` | +| `path` | `DirectoryOnDisk` | + +## Usage Example + +```cpp +// Run via the osquery integration test harness: +// ctest -R sandboxes --output-on-failure + +// The test executes this query internally: +QueryData data = execute_query("select * from sandboxes"); + +// Rows are validated against the schema map: +ValidationMap row_map = { + {"label", NonEmptyString}, + {"user", NonEmptyString}, + {"enabled", Bool}, + {"build_id", NonEmptyString}, + {"bundle_path", NormalType}, + {"path", DirectoryOnDisk}, +}; +validate_rows(data, row_map); +``` + +> **Note:** This test is Darwin-only (see `specs/darwin/sandboxes.table`). An empty result set is tolerated and logged as a warning rather than a test failure, since sandboxes may not be present on all macOS hosts. \ No newline at end of file diff --git a/tests/integration/tables/.scheduled_tasks.md b/tests/integration/tables/.scheduled_tasks.md new file mode 100644 index 00000000000..69330304aad --- /dev/null +++ b/tests/integration/tables/.scheduled_tasks.md @@ -0,0 +1,35 @@ + +Integration test sanity check for the `scheduled_tasks` osquery table on Windows, verifying the table can be queried without errors. + +## Key Components + +- **`scheduledTasks`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(scheduledTasks, test_sanity)`** — Executes a `SELECT *` query against the `scheduled_tasks` table and provides a scaffold for row count assertions and field-level validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery's integration test runner) +// The test queries the scheduled_tasks table and validates results + +// To enable full validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"name", NormalType}, + {"action", NormalType}, + {"path", NormalType}, + {"enabled", IntType}, + {"state", NormalType}, + {"hidden", IntType}, + {"last_run_time", IntType}, + {"next_run_time", IntType}, + {"last_run_message", NormalType}, + {"last_run_code", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Windows-specific table spec at `specs/windows/scheduled_tasks.table`. +- Row count assertions and `validate_rows()` are intentionally commented out, leaving the test as a lightweight query-execution check. +- Extend by uncommenting the `ValidationMap` block and calling `validate_rows()` to enforce per-column type constraints. \ No newline at end of file diff --git a/tests/integration/tables/.secureboot.md b/tests/integration/tables/.secureboot.md new file mode 100644 index 00000000000..3756c7b87f3 --- /dev/null +++ b/tests/integration/tables/.secureboot.md @@ -0,0 +1,38 @@ + +Integration test for the `secureboot` osquery table, validating that Secure Boot status data is correctly reported across UEFI-enabled platforms (Windows, Linux, and macOS). + +## Key Components + +- **`Secureboot`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(Secureboot, test_sanity)`** — Primary sanity test that: + - Queries `platform_info` to determine firmware type and skip non-UEFI systems. + - Conditionally skips ARM64 macOS (`aarch64`), which does not support Secure Boot in this context. + - Queries the `secureboot` table and validates column values against an `IntOrEmpty` constraint. + - Applies platform-specific column checks: `setup_mode` on Windows/Linux, `secure_mode` on macOS. + +## Usage Example + +```cpp +// This test is run via the osquery integration test harness. +// It is not invoked manually; use the test runner: + +// Build and run integration tests: +// $ cmake --build . --target osquery_integration_tests +// $ ./osquery_integration_tests --gtest_filter=Secureboot* + +// The test internally executes these queries: +auto platform_info_rows = execute_query("SELECT firmware_type FROM platform_info;"); +auto secureboot_data = execute_query("SELECT * FROM secureboot;"); + +// Validation schema applied (platform-dependent): +ValidationMap row_map{ + {"secure_boot", IntOrEmpty}, // All platforms + {"setup_mode", IntOrEmpty}, // Windows & Linux only + {"secure_mode", IntOrEmpty}, // macOS only +}; +``` + +## Notes + +- Tests are skipped silently on non-UEFI systems (BIOS firmware) and on Apple Silicon Macs. +- All `secureboot` table values are expected to be integers or empty strings — no free-form text is accepted. \ No newline at end of file diff --git a/tests/integration/tables/.security_profile_info.md b/tests/integration/tables/.security_profile_info.md new file mode 100644 index 00000000000..3b7883e2b67 --- /dev/null +++ b/tests/integration/tables/.security_profile_info.md @@ -0,0 +1,34 @@ + +Sanity check integration test for the `security_profile_info` osquery table on Windows, verifying that queried security profile data conforms to expected types and constraints. + +## Key Components + +- **`WindowsSecurityProfileTests`** — Test fixture class extending `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity_profile_info`** — Core test case that: + - Skips execution if running as a WoW64 (32-bit on 64-bit Windows) process using `SceClientHelper::isWow64Process()`. + - Executes `SELECT * FROM security_profile_info` via `execute_query()`. + - Validates all returned rows against a `ValidationMap` defining expected field types and nullability. + +## Validated Fields + +| Field | Constraint | +|---|---| +| `minimum_password_age`, `maximum_password_age`, `minimum_password_length` | `IntType`, `NonEmpty`, `NonNull` | +| `password_complexity`, `password_history_size`, `lockout_bad_count` | `IntType`, `NonEmpty`, `NonNull` | +| `new_administrator_name`, `new_guest_name` | `EmptyOk` | +| `clear_text_password`, `lsa_anonymous_name_lookup` | `IntType`, `NonEmpty`, `NonNull` | +| `audit_*` fields (8 total) | `IntType`, `NonEmpty`, `NonNull` | + +## Usage Example + +```cpp +// Run via osquery integration test suite (non-WoW64 Windows environment only) +TEST_F(WindowsSecurityProfileTests, test_sanity_profile_info) { + if (!osquery::tables::SceClientHelper::isWow64Process()) { + auto const data = execute_query("select * from security_profile_info"); + validate_rows(data, rowMap); + } +} +``` + +> **Note:** This test is skipped automatically on WoW64 processes. Ensure tests run in a native 64-bit Windows context for full coverage. \ No newline at end of file diff --git a/tests/integration/tables/.selinux_events.md b/tests/integration/tables/.selinux_events.md new file mode 100644 index 00000000000..4c70fb1e4fd --- /dev/null +++ b/tests/integration/tables/.selinux_events.md @@ -0,0 +1,32 @@ + +Integration test file for the `selinux_events` osquery table, providing a sanity check to verify the table can be queried without errors on Linux systems. + +## Key Components + +- **`selinuxEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(selinuxEvents, test_sanity)`** — Basic integration test that executes a `SELECT *` query against the `selinux_events` table to confirm it is queryable. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery integration test runner) +// Queries the selinux_events table and optionally validates row structure: + +ValidationMap row_map = { + {"type", NormalType}, + {"message", NormalType}, + {"time", IntType}, + {"uptime", IntType}, + {"eid", NormalType}, +}; + +auto const data = execute_query("select * from selinux_events"); +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/linux/selinux_events.table`. +- Row count assertions and `validate_rows()` calls are present but **commented out**, meaning the test currently only checks that the query executes without failure. +- To enable full validation, uncomment the `ValidationMap` block and the `validate_rows()` call, and add an appropriate size assertion (`ASSERT_GE`, `ASSERT_EQ`) based on expected event data availability. +- Depends on `osquery/tests/integration/tables/helper.h` for test utilities and validation primitives. \ No newline at end of file diff --git a/tests/integration/tables/.services.md b/tests/integration/tables/.services.md new file mode 100644 index 00000000000..7229110eeea --- /dev/null +++ b/tests/integration/tables/.services.md @@ -0,0 +1,35 @@ + +Integration test stub for the `services` osquery table on Windows, verifying that the `services` table can be queried without errors. + +## Key Components + +- **`services` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(services, test_sanity)`** — Executes `SELECT * FROM services` and provides a scaffolded (commented-out) structure for size assertions and row validation. + +## Usage Example + +```cpp +// Extend the test by uncommenting and configuring the validation map: +ValidationMap row_map = { + {"name", NormalType}, + {"service_type", NormalType}, + {"display_name", NormalType}, + {"status", NormalType}, + {"pid", IntType}, + {"start_type", NormalType}, + {"win32_exit_code", IntType}, + {"service_exit_code", IntType}, + {"path", NormalType}, + {"module_path", NormalType}, + {"description", NormalType}, + {"user_account", NormalType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the osquery table spec at `specs/windows/services.table`. +- Size assertions (`ASSERT_GE`, `ASSERT_EQ`) and `validate_rows` are intentionally commented out — activate them as needed for stricter validation. +- See `osquery/tests/integration/tables/helper.h` for available type flags (`NormalType`, `IntType`, etc.) and custom `DataCheck` objects. \ No newline at end of file diff --git a/tests/integration/tables/.shadow.md b/tests/integration/tables/.shadow.md new file mode 100644 index 00000000000..a9c9dc76e93 --- /dev/null +++ b/tests/integration/tables/.shadow.md @@ -0,0 +1,31 @@ + +Integration sanity test for the osquery `shadow` table, verifying that the `SELECT * FROM shadow` query executes without error on Linux systems. + +## Key Components + +- **`shadow` (test class)** — Inherits from `testing::Test`; sets up the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(shadow, test_sanity)`** — Executes a basic query against the `shadow` table and provides a scaffolded (commented-out) validation block for future use. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test queries the shadow table and can be extended with row validation: + +ValidationMap row_map = { + {"username", NormalType}, + {"password_status", NormalType}, + {"hash_alg", NormalType}, + {"last_change", IntType}, + {"min", IntType}, + {"max", IntType}, + {"warning", IntType}, + {"inactive", IntType}, + {"expire", IntType}, + {"flag", IntType}, +}; + +validate_rows(data, row_map); +``` + +> **Note:** The validation map and `validate_rows` call are currently commented out. To enable full validation, uncomment and populate the `ValidationMap` block, then call `validate_rows(data, row_map)`. Size assertions (`ASSERT_GE`, `ASSERT_EQ`) are also scaffolded for optional use depending on expected row counts. \ No newline at end of file diff --git a/tests/integration/tables/.shared_folders.md b/tests/integration/tables/.shared_folders.md new file mode 100644 index 00000000000..6d41bc3d236 --- /dev/null +++ b/tests/integration/tables/.shared_folders.md @@ -0,0 +1,28 @@ + +Integration test stub for the `shared_folders` osquery table on Darwin (macOS), verifying basic query execution sanity. + +## Key Components + +- **`sharedFolders`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(sharedFolders, test_sanity)`** — Executes a `SELECT * FROM shared_folders` query and provides scaffolding for row count assertions and column-level validation (currently commented out). + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the shared_folders table and can be extended like: + +ValidationMap row_map = { + {"name", NormalType}, + {"path", NormalType} +}; +validate_rows(data, row_map); +``` + +To enable full validation, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)` with the appropriate column flags defined in `helper.h`. + +## Notes + +- Targets **Darwin (macOS)** only — spec file: `specs/darwin/shared_folders.table` +- Size assertions (`ASSERT_GE`, `ASSERT_EQ`) are intentionally commented out, as shared folder count varies per machine +- Part of the [osquery](https://osquery.io) integration test suite under the `osquery::table_tests` namespace \ No newline at end of file diff --git a/tests/integration/tables/.shared_memory.md b/tests/integration/tables/.shared_memory.md new file mode 100644 index 00000000000..1e863b2f52f --- /dev/null +++ b/tests/integration/tables/.shared_memory.md @@ -0,0 +1,39 @@ + +Integration test sanity check for the `shared_memory` osquery table on Linux, verifying the table can be queried without errors. + +## Key Components + +- **`sharedMemory`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(sharedMemory, test_sanity)`** — Executes a `SELECT * FROM shared_memory` query to validate basic table accessibility. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test queries the shared_memory table and can be extended +// to validate rows against the following schema: + +ValidationMap row_map = { + {"shmid", IntType}, + {"owner_uid", IntType}, + {"creator_uid", IntType}, + {"pid", IntType}, + {"creator_pid", IntType}, + {"atime", IntType}, + {"dtime", IntType}, + {"ctime", IntType}, + {"permissions", NormalType}, + {"size", IntType}, + {"attached", IntType}, + {"status", NormalType}, + {"locked", IntType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Linux-specific `shared_memory` table (`specs/linux/shared_memory.table`). +- Row count assertions and schema validation are scaffolded but commented out — uncomment and configure to enable full validation. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants. \ No newline at end of file diff --git a/tests/integration/tables/.shared_resources.md b/tests/integration/tables/.shared_resources.md new file mode 100644 index 00000000000..2ed1c191247 --- /dev/null +++ b/tests/integration/tables/.shared_resources.md @@ -0,0 +1,34 @@ + +Integration test file for the `shared_resources` osquery table on Windows, providing a sanity check to verify the table can be queried without errors. + +## Key Components + +- **`sharedResources`** — Test fixture class extending `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(sharedResources, test_sanity)`** — Single test case that executes a `SELECT *` query against the `shared_resources` table and contains scaffolding for row count assertions and field validation. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test executes the following query internally: +auto const data = execute_query("select * from shared_resources"); + +// Uncommenting the validation map enables field-level type checking: +ValidationMap row_map = { + {"description", NormalType}, + {"install_date", NormalType}, + {"status", NormalType}, + {"allow_maximum", IntType}, + {"maximum_allowed",IntType}, + {"name", NormalType}, + {"path", NormalType}, + {"type", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets **Windows only** (spec: `specs/windows/shared_resources.table`). +- Row count assertions and `validate_rows()` are commented out, meaning the test currently only verifies the query executes without crashing. +- To enable full validation, uncomment the `ValidationMap` block and the `validate_rows()` call along with the appropriate `ASSERT_GE`/`ASSERT_EQ` size check. \ No newline at end of file diff --git a/tests/integration/tables/.sharing_preferences.md b/tests/integration/tables/.sharing_preferences.md new file mode 100644 index 00000000000..27f4edd0c2c --- /dev/null +++ b/tests/integration/tables/.sharing_preferences.md @@ -0,0 +1,27 @@ + +Integration test for the `sharing_preferences` osquery table (Darwin/macOS), validating that the table returns well-formed rows with correct boolean fields. + +## Key Components + +- **`sharingPreferences`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Primary test case that queries `sharing_preferences` and validates the schema and data presence. +- **`ValidationMap row_map`** — Defines the expected schema, asserting all fields return `Bool` values: + - `screen_sharing`, `file_sharing`, `printer_sharing` + - `remote_login`, `remote_management`, `remote_apple_events` + - `internet_sharing`, `bluetooth_sharing`, `disc_sharing`, `content_caching` + +## Usage Example + +```cpp +// Run the sanity test against a live macOS environment +// Executes internally as: +auto const data = execute_query("select * from sharing_preferences"); +ASSERT_FALSE(data.empty()); // Table must return at least one row +validate_rows(data, row_map); // All fields must conform to Bool type +``` + +## Notes + +- **Platform**: macOS (Darwin) only — defined in `specs/darwin/sharing_preferences.table`. +- The test asserts the table is **non-empty** and all columns are valid booleans, ensuring the underlying macOS sharing preference APIs are reachable and correctly parsed. +- Part of the osquery integration test suite; run via the standard osquery test harness, not as a standalone binary. \ No newline at end of file diff --git a/tests/integration/tables/.shell_history.md b/tests/integration/tables/.shell_history.md new file mode 100644 index 00000000000..ae74d109fd5 --- /dev/null +++ b/tests/integration/tables/.shell_history.md @@ -0,0 +1,30 @@ + +Integration test stub for the `shell_history` osquery table, validating that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`shellHistory`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(shellHistory, test_sanity)`** — Basic sanity test that executes `SELECT * FROM shell_history` and provides a scaffolded validation structure (currently inactive). + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test executes the following query internally: +auto const data = execute_query("select * from shell_history"); + +// To enable full row validation, uncomment and configure: +ValidationMap row_map = { + {"uid", IntType}, + {"time", IntType}, + {"command", NormalType}, + {"history_file", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the table spec defined in `specs/posix/shell_history.table`. +- Size assertions and `validate_rows()` calls are scaffolded but commented out — activate them to enforce stricter test guarantees. +- Follows the standard osquery integration test pattern from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.shellbags.md b/tests/integration/tables/.shellbags.md new file mode 100644 index 00000000000..40e7315c16a --- /dev/null +++ b/tests/integration/tables/.shellbags.md @@ -0,0 +1,43 @@ + +Integration test for the `shellbags` osquery table, validating that Windows Shell Bag registry entries can be queried and return well-formed data. + +## Key Components + +### `ShellbagsTest` (class) +A Google Test fixture inheriting from `testing::Test` that initializes the osquery integration test environment via `setUpEnvironment()`. + +### `test_sanity` (test case) +Executes two queries against the `shellbags` table: +- A full table scan (`select * from shellbags`) +- A filtered query scoping results to paths containing `This PC` + +Validates returned rows against a `ValidationMap` enforcing the following schema: + +| Column | Validation | +|---|---| +| `sid` | Non-empty string | +| `source` | Non-empty string | +| `path` | Non-empty string | +| `modified_time` | Normal type | +| `created_time` | Normal type | +| `accessed_time` | Normal type | +| `mft_entry` | Normal type | +| `mft_sequence` | Normal type | + +## Usage Example + +```cpp +// Run this test via the osquery integration test harness +// The test is skipped automatically if shellbags returns no rows +// (e.g., non-Windows environments) + +TEST_F(ShellbagsTest, test_sanity) { + QueryData const rows = execute_query("select * from shellbags"); + if (!rows.empty()) { + // Validates both general and filtered results + validate_rows(rows, row_map); + } +} +``` + +> **Note:** The test body is guarded by `if (!rows.empty())`, making it a no-op on non-Windows systems where the `shellbags` table returns no data. This ensures cross-platform test suite compatibility. \ No newline at end of file diff --git a/tests/integration/tables/.shimcache.md b/tests/integration/tables/.shimcache.md new file mode 100644 index 00000000000..adb1f3c545e --- /dev/null +++ b/tests/integration/tables/.shimcache.md @@ -0,0 +1,33 @@ + +Integration test for the `shimcache` osquery table, validating that the Windows Application Compatibility Cache (ShimCache) table returns well-formed data with expected column types. + +## Key Components + +- **`ShimcacheTest`** — Test fixture inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()` +- **`test_sanity`** — Core test case that executes two queries and validates results: + - Full table scan (`select * from shimcache`) + - Filtered query targeting `.exe` entries via `path like '%.exe'` + +## Validated Columns + +| Column | Validation Rule | +|---|---| +| `entry` | `NonEmptyString` | +| `path` | `NormalType` | +| `modified_time` | `NormalType` | +| `execution_flag` | `NormalType` | + +## Usage Example + +```cpp +// Run as part of the osquery integration test suite: +// Both queries must return at least one row +ASSERT_GT(rows.size(), 0ul); +ASSERT_GT(specific_query_rows.size(), 0ul); + +// Filtered query example used in the test +QueryData const specific_query_rows = + execute_query("select * from shimcache where path like '%.exe'"); +``` + +> **Note:** ShimCache is a Windows-only artifact. This test is expected to run on Windows hosts where the registry-backed shimcache data is populated. The `entry` field is asserted as non-empty, while `path`, `modified_time`, and `execution_flag` are validated as normal (non-null, type-conformant) values. \ No newline at end of file diff --git a/tests/integration/tables/.signature.md b/tests/integration/tables/.signature.md new file mode 100644 index 00000000000..07dba2769c9 --- /dev/null +++ b/tests/integration/tables/.signature.md @@ -0,0 +1,30 @@ + +Integration sanity test for the `signature` osquery table, validating that queries against the Darwin `signature` table execute without errors. + +## Key Components + +- **`signature` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(signature, test_sanity)`** — Executes a basic `SELECT * FROM signature WHERE path = ''` query to verify the table is queryable. Full row count assertions and field-level validation are scaffolded but currently commented out. + +## Usage Example + +```cpp +// Run the sanity test (via osquery integration test runner) +// The test queries the signature table with an empty path filter +auto const data = execute_query("select * from signature where path = ''"); + +// Uncommenting the validation map enables per-column type checks: +// ValidationMap row_map = { +// {"path", NormalType}, +// {"hash_resources", IntType}, +// {"arch", NormalType}, +// {"signed", IntType}, +// {"identifier", NormalType}, +// {"cdhash", NormalType}, +// {"team_identifier", NormalType}, +// {"authority", NormalType}, +// }; +// validate_rows(data, row_map); +``` + +> **Note:** Row count assertions (`ASSERT_GE`, `ASSERT_EQ`) and `validate_rows()` are intentionally commented out. To enable full validation, uncomment the `ValidationMap` block and the appropriate size assertion that matches expected query results for the target Darwin environment. \ No newline at end of file diff --git a/tests/integration/tables/.sip_config.md b/tests/integration/tables/.sip_config.md new file mode 100644 index 00000000000..68231b14101 --- /dev/null +++ b/tests/integration/tables/.sip_config.md @@ -0,0 +1,25 @@ + +Integration test stub for the `sip_config` osquery table on Darwin (macOS), validating that the System Integrity Protection (SIP) configuration table can be queried without errors. + +## Key Components + +- **`sipConfig`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(sipConfig, test_sanity)`** — Sanity check that executes `SELECT * FROM sip_config` and provides a scaffolded (currently commented-out) validation pattern. + +## Usage Example + +```cpp +// Run the integration test (from the osquery build directory) +// The test queries the sip_config table and can be extended with row validation: + +ValidationMap row_map = { + {"config_flag", NormalType}, + {"enabled", IntType}, + {"enabled_nvram", IntType} +}; +validate_rows(data, row_map); +``` + +To activate full validation, uncomment the `ValidationMap` block and the `validate_rows` call, then choose the appropriate `ASSERT_*` size check based on expected row count (typically `1` for SIP config). + +> **Note:** This test targets macOS only. The corresponding table spec is located at `specs/darwin/sip_config.table`. \ No newline at end of file diff --git a/tests/integration/tables/.smart_drive_info.md b/tests/integration/tables/.smart_drive_info.md new file mode 100644 index 00000000000..3658e97a092 --- /dev/null +++ b/tests/integration/tables/.smart_drive_info.md @@ -0,0 +1,33 @@ + +Integration test file for the `smart_drive_info` osquery table, providing a sanity check scaffold to validate query execution against SMART drive data. + +## Key Components + +- **`smartDriveInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(smartDriveInfo, test_sanity)`** — Primary test case that executes a `SELECT * FROM smart_drive_info` query and provides a commented-out validation map template for all expected columns. + +## Covered Columns (ValidationMap template) + +The commented schema includes fields such as `device_name`, `disk_id`, `driver_type`, `model_family`, `device_model`, `serial_number`, `firmware_version`, `smart_supported`, `smart_enabled`, `rotation_rate`, `transport_type`, `sata_version`, `warnings`, and more. + +## Usage Example + +```cpp +// To activate full validation, uncomment and configure the validation map: +ValidationMap row_map = { + {"device_name", NormalType}, + {"disk_id", IntType}, + {"smart_enabled", NormalType}, + {"warnings", NormalType}, + // ...remaining fields +}; + +ASSERT_GE(data.size(), 0ul); +validate_rows(data, row_map); +``` + +## Notes + +- The test currently only executes the query without size assertions or row validation — all checks are commented out as scaffolding. +- Spec file reference: `specs/smart/smart_drive_info.table` +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h` \ No newline at end of file diff --git a/tests/integration/tables/.smbios_tables.md b/tests/integration/tables/.smbios_tables.md new file mode 100644 index 00000000000..933a4aac0f1 --- /dev/null +++ b/tests/integration/tables/.smbios_tables.md @@ -0,0 +1,32 @@ + +Integration test file for the `smbios_tables` osquery table, providing a sanity check that verifies the table can be queried without errors on POSIX systems. + +## Key Components + +- **`smbiosTables`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` query against the `smbios_tables` virtual table to confirm it runs without crashing. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test runner +// The test queries the smbios_tables virtual table and can be +// extended with row validation using the commented-out ValidationMap: + +ValidationMap row_map = { + {"number", IntType}, + {"type", IntType}, + {"description", NormalType}, + {"handle", IntType}, + {"header_size", IntType}, + {"size", IntType}, + {"md5", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/smbios_tables.table`. +- Row count assertions and full `ValidationMap` checks are scaffolded but currently commented out — activate them to enforce stricter validation. +- Uses the shared `osquery/tests/integration/tables/helper.h` utilities (`execute_query`, `validate_rows`, type flags). \ No newline at end of file diff --git a/tests/integration/tables/.smc_keys.md b/tests/integration/tables/.smc_keys.md new file mode 100644 index 00000000000..4abed02f959 --- /dev/null +++ b/tests/integration/tables/.smc_keys.md @@ -0,0 +1,31 @@ + +Integration test stub for the `smc_keys` osquery table on Darwin (macOS), verifying basic query execution against the System Management Controller (SMC) key-value store. + +## Key Components + +- **`smcKeys`** — Test fixture inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(smcKeys, test_sanity)`** — Sanity check that executes `SELECT * FROM smc_keys` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner +// The test executes the following query internally: +auto const data = execute_query("select * from smc_keys"); + +// Uncommenting the validation map enables column-level type checking: +ValidationMap row_map = { + {"key", NormalType}, + {"type", NormalType}, + {"size", IntType}, + {"value", NormalType}, + {"hidden", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- All assertions and the `ValidationMap` are **commented out**, making this a non-failing placeholder test. To activate full validation, uncomment the `row_map` and `validate_rows` call. +- Targets Darwin only — corresponds to `specs/darwin/smc_keys.table`. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.socket_events.md b/tests/integration/tables/.socket_events.md new file mode 100644 index 00000000000..3445ac7abe5 --- /dev/null +++ b/tests/integration/tables/.socket_events.md @@ -0,0 +1,38 @@ + +Sanity check integration test for the `socket_events` osquery table on Linux, verifying the table can be queried without errors. + +## Key Components + +- **`socketEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(socketEvents, test_sanity)`** — Executes a `SELECT *` query against the `socket_events` table and provides a scaffold for row-level validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test directly via osquery test runner +// The test executes the following query internally: +auto const data = execute_query("select * from socket_events"); + +// Validation map (commented out by default, enable as needed): +ValidationMap row_map = { + {"action", NormalType}, + {"pid", IntType}, + {"path", NormalType}, + {"fd", NormalType}, + {"family", IntType}, + {"protocol", IntType}, + {"local_address", NormalType}, + {"remote_address", NormalType}, + {"local_port", IntType}, + {"remote_port", IntType}, + {"time", IntType}, + {"uptime", IntType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the spec file: `specs/linux/socket_events.table`. +- Row count assertions and `validate_rows()` are scaffolded but commented out — enable them to enforce stricter validation in CI. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants (`NormalType`, `IntType`). \ No newline at end of file diff --git a/tests/integration/tables/.ssh_configs.md b/tests/integration/tables/.ssh_configs.md new file mode 100644 index 00000000000..b3828f5c77b --- /dev/null +++ b/tests/integration/tables/.ssh_configs.md @@ -0,0 +1,28 @@ + +Integration test for the `ssh_configs` osquery table, validating that SSH configuration data can be queried without errors across POSIX and Windows platforms. + +## Key Components + +- **`sshConfigs`** — Test fixture class inheriting from `testing::Test`, with platform-specific setup/teardown for Windows user and group services via `initUsersAndGroupsServices` and `Dispatcher`. +- **`TEST_F(sshConfigs, test_sanity)`** — Sanity check that executes `SELECT * FROM ssh_configs` and provides scaffolding for row validation (currently passive — validation map is commented out). + +## Usage Example + +```cpp +// Run the integration test (typically via CMake/CTest): +// ctest -R ssh_configs + +// The test executes this query against the osquery runtime: +auto const data = execute_query("select * from ssh_configs"); + +// Commented-out validation example (can be enabled as needed): +// ValidationMap row_map = { +// {"uid", IntType}, +// {"block", NormalType}, +// {"option", NormalType}, +// {"ssh_config_file", NormalType}, +// }; +// validate_rows(data, row_map); +``` + +> **Note:** The validation map and row count assertions are intentionally commented out. To harden the test, uncomment and configure `validate_rows()` with expected column types matching the `ssh_configs` table spec (`specs/posix/ssh_configs.table`). \ No newline at end of file diff --git a/tests/integration/tables/.startup_items.md b/tests/integration/tables/.startup_items.md new file mode 100644 index 00000000000..3f7909d140e --- /dev/null +++ b/tests/integration/tables/.startup_items.md @@ -0,0 +1,45 @@ + +Integration test for the `startup_items` osquery table, validating schema correctness and status value constraints across supported startup item types. + +## Key Components + +- **`StartupItemsTest`** — Google Test fixture class that initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(StartupItemsTest, test_sanity)`** — Primary test case that: + - Executes `SELECT * FROM startup_items` and validates all returned rows against a schema map. + - Enforces type-specific `status` column constraints. + +## Validation Rules + +| Column | Constraint | +|--------|-----------| +| `name` | Non-empty string | +| `path` | Normal type | +| `args` | Normal type | +| `type` | `"Startup Item"` or `"systemd unit"` | +| `source` | Normal type | +| `status` | Non-empty string | +| `username` | Normal type | + +**Status logic by type:** + +- `"Startup Item"` — status must always be `"enabled"` (scripts/.desktop files have no disabled state). +- `"systemd unit"` — status must be one of: `active`, `inactive`, `failed`, `maintenance`, `reloading`, `activating`, `deactivating`. + +## Usage Example + +```cpp +// Run via osquery integration test harness +TEST_F(StartupItemsTest, test_sanity) { + auto const data = execute_query("select * from startup_items"); + validate_rows(data, row_map); + + // Type-specific status assertion example + if (row.at("type") == "Startup Item") { + EXPECT_EQ(row.at("status"), "enabled"); + } else if (row.at("type") == "systemd unit") { + // Validates against valid systemd states + } +} +``` + +> **Spec file:** `specs/macwin/startup_items.table` \ No newline at end of file diff --git a/tests/integration/tables/.sudoers.md b/tests/integration/tables/.sudoers.md new file mode 100644 index 00000000000..81c2bcad442 --- /dev/null +++ b/tests/integration/tables/.sudoers.md @@ -0,0 +1,36 @@ + +Integration test for the `sudoers` osquery table, validating that query results contain well-formed, non-empty data for all expected columns. + +## Key Components + +- **`Sudoers`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(Sudoers, test_sanity)`** — Executes `SELECT * FROM sudoers` and validates each returned row against a `ValidationMap`. + +### Validated Columns + +| Column | Validation Rule | +|---|---| +| `source` | `NonEmptyString` | +| `header` | `NonEmptyString` | +| `rule_details` | `NonEmptyString` | + +## Usage Example + +```cpp +// Run the integration test via osquery's test harness +// Spec file: specs/posix/sudoers.table + +TEST_F(Sudoers, test_sanity) { + auto const data = execute_query("select * from sudoers"); + + ValidationMap row_map = { + {"source", NonEmptyString}, + {"header", NonEmptyString}, + {"rule_details", NonEmptyString}, + }; + + validate_rows(data, row_map); +} +``` + +> **Note:** This test targets POSIX platforms only (Linux/macOS). It will not run on Windows environments. Ensure the test runner has sufficient privileges to read `/etc/sudoers` and related include files. \ No newline at end of file diff --git a/tests/integration/tables/.suid_bin.md b/tests/integration/tables/.suid_bin.md new file mode 100644 index 00000000000..e5a081e6927 --- /dev/null +++ b/tests/integration/tables/.suid_bin.md @@ -0,0 +1,39 @@ + +Integration test for the `suid_bin` osquery table, validating that SUID (Set User ID) binary data is correctly queried and structured on POSIX systems. + +## Key Components + +- **`suidBin`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Primary test case that validates the `suid_bin` table schema and data integrity. + +### Validation Map Fields + +| Field | Type | +|---|---| +| `path` | `NormalType` | +| `username` | `NormalType` | +| `groupname` | `NormalType` | +| `permissions` | `NormalType` | + +## Usage Example + +```cpp +// Runs two queries against the suid_bin table: + +// 1. Full table scan — asserts results are non-empty and schema-valid +auto const data = execute_query("select * from suid_bin"); +ASSERT_FALSE(data.empty()); +validate_rows(data, row_map); + +// 2. Filtered query — checks a known SUID binary exists +auto const data_newgrp = + execute_query("select * from suid_bin where path = '/usr/bin/newgrp'"); +ASSERT_FALSE(data_newgrp.empty()); +validate_rows(data_newgrp, row_map); +``` + +The test confirms that: +1. The `suid_bin` table returns at least one row (SUID binaries are always present on POSIX systems). +2. A specific known SUID binary (`/usr/bin/newgrp`) is discoverable and its row conforms to the expected schema. + +> **Spec reference:** `specs/posix/suid_bin.table` \ No newline at end of file diff --git a/tests/integration/tables/.syslog_events.md b/tests/integration/tables/.syslog_events.md new file mode 100644 index 00000000000..6189bf4bf31 --- /dev/null +++ b/tests/integration/tables/.syslog_events.md @@ -0,0 +1,34 @@ + +Integration sanity test for the `syslog_events` osquery table, verifying the table can be queried without errors on Linux systems. + +## Key Components + +- **`syslogEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(syslogEvents, test_sanity)`** — Executes a `SELECT *` against the `syslog_events` table and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the syslog_events table and validates schema + +// Uncommenting the validation map enables full row validation: +ValidationMap row_map = { + {"time", IntType}, + {"datetime", NormalType}, + {"host", NormalType}, + {"severity", IntType}, + {"facility", NormalType}, + {"tag", NormalType}, + {"message", NormalType}, + {"eid", NormalType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec source: `specs/linux/syslog_events.table` +- Size assertions and row validation are scaffolded but **commented out** — the test currently only confirms the query executes without crashing. +- To enable full validation, uncomment the `ValidationMap` block and call `validate_rows(data, row_map)`. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and type flag constants. \ No newline at end of file diff --git a/tests/integration/tables/.system_controls.md b/tests/integration/tables/.system_controls.md new file mode 100644 index 00000000000..827583ecffb --- /dev/null +++ b/tests/integration/tables/.system_controls.md @@ -0,0 +1,36 @@ + +Integration test for the `system_controls` osquery table, validating that sysctl/kernel parameter data is returned with correct structure and expected field values across POSIX systems. + +## Key Components + +- **`SystemControlsTest`** — Test fixture extending `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that queries `system_controls` and validates row schema and value constraints. +- **`ValidationMap`** — Defines per-column validation rules applied to all returned rows. + +## Validation Rules + +| Column | Constraint | +|---|---| +| `name` | Non-empty string | +| `oid` | Any normal type | +| `subsystem` | One of: `""`, `abi`, `debug`, `dev`, `fs`, `fscache`, `hw`, `kern`, `kernel`, `machdep`, `net`, `sunrpc`, `user`, `vfs`, `vm` | +| `current_value` | Any normal type | +| `config_value` | Any normal type | +| `type` | One of: `""`, `node`, `int`, `string`, `quad`, `opaque`, `struct` | +| `field_name` | Any normal type *(macOS only)* | + +## Usage Example + +```cpp +// Run the integration test via osquery's test harness +// Corresponds to: specs/posix/system_controls.table + +TEST_F(SystemControlsTest, test_sanity) { + auto const rows = execute_query("select * from system_controls"); + // rows are validated against the ValidationMap — + // any unexpected subsystem or type value will fail the test + validate_rows(rows, row_map); +} +``` + +> **Note:** The `field_name` column is conditionally validated only on Apple platforms (`#ifdef __APPLE__`), reflecting macOS-specific sysctl metadata not present on Linux. \ No newline at end of file diff --git a/tests/integration/tables/.system_extensions.md b/tests/integration/tables/.system_extensions.md new file mode 100644 index 00000000000..fb25742fd0e --- /dev/null +++ b/tests/integration/tables/.system_extensions.md @@ -0,0 +1,40 @@ + +Integration test for the `system_extensions` osquery table, validating that queried rows contain expected columns with correct data types. + +## Key Components + +- **`SystemExtension`** — Google Test fixture class that initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a `SELECT *` against the `system_extensions` virtual table and validates the result schema. + +## Validated Columns + +| Column | Validation Type | +|---|---| +| `path` | `NormalType` | +| `UUID` | `NormalType` | +| `state` | `NormalType` | +| `identifier` | `NormalType` | +| `version` | `NormalType` | +| `category` | `NormalType` | +| `bundle_path` | `NormalType` | +| `team` | `NormalType` | +| `mdm_managed` | `NonNegativeInt` | + +## Usage Example + +```cpp +// Run this specific test via osquery's integration test runner: +// (from build directory) +// ./osquery_integration_tests --gtest_filter=SystemExtension.test_sanity + +TEST_F(SystemExtension, test_sanity) { + QueryData data = execute_query("select * from system_extensions"); + ValidationMap row_map = { + {"path", NormalType}, + {"mdm_managed", NonNegativeInt} // MDM-managed flag is integer only + }; + validate_rows(data, row_map); +} +``` + +> **Note:** `mdm_managed` is the only column enforced as a non-negative integer; all other columns accept any non-null string value (`NormalType`). This test corresponds to the spec defined in `specs/system_info.table`. \ No newline at end of file diff --git a/tests/integration/tables/.system_info.md b/tests/integration/tables/.system_info.md new file mode 100644 index 00000000000..5500cb370d0 --- /dev/null +++ b/tests/integration/tables/.system_info.md @@ -0,0 +1,35 @@ + +Sanity check integration test for the `system_info` osquery virtual table, validating that the table returns exactly one row with correctly typed system hardware and CPU fields. + +## Key Components + +- **`SystemInfo`** — Test fixture inheriting from `testing::Test`; calls `setUpEnvironment()` in `SetUp()` to initialize the osquery test environment. +- **`TEST_F(SystemInfo, test_sanity)`** — Core test case that: + - Executes `SELECT * FROM system_info` and asserts exactly one row is returned. + - Validates field types via a `ValidationMap` covering hostname, UUID, CPU info, memory, hardware identifiers, and board details. + - Conditionally adds `emulated_cpu_type` validation on Windows platforms using `isPlatform(PlatformType::TYPE_WINDOWS)`. + +## Validated Fields + +| Field | Validation | +|---|---| +| `uuid` | `ValidUUID` | +| `cpu_type`, `local_hostname` | `NonEmptyString` | +| `cpu_physical_cores`, `cpu_logical_cores`, `cpu_sockets`, `physical_memory` | `NonNegativeInt` | +| `hostname`, `cpu_brand`, `hardware_*`, `board_*`, etc. | `NormalType` | +| `emulated_cpu_type` *(Windows only)* | `NormalType` | + +## Usage Example + +```cpp +// Run via the osquery integration test suite: +// The test executes automatically through Google Test + +TEST_F(SystemInfo, test_sanity) { + QueryData data = execute_query("select * from system_info"); + ASSERT_EQ(data.size(), 1ul); // system_info always returns a single row + // ... field validation follows +} +``` + +> This test corresponds to the table spec at `specs/system_info.table` and is part of osquery's integration test suite under `osquery/tests/integration/tables/`. \ No newline at end of file diff --git a/tests/integration/tables/.systemd_units.md b/tests/integration/tables/.systemd_units.md new file mode 100644 index 00000000000..2ad039c8af3 --- /dev/null +++ b/tests/integration/tables/.systemd_units.md @@ -0,0 +1,46 @@ + +Integration test that validates the `systemd_units` osquery table returns well-formed rows with the expected schema on Linux systems. + +## Key Components + +- **`SystemdUnitsTest`** — Test fixture inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Executes `SELECT * FROM systemd_units` and validates that all returned rows conform to the expected column schema. +- **`ValidationMap`** — Defines the expected columns and their type constraints, all marked as `NormalType`. + +### Validated Columns + +| Column | Type | +|---|---| +| `id` | NormalType | +| `description` | NormalType | +| `load_state` | NormalType | +| `active_state` | NormalType | +| `sub_state` | NormalType | +| `unit_file_state` | NormalType | +| `following` | NormalType | +| `object_path` | NormalType | +| `job_id` | NormalType | +| `job_type` | NormalType | +| `job_path` | NormalType | +| `fragment_path` | NormalType | +| `user` | NormalType | +| `source_path` | NormalType | + +## Usage Example + +```cpp +// Run the sanity test directly via the osquery test harness +TEST_F(SystemdUnitsTest, test_sanity) { + auto const data = execute_query("select * from systemd_units"); + + ValidationMap row_map = { + {"id", NormalType}, + {"active_state", NormalType}, + // ... additional columns + }; + + validate_rows(data, row_map); // Asserts schema compliance +} +``` + +**Dependencies:** Requires `libdbus` and a running systemd instance. Spec defined in `specs/linux/systemd_units.table`. \ No newline at end of file diff --git a/tests/integration/tables/.temperature_sensors.md b/tests/integration/tables/.temperature_sensors.md new file mode 100644 index 00000000000..2829f57a629 --- /dev/null +++ b/tests/integration/tables/.temperature_sensors.md @@ -0,0 +1,30 @@ + +Integration test stub for the `temperature_sensors` osquery table on Darwin (macOS), verifying basic query execution as a sanity check. + +## Key Components + +- **`temperatureSensors`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(temperatureSensors, test_sanity)`** — Single test case that executes `SELECT * FROM temperature_sensors` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Extend the test by uncommenting and configuring the validation map: +ValidationMap row_map = { + {"key", NormalType}, + {"name", NormalType}, + {"celsius", NormalType}, + {"fahrenheit", NormalType}, +}; +validate_rows(data, row_map); + +// Optionally assert expected row counts: +ASSERT_GE(data.size(), 0ul); +``` + +## Notes + +- Spec source: `specs/darwin/temperature_sensors.table` +- Validation logic (`validate_rows`, `ValidationMap`, flags) is defined in `osquery/tests/integration/tables/helper.h` +- Row count assertions and schema validation are scaffolded but commented out — activate them when expected behavior is stable +- This test is Darwin-only; it will not run on Linux or Windows builds \ No newline at end of file diff --git a/tests/integration/tables/.time.md b/tests/integration/tables/.time.md new file mode 100644 index 00000000000..b5247ac1041 --- /dev/null +++ b/tests/integration/tables/.time.md @@ -0,0 +1,38 @@ + +Integration test for the osquery `time` table, validating that querying `select * from time` returns exactly one row with correctly typed and range-validated time fields across platforms. + +## Key Components + +- **`Time` (test fixture)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(Time, test_sanity)`** — Core test case that: + - Executes `select * from time` and asserts exactly one row is returned. + - Defines a `ValidationMap` with field-level type and range constraints. + - Conditionally adds `win_timestamp` validation on Windows platforms. + - Calls `validate_rows()` to assert all fields pass their constraints. + +## Validation Rules + +| Field | Constraint | +|---|---| +| `weekday`, `timezone`, `local_timezone`, `timestamp`, `datetime`, `iso_8601` | `NonEmptyString` | +| `year` | `IntType` | +| `month` | `IntMinMaxCheck(1, 12)` | +| `day` | `IntMinMaxCheck(1, 31)` | +| `hour` | `IntMinMaxCheck(0, 24)` | +| `minutes`, `seconds` | `IntMinMaxCheck(0, 59)` | +| `unix_time` | `NonNegativeInt` | +| `win_timestamp` *(Windows only)* | `NonNegativeInt` | + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner: +// ./osquery_integration_tests --gtest_filter=Time.test_sanity + +// The test internally executes: +QueryData data = execute_query("select * from time"); +ASSERT_EQ(data.size(), 1ul); +validate_rows(data, row_map); +``` + +> **Spec reference:** `specs/utility/time.table` — ensure the table spec is deployed before running this test. \ No newline at end of file diff --git a/tests/integration/tables/.time_machine_backups.md b/tests/integration/tables/.time_machine_backups.md new file mode 100644 index 00000000000..539cf4c4d15 --- /dev/null +++ b/tests/integration/tables/.time_machine_backups.md @@ -0,0 +1,30 @@ + +Integration sanity test for the `time_machine_backups` osquery table, verifying that the table can be queried without errors on Darwin/macOS systems. + +## Key Components + +- **`timeMachineBackups`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that executes `SELECT * FROM time_machine_backups` and provides scaffolding for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via the osquery integration test runner) +// To enable row validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"destination_id", NormalType}, + {"backup_date", IntType} +}; +validate_rows(data, row_map); + +// To assert expected row counts, uncomment one of: +// ASSERT_GE(data.size(), 0ul); // at least 0 rows +// ASSERT_EQ(data.size(), 1ul); // exactly 1 row +``` + +## Notes + +- Targets **Darwin only** (`specs/darwin/time_machine_backups.table`). +- Row count assertions and `ValidationMap` checks are intentionally commented out, as Time Machine backup availability varies per test host. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.time_machine_destinations.md b/tests/integration/tables/.time_machine_destinations.md new file mode 100644 index 00000000000..7ee315e33d2 --- /dev/null +++ b/tests/integration/tables/.time_machine_destinations.md @@ -0,0 +1,31 @@ + +Integration sanity test for the `time_machine_destinations` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`timeMachineDestinations`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Executes `SELECT * FROM time_machine_destinations` and provides a scaffolded (currently commented-out) validation structure for row count assertions and column type checks. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test queries the table and can be extended with row validation: + +ValidationMap row_map = { + {"alias", NormalType}, + {"destination_id", NormalType}, + {"consistency_scan_date", IntType}, + {"root_volume_uuid", NormalType}, + {"bytes_available", IntType}, + {"bytes_used", IntType}, + {"encryption", NormalType}, +}; + +validate_rows(data, row_map); +``` + +> **Note:** Row count assertions and `validate_rows()` are scaffolded but commented out. Enable them to enforce expected column types and result counts for your environment. + +**Spec file:** `specs/darwin/time_machine_destinations.table` +**Platform:** Darwin (macOS) only \ No newline at end of file diff --git a/tests/integration/tables/.tpm_info.md b/tests/integration/tables/.tpm_info.md new file mode 100644 index 00000000000..4afe97ad282 --- /dev/null +++ b/tests/integration/tables/.tpm_info.md @@ -0,0 +1,42 @@ + +Integration test that validates the `tpm_info` osquery table returns correctly typed data for Windows TPM (Trusted Platform Module) chip information. + +## Key Components + +- **`TpmInfo`** — Test fixture class extending `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(TpmInfo, test_sanity)`** — Executes `SELECT * FROM tpm_info` and validates each row against an expected schema. +- **`ValidationMap row_map`** — Defines the expected column names and types for the `tpm_info` table. + +## Validated Columns + +| Column | Type | +|---|---| +| `activated` | `IntType` | +| `enabled` | `IntType` | +| `owned` | `IntType` | +| `manufacturer_version` | `NormalType` | +| `manufacturer_id` | `IntType` | +| `manufacturer_name` | `NormalType` | +| `product_name` | `NormalType` | +| `physical_presence_version` | `NormalType` | +| `spec_version` | `NormalType` | + +## Usage Example + +```cpp +// Run this test as part of the osquery integration test suite +// It queries the tpm_info virtual table and validates column types + +auto const data = execute_query("select * from tpm_info"); + +ValidationMap row_map{ + {"activated", IntType}, + {"enabled", IntType}, + {"owned", IntType}, + {"manufacturer_version", NormalType}, +}; + +validate_rows(data, row_map); +``` + +> This test targets Windows only (WMI-backed table) and serves as a sanity check to ensure the `tpm_info` table schema is consistent and well-formed at runtime. \ No newline at end of file diff --git a/tests/integration/tables/.ulimit_info.md b/tests/integration/tables/.ulimit_info.md new file mode 100644 index 00000000000..66fbbe5b1c4 --- /dev/null +++ b/tests/integration/tables/.ulimit_info.md @@ -0,0 +1,34 @@ + +Integration test file for the `ulimit_info` osquery table, providing a sanity check that verifies the table can be queried successfully on POSIX systems. + +## Key Components + +- **`ulimitInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()` +- **`test_sanity`** — Core test case that executes `SELECT * FROM ulimit_info` and provides scaffolding for row count assertions and schema validation + +## Usage Example + +```cpp +// Run the sanity test (validates table is queryable) +TEST_F(ulimitInfo, test_sanity) { + auto const data = execute_query("select * from ulimit_info"); + + // Uncomment to enable row count assertions: + // ASSERT_GE(data.size(), 0ul); + + // Uncomment to enable schema validation: + // ValidationMap row_map = { + // {"type", NormalType}, + // {"soft_limit", NormalType}, + // {"hard_limit", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/ulimit_info.table` +- Validation logic (row count checks and `ValidationMap`) is stubbed out and ready to be enabled — uncomment the relevant blocks to enforce stricter assertions +- Expected columns: `type`, `soft_limit`, `hard_limit` +- Depends on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and flag constants like `NormalType` \ No newline at end of file diff --git a/tests/integration/tables/.unified_log.md b/tests/integration/tables/.unified_log.md new file mode 100644 index 00000000000..4c8439997ea --- /dev/null +++ b/tests/integration/tables/.unified_log.md @@ -0,0 +1,42 @@ + +Integration test for the `unified_log` osquery table, validating schema correctness, `max_rows` constraint behavior, and sequential query pointer advancement on macOS. + +## Key Components + +### `UnifiedLogTest` (Test Fixture) +A `testing::Test` subclass that initializes the osquery test environment via `setUpEnvironment()` before each test case. + +### `DeltaContext` (Struct) +Tracks the persistent state of the unified log cursor between queries by reading `ual_timestamp` and `ual_counter` from the osquery persistent settings database. Used to verify that sequential queries advance the internal log pointer. + +| Member | Description | +|---|---| +| `timestamp` | Last log timestamp read from persistent DB | +| `count` | Log entry counter from persistent DB | +| `load()` | Hydrates fields from `kPersistentSettings` | +| `operator<` | Returns `true` if either field has advanced | + +### `TEST_F(UnifiedLogTest, test_sanity)` +Primary integration test covering three scenarios: + +1. **Schema validation** — Queries `unified_log` for PIDs 100–105 and asserts all expected columns are present with correct types. +2. **`max_rows` constraint** — Verifies that `max_rows = 50/1/0/-1` returns exactly 50, 1, 0, and 0 rows respectively. +3. **Sequential cursor advancement** — Confirms that successive queries return different rows and that the persistent DB cursor (`ual_timestamp`/`ual_counter`) has moved forward. + +## Usage Example + +```bash +# Run only the unified_log integration test +./osquery_tests --gtest_filter="UnifiedLogTest.test_sanity" +``` + +```cpp +// DeltaContext usage pattern within the test +DeltaContext dc1, dc2; +dc1.load(); // snapshot cursor before query +execute_query("select * from unified_log where max_rows = 1 and timestamp > -1 and category = 'General'"); +dc2.load(); // snapshot cursor after query +EXPECT_TRUE(dc1 < dc2); // assert pointer advanced +``` + +> **Note:** Queries against `unified_log` without a `timestamp` constraint exhibit degraded performance. All test queries include a recent `timestamp` bound as a workaround for [osquery/osquery#8274](https://github.com/osquery/osquery/pull/8274). \ No newline at end of file diff --git a/tests/integration/tables/.uptime.md b/tests/integration/tables/.uptime.md new file mode 100644 index 00000000000..5937b47bfd1 --- /dev/null +++ b/tests/integration/tables/.uptime.md @@ -0,0 +1,35 @@ + +Integration test for the `uptime` osquery table, validating that system uptime data is returned correctly with proper value constraints. + +## Key Components + +- **`UptimeTests`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` +- **`test_sanity`** — Core test case that queries the `uptime` table and validates the result structure and value ranges + +## Usage Example + +```cpp +// The test executes the following query internally: +QueryData data = execute_query("select * from uptime"); + +// Validated fields and their constraints: +ValidationMap row_map = { + {"days", NonNegativeInt}, // >= 0 + {"hours", IntMinMaxCheck(0, 24)}, // 0–24 + {"minutes", IntMinMaxCheck(0, 60)}, // 0–60 + {"seconds", IntMinMaxCheck(0, 60)}, // 0–60 + {"total_seconds", NonNegativeInt} // >= 0 +}; +``` + +## Validation Logic + +| Column | Constraint | Description | +|---|---|---| +| `days` | `NonNegativeInt` | Total uptime days | +| `hours` | `IntMinMaxCheck(0, 24)` | Hour component of uptime | +| `minutes` | `IntMinMaxCheck(0, 60)` | Minute component | +| `seconds` | `IntMinMaxCheck(0, 60)` | Second component | +| `total_seconds` | `NonNegativeInt` | Cumulative uptime in seconds | + +The test asserts exactly **one row** is returned (`data.size() == 1`), consistent with the `uptime` table being a singleton system-level metric. \ No newline at end of file diff --git a/tests/integration/tables/.usb_devices.md b/tests/integration/tables/.usb_devices.md new file mode 100644 index 00000000000..58483ab0bc0 --- /dev/null +++ b/tests/integration/tables/.usb_devices.md @@ -0,0 +1,39 @@ + +Integration test sanity check for the `usb_devices` osquery table, verifying that the table can be queried without errors on POSIX systems. + +## Key Components + +- **`usbDevices`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM usb_devices` and provides a scaffolded (commented-out) validation structure for row-level assertions. + +## Usage Example + +```cpp +// Run the sanity test against the usb_devices table +TEST_F(usbDevices, test_sanity) { + auto const data = execute_query("select * from usb_devices"); + + // Uncomment and configure to enable full row validation: + ValidationMap row_map = { + {"usb_address", IntType}, + {"usb_port", IntType}, + {"vendor", NormalType}, + {"vendor_id", NormalType}, + {"version", NormalType}, + {"model", NormalType}, + {"model_id", NormalType}, + {"serial", NormalType}, + {"class", NormalType}, + {"subclass", NormalType}, + {"protocol", NormalType}, + {"removable", IntType}, + }; + validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/usb_devices.table`. +- Size assertions and `validate_rows()` are intentionally commented out — activate them to enforce expected row counts and column type constraints. +- Uses the shared osquery integration test helper (`osquery/tests/integration/tables/helper.h`) for query execution and validation utilities. \ No newline at end of file diff --git a/tests/integration/tables/.user_events.md b/tests/integration/tables/.user_events.md new file mode 100644 index 00000000000..73b3803d8ad --- /dev/null +++ b/tests/integration/tables/.user_events.md @@ -0,0 +1,37 @@ + +Integration sanity test for the `user_events` osquery table, verifying the table can be queried without errors on POSIX systems. + +## Key Components + +- **`userEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(userEvents, test_sanity)`** — Basic smoke test that executes `SELECT * FROM user_events` and provides a scaffolded (commented-out) validation structure for future use. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test harness +// The test executes the following query internally: +auto const data = execute_query("select * from user_events"); + +// To enable full row validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"uid", IntType}, + {"auid", IntType}, + {"pid", IntType}, + {"message", NormalType}, + {"type", IntType}, + {"path", NormalType}, + {"address", NormalType}, + {"terminal", NormalType}, + {"time", IntType}, + {"uptime", IntType}, + {"eid", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/posix/user_events.table`. +- Row count assertions and `validate_rows()` calls are intentionally commented out — the test currently only confirms the query executes without crashing. +- Extend by uncommenting the `ValidationMap` block and calling `validate_rows(data, row_map)` to enforce column type constraints. \ No newline at end of file diff --git a/tests/integration/tables/.user_groups.md b/tests/integration/tables/.user_groups.md new file mode 100644 index 00000000000..ea701fb6b62 --- /dev/null +++ b/tests/integration/tables/.user_groups.md @@ -0,0 +1,27 @@ + +Integration test for the `user_groups` osquery table, validating that the table returns results and that `uid`/`gid` fields contain valid UID/GID values. + +## Key Components + +- **`UserGroups`** — Test fixture inheriting from `testing::Test`, with environment setup via `setUpEnvironment()`. On Windows, also initializes and tears down the users/groups background services using `initUsersAndGroupsServices` and `deinitUsersAndGroupsServices`. +- **`TEST_F(UserGroups, test_sanity)`** — Executes `SELECT * FROM user_groups`, asserts at least one row is returned, and validates that `uid` and `gid` columns pass `verifyUidGid` checks. + +## Usage Example + +```cpp +// Run the integration test via osquery's test runner: +// The test executes the following query internally: +QueryData data = execute_query("select * from user_groups"); + +// Validation schema applied to each row: +ValidationMap row_map = { + {"uid", verifyUidGid}, + {"gid", verifyUidGid} +}; +validate_rows(data, row_map); +``` + +## Platform Notes + +- **Windows only**: `SetUpTestSuite` / `TearDownTestSuite` manage the lifecycle of users and groups background dispatcher services, which are required for the table to function on Windows. +- **All platforms**: `SetUp` calls `setUpEnvironment()` before each test to ensure a clean test context. \ No newline at end of file diff --git a/tests/integration/tables/.user_interaction_events.md b/tests/integration/tables/.user_interaction_events.md new file mode 100644 index 00000000000..4992530576b --- /dev/null +++ b/tests/integration/tables/.user_interaction_events.md @@ -0,0 +1,25 @@ + +Integration test stub for the `user_interaction_events` osquery table on Darwin (macOS), verifying basic query execution without row-level validation. + +## Key Components + +- **`userInteractionEvents`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — A sanity-check test case that executes `SELECT * FROM user_interaction_events` and provides scaffolding for optional size assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test (via osquery integration test runner) +// The test executes the query and can be extended with validation: + +ValidationMap row_map = { + {"time", IntType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Darwin-specific table defined in `specs/darwin/user_interaction_events.table`. +- Size assertions (`ASSERT_GE`, `ASSERT_EQ`) and `ValidationMap` checks are intentionally commented out — this table may return zero rows in CI/test environments where no user interaction events are present. +- To extend validation, uncomment and populate the `ValidationMap` with expected column types from `helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.user_ssh_keys.md b/tests/integration/tables/.user_ssh_keys.md new file mode 100644 index 00000000000..1124f5f1799 --- /dev/null +++ b/tests/integration/tables/.user_ssh_keys.md @@ -0,0 +1,40 @@ + +Sanity check integration test for the `user_ssh_keys` osquery table, validating that query results return correctly typed fields for user SSH key data on POSIX systems. + +## Key Components + +- **`userSshKeys`** — Test fixture class inheriting from `testing::Test`, with Windows-specific setup/teardown that initializes the users and groups background services via the osquery `Dispatcher`. +- **`TEST_F(userSshKeys, test_sanity)`** — Core test case that: + 1. Executes `SELECT * FROM user_ssh_keys` + 2. Asserts the result set is non-negative in size + 3. Validates each row against a typed schema map + +## Validation Schema + +| Column | Expected Type | +|---|---| +| `uid` | `IntType` | +| `path` | `NormalType` | +| `encrypted` | `IntType` | +| `key_type` | `NormalType` | +| `key_group_name` | `NormalType` | +| `key_length` | `IntType` | +| `key_security_bits` | `IntType` | + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// Corresponds to the table spec at: specs/posix/user_ssh_keys.table + +// Manually invoke the query in an osquery shell: +// SELECT uid, path, encrypted, key_type, key_length, key_security_bits +// FROM user_ssh_keys; + +// Windows builds require dispatcher lifecycle management: +SetUpTestSuite(); // initUsersAndGroupsServices(true, false) +// ... run tests ... +TearDownTestSuite(); // Dispatcher::stopServices() + deinitUsersAndGroupsServices +``` + +> **Note:** The test only asserts `data.size() >= 0`, meaning an empty result (no SSH keys present on the host) is a valid outcome and will not fail the test. \ No newline at end of file diff --git a/tests/integration/tables/.userassist.md b/tests/integration/tables/.userassist.md new file mode 100644 index 00000000000..cfd429c6bd1 --- /dev/null +++ b/tests/integration/tables/.userassist.md @@ -0,0 +1,46 @@ + +Integration test for the `userassist` osquery table, validating that the table returns expected data with correct column types on Windows systems. + +## Key Components + +- **`UserassistTest`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(UserassistTest, test_sanity)`** — Primary sanity test that executes two queries against the `userassist` table and validates results. + +## Validation Schema + +| Column | Validation Rule | +|---|---| +| `path` | `NonEmptyString` | +| `last_execution_time` | `NormalType` | +| `count` | `NormalType` | +| `sid` | `NonEmptyString` | + +## Usage Example + +```cpp +// Runs automatically via the osquery integration test suite. +// To execute manually, build and run the integration tests targeting userassist: + +// Query all userassist entries +QueryData rows = execute_query("select * from userassist"); + +// Query a specific path entry +QueryData specific = execute_query( + "select * from userassist where path is 'UEME_CTLSESSION'" +); + +// Validate results against expected schema +ValidationMap row_map = { + {"path", NonEmptyString}, + {"last_execution_time", NormalType}, + {"count", NormalType}, + {"sid", NonEmptyString}, +}; +validate_rows(rows, row_map); +``` + +## Notes + +- The test asserts both the full table query and the filtered query (`UEME_CTLSESSION` path) return at least one row. +- `UEME_CTLSESSION` is a well-known UserAssist registry key entry tracking Windows session activity, making it a reliable anchor for sanity checks. +- Relies on the shared integration test helper at `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.users.md b/tests/integration/tables/.users.md new file mode 100644 index 00000000000..1b7e775c214 --- /dev/null +++ b/tests/integration/tables/.users.md @@ -0,0 +1,60 @@ + +Integration test for the osquery `users` table that validates query results and column types across Windows, macOS, and Linux platforms. + +## Key Components + +### `UsersTest` (Test Fixture) +A Google Test fixture class that handles environment setup and teardown, including Windows-specific user/group service initialization via `initUsersAndGroupsServices` and `Dispatcher` lifecycle management. + +### `TEST_F(UsersTest, test_sanity)` +The primary test case validating the `users` table across three query scenarios: + +| Scenario | Query | +|---|---| +| Full select | `select * from users` | +| Filter by UID | `select * from users where uid=` | +| Filter by username | `select * from users where username=''` | +| Remote users (Linux only) | `select * from users where include_remote=1` | + +## Platform-Specific Validation + +```cpp +// Column type rules vary by platform +if (isPlatform(PlatformType::TYPE_WINDOWS)) { + row_map.emplace("gid", IntType); // may be signed + row_map.emplace("username", NormalType); // may be empty +} else { + row_map.emplace("gid", NonNegativeInt); + row_map.emplace("username", NonEmptyString); +} + +// macOS-specific columns +if (isPlatform(PlatformType::TYPE_OSX)) { + row_map.emplace("uuid", ValidUUID); + row_map.emplace("is_hidden", IntType); +} +``` + +## Usage Example + +```cpp +// Run all users table integration tests +// From osquery build directory: +// ./osquery_tests --gtest_filter="UsersTest.*" + +// Core validation map used in test +auto row_map = ValidationMap{ + {"uid", NonNegativeInt}, + {"uid_signed", IntType}, + {"gid_signed", IntType}, + {"description", NormalType}, + {"shell", NonEmptyString}, +}; +validate_rows(execute_query("select * from users"), row_map); +``` + +## Notes + +- Expects at least one row returned (`ASSERT_GE(rows.size(), 1ul)`) +- `include_remote=1` filtering is **Linux-only** (`#ifdef OSQUERY_LINUX`) +- Windows requires explicit service init/teardown via `SetUpTestSuite`/`TearDownTestSuite` \ No newline at end of file diff --git a/tests/integration/tables/.video_info.md b/tests/integration/tables/.video_info.md new file mode 100644 index 00000000000..b5e1028ba2d --- /dev/null +++ b/tests/integration/tables/.video_info.md @@ -0,0 +1,35 @@ + +Integration test stub for the `video_info` osquery table on Windows, verifying that the table can be queried without errors. + +## Key Components + +- **`videoInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic sanity test that executes `SELECT * FROM video_info` and provides a scaffolded structure for size assertions and row validation. + +## Usage Example + +```cpp +// Run the sanity test against the video_info table +TEST_F(videoInfo, test_sanity) { + auto const data = execute_query("select * from video_info"); + + // Uncomment and configure validation as needed: + // ValidationMap row_map = { + // {"color_depth", IntType}, + // {"driver", NormalType}, + // {"driver_date", NormalType}, + // {"driver_version", NormalType}, + // {"manufacturer", NormalType}, + // {"model", NormalType}, + // {"series", NormalType}, + // {"video_mode", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Targets the Windows-specific table defined in `specs/windows/video_info.table`. +- Validation logic (row count assertions and `ValidationMap`) is intentionally commented out, serving as a template for future test completion. +- Expected columns include `color_depth` (integer), and `driver`, `driver_date`, `driver_version`, `manufacturer`, `model`, `series`, `video_mode` (normal string types). \ No newline at end of file diff --git a/tests/integration/tables/.virtual_memory_info.md b/tests/integration/tables/.virtual_memory_info.md new file mode 100644 index 00000000000..57898d27f5d --- /dev/null +++ b/tests/integration/tables/.virtual_memory_info.md @@ -0,0 +1,42 @@ + +Integration test file for the `virtual_memory_info` osquery table on Darwin (macOS), verifying the table can be queried without errors. + +## Key Components + +- **`virtualMemoryInfo`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()` +- **`test_sanity`** — Basic smoke test that executes `SELECT * FROM virtual_memory_info` to confirm the table is queryable + +## Tested Table Columns + +The commented-out `ValidationMap` documents the expected schema of the `virtual_memory_info` table (all `IntType`): + +| Column | Description | +|---|---| +| `free` / `active` / `inactive` | Page states | +| `speculative` / `throttled` / `wired` | Memory classifications | +| `faults` / `copy` / `zero_fill` | Memory events | +| `compressor` / `compressed` / `decompressed` | Compression stats | +| `page_ins` / `page_outs` / `swap_ins` / `swap_outs` | I/O counters | + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner: +// The test queries the table and confirms execution succeeds. + +TEST_F(virtualMemoryInfo, test_sanity) { + auto const data = execute_query("select * from virtual_memory_info"); + + // Enable full validation by uncommenting: + ValidationMap row_map = { + {"free", IntType}, + {"active", IntType}, + {"swap_ins", IntType}, + {"swap_outs", IntType}, + // ... remaining columns + }; + validate_rows(data, row_map); +} +``` + +> **Note:** Full row count assertions and schema validation are scaffolded but commented out, making this a lightweight connectivity check. Enable the `ValidationMap` block to enforce column type correctness in CI. \ No newline at end of file diff --git a/tests/integration/tables/.vscode_extensions.md b/tests/integration/tables/.vscode_extensions.md new file mode 100644 index 00000000000..b7eb5546edb --- /dev/null +++ b/tests/integration/tables/.vscode_extensions.md @@ -0,0 +1,44 @@ + +Sanity check integration test for the `vscode_extensions` osquery table, validating that query results conform to expected column types and formats. + +## Key Components + +### `vscodeExtensions` (Test Fixture) +- Inherits from `testing::Test` +- `SetUp()` — initializes the test environment via `setUpEnvironment()` +- **Windows-only** (`OSQUERY_WINDOWS`): + - `SetUpTestSuite()` — initializes user/group services before the test suite runs + - `TearDownTestSuite()` — stops/joins dispatcher services and tears down user/group services after the suite completes + +### `TEST_F(vscodeExtensions, test_sanity)` +Executes `SELECT * FROM vscode_extensions` and validates each returned row against a `ValidationMap` with the following column expectations: + +| Column | Expected Type | +|---|---| +| `name` | `NormalType` | +| `uuid` | `NormalType` | +| `version` | `NormalType` | +| `path` | `NormalType` | +| `publisher` | `NormalType` | +| `publisher_id` | `NormalType` | +| `installed_at` | `NonNegativeInt` | +| `prerelease` | `Bool \| EmptyOk` | +| `uid` | `NonNegativeInt` | +| `vscode_edition` | `NormalType` | + +If the query returns no rows, the test logs a warning (assuming VS Code is not installed) and exits gracefully without failing. + +## Usage Example + +```cpp +// Run this specific integration test via osquery's test runner: +// (from the osquery build directory) + +// Linux/macOS +./osquery_tests --gtest_filter="vscodeExtensions.test_sanity" + +// Windows +osquery_tests.exe --gtest_filter="vscodeExtensions.test_sanity" +``` + +> **Note:** This test requires VS Code to be installed on the host system to produce non-empty results. On systems without VS Code, the test passes with a warning rather than failing. \ No newline at end of file diff --git a/tests/integration/tables/.wifi_networks.md b/tests/integration/tables/.wifi_networks.md new file mode 100644 index 00000000000..94dd351873b --- /dev/null +++ b/tests/integration/tables/.wifi_networks.md @@ -0,0 +1,40 @@ + +Integration test for the `wifi_networks` osquery table, validating that the table can be queried on Darwin (macOS) systems without errors. + +## Key Components + +- **`wifiNetworks`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic sanity test that executes a `SELECT *` query against the `wifi_networks` table to verify the table is queryable. + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the wifi_networks table and validates results + +TEST_F(wifiNetworks, test_sanity) { + auto const data = execute_query("select * from wifi_networks"); + + // Optional: enable row count assertions + // ASSERT_GE(data.size(), 0ul); + + // Optional: enable schema validation against expected columns: + // ValidationMap row_map = { + // {"ssid", NormalType}, + // {"network_name", NormalType}, + // {"security_type", NormalType}, + // {"last_connected", IntType}, + // {"passpoint", IntType}, + // {"possibly_hidden", IntType}, + // {"roaming", IntType}, + // {"roaming_profile", NormalType}, + // {"captive_portal", IntType}, + // {"auto_login", IntType}, + // {"temporarily_disabled",IntType}, + // {"disabled", IntType}, + // }; + // validate_rows(data, row_map); +} +``` + +> **Note:** Row count assertions and schema validation via `ValidationMap` are currently commented out. To enable full validation, uncomment the `row_map` definition and call `validate_rows(data, row_map)`. Spec file reference: `specs/darwin/wifi_networks.table`. \ No newline at end of file diff --git a/tests/integration/tables/.wifi_status.md b/tests/integration/tables/.wifi_status.md new file mode 100644 index 00000000000..f74f71f05de --- /dev/null +++ b/tests/integration/tables/.wifi_status.md @@ -0,0 +1,39 @@ + +Integration sanity test for the `wifi_status` osquery table on Darwin (macOS), verifying that the table can be queried without errors. + +## Key Components + +- **`wifiStatus`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(wifiStatus, test_sanity)`** — Executes a `SELECT *` query against the `wifi_status` table and provides a scaffolded (commented-out) validation structure for future expansion. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test executes the following query internally: +auto const data = execute_query("select * from wifi_status"); + +// Uncomment and extend the validation map to enforce column types: +ValidationMap row_map = { + {"interface", NormalType}, + {"ssid", NormalType}, + {"bssid", NormalType}, + {"network_name", NormalType}, + {"country_code", NormalType}, + {"security_type", NormalType}, + {"rssi", IntType}, + {"noise", IntType}, + {"channel", IntType}, + {"channel_width", IntType}, + {"channel_band", IntType}, + {"transmit_rate", NormalType}, + {"mode", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Darwin platform only (spec: `specs/darwin/wifi_status.table`). +- Row count assertions and `validate_rows()` are intentionally commented out — activate them to enforce expected row counts and column type constraints as the test matures. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.wifi_survey.md b/tests/integration/tables/.wifi_survey.md new file mode 100644 index 00000000000..ed3640ecd80 --- /dev/null +++ b/tests/integration/tables/.wifi_survey.md @@ -0,0 +1,40 @@ + +Integration test file for the `wifi_survey` osquery table, providing a sanity check that the table can be queried without errors on Darwin (macOS) platforms. + +## Key Components + +- **`wifiSurvey`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(wifiSurvey, test_sanity)`** — Executes a `SELECT * FROM wifi_survey` query and provides a scaffold for row count assertions and schema validation (currently commented out). + +## Expected Schema + +The commented validation map documents the expected columns: + +| Column | Type | +|---|---| +| `interface` | NormalType | +| `ssid` | NormalType | +| `bssid` | NormalType | +| `network_name` | NormalType | +| `country_code` | NormalType | +| `rssi` | IntType | +| `noise` | IntType | +| `channel` | IntType | +| `channel_width` | IntType | +| `channel_band` | IntType | + +## Usage Example + +```cpp +// To enable full validation, uncomment and configure: +ValidationMap row_map = { + {"interface", NormalType}, + {"ssid", NormalType}, + {"rssi", IntType}, + {"channel", IntType}, + {"channel_width", IntType}, +}; +validate_rows(data, row_map); +``` + +> **Note:** Row count assertions and schema validation are scaffolded but commented out, making this a minimal smoke test. Enable `validate_rows` and size assertions when stricter coverage is required. Spec source: `specs/darwin/wifi_scan.table`. \ No newline at end of file diff --git a/tests/integration/tables/.winbaseobj.md b/tests/integration/tables/.winbaseobj.md new file mode 100644 index 00000000000..8ed27b989d3 --- /dev/null +++ b/tests/integration/tables/.winbaseobj.md @@ -0,0 +1,35 @@ + +Integration test stub for the `winbaseobj` osquery table, providing a sanity check framework to validate Windows base object data retrieval. + +## Key Components + +- **`winbaseobj` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(winbaseobj, test_sanity)`** — Executes a `SELECT * FROM winbaseobj` query and provides scaffolding for row count assertions and column type validation (currently commented out). + +## Usage Example + +```cpp +// Run the integration test (typical osquery test invocation) +// The test queries the winbaseobj table and can validate columns such as: + +ValidationMap row_map = { + {"session_id", IntType}, + {"object_name", NormalType}, + {"object_type", NormalType}, +}; +validate_rows(data, row_map); +``` + +To activate validation, uncomment the `ValidationMap` block and the `validate_rows()` call, then optionally assert expected row counts: + +```cpp +ASSERT_GE(data.size(), 0ul); // At least 0 rows +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the table spec at `specs/windows/winbaseobj.table`. +- All assertion and validation logic is **commented out by default** — this is intentional for a baseline sanity stub. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query()`, `validate_rows()`, and type flag constants (`IntType`, `NormalType`, etc.). +- Windows-only table; tests should only be run in a Windows environment. \ No newline at end of file diff --git a/tests/integration/tables/.windows_crashes.md b/tests/integration/tables/.windows_crashes.md new file mode 100644 index 00000000000..06bfb265cca --- /dev/null +++ b/tests/integration/tables/.windows_crashes.md @@ -0,0 +1,34 @@ + +Integration test file for the `windows_crashes` osquery table, providing a sanity check that verifies the table can be queried without errors on Windows systems. + +## Key Components + +- **`windowsCrashes`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Core test case that executes `SELECT * FROM windows_crashes` and provides a scaffolded (currently commented-out) validation structure. + +## Usage Example + +```cpp +// Run the integration test via osquery's test harness +// The test executes the following query internally: +auto const data = execute_query("select * from windows_crashes"); + +// Uncomment and configure to enable row validation: +ValidationMap row_map = { + {"datetime", NormalType}, + {"pid", IntType}, + {"tid", IntType}, + {"exception_code", NormalType}, + {"exception_message", NormalType}, + {"stack_trace", NormalType}, + {"crash_path", NormalType}, + // ... additional fields +}; +validate_rows(data, row_map); +``` + +## Notes + +- The validation map and row count assertions are **commented out**, meaning the test currently only confirms the query executes without crashing — it does not validate schema or row contents. +- Covers fields including: `datetime`, `module`, `path`, `pid`, `tid`, `version`, `process_uptime`, `stack_trace`, `exception_code`, `exception_message`, `exception_address`, `registers`, `command_line`, `username`, `machine_name`, OS version fields (`major_version`, `minor_version`, `build_number`), `type`, and `crash_path`. +- Spec source: `specs/windows/windows_crashes.table`. \ No newline at end of file diff --git a/tests/integration/tables/.windows_eventlog.md b/tests/integration/tables/.windows_eventlog.md new file mode 100644 index 00000000000..03025618e54 --- /dev/null +++ b/tests/integration/tables/.windows_eventlog.md @@ -0,0 +1,38 @@ + +Integration test for the `windows_eventlog` osquery table, validating that querying Windows Event Log entries from the `Application` channel returns correctly typed and structured data. + +## Key Components + +- **`windowsEventLog`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(windowsEventLog, test_sanity)`** — Sanity test that executes a SQL query against the `windows_eventlog` virtual table filtered by the `Application` channel and validates the result schema. + +## Validated Schema + +| Column | Expected Type | +|---|---| +| `channel` | `NonEmptyString` | +| `datetime` | `NonEmptyString` | +| `eventid` | `IntType` | +| `pid` | `IntType` | +| `tid` | `IntType` | +| `provider_name` | `NormalType` | +| `provider_guid` | `NormalType` | +| `computer_name` | `NormalType` | +| `task` | `IntType` | +| `level` | `IntType` | +| `keywords` | `NormalType` | +| `data` | `NormalType` | + +## Usage Example + +```cpp +// The test executes the following osquery SQL query internally: +"select * from windows_eventlog where channel = 'Application'" + +// Validation asserts: +// 1. Result set size >= 0 (table is queryable) +// 2. Each row conforms to the ValidationMap schema +validate_rows(data, row_map); +``` + +> **Note:** This test targets Windows only. The corresponding table spec is located at `specs/windows/windows_eventlog.table`. The `ASSERT_GE(data.size(), 0ul)` check is intentionally permissive — it confirms the query runs without error even if no events exist in the `Application` channel. \ No newline at end of file diff --git a/tests/integration/tables/.windows_events.md b/tests/integration/tables/.windows_events.md new file mode 100644 index 00000000000..110dad66d6c --- /dev/null +++ b/tests/integration/tables/.windows_events.md @@ -0,0 +1,38 @@ + +Integration sanity test for the `windows_events` osquery table, verifying the table can be queried without errors on Windows systems. + +## Key Components + +- **`windowsEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM windows_events` and provides a scaffolded structure for row count assertions and column type validation. + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test executes the following query internally: +auto const data = execute_query("select * from windows_events"); + +// To enable full validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"time", IntType}, + {"datetime", NormalType}, + {"source", NormalType}, + {"provider_name", NormalType}, + {"provider_guid", NormalType}, + {"computer_name", NormalType}, + {"eventid", IntType}, + {"task", IntType}, + {"level", IntType}, + {"keywords", IntType}, + {"data", NormalType}, + {"eid", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Validation logic (row count assertions and `validate_rows`) is intentionally commented out, making this a **non-asserting smoke test** — it confirms the query executes without crashing. +- Corresponds to the table spec at `specs/windows/windows_events.table`. +- Relies on `osquery/tests/integration/tables/helper.h` for shared test utilities (`execute_query`, `ValidationMap`, type constants). \ No newline at end of file diff --git a/tests/integration/tables/.windows_firewall_rules.md b/tests/integration/tables/.windows_firewall_rules.md new file mode 100644 index 00000000000..4b527f975ad --- /dev/null +++ b/tests/integration/tables/.windows_firewall_rules.md @@ -0,0 +1,40 @@ + +Integration test that validates the `windows_firewall_rules` osquery table returns correctly typed data for Windows Firewall rule entries. + +## Key Components + +- **`windows_firewall_rules` test fixture** — inherits from `testing::Test`, initializes the osquery environment via `setUpEnvironment()` +- **`TEST_F(windows_firewall_rules, test_sanity)`** — sanity check that queries one row from the table and validates all column types + +## Validated Columns + +| Column | Type | +|---|---| +| `name`, `app_name`, `action` | `NormalType` | +| `enabled` | `IntType` | +| `grouping`, `direction`, `protocol` | `NormalType` | +| `local_addresses`, `remote_addresses` | `NormalType` | +| `local_ports`, `remote_ports` | `NormalType` | +| `icmp_types_codes` | `NormalType` | +| `profile_domain`, `profile_private`, `profile_public` | `IntType` | +| `service_name` | `NormalType` | + +## Usage Example + +```cpp +// Run the integration test directly via osquery test runner +// Spec file: specs/windows/windows_firewall_rules.table + +TEST_F(windows_firewall_rules, test_sanity) { + // Executes: SELECT * FROM windows_firewall_rules LIMIT 1 + auto const data = execute_query("select * from windows_firewall_rules LIMIT 1"); + + // Asserts exactly one row is returned + ASSERT_EQ(data.size(), 1ul); + + // Validates each column matches expected type + validate_rows(data, row_map); +} +``` + +> **Note:** This test requires a Windows environment with the Windows Firewall service active and at least one firewall rule configured to return a valid row. \ No newline at end of file diff --git a/tests/integration/tables/.windows_search.md b/tests/integration/tables/.windows_search.md new file mode 100644 index 00000000000..a87f69df516 --- /dev/null +++ b/tests/integration/tables/.windows_search.md @@ -0,0 +1,44 @@ + +Integration sanity test for the `windows_search` osquery table, verifying that the table can execute a Windows Search query and return results with correctly typed columns. + +## Key Components + +- **`windows_search` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(windows_search, test_sanity)`** — Executes a sample Windows Search query with filtering parameters and validates the schema of returned rows. + +## Usage Example + +```cpp +// The test issues this SQL query internally: +auto const data = execute_query( + "select * from windows_search " + "where query = '\"some text in file\" folder:documents' " + "and additional_properties = 'system.mimetype,system.itemurl' " + "and max_results = 10 " + "and sort = 'system.size desc'" +); + +// Validated column schema: +ValidationMap row_map = { + {"name", NormalType}, + {"path", NormalType}, + {"size", IntType}, + {"date_created", IntType}, + {"date_modified", IntType}, + {"owner", NormalType}, + {"type", NormalType}, + {"properties", NormalType}, + {"query", NormalType}, + {"sort", NormalType}, + {"max_results", IntType}, + {"additional_properties", NormalType}, +}; + +validate_rows(data, row_map); +``` + +## Notes + +- Corresponds to the spec file `specs/windows/windows_search.table`. +- Required query parameters: `query`, `additional_properties`, `max_results`, and `sort` — these are mandatory constraints for the virtual table. +- `IntType` columns (`size`, `date_created`, `date_modified`, `max_results`) are validated as integers; all others as `NormalType` strings. \ No newline at end of file diff --git a/tests/integration/tables/.windows_security_products.md b/tests/integration/tables/.windows_security_products.md new file mode 100644 index 00000000000..54d0f1a82b9 --- /dev/null +++ b/tests/integration/tables/.windows_security_products.md @@ -0,0 +1,42 @@ + +Integration test for the `windows_security_products` osquery table, validating that query results conform to expected schema and value constraints. + +## Key Components + +### `WindowsSecurityProductsTest` +A `testing::Test` subclass that initializes the osquery test environment via `setUpEnvironment()` in `SetUp()`. + +### `test_sanity` +Executes `SELECT * FROM windows_security_products` and validates each returned row against a `ValidationMap` with the following field constraints: + +| Column | Validation | +|---|---| +| `type` | One of: `Firewall`, `Antivirus`, `Antispyware` | +| `name` | Non-empty string | +| `state` | One of: `On`, `Off`, `Snoozed`, `Expired` | +| `state_timestamp` | Normal type (any valid value) | +| `remediation_path` | Normal type (any valid value) | +| `signatures_up_to_date` | Boolean | + +## Usage Example + +```cpp +// Run the integration test via osquery test runner +// The test queries the live Windows Security Center WMI provider + +TEST_F(WindowsSecurityProductsTest, test_sanity) { + auto const data = execute_query( + "select * from windows_security_products" + ); + + ValidationMap row_map = { + {"type", SpecificValuesCheck{"Firewall", "Antivirus", "Antispyware"}}, + {"name", NonEmptyString}, + {"state", SpecificValuesCheck{"On", "Off", "Snoozed", "Expired"}}, + {"signatures_up_to_date", Bool}, + }; + validate_rows(data, row_map); +} +``` + +> **Note:** This test is Windows-only and requires the Windows Security Center service to be running. It corresponds to the table spec at `specs/windows/windows_security_products.table`. \ No newline at end of file diff --git a/tests/integration/tables/.windows_update_history.md b/tests/integration/tables/.windows_update_history.md new file mode 100644 index 00000000000..1e833cc7d5f --- /dev/null +++ b/tests/integration/tables/.windows_update_history.md @@ -0,0 +1,42 @@ + +Integration test for the `windows_update_history` osquery table, validating query execution and column schema correctness on Windows systems. + +## Key Components + +- **`windows_update_history` (test class)** — Google Test fixture that initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity` (TEST_F)** — Executes a `LIMIT 1` query against `windows_update_history` and validates the returned row schema if update history exists. +- **`ValidationMap`** — Defines expected column types for schema validation across 12 fields. + +### Validated Columns + +| Column | Type | +|---|---| +| `client_app_id` | `NormalType` | +| `date` | `IntType` | +| `description` | `NormalType` | +| `hresult` | `IntType` | +| `operation` | `NormalType` | +| `result_code` | `NormalType` | +| `server_selection` | `NormalType` | +| `service_id` | `NormalType` | +| `support_url` | `NormalType` | +| `title` | `NormalType` | +| `update_id` | `NormalType` | +| `update_revision` | `IntType` | + +## Usage Example + +```cpp +// Run via osquery integration test suite on a Windows host +TEST_F(windows_update_history, test_sanity) { + auto const data = + execute_query("select * from windows_update_history LIMIT 1"); + + // Gracefully skips validation if no update history is present + if (data.size() > 0) { + validate_rows(data, row_map); + } +} +``` + +> **Note:** The test is non-fatal when no update history exists on the host (e.g., clean CI build agents), making it safe for environments where Windows Update has never been run. \ No newline at end of file diff --git a/tests/integration/tables/.wmi_bios_info.md b/tests/integration/tables/.wmi_bios_info.md new file mode 100644 index 00000000000..7c881e9f18d --- /dev/null +++ b/tests/integration/tables/.wmi_bios_info.md @@ -0,0 +1,30 @@ + +Integration sanity test for the `wmi_bios_info` osquery table on Windows, verifying the table can be queried without errors. + +## Key Components + +- **`wmiBiosInfo`** — Test fixture class inheriting from `testing::Test`, initializes the osquery environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(wmiBiosInfo, test_sanity)`** — Executes a `SELECT * FROM wmi_bios_info` query and provides scaffolding for row count assertions and schema validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test (executed via the osquery integration test harness) +// To enable full validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"name", NormalType}, + {"value", NormalType} +}; +validate_rows(data, row_map); + +// To assert expected row counts, uncomment one of: +// ASSERT_GE(data.size(), 0ul); // at least 0 rows +// ASSERT_EQ(data.size(), 1ul); // exactly 1 row +``` + +## Notes + +- Corresponds to the table spec at `specs/windows/wmi_bios_info.table`. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and validation flag types (`NormalType`, etc.). +- Validation logic is stubbed out — extend `row_map` with the expected column names and type flags to enforce schema correctness in CI. \ No newline at end of file diff --git a/tests/integration/tables/.wmi_cli_event_consumers.md b/tests/integration/tables/.wmi_cli_event_consumers.md new file mode 100644 index 00000000000..2dc044ac5b1 --- /dev/null +++ b/tests/integration/tables/.wmi_cli_event_consumers.md @@ -0,0 +1,35 @@ + +Integration test file for the `wmi_cli_event_consumers` osquery table on Windows, providing a sanity check to verify the table can be queried without errors. + +## Key Components + +- **`wmiCliEventConsumers`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes a `SELECT *` query against the `wmi_cli_event_consumers` table to confirm it runs successfully. + +## Usage Example + +```cpp +// Run the sanity test against the wmi_cli_event_consumers table +TEST_F(wmiCliEventConsumers, test_sanity) { + auto const data = execute_query("select * from wmi_cli_event_consumers"); + + // Optionally enable row count assertions: + // ASSERT_GE(data.size(), 0ul); + + // Optionally enable column validation: + // ValidationMap row_map = { + // {"name", NormalType}, + // {"command_line_template", NormalType}, + // {"executable_path", NormalType}, + // {"class", NormalType}, + // {"relative_path", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/windows/wmi_cli_event_consumers.table`. +- Row count assertions and column-level `ValidationMap` checks are scaffolded but commented out, as WMI CLI event consumers may not be present on all test systems. +- Extend validation by uncommenting the `ValidationMap` block and calling `validate_rows()` when consistent test data is guaranteed. \ No newline at end of file diff --git a/tests/integration/tables/.wmi_event_filters.md b/tests/integration/tables/.wmi_event_filters.md new file mode 100644 index 00000000000..9bbfd17bea3 --- /dev/null +++ b/tests/integration/tables/.wmi_event_filters.md @@ -0,0 +1,27 @@ + +Integration sanity test for the `wmi_event_filters` osquery table on Windows, verifying the table can be queried without errors. + +## Key Components + +- **`wmiEventFilters`** — Test fixture class inheriting from `testing::Test`, initializes the osquery environment via `setUpEnvironment()` in `SetUp()`. +- **`test_sanity`** — Core test case that executes `SELECT * FROM wmi_event_filters` and provides scaffolding for row validation (currently commented out). + +## Usage Example + +```cpp +// Run the sanity test via osquery's integration test runner +// The test executes the query and can be extended with row validation: + +ValidationMap row_map = { + {"name", NormalType}, + {"query", NormalType}, + {"query_language", NormalType}, + {"class", NormalType}, + {"relative_path", NormalType}, +}; +validate_rows(data, row_map); +``` + +To enable full validation, uncomment the `ValidationMap` block and the `validate_rows` call, then optionally assert on `data.size()` to enforce expected row counts. + +> **Note:** This test targets Windows only. The spec is defined in `specs/windows/wmi_event_filters.table`. See `osquery/tests/integration/tables/helper.h` for available validation flags and `DataCheck` utilities. \ No newline at end of file diff --git a/tests/integration/tables/.wmi_filter_consumer_binding.md b/tests/integration/tables/.wmi_filter_consumer_binding.md new file mode 100644 index 00000000000..0e38c2a75d3 --- /dev/null +++ b/tests/integration/tables/.wmi_filter_consumer_binding.md @@ -0,0 +1,28 @@ + +Integration test file for the `wmi_filter_consumer_binding` osquery table on Windows, providing a sanity check to verify the table can be queried without errors. + +## Key Components + +- **`wmiFilterConsumerBinding`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM wmi_filter_consumer_binding` to validate the table is queryable. + +## Usage Example + +```cpp +// Run the sanity test (executed by the osquery integration test runner) +// To enable full row validation, uncomment and configure the ValidationMap: + +ValidationMap row_map = { + {"consumer", NormalType}, + {"filter", NormalType}, + {"class", NormalType}, + {"relative_path", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- The table maps WMI filter-to-consumer bindings, which are commonly abused for persistence by malware (WMI subscriptions). +- Row count assertions and `validate_rows()` calls are scaffolded but commented out — enable them once expected table behavior is confirmed in CI. +- Spec source: `specs/windows/wmi_filter_consumer_binding.table` \ No newline at end of file diff --git a/tests/integration/tables/.wmi_script_event_consumers.md b/tests/integration/tables/.wmi_script_event_consumers.md new file mode 100644 index 00000000000..f58d3991315 --- /dev/null +++ b/tests/integration/tables/.wmi_script_event_consumers.md @@ -0,0 +1,36 @@ + +Integration test file for the `wmi_script_event_consumers` osquery table, providing a sanity check to verify the table can be queried on Windows systems. + +## Key Components + +- **`wmiScriptEventConsumers`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a `SELECT *` query against the `wmi_script_event_consumers` table to validate basic query functionality. + +## Usage Example + +```cpp +// Run the sanity test (validates table is queryable) +TEST_F(wmiScriptEventConsumers, test_sanity) { + auto const data = execute_query("select * from wmi_script_event_consumers"); + + // Optionally enable row count assertions: + // ASSERT_GE(data.size(), 0ul); + + // Optionally enable column validation: + // ValidationMap row_map = { + // {"name", NormalType}, + // {"scripting_engine", NormalType}, + // {"script_file_name", NormalType}, + // {"script_text", NormalType}, + // {"class", NormalType}, + // {"relative_path", NormalType}, + // }; + // validate_rows(data, row_map); +} +``` + +## Notes + +- Corresponds to the table spec at `specs/windows/wmi_script_event_consumers.table`. +- Row count assertions and column-level `ValidationMap` checks are stubbed out and can be enabled as the table matures. +- Expected columns when validation is enabled: `name`, `scripting_engine`, `script_file_name`, `script_text`, `class`, `relative_path`. \ No newline at end of file diff --git a/tests/integration/tables/.xprotect_entries.md b/tests/integration/tables/.xprotect_entries.md new file mode 100644 index 00000000000..0f6bb989011 --- /dev/null +++ b/tests/integration/tables/.xprotect_entries.md @@ -0,0 +1,33 @@ + +Integration test stub for the `xprotect_entries` osquery table on macOS Darwin, verifying basic query execution against XProtect malware definition entries. + +## Key Components + +- **`xprotectEntries`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`TEST_F(xprotectEntries, test_sanity)`** — Sanity test that executes `SELECT * FROM xprotect_entries` and provides commented scaffolding for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the integration test (from osquery build directory) +// The test queries the xprotect_entries virtual table and +// validates returned rows against an expected schema. + +// Uncomment and configure validation to enforce schema checks: +ValidationMap row_map = { + {"name", NormalType}, + {"launch_type", NormalType}, + {"identity", NormalType}, + {"filename", NormalType}, + {"filetype", NormalType}, + {"optional", IntType}, + {"uses_pattern", IntType} +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the Darwin (macOS) platform only; spec defined in `specs/darwin/xprotect_entries.table`. +- Size assertions and `validate_rows()` calls are intentionally commented out, making this a non-enforcing smoke test — activate them to add stricter coverage. +- Follows the standard osquery integration test pattern from `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.xprotect_meta.md b/tests/integration/tables/.xprotect_meta.md new file mode 100644 index 00000000000..463a84e82d1 --- /dev/null +++ b/tests/integration/tables/.xprotect_meta.md @@ -0,0 +1,29 @@ + +Integration test file for the `xprotect_meta` osquery table on macOS (Darwin), providing a sanity check that the table can be queried without errors. + +## Key Components + +- **`xprotectMeta`** — Test fixture class inheriting from `testing::Test`, initializes the osquery test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM xprotect_meta` to verify the table is queryable. + +## Usage Example + +```cpp +// Run the sanity test (via osquery test runner) +// The test queries the xprotect_meta table and optionally validates rows: + +ValidationMap row_map = { + {"identifier", NormalType}, + {"type", NormalType}, + {"developer_id", NormalType}, + {"min_version", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Targets the `xprotect_meta` table defined in `specs/darwin/xprotect_meta.table`. +- Row count assertions and full `ValidationMap` validation are scaffolded but commented out — enable them as coverage matures. +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and flag constants. +- macOS-only; XProtect metadata reflects Apple's built-in malware detection configuration. \ No newline at end of file diff --git a/tests/integration/tables/.xprotect_reports.md b/tests/integration/tables/.xprotect_reports.md new file mode 100644 index 00000000000..75aef4da4ac --- /dev/null +++ b/tests/integration/tables/.xprotect_reports.md @@ -0,0 +1,32 @@ + +Integration sanity test for the `xprotect_reports` osquery table on macOS Darwin, verifying the table can be queried without errors. + +## Key Components + +- **`xprotectReports`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(xprotectReports, test_sanity)`** — Executes a `SELECT *` against the `xprotect_reports` table and provides scaffolding for row count assertions and column validation (currently commented out). + +## Usage Example + +```cpp +// Run the integration test (via osquery test runner) +// The test queries the xprotect_reports table and validates results + +// To enable full validation, uncomment and configure the ValidationMap: +ValidationMap row_map = { + {"name", NormalType}, + {"user_action", NormalType}, + {"time", NormalType}, +}; +validate_rows(data, row_map); + +// To assert expected row counts, uncomment one of: +ASSERT_GE(data.size(), 0ul); // at least 0 rows +ASSERT_EQ(data.size(), 1ul); // exactly 1 row +``` + +## Notes + +- Targets the Darwin (`specs/darwin/xprotect_reports.table`) spec. +- Row count and schema validation are scaffolded but commented out, typical for tables where results depend on host state (XProtect activity may not exist in all CI environments). +- Relies on `osquery/tests/integration/tables/helper.h` for `execute_query`, `validate_rows`, and `ValidationMap` utilities. \ No newline at end of file diff --git a/tests/integration/tables/.yara.md b/tests/integration/tables/.yara.md new file mode 100644 index 00000000000..6a437199c80 --- /dev/null +++ b/tests/integration/tables/.yara.md @@ -0,0 +1,32 @@ + +Integration sanity test for the `yara` osquery table, verifying that querying the table with an empty path executes without error. + +## Key Components + +- **`yara` (test class)** — Inherits from `testing::Test`; initializes the test environment via `setUpEnvironment()` in `SetUp()`. +- **`TEST_F(yara, test_sanity)`** — Executes a basic query (`select * from yara where path = ''`) against the `yara` table to confirm the query runs successfully. Row count assertions and schema validation are stubbed out as comments for future use. + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test suite: +// ctest -R yara + +// The stubbed validation map (commented out) describes expected column types: +// ValidationMap row_map = { +// {"path", NormalType}, +// {"matches", NormalType}, +// {"count", IntType}, +// {"sig_group", NormalType}, +// {"sigfile", NormalType}, +// {"strings", NormalType}, +// {"tags", NormalType}, +// }; +// validate_rows(data, row_map); +``` + +## Notes + +- Validation logic (row count assertions and `validate_rows`) is intentionally commented out, indicating this test currently only checks that the query executes without crashing. +- Corresponds to the spec file `specs/yara.table`. +- Follows the standard osquery integration test pattern defined in `osquery/tests/integration/tables/helper.h`. \ No newline at end of file diff --git a/tests/integration/tables/.yara_events.md b/tests/integration/tables/.yara_events.md new file mode 100644 index 00000000000..3d99685632f --- /dev/null +++ b/tests/integration/tables/.yara_events.md @@ -0,0 +1,36 @@ + +Integration sanity test for the `yara_events` osquery table, verifying the table can be queried without errors on POSIX systems. + +## Key Components + +- **`yaraEvents`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Basic integration test that executes `SELECT * FROM yara_events` and provides a scaffolded (commented-out) validation structure for future assertion coverage. + +## Usage Example + +```cpp +// Run the sanity test via osquery integration test runner +// The test executes the following query internally: +auto const data = execute_query("select * from yara_events"); + +// Scaffolded validation map (uncomment to enable full row validation): +ValidationMap row_map = { + {"target_path", NormalType}, + {"category", NormalType}, + {"action", NormalType}, + {"transaction_id", IntType}, + {"matches", NormalType}, + {"count", IntType}, + {"strings", NormalType}, + {"tags", NormalType}, + {"time", IntType}, + {"eid", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/posix/yara_events.table` +- Size assertions and `validate_rows()` calls are intentionally commented out — this test currently only confirms the query executes without crashing. +- To extend coverage, uncomment the `ValidationMap` block and the corresponding `validate_rows()` call, selecting the appropriate size assertion (`ASSERT_GE`, `ASSERT_EQ`) based on expected event volume in the test environment. \ No newline at end of file diff --git a/tests/integration/tables/.ycloud_instance_metadata.md b/tests/integration/tables/.ycloud_instance_metadata.md new file mode 100644 index 00000000000..e1265173a85 --- /dev/null +++ b/tests/integration/tables/.ycloud_instance_metadata.md @@ -0,0 +1,36 @@ + +Integration test for the `ycloud_instance_metadata` osquery table, validating that query results conform to expected column types when metadata is available. + +## Key Components + +- **`ycloudInstanceMetadata`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`test_sanity`** — Single test case that executes a `SELECT *` query against the `ycloud_instance_metadata` table and validates row structure if data is returned. +- **`ValidationMap`** — Maps each expected column to `NormalType`, ensuring all fields return valid, non-null string values. + +### Validated Columns + +| Column | Type | +|---|---| +| `instance_id` | `NormalType` | +| `folder_id` | `NormalType` | +| `name` | `NormalType` | +| `description` | `NormalType` | +| `hostname` | `NormalType` | +| `zone` | `NormalType` | +| `ssh_public_key` | `NormalType` | +| `serial_port_enabled` | `NormalType` | + +## Usage Example + +```cpp +// Run the sanity test via the osquery integration test harness +// The test gracefully skips validation if no YCloud metadata is present +// (e.g., when not running on a Yandex Cloud instance) + +auto const data = execute_query("select * from ycloud_instance_metadata"); +if (!data.empty()) { + validate_rows(data, row_map); // validates column types for each row +} +``` + +> **Note:** The test is non-fatal when `data` is empty — validation is skipped if the host is not a Yandex Cloud instance, making it safe to run in any environment. \ No newline at end of file diff --git a/tests/integration/tables/.yum_sources.md b/tests/integration/tables/.yum_sources.md new file mode 100644 index 00000000000..75d7edd4ebb --- /dev/null +++ b/tests/integration/tables/.yum_sources.md @@ -0,0 +1,36 @@ + +Integration sanity test for the `yum_sources` osquery table, verifying basic query execution against YUM repository source data on Linux systems. + +## Key Components + +- **`yumSources`** — Test fixture class inheriting from `testing::Test`, initializes the test environment via `setUpEnvironment()`. +- **`TEST_F(yumSources, test_sanity)`** — Executes a `SELECT *` query against the `yum_sources` table and provides a scaffolded structure for row count assertions and schema validation. + +## Usage Example + +```cpp +// Run the sanity test (executed via osquery test runner) +// The test queries the yum_sources table and can be extended +// with row count and schema validation: + +auto const data = execute_query("select * from yum_sources"); + +// Uncomment to assert row counts: +// ASSERT_GE(data.size(), 0ul); + +// Uncomment and populate to validate schema: +ValidationMap row_map = { + {"name", NormalType}, + {"baseurl", NormalType}, + {"enabled", NormalType}, + {"gpgcheck", NormalType}, + {"gpgkey", NormalType}, +}; +validate_rows(data, row_map); +``` + +## Notes + +- Spec file: `specs/linux/yum_sources.table` +- Row count assertions and `validate_rows()` calls are intentionally commented out, leaving the test as a non-failing execution probe. +- To enable full validation, uncomment the `ValidationMap` block and the appropriate `ASSERT_*` macro matching the expected number of configured YUM repositories on the test host. \ No newline at end of file diff --git a/tools/analysis/.profile.md b/tools/analysis/.profile.md new file mode 100644 index 00000000000..be94e8ff923 --- /dev/null +++ b/tools/analysis/.profile.md @@ -0,0 +1,41 @@ + +Profiles osquery performance and memory usage by executing queries through the `osqueryi` shell, collecting metrics, and optionally comparing results against baselines to detect regressions. + +## Key Components + +| Name | Type | Description | +|------|------|-------------| +| `check_leaks_linux` | Function | Runs Valgrind memcheck against a query and parses leak summaries | +| `check_leaks_darwin` | Function | Uses macOS `leaks` tool to detect memory leaks while the shell runs | +| `check_leaks` | Function | Platform-aware dispatcher for leak checking | +| `profile_leaks` | Function | Iterates queries and aggregates leak reports with colored output | +| `run_query` | Function | Executes `osqueryi` in profile mode and returns resource metrics | +| `profile` | Function | Runs queries across multiple rounds and computes average resource metrics | +| `summary` | Function | Maps raw metrics to ranked thresholds for utilization, CPU time, memory, FDs, and duration | +| `summary_line` | Function | Prints a color-coded single-line summary for a query result | +| `compare` | Function | Side-by-side comparison of two JSON profile outputs | +| `regress_check` | Function | Returns non-zero exit code if any metric rank has worsened between two profiles | +| `RANGES` | Constant | Threshold tuples defining green/yellow/red boundaries for each metric | + +## Usage Example + +```bash +# Profile all tables against the default osqueryi build +python3 profile.py --shell ./build/osquery/osqueryi --tables ./specs + +# Profile a single query for 5 rounds, writing JSON output +python3 profile.py --query "SELECT * FROM processes" --rounds 5 --output results.json + +# Check for regressions against a previous run +python3 profile.py --query "SELECT * FROM users" --check baseline.json + +# Compare two saved profile outputs +python3 profile.py --compare baseline.json current.json + +# Check for memory leaks on Linux with a suppression file +python3 profile.py --leaks --query "SELECT * FROM processes" \ + --suppressions ./tools/analysis/valgrind.supp + +# Use scheduled queries from an osqueryd config +python3 profile.py --config /etc/osquery/osquery.conf --count 3 --timeout 30 +``` \ No newline at end of file diff --git a/tools/ci/scripts/.check_copyright_headers.md b/tools/ci/scripts/.check_copyright_headers.md new file mode 100644 index 00000000000..d0290c8ecbe --- /dev/null +++ b/tools/ci/scripts/.check_copyright_headers.md @@ -0,0 +1,41 @@ + +Validates that all C/C++/Objective-C source files in the osquery repository contain the required copyright header block. + +## Key Components + +- **`valid_extension_list`** — File extensions checked: `.c`, `.h`, `.cpp`, `.m`, `.mm` +- **`ignored_folder_list`** — Directories skipped during traversal (`libraries`, `osquery/extensions/thrift/gen`) +- **`ignored_file_list`** — Specific files exempt from the copyright check +- **`copyright_header`** — The exact `/** ... */` block that must appear in each file +- **`main()`** — Walks the repository tree from CWD, filters by extension and ignore rules, and reports any files missing the header. Returns `1` on failure, `0` on success + +## Usage Example + +Must be run from the osquery repository root (where `.clang-format` and `LICENSE` exist): + +```python +# Run directly from the CLI +python3 check_copyright_headers.py +``` + +```bash +# Typical CI usage +cd /path/to/osquery +python3 tools/check_copyright_headers.py +# Exit code 0 = all files valid +# Exit code 1 = missing headers found, offenders printed to stdout +``` + +**Example output when violations are found:** + +```text +The following source files do not contain the required copyright header: + /path/to/osquery/osquery/foo/bar.cpp + /path/to/osquery/osquery/baz/qux.h +``` + +## Notes + +- The script **must** be executed from the repository root; it validates this by checking for `.clang-format` and `LICENSE` in CWD +- To exempt a file, add its relative path to `ignored_file_list` +- To exempt an entire directory, add its relative path prefix to `ignored_folder_list` \ No newline at end of file diff --git a/tools/ci/scripts/cve/.third_party_libraries_cves_scanner.md b/tools/ci/scripts/cve/.third_party_libraries_cves_scanner.md new file mode 100644 index 00000000000..46f49b2f007 --- /dev/null +++ b/tools/ci/scripts/cve/.third_party_libraries_cves_scanner.md @@ -0,0 +1,51 @@ + +Scans third-party library dependencies against the NVD (NIST) CVE database and optionally creates GitHub issues for newly discovered vulnerabilities. + +## Key Components + +| Component | Type | Description | +|-----------|------|-------------| +| `getCVES()` | Function | Queries NVD via `nvdlib` for CVEs by vendor/product CPE string or date range, with exponential backoff retry logic | +| `parseCVEFromTitle()` | Function | Extracts a CVE ID from a GitHub issue title using regex | +| `CVE` | Class | Data container holding CVE name, severity, description, and reference URL | +| `libraries_without_cpe` | Import | Manifest-defined exclusion list for libraries lacking CPE identifiers | +| `validateManifestFormat()` | Import | Validates the structure of the JSON library manifest before processing | + +## Usage Example + +```bash +# Basic scan using a manifest file +python3 third_party_libraries_cves_scanner.py \ + --manifest /path/to/libraries.json + +# Scan specific libraries with NVD API key and auto-create GitHub issues +python3 third_party_libraries_cves_scanner.py \ + --manifest /path/to/libraries.json \ + --api-key $NIST_API_KEY \ + --github-token $GITHUB_TOKEN \ + --create-issues \ + --libraries "openssl,zlib,curl" + +# Test against a fork without opening real issues +python3 third_party_libraries_cves_scanner.py \ + --manifest /path/to/libraries.json \ + --source-repo osquery/osquery \ + --dest-repo myorg/myrepo \ + --create-issues \ + --debug +``` + +## CLI Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `--manifest` | ✅ | Path to the JSON library manifest | +| `--api-key` | ❌ | NVD NIST API key (falls back to `$NIST_API_KEY` env var) | +| `--github-token` | ❌ | GitHub token (falls back to `$GITHUB_TOKEN` env var) | +| `--create-issues` | ❌ | Auto-open GitHub issues for new CVEs | +| `--libraries` | ❌ | Comma-separated list to limit which libraries are checked | +| `--source-repo` | ❌ | Repo to search for existing CVE issues (default: `osquery/osquery`) | +| `--dest-repo` | ❌ | Repo where new issues are created (default: `osquery/osquery`) | +| `--debug` | ❌ | Enable verbose error output | + +> **Note:** Without an API key the scanner enforces a 6-second base interval between NVD requests to avoid rate limiting. With a key, this drops to 1 second. Intervals double on errors and recover gradually on success. \ No newline at end of file diff --git a/tools/ci/scripts/cve/.validate_manifest_libraries_versions.md b/tools/ci/scripts/cve/.validate_manifest_libraries_versions.md new file mode 100644 index 00000000000..1de7a8e7ba6 --- /dev/null +++ b/tools/ci/scripts/cve/.validate_manifest_libraries_versions.md @@ -0,0 +1,43 @@ + +Validates an osquery repository manifest by cross-referencing declared library versions against actual Git submodule commits and external library definitions (e.g., OpenSSL) found in the repository. + +## Key Components + +| Component | Description | +|---|---| +| `pygit2.Repository` | Opens the target Git repository and enumerates all registered submodules | +| `submodules_name_and_commit` | List of `(name, commit_hex)` tuples derived from active submodule HEAD commits | +| `external_libraries_name_and_version` | List of `(name, version)` tuples parsed from CMake formula files (currently OpenSSL) | +| `validateManifestFormat()` | Imported from `osquery.manifest_api` — validates the manifest JSON structure | +| `validateLibrariesVersions()` | Imported from `osquery.manifest_api` — cross-checks manifest entries against discovered versions/commits | + +## Usage Example + +```bash +python3 validate_manifest_libraries_versions.py \ + --repository /path/to/osquery \ + --manifest /path/to/manifest.json +``` + +```python +# Internal flow (simplified) +repo = pygit2.Repository("/path/to/osquery") + +# Collect submodule commits +for path in repo.listall_submodules(): + sub = repo.lookup_submodule(path) + submodules_name_and_commit.append((name, sub.head_id.hex)) + +# Parse OpenSSL version from CMake +match = re.compile('OPENSSL_VERSION[\\s]*"(.*)"').search(line) + +# Validate against manifest +validateManifestFormat(manifest) # exits 1 on failure +validateLibrariesVersions(manifest, ...) # exits 1 on mismatch +``` + +## Notes + +- Submodules with non-existent paths (e.g., removed but not unregistered) are **skipped automatically** +- Submodules named `src` inherit their **parent directory name** as the library name +- Exits with code `1` on any validation failure; prints `Done. The manifest is valid` on success \ No newline at end of file diff --git a/tools/ci/scripts/cve/osquery/.github_api.md b/tools/ci/scripts/cve/osquery/.github_api.md new file mode 100644 index 00000000000..3b72d5a7849 --- /dev/null +++ b/tools/ci/scripts/cve/osquery/.github_api.md @@ -0,0 +1,46 @@ + +Provides a rate-limited GitHub REST API client for creating issues and paginating through existing issues across two repositories. + +## Key Components + +- **`IssueCreationError`** — Exception raised when issue creation fails after retries. +- **`IssuesListingError`** — Exception raised when issue listing fails after retries. +- **`GithubIssueState`** — Enum for filtering issues by state (`Open`, `Close`, `All`). +- **`GithubAPI`** — Core client class initialized with source/destination repos and a GitHub token. + - `makePostRequest(url, data)` — Rate-limited POST (min 5s between requests). + - `makeGetRequest(url, params)` — Rate-limited GET (min 5s between requests). + - `createIssue(title, content, labels)` — Creates an issue in `dest_repo` with up to 3 retry attempts. + - `getRecentOpenIssues(creator, state, labels)` — Paginates through all issues in `source_repo` created/updated within the last 180 days, returning batches of up to 100. + +## Usage Example + +```python +from github_api import GithubAPI, GithubIssueState + +api = GithubAPI( + source_repo="org/source-repo", + dest_repo="org/dest-repo", + github_token="ghp_yourTokenHere", + debug=True +) + +# Create an issue in the destination repo +api.createIssue( + title="CVE-2024-12345 detected", + content="Vulnerability found in dependency X.", + labels=["security", "automated"] +) + +# Fetch all recent open issues from the source repo created by a bot +issue_batches = api.getRecentOpenIssues( + creator="github-actions[bot]", + state=GithubIssueState.Open, + labels=["security"] +) + +for batch in issue_batches: + for issue in batch: + print(issue["title"], issue["html_url"]) +``` + +> **Note:** All requests are throttled to a minimum of 5 seconds apart to avoid GitHub API rate limits. The `createIssue` method retries up to 3 times with incremental backoff on failure. \ No newline at end of file diff --git a/tools/ci/scripts/cve/osquery/.manifest_api.md b/tools/ci/scripts/cve/osquery/.manifest_api.md new file mode 100644 index 00000000000..0eb9e2c6b6d --- /dev/null +++ b/tools/ci/scripts/cve/osquery/.manifest_api.md @@ -0,0 +1,43 @@ + +Provides validation utilities for osquery's third-party library manifest, ensuring metadata fields are correct and library versions/commits stay in sync with what's actually used in the build. + +## Key Components + +### Module-Level Constants + +| Constant | Purpose | +|---|---| +| `libraries_without_commit` | Libraries not tracked as Git submodules (e.g., `openssl`) | +| `libraries_without_cpe` | Libraries with no CPE identifier (e.g., AWS SDK components) | +| `libraries_to_ignore` | Libraries excluded from version staleness checks (e.g., `googletest`) | +| `libraries_without_version` | Libraries versioned by date rather than semver (e.g., `gnulib`) | + +### Functions + +- **`print_err(message)`** — Writes a prefixed error message to `stderr`. +- **`validateManifestFormat(manifest)`** — Validates that each library entry in the manifest dict contains the correct fields and types based on its category. Returns `True` if all entries are valid, `False` otherwise. +- **`validateLibrariesVersions(manifest, versions, commits)`** — Cross-checks the manifest against detected library versions and Git commit hashes. Reports missing entries, stale versions, outdated commits, and manifest entries that can be pruned. + +## Usage Example + +```python +import json +from manifest_api import validateManifestFormat, validateLibrariesVersions + +# Load your manifest JSON +with open("libraries_manifest.json") as f: + manifest = json.load(f) + +# Validate structure +if not validateManifestFormat(manifest): + raise SystemExit("Manifest format is invalid") + +# Cross-check against detected versions and submodule commits +versions = [("zlib", "1.3.1"), ("boost", "1.84.0")] +commits = [("zlib", "abc123def456"), ("boost", "deadbeef1234")] + +if not validateLibrariesVersions(manifest, versions, commits): + raise SystemExit("Manifest versions are out of date") +``` + +> **Note:** `validateLibrariesVersions` also prints any manifest entries not found in the detected library lists — useful for identifying stale entries that can be removed. \ No newline at end of file diff --git a/tools/cmake/.downloader.md b/tools/cmake/.downloader.md new file mode 100644 index 00000000000..d7c0492cf94 --- /dev/null +++ b/tools/cmake/.downloader.md @@ -0,0 +1,37 @@ + +A command-line utility that downloads a file from a URL to a local destination and verifies its integrity using SHA-256 hash comparison. + +## Key Components + +- **`main(argc, argv)`** — Entry point that handles argument parsing, file downloading via `urllib`, and hash verification. Returns `0` on success, `1` on failure. +- **`get_file_hash(path)`** — Computes the SHA-256 hash of a local file using 1MB buffered reads. Returns the hex digest string or `None` on error. + +## Usage Example + +```bash +# Command-line usage +python3 downloader.py https://example.com/file.txt /tmp/file.txt +``` + +```python +# Using get_file_hash directly in another script +from downloader import get_file_hash + +file_hash = get_file_hash("/path/to/local/file.txt") +if file_hash: + print(f"SHA-256: {file_hash}") +else: + print("Failed to compute hash") +``` + +## Behavior & Error Handling + +| Step | Failure Condition | Exit Code | +|---|---|---| +| Argument validation | Not exactly 3 args provided | `1` | +| File download | Network error or invalid URL | `1` | +| Hash computation | File unreadable after download | `1` | +| Hash verification | Downloaded file hash mismatch | `1` | +| Success | All checks pass | `0` | + +> **Note:** The file is read in 1MB chunks (`1048576` bytes) during hashing, making it safe for large files without excessive memory usage. \ No newline at end of file diff --git a/tools/codegen/.amalgamate.md b/tools/codegen/.amalgamate.md new file mode 100644 index 00000000000..12a014bc617 --- /dev/null +++ b/tools/codegen/.amalgamate.md @@ -0,0 +1,37 @@ + +Generates a C++ amalgamation file by combining table plugin source files using a template, used in the osquery build system to merge generated table implementations into a single compilation unit. + +## Key Components + +- **`genTableData(filename)`** — Parses a `.cpp` file and extracts code between `/// BEGIN[GENTABLE]` and `/// END[GENTABLE]` markers; returns `None` if no markers are found. +- **`main(argc, argv)`** — CLI entry point that walks a source directory, collects table data from each file, renders the amalgamation via `templite.Templite`, and writes the output `.cpp` file. +- **`TEMPLATE_NAME`** — Constant pointing to `amalgamation.cpp.in`, the base template for the output file. + +## CLI Arguments + +| Argument | Description | +|---|---| +| `--foreign` | Flag to generate a foreign table set amalgamation | +| `--templates` | Path to the directory containing `.cpp.in` template files | +| `--category` | Category name used to skip a specific filename during source walk | +| `--sources` | Directory containing generated `.cpp` source files | +| `--output` | Destination path for the amalgamated `.cpp` output | + +## Usage Example + +```bash +python3 amalgamate.py \ + --templates path/to/templates \ + --category utility \ + --sources path/to/generated/tables \ + --output path/to/output/amalgamation.cpp +``` + +```python +# Programmatic extraction of table data from a single file +table_data = genTableData("generated_table.cpp") +if table_data: + print(table_data) +``` + +> The output directory is created automatically if it does not exist; existing directories are silently ignored. \ No newline at end of file diff --git a/tools/codegen/.genapi.md b/tools/codegen/.genapi.md new file mode 100644 index 00000000000..e13c2015aa3 --- /dev/null +++ b/tools/codegen/.genapi.md @@ -0,0 +1,43 @@ + +Generates osquery table API documentation in JSON format by parsing table spec files and producing structured output. Supports diff comparison between API versions and optional profiling data integration. + +## Key Components + +| Name | Type | Description | +|------|------|-------------| +| `NoIndent` | Class | Wrapper that prevents JSON newline expansion for compact inline objects | +| `Encoder` | Class | Custom `JSONEncoder` that selectively compacts `NoIndent`-wrapped values | +| `gen_api_json(api)` | Function | Renders the final API structure into the `TEMPLATE_API_DEFINITION` JSON template | +| `gen_spec(tree)` | Function | Executes a parsed table AST and extracts columns, foreign keys, and metadata | +| `gen_diff(old, new)` | Function | Compares two API JSON files and prints added/removed tables and columns | +| `gen_api(tables_path, profile)` | Function | Walks the specs directory tree, builds categorized table data with denylist support | +| `CANONICAL_PLATFORMS` | Dict | Maps directory names (e.g. `darwin`, `linux`) to human-readable platform labels | +| `main(argc, argv)` | Function | CLI entry point handling `--tables`, `--diff`, `--output`, `--profile`, `--directory` flags | + +## Usage Example + +```bash +# Generate API JSON to stdout +python3 genapi.py --tables specs + +# Generate and save to a version-tagged file +python3 genapi.py --tables specs --output --directory ./output/ + +# Compare two API snapshots +python3 genapi.py --diff v5.10.json v5.11.json + +# Include profiling data +python3 genapi.py --tables specs --profile profile_summary.json + +# Enable debug logging +python3 genapi.py --tables specs --debug +``` + +```python +# Use gen_api programmatically +from genapi import gen_api, gen_api_json + +api_categories = gen_api("./specs", profile={}) +json_output = gen_api_json(api_categories) +print(json_output) +``` \ No newline at end of file diff --git a/tools/codegen/.gentable.md b/tools/codegen/.gentable.md new file mode 100644 index 00000000000..91d6d7683a3 --- /dev/null +++ b/tools/codegen/.gentable.md @@ -0,0 +1,35 @@ + +Generates C++ virtual table implementation files from osquery table spec definitions by parsing Python-based schema descriptors and rendering them through template files. + +## Key Components + +- **`DataType`** — Represents a SQL column type pairing a SQL affinity string (e.g., `TEXT_TYPE`) with a C++ type (e.g., `std::string`). +- **`TableState`** (Singleton) — Maintains the full state of a table spec during execution: name, schema, columns, attributes, aliases, and generation flags. +- **`Column`** — Describes a single table column with name, type, description, aliases, platform constraints, and keyword options. +- **`ForeignKey`** — Loosely defines a foreign key reference between table specs. +- **`table_name()`** — Sets the active table name and resets state metadata. +- **`schema()`** — Registers a list of `Column`/`ForeignKey` objects for the active table. +- **`extended_schema()`** — Registers platform-conditional schema extensions. +- **`is_denylisted()`** — Checks a `denylist` file to suppress code generation for specific tables (optionally platform-scoped). +- **`setup_templates()`** — Loads `.in` template files from disk into the `TEMPLATES` dict for use by `TableState.generate()`. +- **`to_camel_case()` / `to_upper_camel_case()`** — Converts `snake_case` table names to C++ class-name conventions. + +## Usage Example + +```python +# Typical usage inside an osquery table spec file (e.g., processes.table) +table_name("processes", aliases=["running_processes"]) + +schema([ + Column("pid", BIGINT, "Process identifier", index=True), + Column("name", TEXT, "Process name", required=True), + Column("path", TEXT, "Path to the executable"), + ForeignKey(column="pid", table="process_open_files"), +]) + +# After spec execution, generate the C++ implementation +setup_templates("/osquery/specs/templates") +table.generate("/output/processes.cpp", template="default") +``` + +> **Note:** Type constants (`TEXT`, `INTEGER`, `BIGINT`, etc.) and option flags (`COLUMN_OPTIONS`, `TABLE_ATTRIBUTES`) are defined at module level and used directly inside spec files as a DSL. \ No newline at end of file diff --git a/tools/codegen/.gentargets.md b/tools/codegen/.gentargets.md new file mode 100644 index 00000000000..7f2f12781c2 --- /dev/null +++ b/tools/codegen/.gentargets.md @@ -0,0 +1,46 @@ + +Generates a Buck/Blaze `TARGETS` build file for the osquery SDK by parsing CMake compile metadata (JSON) and copying relevant source files to an output directory. + +## Key Components + +### `get_files_to_compile(json_data)` +Filters and normalizes source file paths from CMake JSON metadata. Excludes test files, benchmarks, example files, generated files, and test utilities. Returns a sorted list of relative paths rooted at `osquery/`. + +### Template Constants +- **`TARGETS_PREAMBLE`** — Static header defining the `thrift_library` and opening the `cpp_library` (`osquery_sdk`) source list. +- **`TARGETS_POSTSCRIPT`** — Static footer containing compiler flags, preprocessor definitions (version-interpolated via `%s`), and dependency declarations for Folly, RocksDB, Thrift, Boost, glog, gflags, etc. + +### CLI Entry Point (`__main__`) +Parses arguments and orchestrates the build file generation: + +| Argument | Flag | Description | +|---|---|---| +| Input JSON | `-i` | CMake compile commands JSON | +| Version | `-v` | `OSQUERY_BUILD_VERSION` string | +| Output dir | `-o` | Destination for `TARGETS` and source copies | +| Sources dir | `-s` | Root directory of osquery source files | +| SDK version | `--sdk` | `OSQUERY_BUILD_SDK_VERSION` string | + +## Usage Example + +```bash +python3 gentargets.py \ + --input compile_commands.json \ + --version 5.10.0 \ + --sdk 5.10.0 \ + --output ./buck-output \ + --sources ./osquery +``` + +```python +# Programmatic use of the filter function +import json + +with open("compile_commands.json") as f: + json_data = json.load(f) + +source_files = get_files_to_compile(json_data) +# Returns: ['core/flags.cpp', 'filesystem/fileops.cpp', ...] +``` + +> **Note:** The `impl_thrift.cpp` source is automatically remapped to `impl_fbthrift.cpp` for Facebook's internal Thrift implementation (`-DFBTHRIFT`). The generated `TARGETS` file targets CentOS 7 and is not intended to be manually edited. \ No newline at end of file diff --git a/tools/codegen/.genwebsitejson.md b/tools/codegen/.genwebsitejson.md new file mode 100644 index 00000000000..d5591fad04a --- /dev/null +++ b/tools/codegen/.genwebsitejson.md @@ -0,0 +1,37 @@ + +Generates a complete JSON table specification for the osquery website by parsing `.table` spec files from a given specs directory and outputting structured metadata to stdout. + +## Key Components + +- **`PLATFORM_DIRS`** — Mapping of spec subdirectory names to their supported osquery platforms (`darwin`, `linux`, `windows`). +- **`platform_for_spec(path)`** — Resolves the target platforms for a spec file based on its parent directory name. +- **`url_for_spec(specs_dir, path)`** — Constructs the GitHub source URL for a given spec file. +- **`generate_table_metadata(specs_dir, full_path)`** — Parses and executes a `.table` spec file (valid Python), then extracts table name, description, platforms, columns, and attributes into a dictionary. +- **`main(argc, argv)`** — CLI entrypoint; walks the specs directory recursively, collects metadata for all `.table` files (skipping `example.table`), and prints sorted JSON to stdout. + +## Usage Example + +```bash +python tools/codegen/genwebsitejson.py --specs=./specs +``` + +```python +# Programmatic equivalent of what main() does +import json, os +from genwebsitejson import generate_table_metadata, url_for_spec + +specs_dir = os.path.abspath("./specs") +full_path = "./specs/linux/users.table" + +metadata = generate_table_metadata(specs_dir, full_path) +print(json.dumps(metadata, indent=2)) +# { +# "name": "users", +# "description": "...", +# "platforms": ["linux"], +# "evented": false, +# "columns": [...] +# } +``` + +> **Note:** Each `.table` file is parsed via `ast.parse` and executed with `exec`, relying on symbols imported from `gentable` (e.g., `table`, `argparse`). The output JSON is sorted alphabetically by table name and written to stdout. \ No newline at end of file diff --git a/tools/codegen/.substitute.md b/tools/codegen/.substitute.md new file mode 100644 index 00000000000..b36d4a47549 --- /dev/null +++ b/tools/codegen/.substitute.md @@ -0,0 +1,41 @@ + +A CLI utility that performs regex-based find-and-replace across all lines of an input file, writing the results to an output file. + +## Key Components + +- **`main(args)`** — Core logic; compiles the regex pattern and iterates over each input line, writing substituted output. +- **`parse_args()`** — Configures and returns CLI arguments: `--infile`, `--outfile`, `--pattern`, and `--replacement`. +- **`run()`** — Entry point that wires `parse_args()` into `main()`. + +## CLI Arguments + +| Argument | Required | Default | Description | +|---|---|---|---| +| `-i` / `--infile` | No | `stdin` | Source file to read | +| `-o` / `--outfile` | No | `stdout` | Destination file to write | +| `--pattern` | **Yes** | — | Regex pattern to match | +| `--replacement` | **Yes** | — | Replacement string for matches | + +## Usage Example + +```bash +# Replace all occurrences of "foo" with "bar" in a file +python3 substitute.py --pattern "foo" --replacement "bar" -i input.txt -o output.txt + +# Use stdin/stdout (pipeline-friendly) +echo "hello world" | python3 substitute.py --pattern "world" --replacement "osquery" + +# Regex group capture and backreference +python3 substitute.py --pattern "(version)=([0-9]+)" --replacement "\1=99" -i config.txt -o config_new.txt +``` + +```python +# Equivalent programmatic usage +import re + +pattern = re.compile(r"foo") +for line in open("input.txt"): + print(pattern.sub("bar", line)) +``` + +> **Note:** Each input line is written with an explicit `\n` appended, so the output always has Unix-style line endings regardless of the source file's line endings. \ No newline at end of file diff --git a/tools/codegen/.templite.md b/tools/codegen/.templite.md new file mode 100644 index 00000000000..7550359abf4 --- /dev/null +++ b/tools/codegen/.templite.md @@ -0,0 +1,45 @@ + +A lightweight, general-purpose Python templating engine based on Tomer Filiba's Templite, extended by joonis new media. It compiles template strings or files into executable Python code, supporting embedded logic blocks, variable output, and file includes. + +## Key Components + +### `Templite` class + +| Member | Type | Description | +|---|---|---| +| `__init__` | Method | Loads a template from a string or file path with optional caching | +| `_compile` | Method | Parses template source into compiled Python bytecode | +| `render` | Method | Executes the compiled template with a given variable namespace | +| `delimiters` | Class attr | Default token delimiters `${ ... }$` (must be 2 chars each) | +| `autowrite` | Class attr | Regex that auto-wraps literals/identifiers in `write()` calls | +| `cache` | Class attr | Class-level dict caching compiled templates by file path or text hash | + +## Usage Example + +```python +from templite import Templite + +# From a string +tmpl = Templite(text=""" +Hello, ${ name }$! +${ for i in range(3): }$ + Item ${ i }$ +${ : }$ +""") + +print(tmpl.render(name="World")) + +# From a file with caching enabled +tmpl = Templite(filename="report.html", caching=True) +output = tmpl.render(title="My Report", items=[1, 2, 3]) + +# Custom delimiters +tmpl = Templite(text="Hello, <%name%>!", delimiters=("<%", "%>")) +print(tmpl.render(name="Alice")) +``` + +**Template syntax rules:** +- `${ expression }$` — evaluates and auto-writes literals or identifiers +- `${ for x in items: }$` ... `${ : }$` — block statements use `:` to open/close +- `${ write("custom") }$` — explicit write call +- `${ include("partial.html") }$` — includes another template file relative to the current \ No newline at end of file diff --git a/tools/deployment/.getfiles.md b/tools/deployment/.getfiles.md new file mode 100644 index 00000000000..5114d039389 --- /dev/null +++ b/tools/deployment/.getfiles.md @@ -0,0 +1,38 @@ + +Parses `compile_commands.json` from an osquery build directory and prints the list of source files, excluding test and benchmark files. + +## Key Components + +- **CLI Arguments** + - `--build` — Path to the osquery build directory containing `compile_commands.json` + - `--base` — Optional real path of the source base (default: empty string) + +- **Filtering Logic** — Skips files matching: + - `_tests.cpp` (unit test files) + - `_benchmark` (benchmark files) + - `gtest` (Google Test framework files) + +## Usage Example + +```bash +# Generate compile_commands.json first (requires cmake with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) +cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. + +# Run the script against your build directory +python3 getfiles.py --build ./build/linux + +# Pipe output to another tool (e.g., clang-tidy, cppcheck) +python3 getfiles.py --build ./build/linux | xargs clang-tidy +``` + +```python +# Example compile_commands.json entry processed by the script +{ + "directory": "/osquery/build/linux", + "command": "clang++ -o osquery/main.cpp.o -c osquery/main.cpp", + "file": "/osquery/osquery/main.cpp" # included +} +# Files like "osquery_tests.cpp" or "gtest/gtest.h" are excluded +``` + +> **Note:** This script is a build utility for the [osquery](https://osquery.io) project and is intended to be used in CI pipelines or static analysis workflows where a filtered list of production source files is needed. \ No newline at end of file diff --git a/tools/formatting/.format-check.md b/tools/formatting/.format-check.md new file mode 100644 index 00000000000..a7da7b562d0 --- /dev/null +++ b/tools/formatting/.format-check.md @@ -0,0 +1,36 @@ + +Validates C/C++ code formatting by comparing staged changes against a base branch using `git-clang-format.py` and `clang-format`. + +## Key Components + +- **`check(base_commit, exclude_folders, clang_binary)`** — Invokes `git-clang-format.py` with `--diff` mode and interprets output to determine if changed files meet formatting requirements. Returns `True` on pass, `False` on failure. +- **`get_base_commit(base_branch)`** — Resolves the merge-base commit SHA between `HEAD` and the specified branch using `git merge-base`. +- **`main()`** — Parses CLI arguments and orchestrates the format check workflow. + +## CLI Arguments + +| Argument | Default | Description | +|---|---|---| +| `base_branch` | `master` | Branch to diff against | +| `--exclude-folders` | `""` | Comma-separated folder paths to skip | +| `--binary` | `""` | Path to a specific `clang-format` binary | + +## Usage Example + +```python +# Run directly from the command line: +# python format-check.py main --exclude-folders third_party,build --binary /usr/bin/clang-format-15 +``` + +```bash +# Basic usage — compare against master +python format-check.py + +# Compare against a feature branch, excluding vendored code +python format-check.py main --exclude-folders third_party + +# Use a specific clang-format version +python format-check.py main --binary /usr/bin/clang-format-15 +``` + +**Exit codes:** `0` = formatting passes, `1` = formatting violations detected or script error. \ No newline at end of file diff --git a/tools/formatting/.git-clang-format.md b/tools/formatting/.git-clang-format.md new file mode 100644 index 00000000000..ae073ddc5ca --- /dev/null +++ b/tools/formatting/.git-clang-format.md @@ -0,0 +1,49 @@ + +Git integration script that runs `clang-format` on lines changed between the working directory and a specified git commit, applying or previewing formatting changes for supported source file extensions. + +## Key Components + +| Function | Description | +|---|---| +| `main()` | Entry point; parses CLI args, orchestrates diff computation, filtering, formatting, and applying changes | +| `load_git_config()` | Reads git configuration settings (e.g., `clangFormat.binary`, `clangFormat.style`) | +| `interpret_args()` | Resolves positional args into `(commit, files)` with fallback to `HEAD` | +| `compute_diff()` | Runs `git diff-index -U0` to produce a zero-context unified diff | +| `extract_lines()` | Parses the diff output into a `{filename: [Range]}` dictionary | +| `filter_by_extension()` | Removes files from the diff map that don't match allowed extensions | +| `filter_by_path()` | Removes files under excluded folder paths | +| `disambiguate_revision()` | Determines whether a positional arg is a commit ref or a filename | +| `get_object_type()` | Queries git for an object's type (commit, tag, blob, etc.) | +| `compute_diff_and_extract_lines()` | Combines `compute_diff()` and `extract_lines()` into one call | + +**Named tuple:** `Range(start, count)` — represents a contiguous changed line region. + +## Usage Example + +```bash +# Format all changed files relative to HEAD +git clang-format + +# Preview diff without applying changes +git clang-format --diff + +# Format changes since a specific commit, only .cpp and .h files +git clang-format abc1234 -- src/foo.cpp include/bar.h + +# Use a custom clang-format binary and style, excluding a folder +git clang-format \ + --binary /usr/local/bin/clang-format \ + --style file \ + --exclude-folders third_party,vendor \ + --verbose +``` + +**Git config defaults** (set in `.git/config` or `~/.gitconfig`): + +```text +[clangFormat] + binary = clang-format + commit = HEAD + extensions = c,h,cpp,hpp + style = file +``` \ No newline at end of file diff --git a/tools/tests/.plist_benchmark.md b/tools/tests/.plist_benchmark.md new file mode 100644 index 00000000000..be83b42e6c4 --- /dev/null +++ b/tools/tests/.plist_benchmark.md @@ -0,0 +1,44 @@ + +Performance benchmark for parsing Apple Property List (plist) files using osquery's `parsePlistContent` utility, measuring throughput across configurable iteration counts. + +## Key Components + +- **`PlistBenchmark`** — GTest fixture class serving as the test suite container +- **`FLAGS_plist_iterations`** — Command-line flag (`--plist_iterations`, default: `100`) controlling how many parse cycles are executed +- **`bench_parse_plist_content`** — Core benchmark test that repeatedly reads and parses a plist file, validating correctness of parsed fields on each iteration + +## What It Measures + +Each iteration performs: +1. File read of `test.plist` from the test data path +2. Full plist parse via `parsePlistContent()` +3. Field validation (boolean, string, and array node extraction via `boost::property_tree`) + +Elapsed wall-clock time is reported via `LOG(ERROR)` (intentionally uses `ERROR` level so output is visible even on passing tests). + +## Usage Example + +```bash +# Run with default 100 iterations +./plist_benchmark + +# Run with a custom iteration count +./plist_benchmark --plist_iterations=9001 + +# Run with verbose logging +./plist_benchmark --plist_iterations=500 --logtostderr +``` + +```cpp +// Validated plist fields in each iteration: +EXPECT_EQ(tree.get("Disabled"), true); +EXPECT_EQ(tree.get("Label"), "com.apple.FileSyncAgent.sshd"); +EXPECT_THROW(tree.get("foobar"), pt::ptree_bad_path); // missing key check +// ProgramArguments array is also extracted and compared +``` + +## Notes + +- Depends on `kTestDataPath + "test.plist"` being present in the test data directory +- Timing is measured using `getUnixTime()` (second-level granularity); use high iteration counts for meaningful results +- Entry point manually initializes GFlags, GTest, and GLog before running \ No newline at end of file diff --git a/tools/tests/.test.md b/tools/tests/.test.md new file mode 100644 index 00000000000..30f3a987be1 --- /dev/null +++ b/tools/tests/.test.md @@ -0,0 +1,23 @@ + +A minimal C++ test program that prints the string `"foobar"` to standard output and exits. + +## Key Components + +- **`main(int argc, char* argv[])`** — Entry point; declares a string literal and writes it to `stdout` via `std::cout`. + +## Usage Example + +```cpp +// Build and run +// $ g++ test.cpp -o test && ./test +// Output: +// foobar + +int main(int argc, char* argv[]) { + auto s = "foobar"; + std::cout << s << std::endl; + return 0; +} +``` + +> **Note:** This file is part of the [osquery](https://github.com/osquery/osquery) project, licensed under Apache-2.0 OR GPL-2.0-only. It serves as a placeholder or smoke-test binary with no functional logic beyond basic I/O verification. \ No newline at end of file diff --git a/tools/tests/.test_additional.md b/tools/tests/.test_additional.md new file mode 100644 index 00000000000..24d814f783b --- /dev/null +++ b/tools/tests/.test_additional.md @@ -0,0 +1,38 @@ + +Tests additional osquery daemon features, specifically validating query pack loading, platform filtering, and schedule integration via the osquery extension interface. + +## Key Components + +- **`AdditionalFeatureTests`** — Test class inheriting from `test_base.ProcessGenerator` and `unittest.TestCase`, providing daemon lifecycle management alongside standard assertions. +- **`test_query_packs()`** — Decorated with `@test_base.flaky`; writes a temporary pack config with two queries (one with a non-existent platform), spawns a daemon with that pack loaded, then verifies: + - Both queries appear in `osquery_packs` + - Only the platform-compatible query is added to `osquery_schedule` + +## Usage Example + +```python +# Run standalone via test_base.Tester +python3 test_additional.py --build /path/to/osquery/build + +# Run via unittest discovery +python3 -m unittest test_additional.AdditionalFeatureTests.test_query_packs +``` + +## Test Flow + +```mermaid +graph TD + A[Write pack config] --> B[Spawn osquery daemon] + B --> C[Open extension client] + C --> D{Query osquery_packs} + D --> E[Assert 2 rows returned] + E --> F{Query osquery_schedule} + F --> G["Assert 1 row (platform filtered)"] + G --> H[Kill daemon] +``` + +## Notes + +- Requires a compiled osquery build with Thrift bindings (`test_base.loadThriftFromBuild`). +- The `@test_base.flaky` decorator retries on intermittent failures caused by daemon startup lag. +- `test_base.expectTrue` is used as a polling helper to wait for async pack parsing before asserting. \ No newline at end of file diff --git a/tools/tests/.test_base.md b/tools/tests/.test_base.md new file mode 100644 index 00000000000..ea2a1d9742d --- /dev/null +++ b/tools/tests/.test_base.md @@ -0,0 +1,37 @@ + +Provides the shared base infrastructure for osquery Python integration tests, including REPL interaction, subprocess management, and test configuration utilities. + +## Key Components + +- **`OsqueryWrapper`** — A `pexpect`/`winexpect` REPL wrapper for interacting with `osqueryi`. Parses tabular query output into lists of dicts via `run_query()`. +- **`ProcRunner`** — Manages a subprocess in a background daemon thread, exposing process lifecycle controls (`isAlive`, `isDead`, `kill`, `getChildren`). +- **`patched_run_command`** — Monkey-patch for `pexpect.replwrap.REPLWrapper.run_command` to handle `bytes` encoding/decoding compatibility. +- **`getLatestOsqueryBinary`** — Resolves the correct osquery binary path across build configurations (CMake, MSBuild Release/RelWithDebInfo) on Windows. +- **`OsqueryException` / `OsqueryUnknownException`** — Custom exceptions for REPL error responses and unparseable output. +- **`DEFAULT_CONFIG`** — Baseline osquery options dict used to configure `osqueryi` instances during tests. +- **`timeout_decorator`** — No-op stub on Windows; real decorator on POSIX via the `timeout_decorator` package. + +## Usage Example + +```python +import test_base + +# Set global config before using OsqueryWrapper +test_base.CONFIG = test_base.DEFAULT_CONFIG + +# Interact with osqueryi REPL +with test_base.OsqueryWrapper(command='/usr/bin/osqueryi') as shell: + rows = shell.run_query("SELECT pid, name FROM processes LIMIT 5") + for row in rows: + print(row["pid"], row["name"]) + +# Launch and monitor a background subprocess +runner = test_base.ProcRunner( + name="osqueryd", + path="/usr/bin/osqueryd", + _args=["--flagfile=/etc/osquery/osquery.flags"] +) +if runner.isAlive(): + print("PID:", runner.pid) + runner.kill() +``` \ No newline at end of file diff --git a/tools/tests/.test_docker_container_fs_changes_table.md b/tools/tests/.test_docker_container_fs_changes_table.md new file mode 100644 index 00000000000..e83add30ee5 --- /dev/null +++ b/tools/tests/.test_docker_container_fs_changes_table.md @@ -0,0 +1,36 @@ + +Tests the `docker_container_fs_changes` osquery virtual table by spinning up a live Docker container, creating a file inside it, and verifying the filesystem change is correctly reported. + +## Key Components + +- **`FsChangesTableTest`** — `unittest.TestCase` subclass orchestrating the full test lifecycle +- **`setUp()`** — Launches an `ubuntu:18.04` container in detached mode, executes `touch xxx` inside it to produce a tracked filesystem change, and initializes the `OsqueryWrapper` +- **`tearDown()`** — Stops the container and closes the Docker client regardless of test outcome +- **`testFsChangesTable()`** — Queries `docker_container_fs_changes` filtered by container ID, asserts at least one result exists, and validates the `/xxx` entry carries change type `A` (Added) + +## Usage Example + +```python +# Run directly +python3 test_docker_container_fs_changes_table.py + +# Or via the shared test runner +python3 -m unittest test_docker_container_fs_changes_table.FsChangesTableTest +``` + +```python +# Core query pattern exercised by the test +query = ( + "SELECT * FROM docker_container_fs_changes " + f"WHERE id = '{container.id}';" +) +# Expected matching row shape: +# { "id": "", "path": "/xxx", "change_type": "A" } +``` + +## Notes + +- Requires a running Docker daemon; Docker SDK (`docker`) and `requests` must be installed +- On Docker connectivity failure, the test **skips gracefully** with a printed warning rather than hard-failing +- Depends on `test_base.OsqueryWrapper` and `test_base.getLatestOsqueryBinary` from the shared test harness +- `change_type` values follow Docker's diff notation: `A` = Added, `C` = Changed, `D` = Deleted \ No newline at end of file diff --git a/tools/tests/.test_example_queries.md b/tools/tests/.test_example_queries.md new file mode 100644 index 00000000000..d404a027e3c --- /dev/null +++ b/tools/tests/.test_example_queries.md @@ -0,0 +1,28 @@ + +Runs osquery example queries organized by platform category (cross-platform, POSIX, platform-specific, and utility) as integration tests against a live osquery instance. + +## Key Components + +- **`ExampleQueryTests`** — `unittest`-based test class inheriting from `test_base.QueryTester` with three test methods: + - `test_cross_platform_queries` — Executes queries tagged under `specs` (all platforms) + - `test_platform_specific_queries` — Executes POSIX and/or platform-specific queries based on the current OS + - `test_utility_queries` — Executes utility category queries +- **`PLATFORM_EXAMPLES`** — Runtime-built dict mapping platform keys to lists of SQL query strings, generated from the osquery spec API via `genapi.gen_api()` +- **`test_base.loadThriftFromBuild`** — Loads the Thrift-generated Python client from the build directory to communicate with the osquery extension socket + +## Usage Example + +```python +# Run all example query tests against a built osquery instance +python test_example_queries.py --build /path/to/osquery/build + +# The test runner auto-generates queries from specs, falling back to: +# "select * from limit 1" when no examples are defined + +# Platform routing logic (simplified): +if utils.platform() in ["darwin", "linux"]: + run(PLATFORM_EXAMPLES["posix"]) +run(PLATFORM_EXAMPLES[utils.platform()]) # e.g., "darwin", "linux", "windows" +``` + +> **Note:** This script requires a compiled osquery build directory (`--build` flag) and a running osquery daemon/shell with the Thrift extension socket active. It is not intended for standalone use outside the osquery test suite. \ No newline at end of file diff --git a/tools/tests/.test_extensions.md b/tools/tests/.test_extensions.md new file mode 100644 index 00000000000..55b01fd07d1 --- /dev/null +++ b/tools/tests/.test_extensions.md @@ -0,0 +1,42 @@ + +Integration test suite for osquery's extension system, validating daemon-extension lifecycle, Thrift API communication, autoloading, and config plugin behavior. + +## Key Components + +| Component | Description | +|---|---| +| `ExtensionTests` | Main test class inheriting from `test_base.ProcessGenerator` and `unittest.TestCase` | +| `EXTENSION_TIMEOUT` | Global timeout constant (10 seconds) for extension connection attempts | +| `test_daemon_without_extensions` | Verifies socket is unavailable when extensions are disabled | +| `test_daemon_api` | Validates core Thrift API: ping, query, and column introspection | +| `test_example_extension` | End-to-end test of extension registration, custom table, and cross-routing | +| `test_extension_dies` | Verifies daemon detects extension death and handles reconnection | +| `test_extension_timeout` | Tests extension-first startup (extension waits for daemon) | +| `test_extensions_autoload` | Validates file-based extension autoloading | +| `test_extensions_directory_autoload` | Validates directory-based extension autoloading | +| `test_external_config` | Tests using an extension as a config plugin | +| `test_external_config_update` | Validates config update flow via extension UUID resolution | + +## Usage Example + +```bash +# Run the full extension test suite +python3 test_extensions.py --build /path/to/osquery/build + +# Run a specific test case +python3 -m unittest test_extensions.ExtensionTests.test_daemon_api +``` + +```python +# Typical test pattern used throughout the suite +daemon = self._run_daemon({"disable_watchdog": True}) +client = test_base.EXClient(daemon.options["extensions_socket"]) +client.open(timeout=EXTENSION_TIMEOUT) +em = client.getEM() + +result = test_base.expect(em.extensions, 1) # wait for N extensions +client.close() +daemon.kill() +``` + +> **Note:** Tests depend on a compiled osquery build (`example_extension.ext`) and the `test_base`/`utils` helper modules. The `test_external_config_update` test is incomplete — extension UUID resolution is started but assertions are absent in the current source. \ No newline at end of file diff --git a/tools/tests/.test_http_server.md b/tools/tests/.test_http_server.md new file mode 100644 index 00000000000..4ea2b1b6a1f --- /dev/null +++ b/tools/tests/.test_http_server.md @@ -0,0 +1,50 @@ + +A lightweight HTTP/HTTPS test server simulating a TLS-based osquery enrollment and configuration backend for integration testing purposes. + +## Key Components + +- **`RealSimpleHandler`** — Core `BaseHTTPRequestHandler` subclass that processes GET/POST requests across osquery TLS API endpoints: + - `/enroll` — Validates enroll secrets and issues node keys + - `/config` — Serves query schedule configs with periodic re-enrollment invalidation + - `/distributed_read` / `/distributed_write` — Handles distributed query workflows + - `/log` — Accepts log submissions + - `/carve_init` / `/carve_block` — Manages multi-block file carving (with zstd/tar detection) + - `/test_read_requests` — Returns full request history for unit test assertions + +- **Global Config Objects** — `EXAMPLE_CONFIG`, `EXAMPLE_ATC_CONFIG`, `EXAMPLE_DISTRIBUTED`, `EXAMPLE_CARVE`, etc. provide pre-built JSON responses for each endpoint + +- **`RECEIVED_REQUESTS`** — Shared list accumulating all inbound requests, queryable via `/test_read_requests` for test verification + +- **`debug()`** — Verbose timestamped logging controlled by the `--verbose` CLI flag + +- **`FILE_CARVE_MAP`** — Tracks in-progress file carve sessions by session ID, reassembling base64-encoded blocks to disk under `/tmp/` + +## Usage Example + +```bash +# Start a basic HTTP test server on default port +python3 test_http_server.py --port 8080 + +# Start with TLS enabled and enroll secret validation +python3 test_http_server.py \ + --port 8080 \ + --tls \ + --cert test_server.pem \ + --key test_server.key \ + --ca test_server_ca.pem \ + --verbose + +# Disable enroll secret check (open enrollment) +python3 test_http_server.py --port 8080 --no_enroll_secret +``` + +```python +# In a unit test: verify osquery made the expected enroll call +import requests + +response = requests.post("http://localhost:8080/test_read_requests") +received = response.json() +assert any(r["command"] == "enroll" for r in received) +``` + +> **Note:** The `/test_read_requests` endpoint is intended exclusively for unit test introspection — it exposes the full history of requests received during the server's lifetime. \ No newline at end of file diff --git a/tools/tests/.test_osqueryd.md b/tools/tests/.test_osqueryd.md new file mode 100644 index 00000000000..4b9fa432e1a --- /dev/null +++ b/tools/tests/.test_osqueryd.md @@ -0,0 +1,34 @@ + +Integration test suite for the `osqueryd` daemon process, verifying startup behavior, watchdog functionality, signal handling, logging configuration, and one-shot diagnostic flags. + +## Key Components + +### `DaemonTests` class +Inherits from `test_base.ProcessGenerator` and `unittest.TestCase`. Each test method spawns a real `osqueryd` process via `_run_daemon()` and asserts on observed runtime behavior. + +| Test Method | Purpose | +|---|---| +| `test_daemon_without_watchdog` | Verifies daemon starts and stays alive with watchdog disabled | +| `test_daemon_with_option` | Confirms file-based logging creates an `osqueryd.INFO*` file | +| `test_daemon_with_watchdog` | Checks that the watcher spawns a child worker process | +| `test_daemon_lost_worker` | Validates that a killed worker is automatically respawned | +| `daemon_sigint_test_helper` | Shared helper — sends `SIGINT` and asserts clean shutdown + pidfile cleanup | +| `test_daemon_sigint` | Runs the SIGINT helper twice to catch pidfile residue across restarts | +| `test_logger_mode` | Asserts that `.log` files are created with the exact `logger_mode` permission bits | +| `test_logger_stdout` | Confirms no log file is written when using the `stdout` logger plugin | +| `test_hostid_uuid` / `test_hostid_instance` | Smoke-tests alternative `host_identifier` modes | +| `test_config_check_exits` | Ensures `--config_check` flag causes immediate clean exit | +| `test_config_dump_exits` | Ensures `--config_dump` flag causes immediate clean exit | +| `test_database_dump_exits` | Ensures `--database_dump` flag causes immediate clean exit | + +## Usage Example + +```bash +# Run all daemon integration tests +python3 test_osqueryd.py + +# Run a single test case +python3 -m unittest test_osqueryd.DaemonTests.test_daemon_with_watchdog +``` + +> **Note:** Tests involving the watchdog (`test_daemon_with_watchdog`, `test_daemon_lost_worker`) are skipped automatically when the `SANITIZE` environment variable is set, as sanitizer builds do not join service threads cleanly. \ No newline at end of file diff --git a/tools/tests/.test_osqueryi.md b/tools/tests/.test_osqueryi.md new file mode 100644 index 00000000000..deb0ed73bb8 --- /dev/null +++ b/tools/tests/.test_osqueryi.md @@ -0,0 +1,47 @@ + +Integration test suite for the `osqueryi` interactive shell binary, validating config handling, query output formats, meta commands, and table functionality. + +## Key Components + +| Component | Description | +|---|---| +| `OsqueryiTest` | Main `unittest.TestCase` class containing all `osqueryi` integration tests | +| `configFile()` | Helper that resolves a config filename to its full path under `TEST_CONFIGS_DIR` | +| `SHELL_TIMEOUT` | 10-second timeout applied to all subprocess-based test runners | +| `EXIT_CATASTROPHIC` | Exit code `78` used to detect hard failures (e.g. SIGSEGV) vs. normal errors | + +## Key Test Cases + +| Test | What It Validates | +|---|---| +| `test_config_check_success` | Valid config path returns exit code `0` with no stdout | +| `test_config_dump` | `--config_dump` output matches raw config file content | +| `test_config_check_failure_*` | Missing path, bad JSON, and missing plugin all return exit code `1` | +| `test_config_check_failure_missing_plugin` | Also asserts exit code equals `EXIT_CATASTROPHIC` (not a segfault) | +| `test_json_output` | `--json` flag produces correctly formatted JSON (platform line endings handled) | +| `test_json_pretty_output` | `--json_pretty` flag produces indented JSON | +| `test_meta_commands` | All `.help`, `.mode`, `.tables`, `.schema`, etc. shell commands run without crash | +| `test_time` | Queries the `time` table and validates hour/minute/second ranges | +| `test_foreign_tables` | `--enable_foreign` flag adds entries to `osquery_registry` (skipped on Windows) | +| `test_atc` | Auto Table Construction (ATC) produces expected row from `test_atc` table | + +## Usage Example + +```bash +# Run all tests via the test runner +python3 test_osqueryi.py + +# Run a single test case +python3 -m unittest test_osqueryi.OsqueryiTest.test_json_output +``` + +```python +# Internal pattern used by most subprocess tests +proc = test_base.TimeoutRunner([ + self.binary, + "--config_check", + "--config_path=%s" % configFile("test.config"), + "--verbose", +], SHELL_TIMEOUT) +self.assertEqual(proc.proc.poll(), 0) # assert clean exit +``` \ No newline at end of file diff --git a/tools/tests/.test_query_packs.md b/tools/tests/.test_query_packs.md new file mode 100644 index 00000000000..eae192846de --- /dev/null +++ b/tools/tests/.test_query_packs.md @@ -0,0 +1,38 @@ + +Tests osquery query packs by loading JSON pack files from a configured directory, filtering queries by platform compatibility, and executing them via the base test framework. + +## Key Components + +### `allowed_platform(qp)` +Determines if a query pack or individual query is compatible with the current platform. + +- Returns `True` for `"all"`, `"any"`, or empty platform strings +- Matches `"posix"` against `linux` and `darwin` platforms +- Uses `utils.platform()` to detect the current OS + +### `QueryPacksTests(test_base.QueryTester)` +Main test class inheriting from the base query tester. + +**`test_pack_queries()`** — Core test method that: +1. Walks the `PACKS_DIR` (`TEST_CONFIGS_DIR/packs`) for JSON pack files +2. Strips line continuations (`\\\n`) before parsing +3. Skips packs missing a `"queries"` key or incompatible with the current platform +4. Collects and executes all platform-compatible queries per pack via `_execute_set()` + +## Usage Example + +```python +# Run directly as a standalone test module +python test_query_packs.py --build /path/to/osquery/build + +# The test automatically: +# 1. Loads all .json pack files from TEST_CONFIGS_DIR/packs/ +# 2. Filters by current platform (linux, darwin, windows, posix) +# 3. Executes each valid SQL query via the osquery Thrift extension socket +``` + +## Notes + +- A unique extension socket path is generated per run using a random port suffix (`osquery-NNNN.em`) to avoid conflicts +- The Thrift interface is loaded dynamically from the build directory at runtime via `test_base.loadThriftFromBuild()` +- Platform filtering applies at both the **pack level** and **individual query level**, allowing fine-grained OS targeting within a single pack file \ No newline at end of file diff --git a/tools/tests/.test_release.md b/tools/tests/.test_release.md new file mode 100644 index 00000000000..14dff2a3e89 --- /dev/null +++ b/tools/tests/.test_release.md @@ -0,0 +1,35 @@ + +Validates that osquery release binaries link only against expected system libraries, ensuring no unintended dynamic dependencies are introduced across Linux, macOS, and Windows platforms. + +## Key Components + +- **`linux_expected_libraries`** — Whitelist of permitted shared libraries for Linux builds (e.g., `libc.so`, `libpthread.so`) +- **`windows_expected_libraries`** — Whitelist of permitted DLL dependencies for Windows builds (e.g., `KERNEL32.dll`, `ADVAPI32.dll`) +- **`ReleaseTests`** — `unittest.TestCase` subclass containing two platform-gated test methods: + - **`test_no_nonsystem_link`** *(Linux/macOS)* — Uses `ldd` or `otool -L` to assert that `osqueryd` links only against system libraries + - **`test_linked_system_libraries`** *(Linux/Windows)* — Uses `ldd` or `dumpbin /DEPENDENTS` to verify the exact set of linked libraries matches the expected whitelist, failing on missing or extra entries + +## Usage Example + +```bash +# Run from the repository root, passing the build directory +python3 test_release.py --build_dir /path/to/build + +# Pass additional unittest flags (e.g., verbose output) +python3 test_release.py --build_dir /path/to/build -v +``` + +```python +# Programmatic invocation (within CI scripts) +import subprocess +result = subprocess.run( + ["python3", "test_release.py", "--build_dir", BUILD_DIR], + check=True +) +``` + +> **Notes:** +> - `test_no_nonsystem_link` runs only on **Linux and macOS**; skipped on Windows +> - `test_linked_system_libraries` runs only on **Linux and Windows**; skipped on macOS +> - On Linux, the expected dynamic linker entry (`ld-linux-x86-64.so` or `ld-linux-aarch64.so`) is appended at runtime based on `platform.processor()` +> - Requires the `utils` helper module (provides `utils.platform()`) to be present in the same directory \ No newline at end of file diff --git a/tools/tests/.test_windows_service.md b/tools/tests/.test_windows_service.md new file mode 100644 index 00000000000..dd4239344bc --- /dev/null +++ b/tools/tests/.test_windows_service.md @@ -0,0 +1,49 @@ + +Integration test suite for validating osquery Windows service lifecycle operations, including installation, startup, shutdown, and uninstallation via the Windows Service Control Manager (SCM). + +## Key Components + +### Helper Functions + +| Function | Description | +|---|---| +| `sc(*args)` | Wrapper around `sc.exe` for SCM interactions; returns `(code, message)` tuple | +| `findOsquerydBinary()` | Locates the `osqueryd.exe` binary in the build output directory | +| `installService(name, path)` | Registers a new Windows service via SCM | +| `uninstallService(name)` | Removes a registered Windows service | +| `startService(name, *argv)` | Starts a service and waits for process confirmation | +| `stopService(name)` | Stops a service and waits for process termination | +| `restartService(name, *argv)` | Stops then starts a service; returns combined success status | +| `cleanOsqueryServices()` | Discovers and removes any residual `osqueryd_test_*` services via PowerShell | +| `assertUserIsAdmin()` | Exits early if not running on Windows with admin privileges | + +### Test Class: `OsquerydTest` + +- **`setUp`** — Creates a temp directory, writes mock config/flags files, and starts a local TLS HTTP server on port 443 +- **`test_install_run_stop_uninstall_windows_service`** — Full lifecycle test: install → start → assert running → verify singleton enforcement → stop → uninstall +- **`test_thrash_windows_service`** — Stress test that restarts the service 5 times to validate stability +- **`tearDown`** — Cleans up temp files and forcibly removes any leftover services + +### Constants + +- `TLS_SERVER_ARGS` — TLS configuration passed to the embedded test HTTP server +- `FLAGS_FILE` — Template for osquery flags including TLS endpoints, logger config, and distributed plugin settings + +## Usage Example + +```bash +# Must be run as Windows Administrator +python test_windows_service.py +``` + +```python +# Standalone SCM interaction example +code, message = sc('query', 'osqueryd_test_12345') +# Returns: (0, '4RUNNING') or (1060, 'service not found') + +# Check process liveness +if serviceAlive(): # expects exactly 2 osqueryd.exe processes + stopService('osqueryd_test_12345') +``` + +> **Note:** This test requires Windows administrator privileges, a built `osqueryd.exe` at `build/windows10/osquery/Release/` or `RelWithDebInfo/`, and uses Python 2 syntax (`except X, err` and `subprocess.CalledProcessError` handling). Run with `test_base.Tester()` rather than directly via `unittest`. \ No newline at end of file diff --git a/tools/tests/.utils.md b/tools/tests/.utils.md new file mode 100644 index 00000000000..6361327b637 --- /dev/null +++ b/tools/tests/.utils.md @@ -0,0 +1,39 @@ + +Utility module for osquery testing and benchmarking, providing helpers for terminal output coloring, config file I/O, query extraction, and process profiling. + +## Key Components + +| Function | Description | +|---|---| +| `red()`, `yellow()`, `green()`, `blue()`, `lightred()` | ANSI terminal color formatters for styled console output | +| `read_config(path)` / `write_config(data, path)` | Read/write osquery JSON config files | +| `reset_dir(p)` / `copy_file(f, d)` | Filesystem helpers for directory reset and file copying | +| `platform()` | Returns normalized platform string (`linux`, `darwin`, `win32`) | +| `queries_from_config(config_path)` | Extracts scheduled queries and pack queries from an osquery config file (strips JS-style comments before parsing) | +| `queries_from_pack(pack_path)` | Extracts queries directly from a standalone osquery pack file | +| `queries_from_tables(path, restrict)` | Generates `SELECT *` queries from `.table` spec files, filtered by platform and optional table name list | +| `get_stats(p, interval)` | Samples CPU, memory, I/O, and FD stats from a `psutil.Process` object | +| `profile_cmd(cmd, ...)` | Runs a subprocess and continuously profiles it, returning aggregated CPU, memory, and timing metrics | + +## Usage Example + +```python +from utils import read_config, queries_from_config, profile_cmd, green, red + +# Color output +print(green("Tests passed")) +print(red("Tests failed")) + +# Extract queries from an osquery config +queries = queries_from_config("/etc/osquery/osquery.conf") +for name, sql in queries.items(): + print(f"{name}: {sql}") + +# Profile a command +results = profile_cmd(["osqueryi", "--json", "SELECT * FROM processes;"], timeout=30) +print(f"Avg CPU: {results['utilization']:.1f}%") +print(f"Peak RSS: {results['memory']} bytes") +print(f"Duration: {results['duration']:.2f}s") +``` + +> **Note:** `profile_cmd` requires `psutil` to be installed. On Windows, FD counts and I/O counters are unavailable. Comment stripping in `queries_from_config` handles `/* */` block comments and `//` inline comments in osquery config files. \ No newline at end of file diff --git a/tools/tests/.winexpect.md b/tools/tests/.winexpect.md new file mode 100644 index 00000000000..ba1f5ea15b3 --- /dev/null +++ b/tools/tests/.winexpect.md @@ -0,0 +1,38 @@ + +A Windows-specific implementation of `pexpect.REPLWrapper` for running interactive subprocess commands in integration tests, using threading and queues to enable non-blocking I/O on Windows. + +## Key Components + +### `WinExpectSpawn` +Launches a subprocess with piped stdin/stdout/stderr and spawns a background daemon thread to continuously read stdout into a thread-safe `Queue`, simulating non-blocking reads. + +- `__init__(command, cwd, env)` — Parses and spawns the command via `subprocess.Popen` with Windows-specific `STARTUPINFO` flags and `CREATE_NEW_PROCESS_GROUP`. +- `read_pipe(out, queue)` — Thread worker that reads lines from stdout and enqueues them. + +### `REPLWrapper` +Wraps a `WinExpectSpawn` instance to provide a simple REPL-style command interface. + +- `run_command(command)` — Writes a command to stdin, waits up to `timeout` seconds for output, drains the queue, and returns the combined response as bytes. + +## Usage Example + +```python +from winexpect import WinExpectSpawn, REPLWrapper + +# Spawn an interactive subprocess +spawn = WinExpectSpawn(command='osqueryi.exe --json', cwd='C:\\osquery') + +# Wrap it with REPL interface +repl = REPLWrapper( + proc=spawn, + orig_prompt='osquery>', + prompt_change=None, + timeout=5 +) + +# Send a query and capture output +result = repl.run_command('SELECT * FROM os_version;') +print(result.decode()) +``` + +> **Note:** Only one query at a time is supported. The `stderr` thread is stubbed out and not yet implemented. Python 2/3 compatibility is handled via a `try/except` import of `Queue`. \ No newline at end of file From 2a83d080e43ecca675025dce3043210e47b2b967 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 23:08:17 +0000 Subject: [PATCH 4/8] chore(docs): Remove 1 orphaned inline files [skip ci] --- .doc-pipeline-status.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .doc-pipeline-status.md diff --git a/.doc-pipeline-status.md b/.doc-pipeline-status.md deleted file mode 100644 index a314c2847a0..00000000000 --- a/.doc-pipeline-status.md +++ /dev/null @@ -1,5 +0,0 @@ -# Documentation Pipeline Started - -Run ID: doc-orchestrator-1779992279215 -Status: In Progress -Started: 2026-05-28 18:18:15 UTC From 8ee0e3e6b0c63bb4db3a262230a06098ee5f2f19 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 23:15:33 +0000 Subject: [PATCH 5/8] docs: Stage 2 - Architecture analysis (11 files) [skip ci] --- docs/diagrams/architecture/.gitignore | 8 + docs/diagrams/architecture/README-10.mmd | 4 + docs/diagrams/architecture/README-11.mmd | 4 + docs/diagrams/architecture/README-2.mmd | 11 + docs/diagrams/architecture/README-3.mmd | 7 + docs/diagrams/architecture/README-4.mmd | 6 + docs/diagrams/architecture/README-5.mmd | 6 + docs/diagrams/architecture/README-6.mmd | 6 + docs/diagrams/architecture/README-7.mmd | 5 + docs/diagrams/architecture/README-8.mmd | 7 + docs/diagrams/architecture/README-9.mmd | 5 + docs/diagrams/architecture/README.mmd | 22 + .../configuration-and-packs-2.mmd | 6 + .../configuration-and-packs-3.mmd | 7 + .../configuration-and-packs-4.mmd | 8 + .../configuration-and-packs-5.mmd | 11 + .../architecture/configuration-and-packs.mmd | 15 + .../architecture/core-init-and-runtime-2.mmd | 15 + .../architecture/core-init-and-runtime-3.mmd | 8 + .../architecture/core-init-and-runtime-4.mmd | 7 + .../architecture/core-init-and-runtime-5.mmd | 9 + .../architecture/core-init-and-runtime-6.mmd | 7 + .../architecture/core-init-and-runtime-7.mmd | 6 + .../architecture/core-init-and-runtime-8.mmd | 9 + .../architecture/core-init-and-runtime.mmd | 16 + .../database-and-storage-plugins-2.mmd | 4 + .../database-and-storage-plugins-3.mmd | 6 + .../database-and-storage-plugins-4.mmd | 5 + .../database-and-storage-plugins-5.mmd | 6 + .../database-and-storage-plugins.mmd | 19 + .../architecture/distributed-querying-2.mmd | 11 + .../architecture/distributed-querying-3.mmd | 6 + .../architecture/distributed-querying-4.mmd | 11 + .../architecture/distributed-querying.mmd | 10 + ...eventing-framework-and-subscriptions-2.mmd | 7 + ...eventing-framework-and-subscriptions-3.mmd | 7 + ...eventing-framework-and-subscriptions-4.mmd | 7 + ...eventing-framework-and-subscriptions-5.mmd | 5 + ...eventing-framework-and-subscriptions-6.mmd | 7 + ...eventing-framework-and-subscriptions-7.mmd | 16 + .../eventing-framework-and-subscriptions.mmd | 9 + .../architecture/extensions-and-ipc-2.mmd | 7 + .../architecture/extensions-and-ipc-3.mmd | 10 + .../architecture/extensions-and-ipc-4.mmd | 4 + .../architecture/extensions-and-ipc-5.mmd | 5 + .../architecture/extensions-and-ipc-6.mmd | 5 + .../architecture/extensions-and-ipc-7.mmd | 6 + .../architecture/extensions-and-ipc.mmd | 12 + .../filesystem-and-path-utilities-2.mmd | 5 + .../filesystem-and-path-utilities-3.mmd | 4 + .../filesystem-and-path-utilities-4.mmd | 5 + .../filesystem-and-path-utilities-5.mmd | 5 + .../filesystem-and-path-utilities-6.mmd | 3 + .../filesystem-and-path-utilities-7.mmd | 4 + .../filesystem-and-path-utilities-8.mmd | 9 + .../filesystem-and-path-utilities.mmd | 21 + .../logging-and-query-observability-2.mmd | 7 + .../logging-and-query-observability-3.mmd | 5 + .../logging-and-query-observability-4.mmd | 4 + .../logging-and-query-observability-5.mmd | 5 + .../logging-and-query-observability-6.mmd | 9 + .../logging-and-query-observability-7.mmd | 5 + .../logging-and-query-observability.mmd | 15 + .../architecture/remote-http-client-2.mmd | 5 + .../architecture/remote-http-client-3.mmd | 4 + .../architecture/remote-http-client-4.mmd | 14 + .../architecture/remote-http-client-5.mmd | 6 + .../architecture/remote-http-client-6.mmd | 9 + .../architecture/remote-http-client.mmd | 13 + .../sql-engine-and-virtual-tables-2.mmd | 5 + .../sql-engine-and-virtual-tables-3.mmd | 4 + .../sql-engine-and-virtual-tables-4.mmd | 5 + .../sql-engine-and-virtual-tables-5.mmd | 12 + .../sql-engine-and-virtual-tables-6.mmd | 10 + .../sql-engine-and-virtual-tables.mmd | 11 + docs/reference/architecture/.gitignore | 8 + docs/reference/architecture/README.md | 366 +++++++++++++++ .../configuration-and-packs.md | 383 ++++++++++++++++ .../core-init-and-runtime.md | 428 ++++++++++++++++++ .../database-and-storage-plugins.md | 357 +++++++++++++++ .../distributed-querying.md | 376 +++++++++++++++ .../eventing-framework-and-subscriptions.md | 363 +++++++++++++++ .../extensions-and-ipc/extensions-and-ipc.md | 375 +++++++++++++++ .../filesystem-and-path-utilities.md | 338 ++++++++++++++ .../logging-and-query-observability.md | 409 +++++++++++++++++ .../remote-http-client/remote-http-client.md | 345 ++++++++++++++ .../sql-engine-and-virtual-tables.md | 351 ++++++++++++++ 87 files changed, 4703 insertions(+) create mode 100644 docs/diagrams/architecture/.gitignore create mode 100644 docs/diagrams/architecture/README-10.mmd create mode 100644 docs/diagrams/architecture/README-11.mmd create mode 100644 docs/diagrams/architecture/README-2.mmd create mode 100644 docs/diagrams/architecture/README-3.mmd create mode 100644 docs/diagrams/architecture/README-4.mmd create mode 100644 docs/diagrams/architecture/README-5.mmd create mode 100644 docs/diagrams/architecture/README-6.mmd create mode 100644 docs/diagrams/architecture/README-7.mmd create mode 100644 docs/diagrams/architecture/README-8.mmd create mode 100644 docs/diagrams/architecture/README-9.mmd create mode 100644 docs/diagrams/architecture/README.mmd create mode 100644 docs/diagrams/architecture/configuration-and-packs-2.mmd create mode 100644 docs/diagrams/architecture/configuration-and-packs-3.mmd create mode 100644 docs/diagrams/architecture/configuration-and-packs-4.mmd create mode 100644 docs/diagrams/architecture/configuration-and-packs-5.mmd create mode 100644 docs/diagrams/architecture/configuration-and-packs.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime-2.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime-3.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime-4.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime-5.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime-6.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime-7.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime-8.mmd create mode 100644 docs/diagrams/architecture/core-init-and-runtime.mmd create mode 100644 docs/diagrams/architecture/database-and-storage-plugins-2.mmd create mode 100644 docs/diagrams/architecture/database-and-storage-plugins-3.mmd create mode 100644 docs/diagrams/architecture/database-and-storage-plugins-4.mmd create mode 100644 docs/diagrams/architecture/database-and-storage-plugins-5.mmd create mode 100644 docs/diagrams/architecture/database-and-storage-plugins.mmd create mode 100644 docs/diagrams/architecture/distributed-querying-2.mmd create mode 100644 docs/diagrams/architecture/distributed-querying-3.mmd create mode 100644 docs/diagrams/architecture/distributed-querying-4.mmd create mode 100644 docs/diagrams/architecture/distributed-querying.mmd create mode 100644 docs/diagrams/architecture/eventing-framework-and-subscriptions-2.mmd create mode 100644 docs/diagrams/architecture/eventing-framework-and-subscriptions-3.mmd create mode 100644 docs/diagrams/architecture/eventing-framework-and-subscriptions-4.mmd create mode 100644 docs/diagrams/architecture/eventing-framework-and-subscriptions-5.mmd create mode 100644 docs/diagrams/architecture/eventing-framework-and-subscriptions-6.mmd create mode 100644 docs/diagrams/architecture/eventing-framework-and-subscriptions-7.mmd create mode 100644 docs/diagrams/architecture/eventing-framework-and-subscriptions.mmd create mode 100644 docs/diagrams/architecture/extensions-and-ipc-2.mmd create mode 100644 docs/diagrams/architecture/extensions-and-ipc-3.mmd create mode 100644 docs/diagrams/architecture/extensions-and-ipc-4.mmd create mode 100644 docs/diagrams/architecture/extensions-and-ipc-5.mmd create mode 100644 docs/diagrams/architecture/extensions-and-ipc-6.mmd create mode 100644 docs/diagrams/architecture/extensions-and-ipc-7.mmd create mode 100644 docs/diagrams/architecture/extensions-and-ipc.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities-2.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities-3.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities-4.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities-5.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities-6.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities-7.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities-8.mmd create mode 100644 docs/diagrams/architecture/filesystem-and-path-utilities.mmd create mode 100644 docs/diagrams/architecture/logging-and-query-observability-2.mmd create mode 100644 docs/diagrams/architecture/logging-and-query-observability-3.mmd create mode 100644 docs/diagrams/architecture/logging-and-query-observability-4.mmd create mode 100644 docs/diagrams/architecture/logging-and-query-observability-5.mmd create mode 100644 docs/diagrams/architecture/logging-and-query-observability-6.mmd create mode 100644 docs/diagrams/architecture/logging-and-query-observability-7.mmd create mode 100644 docs/diagrams/architecture/logging-and-query-observability.mmd create mode 100644 docs/diagrams/architecture/remote-http-client-2.mmd create mode 100644 docs/diagrams/architecture/remote-http-client-3.mmd create mode 100644 docs/diagrams/architecture/remote-http-client-4.mmd create mode 100644 docs/diagrams/architecture/remote-http-client-5.mmd create mode 100644 docs/diagrams/architecture/remote-http-client-6.mmd create mode 100644 docs/diagrams/architecture/remote-http-client.mmd create mode 100644 docs/diagrams/architecture/sql-engine-and-virtual-tables-2.mmd create mode 100644 docs/diagrams/architecture/sql-engine-and-virtual-tables-3.mmd create mode 100644 docs/diagrams/architecture/sql-engine-and-virtual-tables-4.mmd create mode 100644 docs/diagrams/architecture/sql-engine-and-virtual-tables-5.mmd create mode 100644 docs/diagrams/architecture/sql-engine-and-virtual-tables-6.mmd create mode 100644 docs/diagrams/architecture/sql-engine-and-virtual-tables.mmd create mode 100644 docs/reference/architecture/.gitignore create mode 100644 docs/reference/architecture/README.md create mode 100644 docs/reference/architecture/configuration-and-packs/configuration-and-packs.md create mode 100644 docs/reference/architecture/core-init-and-runtime/core-init-and-runtime.md create mode 100644 docs/reference/architecture/database-and-storage-plugins/database-and-storage-plugins.md create mode 100644 docs/reference/architecture/distributed-querying/distributed-querying.md create mode 100644 docs/reference/architecture/eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md create mode 100644 docs/reference/architecture/extensions-and-ipc/extensions-and-ipc.md create mode 100644 docs/reference/architecture/filesystem-and-path-utilities/filesystem-and-path-utilities.md create mode 100644 docs/reference/architecture/logging-and-query-observability/logging-and-query-observability.md create mode 100644 docs/reference/architecture/remote-http-client/remote-http-client.md create mode 100644 docs/reference/architecture/sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md diff --git a/docs/diagrams/architecture/.gitignore b/docs/diagrams/architecture/.gitignore new file mode 100644 index 00000000000..b4f4c7a6c9c --- /dev/null +++ b/docs/diagrams/architecture/.gitignore @@ -0,0 +1,8 @@ +# CodeWiki temp files (dependency graphs can be 7GB+) +temp/ +dependency_graphs/ + +# JSON intermediate files (except schema/config) +*.json +!*-schema.json +!*-config.json diff --git a/docs/diagrams/architecture/README-10.mmd b/docs/diagrams/architecture/README-10.mmd new file mode 100644 index 00000000000..9de352b6a66 --- /dev/null +++ b/docs/diagrams/architecture/README-10.mmd @@ -0,0 +1,4 @@ +flowchart TD + Caller["Module"] --> Client["HTTP Client"] + Client --> TLS["TLS Handshake"] + TLS --> Remote["Remote Server"] diff --git a/docs/diagrams/architecture/README-11.mmd b/docs/diagrams/architecture/README-11.mmd new file mode 100644 index 00000000000..dbc77c3eaf7 --- /dev/null +++ b/docs/diagrams/architecture/README-11.mmd @@ -0,0 +1,4 @@ +flowchart TD + Caller["Subsystem"] --> FS["Filesystem API"] + FS --> POSIX["POSIX Implementation"] + FS --> Windows["Windows Implementation"] diff --git a/docs/diagrams/architecture/README-2.mmd b/docs/diagrams/architecture/README-2.mmd new file mode 100644 index 00000000000..15cf680aafe --- /dev/null +++ b/docs/diagrams/architecture/README-2.mmd @@ -0,0 +1,11 @@ +flowchart TD + Start["Process Start"] --> Init["Initializer"] + Init --> Flags["Parse Flags"] + Init --> Registry["Initialize Registry"] + Init --> DBInit["Initialize Database"] + Init --> ExtMgr["Start Extension Manager"] + Init --> ConfigLoad["Load Configuration"] + ConfigLoad --> Schedule["Build Schedule"] + Schedule --> QueryExec["Execute Queries"] + QueryExec --> Log["Log Results"] + Log --> End["Runtime Loop"] diff --git a/docs/diagrams/architecture/README-3.mmd b/docs/diagrams/architecture/README-3.mmd new file mode 100644 index 00000000000..2bec5262176 --- /dev/null +++ b/docs/diagrams/architecture/README-3.mmd @@ -0,0 +1,7 @@ +flowchart TD + Query["SQL Query"] --> SQLite["SQLite Engine"] + SQLite --> Module["sqlite3_module"] + Module --> VirtualTable["VirtualTable Wrapper"] + VirtualTable --> TablePlugin["TablePlugin"] + TablePlugin --> OS["Operating System Data"] + OS --> Rows["Rows Returned"] diff --git a/docs/diagrams/architecture/README-4.mmd b/docs/diagrams/architecture/README-4.mmd new file mode 100644 index 00000000000..7cb68c8186b --- /dev/null +++ b/docs/diagrams/architecture/README-4.mmd @@ -0,0 +1,6 @@ +flowchart TD + Source["Config Source (File / TLS / Extension)"] --> ConfigCore["Config Singleton"] + ConfigCore --> Parsers["ConfigParserPlugins"] + Parsers --> Schedule["Scheduled Queries"] + Schedule --> Scheduler["Query Scheduler"] + Scheduler --> SQL["SQL Engine"] diff --git a/docs/diagrams/architecture/README-5.mmd b/docs/diagrams/architecture/README-5.mmd new file mode 100644 index 00000000000..15b3d86b3cc --- /dev/null +++ b/docs/diagrams/architecture/README-5.mmd @@ -0,0 +1,6 @@ +flowchart TD + Publisher["EventPublisher"] --> EventFactory["EventFactory"] + Subscriber["EventSubscriber"] --> EventFactory + EventFactory --> Callback["EventCallback"] + Callback --> Storage["Database Storage"] + SQL["SELECT *_events"] --> Subscriber diff --git a/docs/diagrams/architecture/README-6.mmd b/docs/diagrams/architecture/README-6.mmd new file mode 100644 index 00000000000..f6966640dec --- /dev/null +++ b/docs/diagrams/architecture/README-6.mmd @@ -0,0 +1,6 @@ +flowchart TD + Exec["Query Execution"] --> Diff["DiffResults"] + Diff --> LogItem["QueryLogItem"] + LogItem --> Serialize["JSON Serialization"] + Serialize --> Logger["LoggerPlugin"] + Logger --> Sink["External Backend"] diff --git a/docs/diagrams/architecture/README-7.mmd b/docs/diagrams/architecture/README-7.mmd new file mode 100644 index 00000000000..b995aa1bd07 --- /dev/null +++ b/docs/diagrams/architecture/README-7.mmd @@ -0,0 +1,5 @@ +flowchart TD + Modules["Core Modules"] --> Interface["IDatabaseInterface"] + Interface --> Plugin["Database Plugin"] + Plugin --> RocksDB["Persistent Backend"] + Plugin --> Ephemeral["In-Memory Backend"] diff --git a/docs/diagrams/architecture/README-8.mmd b/docs/diagrams/architecture/README-8.mmd new file mode 100644 index 00000000000..f90914fba8e --- /dev/null +++ b/docs/diagrams/architecture/README-8.mmd @@ -0,0 +1,7 @@ +flowchart TD + Server["Central Server"] --> TLS["TLS Distributed Plugin"] + TLS --> Core["Distributed Core"] + Core --> SQL["SQL Engine"] + SQL --> Results["Results"] + Results --> TLS + TLS --> Server diff --git a/docs/diagrams/architecture/README-9.mmd b/docs/diagrams/architecture/README-9.mmd new file mode 100644 index 00000000000..05f3210aec0 --- /dev/null +++ b/docs/diagrams/architecture/README-9.mmd @@ -0,0 +1,5 @@ +flowchart LR + Core["osquery Core"] --> Manager["Extension Manager"] + Extension["External Extension"] --> Manager + Manager --> Registry["RegistryFactory"] + Core --> Extension diff --git a/docs/diagrams/architecture/README.mmd b/docs/diagrams/architecture/README.mmd new file mode 100644 index 00000000000..abd03ad683d --- /dev/null +++ b/docs/diagrams/architecture/README.mmd @@ -0,0 +1,22 @@ +flowchart TD + CLI["osqueryi / osqueryd"] --> Core["Core Init And Runtime"] + + Core --> Config["Configuration And Packs"] + Core --> SQL["SQL Engine And Virtual Tables"] + Core --> Events["Eventing Framework"] + Core --> Logging["Logging And Observability"] + Core --> DB["Database And Storage Plugins"] + Core --> Distributed["Distributed Querying"] + Core --> Extensions["Extensions And IPC"] + Core --> HTTP["Remote HTTP Client"] + Core --> FS["Filesystem And Path Utilities"] + + Config --> SQL + Config --> Events + SQL --> Logging + Events --> DB + Distributed --> SQL + Distributed --> HTTP + Logging --> DB + Extensions --> SQL + Extensions --> DB diff --git a/docs/diagrams/architecture/configuration-and-packs-2.mmd b/docs/diagrams/architecture/configuration-and-packs-2.mmd new file mode 100644 index 00000000000..058d1366de2 --- /dev/null +++ b/docs/diagrams/architecture/configuration-and-packs-2.mmd @@ -0,0 +1,6 @@ +flowchart TD + Start["ConfigRefreshRunner Start"] --> Wait["Sleep refresh_sec"] + Wait --> Check["Interrupted?"] + Check -->|No| Refresh["Config.refresh()"] + Refresh --> Wait + Check -->|Yes| End["End"] diff --git a/docs/diagrams/architecture/configuration-and-packs-3.mmd b/docs/diagrams/architecture/configuration-and-packs-3.mmd new file mode 100644 index 00000000000..14e23b18fcf --- /dev/null +++ b/docs/diagrams/architecture/configuration-and-packs-3.mmd @@ -0,0 +1,7 @@ +flowchart TD + ConfigUpdate["Config Update"] --> BuildPacks["Create Pack Objects"] + BuildPacks --> DiscoveryCheck["Discovery Queries"] + DiscoveryCheck --> ActivePack["Active Pack"] + ActivePack --> Scheduler["ScheduledQuery Execution"] + Scheduler --> Performance["Query Performance Recording"] + Scheduler --> Denylist["Denylist On Failure"] diff --git a/docs/diagrams/architecture/configuration-and-packs-4.mmd b/docs/diagrams/architecture/configuration-and-packs-4.mmd new file mode 100644 index 00000000000..31827beb892 --- /dev/null +++ b/docs/diagrams/architecture/configuration-and-packs-4.mmd @@ -0,0 +1,8 @@ +flowchart TD + ConfigLoad["Config Load"] --> RunLoad["Run Load Decorators"] + QueryExec["Query Execution"] --> RunAlways["Run Always Decorators"] + IntervalTick["Interval Timer"] --> RunInterval["Run Interval Decorators"] + RunLoad --> Decorations[("Decoration Store")] + RunAlways --> Decorations + RunInterval --> Decorations + Decorations --> Logger["Status And Query Logs"] diff --git a/docs/diagrams/architecture/configuration-and-packs-5.mmd b/docs/diagrams/architecture/configuration-and-packs-5.mmd new file mode 100644 index 00000000000..91bbcadc931 --- /dev/null +++ b/docs/diagrams/architecture/configuration-and-packs-5.mmd @@ -0,0 +1,11 @@ +flowchart TD + Source["Config Source"] --> GenConfig["genConfig()"] + GenConfig --> Update["Config.update()"] + Update --> Purge["Purge Stale State"] + Purge --> UpdateSource["updateSource()"] + UpdateSource --> ParseJSON["Validate And Parse JSON"] + ParseJSON --> BuildSchedule["Create Packs And Schedule"] + BuildSchedule --> ApplyParsers["Apply ConfigParserPlugins"] + ApplyParsers --> Reconfigure["Registry.configure()"] + Reconfigure --> EventNotify["EventFactory.configUpdate()"] + EventNotify --> Ready["Runtime Updated"] diff --git a/docs/diagrams/architecture/configuration-and-packs.mmd b/docs/diagrams/architecture/configuration-and-packs.mmd new file mode 100644 index 00000000000..3a720d6d6c3 --- /dev/null +++ b/docs/diagrams/architecture/configuration-and-packs.mmd @@ -0,0 +1,15 @@ +flowchart TD + ConfigPlugin["Config Plugin Registry"] --> ConfigCore["Config Singleton"] + ConfigCore --> RefreshRunner["ConfigRefreshRunner"] + ConfigCore --> Schedule["Schedule"] + Schedule --> Pack["Pack"] + ConfigCore --> Parsers["ConfigParserPlugins"] + Parsers --> OptionsParser["Options Parser"] + Parsers --> FilePathsParser["File Paths Parser"] + Parsers --> DecoratorsParser["Decorators Parser"] + Parsers --> EventsParser["Events Parser"] + Parsers --> ViewsParser["Views Parser"] + ConfigCore --> Database[("Persistent Database")] + Schedule --> Logging["Logging And Query Observability"] + Schedule --> SQLEngine["SQL Engine And Virtual Tables"] + Parsers --> EventFramework["Eventing Framework"] diff --git a/docs/diagrams/architecture/core-init-and-runtime-2.mmd b/docs/diagrams/architecture/core-init-and-runtime-2.mmd new file mode 100644 index 00000000000..12303b48e3e --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime-2.mmd @@ -0,0 +1,15 @@ +sequenceDiagram + participant Main + participant Initializer + participant Registry + participant Config + participant Extensions + participant Events + + Main->>Initializer: Construct(argc, argv) + Initializer->>Initializer: Parse flags + Initializer->>Registry: registryAndPluginInit() + Initializer->>Extensions: startExtensionManager() + Initializer->>Config: load() + Initializer->>Events: attachEvents() + Initializer->>Initializer: start() diff --git a/docs/diagrams/architecture/core-init-and-runtime-3.mmd b/docs/diagrams/architecture/core-init-and-runtime-3.mmd new file mode 100644 index 00000000000..6f1c2a8d3fd --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime-3.mmd @@ -0,0 +1,8 @@ +flowchart LR + Macro["FLAG / CLI_FLAG Macros"] --> FlagCreate["Flag::create()"] + FlagCreate --> FlagRegistry["Internal Flag Map"] + + CLI["Command Line"] --> Parse["ParseCommandLineFlags"] + Config["Config Options"] --> Update["Flag::updateValue()"] + + FlagRegistry --> Runtime["Runtime Access"] diff --git a/docs/diagrams/architecture/core-init-and-runtime-4.mmd b/docs/diagrams/architecture/core-init-and-runtime-4.mmd new file mode 100644 index 00000000000..3464c298d66 --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime-4.mmd @@ -0,0 +1,7 @@ +flowchart TD + Watcher["Watcher Process"] --> Spawn["Spawn Worker"] + Spawn --> Monitor["Monitor CPU And Memory"] + Monitor --> Check{{"Limit Exceeded?"}} + Check -->|"No"| Continue["Continue Monitoring"] + Check -->|"Yes"| Kill["Stop Worker"] + Kill --> Respawn["Respawn Worker"] diff --git a/docs/diagrams/architecture/core-init-and-runtime-5.mmd b/docs/diagrams/architecture/core-init-and-runtime-5.mmd new file mode 100644 index 00000000000..50b08395e59 --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime-5.mmd @@ -0,0 +1,9 @@ +flowchart TD + AnyThread["Any Thread"] --> Request["requestShutdown()"] + Request --> Signal["Condition Variable"] + Signal --> MainThread["Main Thread"] + MainThread --> StopServices["Dispatcher::stopServices()"] + StopServices --> Join["Join Services"] + Join --> StopEvents["EventFactory::end()"] + StopEvents --> CloseDB["shutdownDatabase()"] + CloseDB --> Exit["Return Exit Code"] diff --git a/docs/diagrams/architecture/core-init-and-runtime-6.mmd b/docs/diagrams/architecture/core-init-and-runtime-6.mmd new file mode 100644 index 00000000000..83e50573800 --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime-6.mmd @@ -0,0 +1,7 @@ +flowchart LR + Flag["host_identifier Flag"] --> Mode{{"Mode"}} + Mode -->|"uuid"| Hardware["Hardware UUID"] + Mode -->|"instance"| Instance["Instance UUID"] + Mode -->|"ephemeral"| Ephemeral["Random UUID"] + Mode -->|"specified"| Specified["Configured Identifier"] + Mode -->|"default"| Hostname["System Hostname"] diff --git a/docs/diagrams/architecture/core-init-and-runtime-7.mmd b/docs/diagrams/architecture/core-init-and-runtime-7.mmd new file mode 100644 index 00000000000..0533bd82ae3 --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime-7.mmd @@ -0,0 +1,6 @@ +flowchart TD + SQL["SQL Query"] --> SQLite["SQLite Virtual Table API"] + SQLite --> QueryContext["QueryContext"] + QueryContext --> ConstraintMap["ConstraintMap"] + ConstraintMap --> ConstraintList["ConstraintList"] + ConstraintList --> TablePlugin["TablePlugin::generate()"] diff --git a/docs/diagrams/architecture/core-init-and-runtime-8.mmd b/docs/diagrams/architecture/core-init-and-runtime-8.mmd new file mode 100644 index 00000000000..dd5a802fe24 --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime-8.mmd @@ -0,0 +1,9 @@ +flowchart TD + Core["Core Init And Runtime"] + + Core --> Config["Configuration And Packs"] + Core --> Logging["Logging And Query Observability"] + Core --> Database["Database And Storage Plugins"] + Core --> SQL["SQL Engine And Virtual Tables"] + Core --> Events["Eventing Framework And Subscriptions"] + Core --> Extensions["Extensions And IPC"] diff --git a/docs/diagrams/architecture/core-init-and-runtime.mmd b/docs/diagrams/architecture/core-init-and-runtime.mmd new file mode 100644 index 00000000000..75bf4fac1e6 --- /dev/null +++ b/docs/diagrams/architecture/core-init-and-runtime.mmd @@ -0,0 +1,16 @@ +flowchart TD + Main["Main Entry Point"] --> Initializer["Initializer"] + + Initializer --> Flags["Flag System"] + Initializer --> Registry["Registry And Plugins"] + Initializer --> Database["Database Initialization"] + Initializer --> ExtensionManager["Extension Manager"] + Initializer --> Events["Event Factory"] + Initializer --> Watchdog["Watcher / Worker Model"] + + Watchdog --> Worker["Worker Process"] + Watchdog --> Extensions["Extension Processes"] + + Worker --> Tables["Virtual Tables"] + Worker --> Scheduler["Query Scheduler"] + Worker --> Logger["Logger Plugins"] diff --git a/docs/diagrams/architecture/database-and-storage-plugins-2.mmd b/docs/diagrams/architecture/database-and-storage-plugins-2.mmd new file mode 100644 index 00000000000..605504b8b71 --- /dev/null +++ b/docs/diagrams/architecture/database-and-storage-plugins-2.mmd @@ -0,0 +1,4 @@ +flowchart TD + DB["EphemeralDatabasePlugin"] --> Map["std::map>"] + Map --> DomainMap["Domain Map"] + DomainMap --> KeyValue["boost::variant"] diff --git a/docs/diagrams/architecture/database-and-storage-plugins-3.mmd b/docs/diagrams/architecture/database-and-storage-plugins-3.mmd new file mode 100644 index 00000000000..cea2ef35f02 --- /dev/null +++ b/docs/diagrams/architecture/database-and-storage-plugins-3.mmd @@ -0,0 +1,6 @@ +flowchart TD + Start["Startup"] --> CheckFlag{{"disable_database?"}} + CheckFlag -->|"No"| UseRocksDB["Activate RocksDB Plugin"] + CheckFlag -->|"Yes"| UseEphemeral["Activate Ephemeral Plugin"] + UseRocksDB --> InitDone["Database Initialized"] + UseEphemeral --> InitDone diff --git a/docs/diagrams/architecture/database-and-storage-plugins-4.mmd b/docs/diagrams/architecture/database-and-storage-plugins-4.mmd new file mode 100644 index 00000000000..721f5928c67 --- /dev/null +++ b/docs/diagrams/architecture/database-and-storage-plugins-4.mmd @@ -0,0 +1,5 @@ +flowchart TD + ResetCall["resetDatabase()"] --> LockWrite["Acquire Write Lock"] + LockWrite --> TearDown["Plugin tearDown()"] + TearDown --> SetUp["Plugin setUp()"] + SetUp --> Unlock["Release Lock"] diff --git a/docs/diagrams/architecture/database-and-storage-plugins-5.mmd b/docs/diagrams/architecture/database-and-storage-plugins-5.mmd new file mode 100644 index 00000000000..eed79ca554a --- /dev/null +++ b/docs/diagrams/architecture/database-and-storage-plugins-5.mmd @@ -0,0 +1,6 @@ +flowchart TD + ReadVersion["Read results_version"] --> Compare["Compare with target version"] + Compare -->|"Older"| RunMigration["Execute migrateVxVy()"] + RunMigration --> UpdateVersion["Persist new version"] + UpdateVersion --> Compare + Compare -->|"Match"| Done["Migration Complete"] diff --git a/docs/diagrams/architecture/database-and-storage-plugins.mmd b/docs/diagrams/architecture/database-and-storage-plugins.mmd new file mode 100644 index 00000000000..a4efc77fa93 --- /dev/null +++ b/docs/diagrams/architecture/database-and-storage-plugins.mmd @@ -0,0 +1,19 @@ +flowchart TD + CoreModules["Core And Feature Modules"] --> DBInterface["IDatabaseInterface"] + DBInterface --> OsqueryDB["OsqueryDatabase"] + OsqueryDB --> RegistryLayer["Database Plugin Registry"] + RegistryLayer --> RocksDBPlugin["RocksDB Plugin (Persistent)"] + RegistryLayer --> EphemeralPlugin["Ephemeral Database Plugin (In-Memory)"] + + subgraph storage_domains["Logical Storage Domains"] + PersistentSettings["configurations"] + Queries["queries"] + Events["events"] + Logs["logs"] + Carves["carves"] + Distributed["distributed"] + QueryPerformance["query_performance"] + end + + RocksDBPlugin --> storage_domains + EphemeralPlugin --> storage_domains diff --git a/docs/diagrams/architecture/distributed-querying-2.mmd b/docs/diagrams/architecture/distributed-querying-2.mmd new file mode 100644 index 00000000000..af1e3628736 --- /dev/null +++ b/docs/diagrams/architecture/distributed-querying-2.mmd @@ -0,0 +1,11 @@ +flowchart TD + Start["Pull Updates"] --> Accept["acceptWork()"] + Accept --> Pending{"Pending Queries?"} + Pending -->|"Yes"| Run["runQueries()"] + Run --> Deny{"Denylisted?"} + Deny -->|"No"| Execute["Execute via SQL Engine"] + Execute --> Record["recordQueryPerformance()"] + Record --> Queue["addResult()"] + Queue --> Flush["flushCompleted()"] + Deny -->|"Yes"| Skip["Skip Execution"] + Flush --> End["Cycle Complete"] diff --git a/docs/diagrams/architecture/distributed-querying-3.mmd b/docs/diagrams/architecture/distributed-querying-3.mmd new file mode 100644 index 00000000000..2850535d935 --- /dev/null +++ b/docs/diagrams/architecture/distributed-querying-3.mmd @@ -0,0 +1,6 @@ +flowchart TD + Incoming["Incoming Query"] --> Check["checkAndSetAsRunning()"] + Check --> Running{"Already Running?"} + Running -->|"Within Duration"| Block["Denylisted"] + Running -->|"Expired"| Allow["Allow Execution"] + Check -->|"First Time"| Allow diff --git a/docs/diagrams/architecture/distributed-querying-4.mmd b/docs/diagrams/architecture/distributed-querying-4.mmd new file mode 100644 index 00000000000..da9275ef2f4 --- /dev/null +++ b/docs/diagrams/architecture/distributed-querying-4.mmd @@ -0,0 +1,11 @@ +sequenceDiagram + participant Node + participant TLS as "TLS Distributed Plugin" + participant Server + + Node->>TLS: pullUpdates() + TLS->>Server: POST read endpoint + Server->>TLS: JSON queries + TLS->>Node: return JSON + Node->>TLS: writeResults(JSON) + TLS->>Server: POST write endpoint diff --git a/docs/diagrams/architecture/distributed-querying.mmd b/docs/diagrams/architecture/distributed-querying.mmd new file mode 100644 index 00000000000..c57619110a1 --- /dev/null +++ b/docs/diagrams/architecture/distributed-querying.mmd @@ -0,0 +1,10 @@ +flowchart TD + Server["Central Server"] -->|"HTTPS"| TLSPlugin["TLS Distributed Plugin"] + TLSPlugin -->|"getQueries()"| DistributedCore["Distributed Core"] + DistributedCore -->|"runQueries()"| SQLEngine["SQL Engine"] + SQLEngine -->|"QueryData"| DistributedCore + DistributedCore -->|"writeResults()"| TLSPlugin + TLSPlugin -->|"HTTPS"| Server + + DistributedCore -->|"recordQueryPerformance"| Observability["Query Observability"] + DistributedCore -->|"running state"| Storage["Database Plugins"] diff --git a/docs/diagrams/architecture/eventing-framework-and-subscriptions-2.mmd b/docs/diagrams/architecture/eventing-framework-and-subscriptions-2.mmd new file mode 100644 index 00000000000..0f4dd5e4690 --- /dev/null +++ b/docs/diagrams/architecture/eventing-framework-and-subscriptions-2.mmd @@ -0,0 +1,7 @@ +flowchart TD + RegisterSub["registerEventSubscriber()"] --> SetupSub["setUp()"] + SetupSub --> InitSub["init()"] + InitSub --> RunningSub["EVENT_RUNNING"] + + RegisterPub["registerEventPublisher()"] --> SetupPub["setUp()"] + SetupPub --> ReadyPub["EVENT_SETUP"] diff --git a/docs/diagrams/architecture/eventing-framework-and-subscriptions-3.mmd b/docs/diagrams/architecture/eventing-framework-and-subscriptions-3.mmd new file mode 100644 index 00000000000..22c93bf1ac5 --- /dev/null +++ b/docs/diagrams/architecture/eventing-framework-and-subscriptions-3.mmd @@ -0,0 +1,7 @@ +flowchart TD + Delay["delay()"] --> ThreadSpawn["spawn thread per publisher"] + ThreadSpawn --> RunLoop["run(type_id)"] + RunLoop --> PublisherRun["publisher->run()"] + PublisherRun --> Pause["pause(200ms)"] + Pause --> RunLoop + RunLoop -->|"isEnding()"| TearDown["tearDown()"] diff --git a/docs/diagrams/architecture/eventing-framework-and-subscriptions-4.mmd b/docs/diagrams/architecture/eventing-framework-and-subscriptions-4.mmd new file mode 100644 index 00000000000..13f5eab16f8 --- /dev/null +++ b/docs/diagrams/architecture/eventing-framework-and-subscriptions-4.mmd @@ -0,0 +1,7 @@ +flowchart TD + Callback["EventCallback"] --> AddBatch["addBatch()"] + AddBatch --> AssignID["generateEventIdentifier()"] + AssignID --> Persist["Database write"] + Persist --> IndexUpdate["update time index"] + + ExpireCheck["expireEventBatches()"] --> RemoveOld["delete expired batches"] diff --git a/docs/diagrams/architecture/eventing-framework-and-subscriptions-5.mmd b/docs/diagrams/architecture/eventing-framework-and-subscriptions-5.mmd new file mode 100644 index 00000000000..e165a93a604 --- /dev/null +++ b/docs/diagrams/architecture/eventing-framework-and-subscriptions-5.mmd @@ -0,0 +1,5 @@ +flowchart TD + SQLQuery["SELECT * FROM example_events"] --> GenTable["genTable()"] + GenTable --> GenerateRows["generateRows()"] + GenerateRows --> FilterTime["apply start/stop window"] + FilterTime --> YieldRows["yield Row to SQL engine"] diff --git a/docs/diagrams/architecture/eventing-framework-and-subscriptions-6.mmd b/docs/diagrams/architecture/eventing-framework-and-subscriptions-6.mmd new file mode 100644 index 00000000000..2de3bd7f42b --- /dev/null +++ b/docs/diagrams/architecture/eventing-framework-and-subscriptions-6.mmd @@ -0,0 +1,7 @@ +flowchart LR + FactoryLock["factory_lock_"] --> ProtectPub["event_pubs_"] + FactoryLock --> ProtectSub["event_subs_"] + + SubscriberLock1["event_id_lock_"] --> IDGen["EventID generation"] + SubscriberLock2["event_record_lock_"] --> RecordWrite["record time bins"] + SubscriberLock3["event_query_record_"] --> QueryTracking["query usage tracking"] diff --git a/docs/diagrams/architecture/eventing-framework-and-subscriptions-7.mmd b/docs/diagrams/architecture/eventing-framework-and-subscriptions-7.mmd new file mode 100644 index 00000000000..58804b2f0a6 --- /dev/null +++ b/docs/diagrams/architecture/eventing-framework-and-subscriptions-7.mmd @@ -0,0 +1,16 @@ +sequenceDiagram + participant Config + participant Factory as EventFactory + participant Pub as EventPublisher + participant Sub as EventSubscriber + participant DB as Database + participant SQL + + Config->>Factory: configUpdate() + Factory->>Sub: setMinExpiry() + Factory->>Pub: spawn run loop + Pub->>Sub: fire event (callback) + Sub->>DB: addBatch() + SQL->>Sub: SELECT from event table + Sub->>DB: generateRows() + Sub->>SQL: yield rows diff --git a/docs/diagrams/architecture/eventing-framework-and-subscriptions.mmd b/docs/diagrams/architecture/eventing-framework-and-subscriptions.mmd new file mode 100644 index 00000000000..169b0f41f29 --- /dev/null +++ b/docs/diagrams/architecture/eventing-framework-and-subscriptions.mmd @@ -0,0 +1,9 @@ +flowchart TD + Config["Configuration And Packs"] -->|"scheduled queries"| EventFactory["EventFactory"] + EventFactory -->|"registers"| Publisher["EventPublisherPlugin"] + EventFactory -->|"registers"| Subscriber["EventSubscriberPlugin"] + Subscriber -->|"creates"| SubscriptionObj["Subscription"] + Publisher -->|"dispatches"| Callback["EventCallback"] + Callback -->|"addBatch()"| Storage["Database And Storage Plugins"] + SQL["SQL Engine And Virtual Tables"] -->|"SELECT from _events tables"| Subscriber + Subscriber -->|"generateRows()"| SQL diff --git a/docs/diagrams/architecture/extensions-and-ipc-2.mmd b/docs/diagrams/architecture/extensions-and-ipc-2.mmd new file mode 100644 index 00000000000..566b1b1c48c --- /dev/null +++ b/docs/diagrams/architecture/extensions-and-ipc-2.mmd @@ -0,0 +1,7 @@ +flowchart TD + Client["ExtensionClient"] --> Transport["Thrift Transport"] + Transport --> Socket["UNIX Socket or Named Pipe"] + Socket --> Server["ExtensionRunner or ManagerRunner"] + Server --> Handler["ExtensionHandler or ExtensionManagerHandler"] + Handler --> Interface["ExtensionInterface / ExtensionManagerInterface"] + Interface --> Registry["RegistryFactory"] diff --git a/docs/diagrams/architecture/extensions-and-ipc-3.mmd b/docs/diagrams/architecture/extensions-and-ipc-3.mmd new file mode 100644 index 00000000000..9aabee40835 --- /dev/null +++ b/docs/diagrams/architecture/extensions-and-ipc-3.mmd @@ -0,0 +1,10 @@ +sequenceDiagram + participant Ext as Extension Process + participant EM as Extension Manager + participant Reg as RegistryFactory + + Ext->>EM: registerExtension(info, registry) + EM->>EM: Validate name uniqueness + EM->>EM: Check SDK compatibility + EM->>Reg: addBroadcast(uuid, registry) + EM-->>Ext: Return UUID diff --git a/docs/diagrams/architecture/extensions-and-ipc-4.mmd b/docs/diagrams/architecture/extensions-and-ipc-4.mmd new file mode 100644 index 00000000000..642a20aeb10 --- /dev/null +++ b/docs/diagrams/architecture/extensions-and-ipc-4.mmd @@ -0,0 +1,4 @@ +flowchart LR + SQL["SQL Engine"] --> ExternalSQL["ExternalSQLPlugin"] + ExternalSQL --> ManagerClient["ExtensionManagerClient"] + ManagerClient --> Extension["Extension Process"] diff --git a/docs/diagrams/architecture/extensions-and-ipc-5.mmd b/docs/diagrams/architecture/extensions-and-ipc-5.mmd new file mode 100644 index 00000000000..a4a24d258ed --- /dev/null +++ b/docs/diagrams/architecture/extensions-and-ipc-5.mmd @@ -0,0 +1,5 @@ +flowchart TD + Watcher["ExtensionManagerWatcher"] --> UUIDs["Registered UUIDs"] + UUIDs --> Ping["Ping Extension"] + Ping -->|"Success"| Keep["Keep Registered"] + Ping -->|"Failure x2"| Remove["Remove Broadcast Route"] diff --git a/docs/diagrams/architecture/extensions-and-ipc-6.mmd b/docs/diagrams/architecture/extensions-and-ipc-6.mmd new file mode 100644 index 00000000000..1844fc82272 --- /dev/null +++ b/docs/diagrams/architecture/extensions-and-ipc-6.mmd @@ -0,0 +1,5 @@ +flowchart TD + Start["Core Start"] --> StartManager["startExtensionManager()"] + StartManager --> Watcher["ExtensionManagerWatcher"] + Watcher --> Runner["ExtensionManagerRunner"] + Runner --> Listen["Thrift Listen"] diff --git a/docs/diagrams/architecture/extensions-and-ipc-7.mmd b/docs/diagrams/architecture/extensions-and-ipc-7.mmd new file mode 100644 index 00000000000..82e492bb100 --- /dev/null +++ b/docs/diagrams/architecture/extensions-and-ipc-7.mmd @@ -0,0 +1,6 @@ +flowchart TD + ExtStart["Extension Binary Start"] --> Connect["Connect to Manager Socket"] + Connect --> Register["registerExtension()"] + Register --> UUID["Receive UUID"] + UUID --> StartServer["Start ExtensionRunner"] + StartServer --> Serve["Serve Thrift Requests"] diff --git a/docs/diagrams/architecture/extensions-and-ipc.mmd b/docs/diagrams/architecture/extensions-and-ipc.mmd new file mode 100644 index 00000000000..343a33e6b65 --- /dev/null +++ b/docs/diagrams/architecture/extensions-and-ipc.mmd @@ -0,0 +1,12 @@ +flowchart LR + Core["osquery Core"] -->|"Starts"| Manager["Extension Manager"] + Manager -->|"Registers routes"| Registry["RegistryFactory"] + + ExtensionProc["Extension Process"] -->|"registerExtension()"| Manager + Manager -->|"Assign UUID"| ExtensionProc + + Core -->|"callExtension()"| Manager + Manager -->|"Route to UUID"| ExtensionProc + + ExtensionProc -->|"Response"| Manager + Manager -->|"PluginResponse"| Core diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities-2.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities-2.mmd new file mode 100644 index 00000000000..58aae8b080a --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities-2.mmd @@ -0,0 +1,5 @@ +flowchart TD + Open["Open PlatformFile"] --> Validate["Handle Valid?"] + Validate -->|No| Fail["Return Error"] + Validate -->|Yes| IO["Read / Write / Seek"] + IO --> Close["Destructor Closes Handle"] diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities-3.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities-3.mmd new file mode 100644 index 00000000000..26f4fd78970 --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities-3.mmd @@ -0,0 +1,4 @@ +flowchart LR + Path["File Path"] --> StatCall["platformStat()"] + StatCall -->|POSIX| POSIXStat["struct stat"] + StatCall -->|Windows| WinStat["WINDOWS_STAT"] diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities-4.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities-4.mmd new file mode 100644 index 00000000000..8f8a50b070d --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities-4.mmd @@ -0,0 +1,5 @@ +flowchart TD + Pattern["Input Pattern"] --> Normalize["replaceGlobWildcards()"] + Normalize --> Expand["platformGlob()"] + Expand --> Filter["Apply GLOB_FILES / GLOB_FOLDERS"] + Filter --> Results["Resolved Paths"] diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities-5.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities-5.mmd new file mode 100644 index 00000000000..9efad4a8202 --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities-5.mmd @@ -0,0 +1,5 @@ +flowchart TD + ReadCall["readFile()"] --> Open["PlatformFile"] + Open --> SizeCheck["checkFileReadLimit()"] + SizeCheck --> Loop["Block Read Loop"] + Loop --> Predicate["Callback / Buffer"] diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities-6.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities-6.mmd new file mode 100644 index 00000000000..8b63dd13550 --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities-6.mmd @@ -0,0 +1,3 @@ +flowchart LR + getMounted["getMountedFilesystems()"] --> MountInfo["MountInformation"] + MountInfo --> StatFS["StatFsInfo"] diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities-7.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities-7.mmd new file mode 100644 index 00000000000..60fca930f80 --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities-7.mmd @@ -0,0 +1,4 @@ +flowchart TD + Proc["/proc"] --> Enumerate["procEnumerateProcesses()"] + Enumerate --> FDEnum["procEnumerateProcessDescriptors()"] + FDEnum --> SocketMap["SocketInodeToProcessInfoMap"] diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities-8.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities-8.mmd new file mode 100644 index 00000000000..1359e2b787f --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities-8.mmd @@ -0,0 +1,9 @@ +flowchart TD + File["Candidate File"] --> Exists["Exists?"] + Exists -->|No| Reject["Reject"] + Exists --> Owner["Owner Root or Current User?"] + Owner -->|No| Reject + Owner --> Writable["World Writable?"] + Writable -->|Yes| Reject + Writable --> Exec["Executable Required?"] + Exec --> Accept["Safe"] diff --git a/docs/diagrams/architecture/filesystem-and-path-utilities.mmd b/docs/diagrams/architecture/filesystem-and-path-utilities.mmd new file mode 100644 index 00000000000..44c2abfbebc --- /dev/null +++ b/docs/diagrams/architecture/filesystem-and-path-utilities.mmd @@ -0,0 +1,21 @@ +flowchart TD + Caller["Core / SQL / Extensions"] --> FSAPI["Filesystem API"] + + subgraph abstraction_layer["Cross-Platform Abstraction"] + FSAPI --> PlatformFile["PlatformFile"] + FSAPI --> Glob["platformGlob()"] + FSAPI --> Perms["platformChmod()"] + FSAPI --> Access["platformAccess()"] + FSAPI --> Stat["platformStat() / lstat()"] + end + + subgraph posix_layer["POSIX Implementation"] + PlatformFile --> POSIXFile["open/read/write/lstat"] + Glob --> POSIXGlob["glob()"] + end + + subgraph windows_layer["Windows Implementation"] + PlatformFile --> WinFile["CreateFile / ReadFile"] + Glob --> WinGlob["FindFirstFileW"] + Perms --> WinACL["ACL Manipulation"] + end diff --git a/docs/diagrams/architecture/logging-and-query-observability-2.mmd b/docs/diagrams/architecture/logging-and-query-observability-2.mmd new file mode 100644 index 00000000000..2feabab2fc7 --- /dev/null +++ b/docs/diagrams/architecture/logging-and-query-observability-2.mmd @@ -0,0 +1,7 @@ +flowchart TD + Init["System Initialization"] --> Buffer["Buffer Early Status Logs"] + Buffer --> LoadConfig["Load Configuration"] + LoadConfig --> Discover["Discover Logger Plugin"] + Discover --> InitLogger["LoggerPlugin.init()"] + InitLogger --> Flush["Flush Buffered StatusLogLine Entries"] + Flush --> Runtime["Runtime Logging"] diff --git a/docs/diagrams/architecture/logging-and-query-observability-3.mmd b/docs/diagrams/architecture/logging-and-query-observability-3.mmd new file mode 100644 index 00000000000..7c5a848377e --- /dev/null +++ b/docs/diagrams/architecture/logging-and-query-observability-3.mmd @@ -0,0 +1,5 @@ +flowchart LR + Old["Previous Results"] --> DiffEngine["diff()"] + New["Current Results"] --> DiffEngine + DiffEngine --> Added["Added Rows"] + DiffEngine --> Removed["Removed Rows"] diff --git a/docs/diagrams/architecture/logging-and-query-observability-4.mmd b/docs/diagrams/architecture/logging-and-query-observability-4.mmd new file mode 100644 index 00000000000..93386e9e467 --- /dev/null +++ b/docs/diagrams/architecture/logging-and-query-observability-4.mmd @@ -0,0 +1,4 @@ +flowchart TD + Diff["DiffResults"] --> Serialize["serializeDiffResults()"] + Serialize --> JSONDoc["JSON Document"] + JSONDoc --> Logger["LoggerPlugin"] diff --git a/docs/diagrams/architecture/logging-and-query-observability-5.mmd b/docs/diagrams/architecture/logging-and-query-observability-5.mmd new file mode 100644 index 00000000000..30d12735a02 --- /dev/null +++ b/docs/diagrams/architecture/logging-and-query-observability-5.mmd @@ -0,0 +1,5 @@ +flowchart TD + Item["QueryLogItem"] --> JSON1["serializeQueryLogItem()"] + Item --> JSON2["serializeQueryLogItemJSON()"] + Item --> Events1["serializeQueryLogItemAsEvents()"] + Item --> Events2["serializeQueryLogItemAsEventsJSON()"] diff --git a/docs/diagrams/architecture/logging-and-query-observability-6.mmd b/docs/diagrams/architecture/logging-and-query-observability-6.mmd new file mode 100644 index 00000000000..99c77b8ea17 --- /dev/null +++ b/docs/diagrams/architecture/logging-and-query-observability-6.mmd @@ -0,0 +1,9 @@ +flowchart TD + Config["ScheduledQuery"] --> Exec["SQL Execution"] + Exec --> Results["QueryDataTyped"] + Results --> QueryState["Query.addNewResults()"] + QueryState --> Diff["DiffResults"] + Diff --> LogItem["QueryLogItem"] + LogItem --> Serialize["JSON Serialization"] + Serialize --> Logger["LoggerPlugin"] + Logger --> Sink["External Log System"] diff --git a/docs/diagrams/architecture/logging-and-query-observability-7.mmd b/docs/diagrams/architecture/logging-and-query-observability-7.mmd new file mode 100644 index 00000000000..88b889b3b8e --- /dev/null +++ b/docs/diagrams/architecture/logging-and-query-observability-7.mmd @@ -0,0 +1,5 @@ +flowchart TD + SQ["ScheduledQuery snapshot=true"] --> Exec["Execute Query"] + Exec --> Full["Full Result Set"] + Full --> LogItem["QueryLogItem isSnapshot=true"] + LogItem --> Logger["logSnapshot()"] diff --git a/docs/diagrams/architecture/logging-and-query-observability.mmd b/docs/diagrams/architecture/logging-and-query-observability.mmd new file mode 100644 index 00000000000..82b06f91f54 --- /dev/null +++ b/docs/diagrams/architecture/logging-and-query-observability.mmd @@ -0,0 +1,15 @@ +flowchart TD + Config["Configuration And Packs"] --> Scheduler["Scheduled Query"] + Scheduler --> SQ["ScheduledQuery Struct"] + SQ --> QueryObj["Query State Manager"] + QueryObj --> DB["Database And Storage Plugins"] + + QueryObj --> Diff["DiffResults Engine"] + Diff --> LogItem["QueryLogItem"] + LogItem --> Serializer["JSON Serialization"] + Serializer --> Logger["LoggerPlugin"] + + StatusLogs["Internal Status Events"] --> StatusLine["StatusLogLine"] + StatusLine --> Logger + + Logger --> External["External Logging Backend"] diff --git a/docs/diagrams/architecture/remote-http-client-2.mmd b/docs/diagrams/architecture/remote-http-client-2.mmd new file mode 100644 index 00000000000..810bffb2210 --- /dev/null +++ b/docs/diagrams/architecture/remote-http-client-2.mmd @@ -0,0 +1,5 @@ +flowchart LR + OldOpts["Current Options"] --> Compare["operator=="] + NewOpts["New Options"] --> Compare + Compare -->|"Different"| Reconfigure["Reinitialize Connection"] + Compare -->|"Same"| Reuse["Reuse Existing Settings"] diff --git a/docs/diagrams/architecture/remote-http-client-3.mmd b/docs/diagrams/architecture/remote-http-client-3.mmd new file mode 100644 index 00000000000..6445d351559 --- /dev/null +++ b/docs/diagrams/architecture/remote-http-client-3.mmd @@ -0,0 +1,4 @@ +flowchart TD + Response["HTTP_Response"] --> Headers["Headers"] + Headers --> Iterator["Iterator"] + Iterator --> Pair["(name, value)"] diff --git a/docs/diagrams/architecture/remote-http-client-4.mmd b/docs/diagrams/architecture/remote-http-client-4.mmd new file mode 100644 index 00000000000..52c275c5be9 --- /dev/null +++ b/docs/diagrams/architecture/remote-http-client-4.mmd @@ -0,0 +1,14 @@ +sequenceDiagram + participant Caller + participant Client + participant Resolver + participant Server + + Caller->>Client: get(Request) + Client->>Resolver: async_resolve() + Resolver-->>Client: endpoints + Client->>Server: async_connect() + Client->>Server: async_write(request) + Server-->>Client: HTTP response + Client->>Server: async_read() + Client-->>Caller: Response diff --git a/docs/diagrams/architecture/remote-http-client-5.mmd b/docs/diagrams/architecture/remote-http-client-5.mmd new file mode 100644 index 00000000000..1f2bd2fd1e2 --- /dev/null +++ b/docs/diagrams/architecture/remote-http-client-5.mmd @@ -0,0 +1,6 @@ +flowchart TD + Connect["TCP Connect"] --> TLSCheck{"SSL Enabled?"} + TLSCheck -->|"Yes"| Handshake["TLS Handshake"] + TLSCheck -->|"No"| Plain["Plain HTTP"] + Handshake --> Send["Send Request"] + Plain --> Send diff --git a/docs/diagrams/architecture/remote-http-client-6.mmd b/docs/diagrams/architecture/remote-http-client-6.mmd new file mode 100644 index 00000000000..01586684843 --- /dev/null +++ b/docs/diagrams/architecture/remote-http-client-6.mmd @@ -0,0 +1,9 @@ +flowchart TD + Start["Start Network Call"] --> Timer["Start Deadline Timer"] + Timer --> Async["Async Operation"] + Async --> Complete{"Completed?"} + Complete -->|"Yes"| CancelTimer["Cancel Timer"] + Complete -->|"No"| Timeout["Timer Fires"] + Timeout --> Abort["Abort Socket"] + CancelTimer --> Return["Return Response"] + Abort --> Return diff --git a/docs/diagrams/architecture/remote-http-client.mmd b/docs/diagrams/architecture/remote-http-client.mmd new file mode 100644 index 00000000000..f6bb4f64dff --- /dev/null +++ b/docs/diagrams/architecture/remote-http-client.mmd @@ -0,0 +1,13 @@ +flowchart TD + Caller["Caller Module"] --> Client["Client"] + Client --> Options["Client::Options"] + Client --> Request["HTTP_Request"] + Client --> Response["HTTP_Response"] + + Client --> Resolver["TCP Resolver"] + Client --> Socket["TCP Socket"] + Client --> TLSSocket["SSL Stream"] + Client --> Timer["Deadline Timer"] + + Request --> URI["URI Parser"] + Response --> Headers["Header Iterator"] diff --git a/docs/diagrams/architecture/sql-engine-and-virtual-tables-2.mmd b/docs/diagrams/architecture/sql-engine-and-virtual-tables-2.mmd new file mode 100644 index 00000000000..09946020084 --- /dev/null +++ b/docs/diagrams/architecture/sql-engine-and-virtual-tables-2.mmd @@ -0,0 +1,5 @@ +flowchart LR + Caller["Caller"] --> GetConn["SQLiteDBManager.get()"] + GetConn --> PrimaryCheck{"Primary Available?"} + PrimaryCheck -->|Yes| Primary["Primary SQLiteDBInstance"] + PrimaryCheck -->|No| Transient["Transient SQLiteDBInstance"] diff --git a/docs/diagrams/architecture/sql-engine-and-virtual-tables-3.mmd b/docs/diagrams/architecture/sql-engine-and-virtual-tables-3.mmd new file mode 100644 index 00000000000..6bded7bfd12 --- /dev/null +++ b/docs/diagrams/architecture/sql-engine-and-virtual-tables-3.mmd @@ -0,0 +1,4 @@ +flowchart TD + Prepare["sqlite3_prepare_v2"] --> Authorizer["sqliteAuthorizer"] + Authorizer -->|Allowed| Continue["Execute Statement"] + Authorizer -->|Denied| Reject["SQLITE_DENY"] diff --git a/docs/diagrams/architecture/sql-engine-and-virtual-tables-4.mmd b/docs/diagrams/architecture/sql-engine-and-virtual-tables-4.mmd new file mode 100644 index 00000000000..d79fe38ce6a --- /dev/null +++ b/docs/diagrams/architecture/sql-engine-and-virtual-tables-4.mmd @@ -0,0 +1,5 @@ +flowchart TD + Query["SQL Query"] --> ExplainPlan["EXPLAIN QUERY PLAN"] + Query --> Explain["EXPLAIN"] + Explain --> OpcodeMap["kSQLOpcodes Mapping"] + OpcodeMap --> TypeInference["Apply Column Types"] diff --git a/docs/diagrams/architecture/sql-engine-and-virtual-tables-5.mmd b/docs/diagrams/architecture/sql-engine-and-virtual-tables-5.mmd new file mode 100644 index 00000000000..5a569905d36 --- /dev/null +++ b/docs/diagrams/architecture/sql-engine-and-virtual-tables-5.mmd @@ -0,0 +1,12 @@ +sequenceDiagram + participant SQLite + participant Module as "sqlite3_module" + participant Table as "TablePlugin" + + SQLite->>Module: xBestIndex + Module->>SQLite: Constraint plan + + SQLite->>Module: xFilter + Module->>Table: generate(context) + Table-->>Module: Row set + Module-->>SQLite: Rows diff --git a/docs/diagrams/architecture/sql-engine-and-virtual-tables-6.mmd b/docs/diagrams/architecture/sql-engine-and-virtual-tables-6.mmd new file mode 100644 index 00000000000..ebfb533e38c --- /dev/null +++ b/docs/diagrams/architecture/sql-engine-and-virtual-tables-6.mmd @@ -0,0 +1,10 @@ +flowchart TD + Start["Query Submitted"] --> Prepare["sqlite3_prepare_v2"] + Prepare --> Execute["sqlite3_step Loop"] + Execute -->|Virtual Table Access| VTab + VTab --> xBestIndex + xBestIndex --> xFilter + xFilter --> Generate["TablePlugin.generate()"] + Generate --> Rows["Return Rows"] + Rows --> Finalize["sqlite3_finalize"] + Finalize --> End["Results Returned"] diff --git a/docs/diagrams/architecture/sql-engine-and-virtual-tables.mmd b/docs/diagrams/architecture/sql-engine-and-virtual-tables.mmd new file mode 100644 index 00000000000..f435ddde934 --- /dev/null +++ b/docs/diagrams/architecture/sql-engine-and-virtual-tables.mmd @@ -0,0 +1,11 @@ +flowchart TD + Client["SQL Caller (Scheduler / CLI / Extension)"] --> SQLPlugin["SQLiteSQLPlugin"] + SQLPlugin --> DBManager["SQLiteDBManager"] + DBManager --> DBInstance["SQLiteDBInstance"] + DBInstance --> SQLiteCore[("In-Memory SQLite Engine")] + + SQLiteCore --> VTabModule["sqlite3_module (Virtual Table Module)"] + VTabModule --> VirtualTable["VirtualTable Wrapper"] + VirtualTable --> TablePlugin["TablePlugin (Registry)"] + + SQLiteCore --> Planner["QueryPlanner"] diff --git a/docs/reference/architecture/.gitignore b/docs/reference/architecture/.gitignore new file mode 100644 index 00000000000..b4f4c7a6c9c --- /dev/null +++ b/docs/reference/architecture/.gitignore @@ -0,0 +1,8 @@ +# CodeWiki temp files (dependency graphs can be 7GB+) +temp/ +dependency_graphs/ + +# JSON intermediate files (except schema/config) +*.json +!*-schema.json +!*-config.json diff --git a/docs/reference/architecture/README.md b/docs/reference/architecture/README.md new file mode 100644 index 00000000000..d8bef8be03c --- /dev/null +++ b/docs/reference/architecture/README.md @@ -0,0 +1,366 @@ +# osquery Repository Overview + +## 1. Purpose of the Repository + +**osquery** is a cross-platform operating system instrumentation framework that exposes system state and activity as relational data. It embeds a hardened SQLite engine and represents operating system concepts (processes, files, sockets, users, registry keys, etc.) as **virtual tables**, enabling users to query live system data using SQL. + +The repository implements: + +- A secure in-memory SQL engine +- A virtual table plugin framework +- Configuration-driven scheduled querying +- Differential result logging and observability +- Distributed query orchestration +- Event-driven monitoring infrastructure +- Persistent and ephemeral storage backends +- Remote HTTP/TLS communication +- Extension-based runtime plugin model + +At a high level, osquery transforms a host into a **SQL-queryable telemetry node** with local and distributed control capabilities. + +--- + +# 2. End-to-End Architecture + +The system is modular and plugin-driven. Below is the full end-to-end architecture across core subsystems. + +## 2.1 System-Level Architecture + +```mermaid +flowchart TD + CLI["osqueryi / osqueryd"] --> Core["Core Init And Runtime"] + + Core --> Config["Configuration And Packs"] + Core --> SQL["SQL Engine And Virtual Tables"] + Core --> Events["Eventing Framework"] + Core --> Logging["Logging And Observability"] + Core --> DB["Database And Storage Plugins"] + Core --> Distributed["Distributed Querying"] + Core --> Extensions["Extensions And IPC"] + Core --> HTTP["Remote HTTP Client"] + Core --> FS["Filesystem And Path Utilities"] + + Config --> SQL + Config --> Events + SQL --> Logging + Events --> DB + Distributed --> SQL + Distributed --> HTTP + Logging --> DB + Extensions --> SQL + Extensions --> DB +``` + +--- + +# 3. Runtime Execution Model + +osquery operates in several runtime modes: + +- **Daemon (`osqueryd`)** +- **Interactive shell (`osqueryi`)** +- **Watcher / Worker model** +- **Extension process** + +## Runtime Lifecycle + +```mermaid +flowchart TD + Start["Process Start"] --> Init["Initializer"] + Init --> Flags["Parse Flags"] + Init --> Registry["Initialize Registry"] + Init --> DBInit["Initialize Database"] + Init --> ExtMgr["Start Extension Manager"] + Init --> ConfigLoad["Load Configuration"] + ConfigLoad --> Schedule["Build Schedule"] + Schedule --> QueryExec["Execute Queries"] + QueryExec --> Log["Log Results"] + Log --> End["Runtime Loop"] +``` + +The **Core Init And Runtime** module governs this lifecycle and ensures safe startup, resource enforcement (watchdog), and graceful shutdown. + +📘 Reference: +`osquery/core` → *Core Init And Runtime* + +--- + +# 4. SQL Engine and Virtual Table Layer + +The SQL engine is built around an in-memory SQLite instance hardened with a strict authorizer. All system data is exposed via dynamically attached **virtual tables** backed by registry plugins. + +```mermaid +flowchart TD + Query["SQL Query"] --> SQLite["SQLite Engine"] + SQLite --> Module["sqlite3_module"] + Module --> VirtualTable["VirtualTable Wrapper"] + VirtualTable --> TablePlugin["TablePlugin"] + TablePlugin --> OS["Operating System Data"] + OS --> Rows["Rows Returned"] +``` + +Key characteristics: + +- In-memory execution +- Strict opcode allowlisting +- Constraint pushdown +- Projection optimization +- Extension-backed writable tables + +📘 Reference: +`osquery/sql` → *Sql Engine And Virtual Tables* + +--- + +# 5. Configuration and Packs + +Configuration defines: + +- Scheduled queries +- Packs (query groups) +- Decorators +- File monitoring rules +- Event subscriptions +- Logger configuration +- Runtime options + +```mermaid +flowchart TD + Source["Config Source (File / TLS / Extension)"] --> ConfigCore["Config Singleton"] + ConfigCore --> Parsers["ConfigParserPlugins"] + Parsers --> Schedule["Scheduled Queries"] + Schedule --> Scheduler["Query Scheduler"] + Scheduler --> SQL["SQL Engine"] +``` + +Supports: + +- Hash-based change detection +- Background refresh runner +- Query denylisting +- Dynamic registry reconfiguration + +📘 Reference: +`osquery/config` → *Configuration And Packs* + +--- + +# 6. Eventing Framework + +The eventing subsystem provides a publish/subscribe model for event-driven monitoring. + +```mermaid +flowchart TD + Publisher["EventPublisher"] --> EventFactory["EventFactory"] + Subscriber["EventSubscriber"] --> EventFactory + EventFactory --> Callback["EventCallback"] + Callback --> Storage["Database Storage"] + SQL["SELECT *_events"] --> Subscriber +``` + +Capabilities: + +- Thread-isolated publishers +- Time-window indexing +- Config-driven retention +- Persistent event buffering +- Optimized incremental queries + +📘 Reference: +`osquery/events` → *Eventing Framework And Subscriptions* + +--- + +# 7. Logging and Query Observability + +osquery converts query executions into structured log artifacts. + +```mermaid +flowchart TD + Exec["Query Execution"] --> Diff["DiffResults"] + Diff --> LogItem["QueryLogItem"] + LogItem --> Serialize["JSON Serialization"] + Serialize --> Logger["LoggerPlugin"] + Logger --> Sink["External Backend"] +``` + +Features: + +- Differential result tracking +- Snapshot support +- Structured JSON output +- Performance telemetry +- Pluggable logging backends + +📘 Reference: +`osquery/core` → *Logging And Query Observability* + +--- + +# 8. Database and Storage Plugins + +All persistent state is stored through a pluggable key-value interface. + +```mermaid +flowchart TD + Modules["Core Modules"] --> Interface["IDatabaseInterface"] + Interface --> Plugin["Database Plugin"] + Plugin --> RocksDB["Persistent Backend"] + Plugin --> Ephemeral["In-Memory Backend"] +``` + +Used for: + +- Query state +- Event buffers +- Distributed query tracking +- Configuration backup +- Performance metrics + +📘 Reference: +`osquery/database` → *Database And Storage Plugins* + +--- + +# 9. Distributed Querying + +Enables centralized orchestration of SQL across fleets. + +```mermaid +flowchart TD + Server["Central Server"] --> TLS["TLS Distributed Plugin"] + TLS --> Core["Distributed Core"] + Core --> SQL["SQL Engine"] + SQL --> Results["Results"] + Results --> TLS + TLS --> Server +``` + +Includes: + +- Discovery query gating +- SHA256-based denylisting +- Execution performance tracking +- Pluggable transport interface + +📘 Reference: +`osquery/distributed` → *Distributed Querying* + +--- + +# 10. Extensions and IPC + +osquery supports runtime plugin extensions via Apache Thrift IPC. + +```mermaid +flowchart LR + Core["osquery Core"] --> Manager["Extension Manager"] + Extension["External Extension"] --> Manager + Manager --> Registry["RegistryFactory"] + Core --> Extension +``` + +Capabilities: + +- Dynamic table plugins +- Remote SQL execution +- UUID-based routing +- Health monitoring +- Secure socket validation + +📘 Reference: +`osquery/extensions` → *Extensions And IPC* + +--- + +# 11. Remote HTTP Client + +Provides secure HTTP/TLS transport used by: + +- Distributed querying +- Remote logging +- Remote configuration plugins + +```mermaid +flowchart TD + Caller["Module"] --> Client["HTTP Client"] + Client --> TLS["TLS Handshake"] + TLS --> Remote["Remote Server"] +``` + +Built on Boost.Asio and Boost.Beast with strict TLS configuration. + +📘 Reference: +`osquery/remote` → *Remote Http Client* + +--- + +# 12. Filesystem and Path Utilities + +Cross-platform abstraction for: + +- Safe file access +- Glob resolution +- Permission enforcement +- Linux `/proc` inspection +- Windows ACL and metadata handling + +```mermaid +flowchart TD + Caller["Subsystem"] --> FS["Filesystem API"] + FS --> POSIX["POSIX Implementation"] + FS --> Windows["Windows Implementation"] +``` + +Security-first design ensures safe extension loading and configuration handling. + +📘 Reference: +`osquery/filesystem` → *Filesystem And Path Utilities* + +--- + +# 13. Repository Structure Summary + +| Module | Path | Responsibility | +|--------|------|----------------| +| Core Init And Runtime | `osquery/core` | Process lifecycle, flags, watchdog | +| Configuration And Packs | `osquery/config` | Scheduled queries, packs, decorators | +| SQL Engine And Virtual Tables | `osquery/sql` | SQLite engine, virtual table layer | +| Eventing Framework | `osquery/events` | Publisher/subscriber event system | +| Logging And Query Observability | `osquery/core` | Result diffing, structured logs | +| Database And Storage Plugins | `osquery/database` | Persistent state abstraction | +| Distributed Querying | `osquery/distributed` | Remote fleet orchestration | +| Extensions And IPC | `osquery/extensions` | Thrift-based runtime extensions | +| Remote HTTP Client | `osquery/remote` | Secure HTTP/TLS transport | +| Filesystem And Path Utilities | `osquery/filesystem` | Cross-platform file abstraction | + +--- + +# 14. Architectural Principles + +1. **SQL as a Universal Interface** +2. **Plugin-Driven Extensibility** +3. **Security-First Execution (SQLite authorizer + permission checks)** +4. **In-Memory Analytical Core** +5. **Config-Driven Behavior** +6. **Differential Logging Efficiency** +7. **Distributed Fleet Control** +8. **Cross-Platform Abstraction Layer** + +--- + +# 15. Conclusion + +The `osquery` repository implements a secure, extensible, SQL-based operating system telemetry platform. + +It combines: + +- A hardened SQLite execution engine +- A virtual table plugin architecture +- Event-driven monitoring +- Differential logging +- Distributed query orchestration +- Runtime extension via IPC +- Secure remote communication + +Together, these modules form a scalable, production-grade system observability framework capable of operating both locally and as part of a centrally managed fleet. \ No newline at end of file diff --git a/docs/reference/architecture/configuration-and-packs/configuration-and-packs.md b/docs/reference/architecture/configuration-and-packs/configuration-and-packs.md new file mode 100644 index 00000000000..f9ca515ae14 --- /dev/null +++ b/docs/reference/architecture/configuration-and-packs/configuration-and-packs.md @@ -0,0 +1,383 @@ +# Configuration And Packs + +The **Configuration And Packs** module is responsible for loading, validating, parsing, distributing, and refreshing osquery configuration data. It transforms raw configuration sources (files, extensions, or remote plugins) into: + +- Scheduled queries and packs +- File monitoring rules +- Event subscriptions +- Logger and runtime options +- SQL views +- Decorations and metadata + +This module acts as the *control plane* for osquery runtime behavior. It connects configuration sources with the scheduler, SQL engine, eventing framework, logging pipeline, and database layer. + +--- + +## Architecture Overview + +At a high level, the module consists of: + +- A **Config registry** for loading configuration sources +- A **ConfigRefreshRunner** background service +- A **Schedule** abstraction built from Packs +- Multiple **ConfigParserPlugin** implementations +- Backup, hashing, and validation logic + +```mermaid +flowchart TD + ConfigPlugin["Config Plugin Registry"] --> ConfigCore["Config Singleton"] + ConfigCore --> RefreshRunner["ConfigRefreshRunner"] + ConfigCore --> Schedule["Schedule"] + Schedule --> Pack["Pack"] + ConfigCore --> Parsers["ConfigParserPlugins"] + Parsers --> OptionsParser["Options Parser"] + Parsers --> FilePathsParser["File Paths Parser"] + Parsers --> DecoratorsParser["Decorators Parser"] + Parsers --> EventsParser["Events Parser"] + Parsers --> ViewsParser["Views Parser"] + ConfigCore --> Database[("Persistent Database")] + Schedule --> Logging["Logging And Query Observability"] + Schedule --> SQLEngine["SQL Engine And Virtual Tables"] + Parsers --> EventFramework["Eventing Framework"] +``` + +--- + +## Core Responsibilities + +### 1. Configuration Loading + +The module defines a `config` registry that supports pluggable configuration sources: + +- `FilesystemConfigPlugin` (default) +- `UpdateConfigPlugin` (internal chaining for extensions) + +Each plugin implements: + +- `genConfig()` → produce configuration map +- `genPack()` → resolve pack references +- `update()` → apply configuration updates + +The active plugin is selected via the `config_plugin` flag. + +--- + +### 2. Config Singleton + +The `Config` class is a process-wide singleton that: + +- Loads configuration +- Maintains the active Schedule +- Applies parser plugins +- Tracks source hashes +- Backs up and restores configuration +- Coordinates plugin reconfiguration + +Key behaviors: + +- JSON validation (max depth and size constraints) +- Comment stripping +- Hash-based change detection +- Safe concurrent access using mutexes +- Performance tracking per scheduled query + +--- + +### 3. Refresh Lifecycle + +The `ConfigRefreshRunner` runs in the background when `config_refresh` is enabled. + +```mermaid +flowchart TD + Start["ConfigRefreshRunner Start"] --> Wait["Sleep refresh_sec"] + Wait --> Check["Interrupted?"] + Check -->|No| Refresh["Config.refresh()"] + Refresh --> Wait + Check -->|Yes| End["End"] +``` + +Features: + +- Normal refresh interval +- Accelerated retry interval on failure +- Optional backup restoration on first failure +- Dynamic reconfiguration of registries and loggers + +--- + +## Packs and Scheduling + +The Schedule is composed of `Pack` objects. + +### Pack + +A **Pack** represents a group of scheduled queries plus optional discovery logic. + +Capabilities: + +- Platform constraints +- Version constraints +- Shard percentage +- Discovery queries +- ScheduledQuery collection +- Execution eligibility (`shouldPackExecute()`) + +### Schedule + +The Schedule: + +- Maintains active packs +- Filters packs via discovery checks +- Tracks denylisted queries +- Restores failed queries on restart +- Provides query iteration to the scheduler + +```mermaid +flowchart TD + ConfigUpdate["Config Update"] --> BuildPacks["Create Pack Objects"] + BuildPacks --> DiscoveryCheck["Discovery Queries"] + DiscoveryCheck --> ActivePack["Active Pack"] + ActivePack --> Scheduler["ScheduledQuery Execution"] + Scheduler --> Performance["Query Performance Recording"] + Scheduler --> Denylist["Denylist On Failure"] +``` + +### Denylist Logic + +If a scheduled query: + +- Causes watchdog termination +- Was executing during shutdown + +It is denylisted temporarily and persisted in the database. + +Expiration logic considers: + +- Time-based expiry +- Event-based exemptions +- Explicit `denylist=false` option + +--- + +## Config Parser Plugins + +The module defines a `config_parser` registry. Each parser consumes specific top-level configuration keys. + +Parsers are applied in this order: + +1. Options parser (always first) +2. Remaining parsers in registry order + +### FilesystemConfigPlugin + +Loads configuration from: + +- Primary JSON file +- Optional `.d` directory fragments +- Multi-pack glob patterns + +Produces a map of `source → JSON content`. + +--- + +### OptionsConfigParserPlugin + +Handles the `options` key. + +- Updates runtime flags +- Prevents modification of CLI-only flags +- Reconfigures logger verbosity dynamically +- Supports `custom_` prefixed flags + +--- + +### FilePathsConfigParserPlugin + +Handles: + +- `file_paths` +- `file_paths_query` +- `file_accesses` +- `exclude_paths` + +Responsibilities: + +- Adds file monitoring rules to Config +- Executes SQL-based path discovery +- Normalizes wildcard globs +- Maintains access category mappings + +This integrates directly with the **Filesystem And Path Utilities** and **Eventing Framework And Subscriptions** modules. + +--- + +### DecoratorsConfigParserPlugin + +Handles the `decorators` key. + +Decorators: + +- Execute SQL queries +- Extract first-row column values +- Attach results to logs + +Supported execution points: + +- `load` +- `always` +- `interval` + +```mermaid +flowchart TD + ConfigLoad["Config Load"] --> RunLoad["Run Load Decorators"] + QueryExec["Query Execution"] --> RunAlways["Run Always Decorators"] + IntervalTick["Interval Timer"] --> RunInterval["Run Interval Decorators"] + RunLoad --> Decorations[("Decoration Store")] + RunAlways --> Decorations + RunInterval --> Decorations + Decorations --> Logger["Status And Query Logs"] +``` + +Thread safety is enforced with dedicated mutexes. + +--- + +### EventsConfigParserPlugin + +Consumes the `events` key and exposes configuration to event publishers and subscribers. + +Acts as a bridge to the **Eventing Framework And Subscriptions** module. + +--- + +### ViewsConfigParserPlugin + +Handles the `views` key. + +- Creates SQL views +- Drops outdated views +- Persists view definitions +- Ensures idempotent updates + +This integrates with the **SQL Engine And Virtual Tables** module. + +--- + +## Backup and Persistence + +If `config_enable_backup` is enabled: + +- Config sources are stored in the database +- Failed refresh attempts restore backup +- Backup keys are namespaced with `config_persistence.` + +The module also persists: + +- Query performance statistics +- Executing query markers +- Denylist entries +- Query result timestamps + +Database interactions use the **Database And Storage Plugins** module. + +--- + +## Hashing and Change Detection + +Each configuration source is hashed (SHA1): + +- Prevents unnecessary reconfiguration +- Enables selective updates +- Supports global config hash generation + +If a source hash does not change: + +- Packs are not rebuilt +- Parsers are not re-applied + +--- + +## Interaction With Other Modules + +### Logging And Query Observability + +- Query performance stats recorded +- Decorations attached to logs +- Denylist logging + +### SQL Engine And Virtual Tables + +- Discovery queries +- Scheduled queries +- View creation + +### Eventing Framework And Subscriptions + +- Event configuration via `events` +- File path monitoring integration + +### Database And Storage Plugins + +- Config backup +- Query stats +- Denylist persistence +- Result expiration + +### Extensions And IPC + +- External extensions can call `Config::update()` +- UpdateConfigPlugin enables update chaining +- External registries propagate updates back to core + +--- + +## Configuration Processing Flow + +```mermaid +flowchart TD + Source["Config Source"] --> GenConfig["genConfig()"] + GenConfig --> Update["Config.update()"] + Update --> Purge["Purge Stale State"] + Purge --> UpdateSource["updateSource()"] + UpdateSource --> ParseJSON["Validate And Parse JSON"] + ParseJSON --> BuildSchedule["Create Packs And Schedule"] + BuildSchedule --> ApplyParsers["Apply ConfigParserPlugins"] + ApplyParsers --> Reconfigure["Registry.configure()"] + Reconfigure --> EventNotify["EventFactory.configUpdate()"] + EventNotify --> Ready["Runtime Updated"] +``` + +--- + +## Concurrency and Safety + +The module uses: + +- Recursive mutexes for schedule and files +- Dedicated mutexes for: + - Hash map + - Backup store + - Performance stats + - Decorations + +Design goals: + +- Safe asynchronous refresh +- Deterministic schedule rebuilds +- Minimal runtime disruption +- Atomic config transitions + +--- + +## Summary + +The **Configuration And Packs** module is the orchestration layer of osquery runtime behavior. It: + +- Loads configuration from pluggable sources +- Builds and manages query packs +- Applies runtime options +- Coordinates parser plugins +- Manages refresh and backup logic +- Integrates deeply with scheduler, SQL engine, event system, logging, and database layers + +Without this module, osquery would have no dynamic runtime control plane. It is the central authority that transforms static configuration data into active system behavior. diff --git a/docs/reference/architecture/core-init-and-runtime/core-init-and-runtime.md b/docs/reference/architecture/core-init-and-runtime/core-init-and-runtime.md new file mode 100644 index 00000000000..03460755d71 --- /dev/null +++ b/docs/reference/architecture/core-init-and-runtime/core-init-and-runtime.md @@ -0,0 +1,428 @@ +# Core Init And Runtime + +The **Core Init And Runtime** module is the foundational execution layer of osquery. It is responsible for: + +- Process initialization and bootstrap +- Command-line flag management +- Worker / watcher lifecycle orchestration +- Graceful and forced shutdown handling +- Platform and identity utilities (UUID, host identity, privilege control) +- Virtual table execution context and constraint handling +- Watchdog-based resource governance + +This module defines the runtime contract that all higher-level subsystems rely on, including: + +- [Configuration And Packs](../configuration-and-packs/configuration-and-packs.md) +- [Logging And Query Observability](../logging-and-query-observability/logging-and-query-observability.md) +- [Database And Storage Plugins](../database-and-storage-plugins/database-and-storage-plugins.md) +- [SQL Engine And Virtual Tables](../sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md) +- [Eventing Framework And Subscriptions](../eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md) +- [Extensions And IPC](../extensions-and-ipc/extensions-and-ipc.md) + +--- + +## 1. Architectural Overview + +At runtime, osquery can operate in multiple modes: + +- **Daemon** (`osqueryd`) +- **Shell** (`osqueryi`) +- **Watcher (watchdog)** +- **Worker** (child process of watcher) +- **Extension process** + +The Core Init And Runtime module orchestrates how these modes are selected, started, monitored, and shut down. + +### High-Level Runtime Architecture + +```mermaid +flowchart TD + Main["Main Entry Point"] --> Initializer["Initializer"] + + Initializer --> Flags["Flag System"] + Initializer --> Registry["Registry And Plugins"] + Initializer --> Database["Database Initialization"] + Initializer --> ExtensionManager["Extension Manager"] + Initializer --> Events["Event Factory"] + Initializer --> Watchdog["Watcher / Worker Model"] + + Watchdog --> Worker["Worker Process"] + Watchdog --> Extensions["Extension Processes"] + + Worker --> Tables["Virtual Tables"] + Worker --> Scheduler["Query Scheduler"] + Worker --> Logger["Logger Plugins"] +``` + +The **Initializer** class is the central orchestrator of this lifecycle. + +--- + +## 2. Initialization Lifecycle + +The `Initializer` class (defined in `system.h` and implemented in `init.cpp`) governs startup behavior. + +### Core Responsibilities + +- Parse CLI flags and flagfiles +- Determine tool type (daemon, shell, extension) +- Configure runtime defaults +- Initialize registries and plugins +- Start extension manager +- Load configuration +- Attach event publishers +- Activate logging and distributed plugins +- Launch watcher/worker model if enabled + +### Startup Sequence + +```mermaid +sequenceDiagram + participant Main + participant Initializer + participant Registry + participant Config + participant Extensions + participant Events + + Main->>Initializer: Construct(argc, argv) + Initializer->>Initializer: Parse flags + Initializer->>Registry: registryAndPluginInit() + Initializer->>Extensions: startExtensionManager() + Initializer->>Config: load() + Initializer->>Events: attachEvents() + Initializer->>Initializer: start() +``` + +The `start()` method finalizes runtime activation after construction. + +--- + +## 3. Flag System + +Core Components: + +- `FlagDetail` +- `FlagInfo` +- `Flag` + +The flag system wraps **Google GFlags** to provide: + +- Structured metadata (shell-only, CLI-only, hidden, extension-only) +- Default value tracking +- Runtime updates +- Custom flags from configuration + +### Flag Architecture + +```mermaid +flowchart LR + Macro["FLAG / CLI_FLAG Macros"] --> FlagCreate["Flag::create()"] + FlagCreate --> FlagRegistry["Internal Flag Map"] + + CLI["Command Line"] --> Parse["ParseCommandLineFlags"] + Config["Config Options"] --> Update["Flag::updateValue()"] + + FlagRegistry --> Runtime["Runtime Access"] +``` + +Important behaviors: + +- Flags can be set via CLI, flagfile, or config (unless CLI-only) +- `Flag::isDefault()` detects overridden values +- Aliases support backwards compatibility + +Flags influence: + +- Database activation +- Watchdog enablement +- Logging and distributed plugins +- OpenFrame mode + +--- + +## 4. Worker and Watcher Model + +Core Components: + +- `LimitDefinition` +- `PerformanceChange` + +The watcher/worker architecture isolates execution for reliability and resource control. + +### Roles + +| Role | Responsibility | +|------|----------------| +| Watcher | Monitors worker and extensions | +| Worker | Executes queries and plugins | +| Extension | External plugin process | + +### Watchdog Control Flow + +```mermaid +flowchart TD + Watcher["Watcher Process"] --> Spawn["Spawn Worker"] + Spawn --> Monitor["Monitor CPU And Memory"] + Monitor --> Check{{"Limit Exceeded?"}} + Check -->|"No"| Continue["Continue Monitoring"] + Check -->|"Yes"| Kill["Stop Worker"] + Kill --> Respawn["Respawn Worker"] +``` + +### Resource Limits + +`LimitDefinition` defines profiles: + +- Normal +- Restrictive +- Disabled + +Enforced metrics: + +- Memory footprint (MB) +- CPU utilization percentage +- Sustained latency duration +- Respawn frequency + +`PerformanceChange` calculates CPU window utilization based on interval and CPU count. + +If limits are exceeded: + +- Worker is gracefully terminated +- Forced kill occurs if needed +- Restart logic applies exponential backoff + +--- + +## 5. Shutdown Coordination + +Core Component: + +- `ShutdownData` + +Shutdown is centralized and thread-safe. + +### Shutdown Model + +```mermaid +flowchart TD + AnyThread["Any Thread"] --> Request["requestShutdown()"] + Request --> Signal["Condition Variable"] + Signal --> MainThread["Main Thread"] + MainThread --> StopServices["Dispatcher::stopServices()"] + StopServices --> Join["Join Services"] + Join --> StopEvents["EventFactory::end()"] + StopEvents --> CloseDB["shutdownDatabase()"] + CloseDB --> Exit["Return Exit Code"] +``` + +Key properties: + +- Shutdown can only be requested once +- Exit code is stored atomically +- `AlarmRunnable` enforces maximum shutdown time +- `shutdownNow()` performs immediate `_Exit()` + +This guarantees deterministic teardown even during deadlocks. + +--- + +## 6. Platform and System Utilities + +Core Components: + +- `PrivateData` +- `stat` +- `tm` + +### Host Identity + +The module manages multiple UUID strategies: + +- Hardware UUID +- Instance UUID (persistent) +- Ephemeral UUID +- Specified UUID +- Hostname fallback + +Flow: + +```mermaid +flowchart LR + Flag["host_identifier Flag"] --> Mode{{"Mode"}} + Mode -->|"uuid"| Hardware["Hardware UUID"] + Mode -->|"instance"| Instance["Instance UUID"] + Mode -->|"ephemeral"| Ephemeral["Random UUID"] + Mode -->|"specified"| Specified["Configured Identifier"] + Mode -->|"default"| Hostname["System Hostname"] +``` + +Persistent identifiers are stored via the database layer. + +### Privilege Dropping (POSIX) + +- Temporarily reduce effective UID/GID +- Restore original groups on destruction +- Used when interacting with filesystem-backed tables + +### Thread Naming + +Cross-platform thread naming for observability and debugging. + +--- + +## 7. Virtual Table Execution Context + +Core Components: + +- `Constraint` +- `ConstraintList` +- `QueryContext` +- `VirtualTableContent` + +This subsystem forms the bridge between: + +- SQLite virtual table engine +- osquery table plugins + +### Constraint Model + +```mermaid +flowchart TD + SQL["SQL Query"] --> SQLite["SQLite Virtual Table API"] + SQLite --> QueryContext["QueryContext"] + QueryContext --> ConstraintMap["ConstraintMap"] + ConstraintMap --> ConstraintList["ConstraintList"] + ConstraintList --> TablePlugin["TablePlugin::generate()"] +``` + +`ConstraintList` supports: + +- Operator-aware filtering +- Affinity-aware matching (TEXT, INTEGER, BIGINT) +- Efficient pre-filter evaluation + +`QueryContext` provides: + +- Column usage tracking +- Constraint expansion +- Cache control flags + +### VirtualTableContent + +Stores: + +- Column definitions +- Attributes (CACHEABLE, EVENT_BASED, etc.) +- Aliases +- Query-scoped caches + +This metadata allows high-performance virtual table execution without repeated registry lookups. + +--- + +## 8. Table Plugin Integration + +Although fully described in [SQL Engine And Virtual Tables](../sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md), Core Init And Runtime provides: + +- Context serialization +- Cache freshness checks +- Generator-based row streaming +- Extension table attachment + +Caching model: + +- Interval-based freshness +- Disabled when constraints are complex +- Serialized into backing database + +--- + +## 9. Signal Handling + +Signals handled: + +- `SIGTERM` +- `SIGINT` +- `SIGUSR1` + +Behavior: + +- `SIGUSR1` → mark resource limit hit +- `SIGTERM` / `SIGINT` → request graceful shutdown +- Alarm timeout enforces upper bound on shutdown time + +--- + +## 10. OpenFrame Mode Integration + +Additional flags: + +- `openframe_mode` +- `openframe_secret` +- `openframe_token_path` + +When enabled: + +- Initializes encryption service +- Extracts token +- Starts token refresher +- Updates authorization manager + +This integrates osquery runtime with OpenFrame-managed authentication. + +--- + +## 11. How This Module Fits the System + +The Core Init And Runtime module: + +- Bootstraps **all subsystems** +- Owns lifecycle and teardown guarantees +- Provides runtime isolation (watchdog model) +- Supplies identity and environment context +- Enables safe virtual table execution +- Enforces resource governance + +Every other module depends on it either directly or indirectly. + +### Dependency Positioning + +```mermaid +flowchart TD + Core["Core Init And Runtime"] + + Core --> Config["Configuration And Packs"] + Core --> Logging["Logging And Query Observability"] + Core --> Database["Database And Storage Plugins"] + Core --> SQL["SQL Engine And Virtual Tables"] + Core --> Events["Eventing Framework And Subscriptions"] + Core --> Extensions["Extensions And IPC"] +``` + +Without this module: + +- No flags would be parsed +- No plugins would activate +- No scheduler would run +- No graceful shutdown would occur + +It is the execution spine of the osquery system. + +--- + +## Summary + +The **Core Init And Runtime** module is responsible for transforming a process invocation into a fully operational, resource-governed, plugin-enabled osquery runtime. + +It provides: + +- Deterministic initialization +- Mode-aware execution (daemon, shell, extension) +- Resource watchdog enforcement +- Safe shutdown semantics +- Host identity management +- Virtual table execution scaffolding + +All higher-level capabilities build on top of this foundational runtime layer. \ No newline at end of file diff --git a/docs/reference/architecture/database-and-storage-plugins/database-and-storage-plugins.md b/docs/reference/architecture/database-and-storage-plugins/database-and-storage-plugins.md new file mode 100644 index 00000000000..c3210d09ee9 --- /dev/null +++ b/docs/reference/architecture/database-and-storage-plugins/database-and-storage-plugins.md @@ -0,0 +1,357 @@ +# Database And Storage Plugins + +## Overview + +The **Database And Storage Plugins** module provides the persistent and ephemeral key-value storage abstraction used by osquery core. It exposes a unified database interface that: + +- Persists configuration state, scheduled query metadata, event buffers, logs, and distributed query state. +- Abstracts the underlying storage engine (e.g., RocksDB or in-memory ephemeral storage). +- Provides safe concurrent access with reset and migration support. +- Enables plugin-based database backends via the internal registry system. + +At runtime, this module acts as the storage backbone for higher-level modules such as: + +- [Configuration And Packs](../configuration-and-packs/configuration-and-packs.md) +- [Logging And Query Observability](../logging-and-query-observability/logging-and-query-observability.md) +- [Distributed Querying](../distributed-querying/distributed-querying.md) +- [Eventing Framework And Subscriptions](../eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md) +- [SQL Engine And Virtual Tables](../sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md) + +It is implemented using a registry-driven plugin architecture, allowing the storage backend to be replaced or disabled. + +--- + +## Architectural Overview + +### High-Level Architecture + +```mermaid +flowchart TD + CoreModules["Core And Feature Modules"] --> DBInterface["IDatabaseInterface"] + DBInterface --> OsqueryDB["OsqueryDatabase"] + OsqueryDB --> RegistryLayer["Database Plugin Registry"] + RegistryLayer --> RocksDBPlugin["RocksDB Plugin (Persistent)"] + RegistryLayer --> EphemeralPlugin["Ephemeral Database Plugin (In-Memory)"] + + subgraph storage_domains["Logical Storage Domains"] + PersistentSettings["configurations"] + Queries["queries"] + Events["events"] + Logs["logs"] + Carves["carves"] + Distributed["distributed"] + QueryPerformance["query_performance"] + end + + RocksDBPlugin --> storage_domains + EphemeralPlugin --> storage_domains +``` + +The module separates concerns into three layers: + +1. **Public Database Interface** – Used by the rest of the system. +2. **Plugin Registry Layer** – Selects and manages the active database backend. +3. **Concrete Database Plugins** – Implement actual storage logic. + +--- + +## Core Components + +### OsqueryDatabase + +**Component:** `osquery.osquery.database.database.OsqueryDatabase` + +`OsqueryDatabase` implements the `IDatabaseInterface` and serves as the primary entry point for all database operations inside osquery. + +It provides: + +- `getDatabaseValue` (string and integer variants) +- `setDatabaseValue` +- `setDatabaseBatch` +- `deleteDatabaseValue` +- `deleteDatabaseRange` +- `scanDatabaseKeys` + +Internally, it delegates all operations to free functions such as: + +- `getDatabaseValue(...)` +- `setDatabaseValue(...)` +- `scanDatabaseKeys(...)` + +These functions: + +- Acquire read/write locks to protect against concurrent resets. +- Route calls through the database plugin registry. +- Support both internal and external (extension-based) registry execution. + +This design ensures that the rest of the system depends only on the interface, not the concrete backend. + +--- + +### EphemeralDatabasePlugin + +**Component:** `osquery.osquery.database.ephemeral.EphemeralDatabasePlugin` + +The `EphemeralDatabasePlugin` is an in-memory implementation of the `DatabasePlugin` interface. + +It is: + +- Registered internally as the `ephemeral` database plugin. +- Used when the `disable_database` flag is enabled. +- Used for testing and fallback scenarios. + +#### Internal Data Structure + +```mermaid +flowchart TD + DB["EphemeralDatabasePlugin"] --> Map["std::map>"] + Map --> DomainMap["Domain Map"] + DomainMap --> KeyValue["boost::variant"] +``` + +Storage model: + +- Outer map: `domain → domain_map` +- Inner map: `key → boost::variant` + +Supported operations: + +- `get` +- `put` +- `putBatch` +- `remove` +- `removeRange` +- `scan` + +Since it is purely in-memory: + +- No persistence across restarts. +- No disk I/O. +- Ideal for unit testing and ephemeral deployments. + +--- + +## Database Domains + +The module defines several logical domains used throughout osquery: + +| Domain | Purpose | +|--------|----------| +| `configurations` | Persistent settings and metadata | +| `queries` | Scheduled query state and results metadata | +| `events` | Event framework buffers | +| `logs` | Buffered logging data | +| `carves` | File carving state | +| `distributed` | Distributed query tasks | +| `distributed_running` | Active distributed query tracking | +| `query_performance` | Performance metrics for queries | + +Domains act as logical namespaces within a single database backend. + +--- + +## Plugin Lifecycle and Initialization + +### Initialization Flow + +```mermaid +flowchart TD + Start["Startup"] --> CheckFlag{{"disable_database?"}} + CheckFlag -->|"No"| UseRocksDB["Activate RocksDB Plugin"] + CheckFlag -->|"Yes"| UseEphemeral["Activate Ephemeral Plugin"] + UseRocksDB --> InitDone["Database Initialized"] + UseEphemeral --> InitDone +``` + +Key behaviors: + +- If `disable_database` is false, the internal persistent plugin (RocksDB) is selected. +- If `disable_database` is true, the `ephemeral` plugin is activated. +- Initialization retries up to 25 times to handle file locks or shutdown races. +- `kDBInitialized` ensures safe access. + +--- + +## Concurrency and Reset Safety + +To protect database operations: + +- A global mutex `kDatabaseReset` is used. +- Read locks guard standard operations. +- Write locks guard reset flows. + +### Reset Flow + +```mermaid +flowchart TD + ResetCall["resetDatabase()"] --> LockWrite["Acquire Write Lock"] + LockWrite --> TearDown["Plugin tearDown()"] + TearDown --> SetUp["Plugin setUp()"] + SetUp --> Unlock["Release Lock"] +``` + +If reset fails: + +- The registry falls back to the `ephemeral` backend. +- A warning is logged. + +This prevents total system failure when persistent storage is corrupted. + +--- + +## Database Migrations + +The module supports schema/data migrations via version tracking. + +- Version key: `results_version` +- Stored under domain: `configurations` + +### Migration Flow + +```mermaid +flowchart TD + ReadVersion["Read results_version"] --> Compare["Compare with target version"] + Compare -->|"Older"| RunMigration["Execute migrateVxVy()"] + RunMigration --> UpdateVersion["Persist new version"] + UpdateVersion --> Compare + Compare -->|"Match"| Done["Migration Complete"] +``` + +Examples of migrations: + +- Converting legacy property-tree JSON to RapidJSON format. +- Renaming event publisher keys. + +If migration fails: + +- Error is logged. +- Database upgrade aborts. + +--- + +## Interaction with Other Modules + +### Configuration And Packs + +Configuration refreshers persist: + +- Pack execution counters. +- Schedule metadata. +- Decorator state. + +All stored under structured domains using `setDatabaseValue` and `scanDatabaseKeys`. + +--- + +### Logging And Query Observability + +This module persists: + +- Scheduled query state. +- Query performance metrics. +- Result differentials. + +The `query_performance` domain ensures metrics survive restarts when persistent storage is enabled. + +--- + +### Distributed Querying + +The distributed module uses domains: + +- `distributed` +- `distributed_running` + +These track: + +- Pending tasks. +- Active query execution state. + +--- + +### Eventing Framework And Subscriptions + +Event publishers and subscribers use the `events` domain to: + +- Persist event cursors. +- Maintain audit or publisher state. + +--- + +## Extension and External Registry Behavior + +If osquery is running as an **extension process**: + +- It does not directly access the database backend. +- Database requests are routed via `Registry::call("database", ...)`. +- The main daemon handles storage. + +This enforces: + +- Centralized persistence. +- No extension-level database corruption risk. + +--- + +## Key Design Characteristics + +### 1. Plugin-Based Storage + +- Storage backend selected via registry. +- Easily replaceable or disabled. +- Uniform request interface (`get`, `put`, `scan`, etc.). + +### 2. Domain Isolation + +Logical partitioning prevents key collisions across features. + +### 3. Migration Safety + +- Explicit version tracking. +- Incremental migration steps. +- Failure-safe version commit. + +### 4. Reset and Recovery Support + +- Safe reset workflow. +- Automatic fallback to ephemeral storage. +- Multi-attempt initialization. + +### 5. Thread Safety + +- Reader/writer locking around reset flows. +- Atomic flags guarding initialization state. + +--- + +## When to Use Each Backend + +| Backend | Use Case | +|----------|----------| +| RocksDB (Persistent) | Production deployments requiring durable state | +| Ephemeral | Testing, debugging, or stateless environments | + +Testing environments typically use: + +```text +initDatabasePluginForTesting() +``` + +Which: + +- Forces `disable_database = true` +- Activates ephemeral storage +- Resets the database + +--- + +## Summary + +The **Database And Storage Plugins** module provides the foundational storage abstraction for osquery. It: + +- Decouples storage implementation from core logic. +- Enables plugin-based extensibility. +- Protects against corruption via reset and migration controls. +- Supports both persistent and in-memory storage. + +Every major subsystem in osquery relies on this module for durable state, making it a critical backbone component of the overall architecture. \ No newline at end of file diff --git a/docs/reference/architecture/distributed-querying/distributed-querying.md b/docs/reference/architecture/distributed-querying/distributed-querying.md new file mode 100644 index 00000000000..37c3bf57f6c --- /dev/null +++ b/docs/reference/architecture/distributed-querying/distributed-querying.md @@ -0,0 +1,376 @@ +# Distributed Querying + +The **Distributed Querying** module enables remote orchestration and execution of SQL queries across a fleet of osquery nodes. It allows a central server to: + +- Push SQL queries to enrolled agents +- Collect query results asynchronously +- Monitor execution performance +- Denylist problematic or long-running queries + +This module acts as the execution bridge between: + +- The **SQL Engine and Virtual Tables** subsystem (local query execution) +- The **Database and Storage Plugins** subsystem (state management) +- The **Logging and Query Observability** subsystem (performance + result logging) +- Remote transport implementations such as the TLS-based plugin + +At its core, Distributed Querying defines the request/response model, execution lifecycle, denylist logic, and plugin interface for retrieving and submitting distributed work. + +--- + +## Architectural Overview + +```mermaid +flowchart TD + Server["Central Server"] -->|"HTTPS"| TLSPlugin["TLS Distributed Plugin"] + TLSPlugin -->|"getQueries()"| DistributedCore["Distributed Core"] + DistributedCore -->|"runQueries()"| SQLEngine["SQL Engine"] + SQLEngine -->|"QueryData"| DistributedCore + DistributedCore -->|"writeResults()"| TLSPlugin + TLSPlugin -->|"HTTPS"| Server + + DistributedCore -->|"recordQueryPerformance"| Observability["Query Observability"] + DistributedCore -->|"running state"| Storage["Database Plugins"] +``` + +### Key Responsibilities + +| Component | Responsibility | +|------------|----------------| +| Distributed Core | Lifecycle, queuing, denylisting, performance tracking | +| Distributed Plugin | Abstract interface for remote work retrieval and result submission | +| TLS Distributed Plugin | HTTPS implementation of Distributed Plugin | +| SQL Engine | Executes queries against virtual tables | +| Database Plugins | Persist running state and deduplication | +| Observability | Tracks performance and execution metrics | + +--- + +# Core Data Structures + +## DistributedQueryRequest + +Represents a single unit of distributed work. + +```text +Fields: +- id: Unique server-assigned identifier +- query: SQL string to execute +``` + +This structure is serialized/deserialized using: + +- `serializeDistributedQueryRequest` +- `deserializeDistributedQueryRequest` +- JSON string helpers for transport safety + +### Example Server Payload + +```json +{ + "queries": { + "id1": "select * from osquery_info", + "id2": "select * from processes" + } +} +``` + +Each key becomes a `DistributedQueryRequest` instance. + +--- + +## DistributedQueryResult + +Encapsulates execution output and metadata. + +```text +Fields: +- request: Original DistributedQueryRequest +- results: QueryData (rows) +- columns: ColumnNames +- status: Execution Status +- message: Optional error or informational message +``` + +Serialized using: + +- `serializeDistributedQueryResult` +- `deserializeDistributedQueryResult` + +This object ensures transport-neutral packaging of results. + +--- + +# Distributed Execution Lifecycle + +The `Distributed` class orchestrates the runtime behavior. + +```mermaid +flowchart TD + Start["Pull Updates"] --> Accept["acceptWork()"] + Accept --> Pending{"Pending Queries?"} + Pending -->|"Yes"| Run["runQueries()"] + Run --> Deny{"Denylisted?"} + Deny -->|"No"| Execute["Execute via SQL Engine"] + Execute --> Record["recordQueryPerformance()"] + Record --> Queue["addResult()"] + Queue --> Flush["flushCompleted()"] + Deny -->|"Yes"| Skip["Skip Execution"] + Flush --> End["Cycle Complete"] +``` + +--- + +## 1. Pull Phase + +```cpp +Status pullUpdates(); +``` + +- Calls the active `DistributedPlugin` +- Retrieves raw JSON work payload +- Delegates parsing to `acceptWork()` + +--- + +## 2. Work Acceptance + +```cpp +Status acceptWork(const std::string& work); +``` + +Behavior: + +- Parses `queries` section +- Evaluates optional `discovery` queries +- Enqueues only valid work + +### Discovery Query Logic + +- If no discovery query exists → enqueue +- If discovery returns rows → enqueue +- If discovery returns zero rows → skip + +This prevents unnecessary execution on nodes where the query is irrelevant. + +--- + +## 3. Denylisting Protection + +To prevent repeated execution of problematic queries, the module includes: + +- `checkAndSetAsRunning()` +- `setAsNotRunning()` +- `denylistedQueryTimestampExpired()` +- `denylistDuration()` +- `hashQuery()` + +### Denylist Mechanism + +```mermaid +flowchart TD + Incoming["Incoming Query"] --> Check["checkAndSetAsRunning()"] + Check --> Running{"Already Running?"} + Running -->|"Within Duration"| Block["Denylisted"] + Running -->|"Expired"| Allow["Allow Execution"] + Check -->|"First Time"| Allow +``` + +The denylist key is derived from: + +```text +SHA256(query) +``` + +This ensures consistent identification across restarts and transports. + +--- + +## 4. Query Execution + +```cpp +Status runQueries(); +``` + +For each pending request: + +1. Pop request from internal storage +2. Execute via SQL engine +3. Measure execution time +4. Record performance +5. Store result +6. Clear running state + +Execution uses: + +```cpp +SQL monitorNonnumeric(const std::string& name, const std::string& query); +``` + +--- + +## 5. Performance Recording + +Performance metrics are stored in: + +```text +std::map performance_ +``` + +Tracked metrics include: + +- Execution duration +- Row count +- Sample process table deltas + +These integrate with the **Logging and Query Observability** module. + +--- + +## 6. Result Flushing + +```cpp +Status flushCompleted(); +``` + +- Serializes accumulated `DistributedQueryResult` objects +- Sends them to the plugin via `writeResults()` +- Clears local result queue + +--- + +# Plugin Interface + +## DistributedPlugin (Abstract) + +Defines the extension point for remote orchestration. + +```cpp +virtual Status getQueries(std::string& json) = 0; +virtual Status writeResults(const std::string& json) = 0; +``` + +The `call()` method acts as the unified entry point for plugin requests. + +Any transport (TLS, filesystem, custom RPC) can implement this interface. + +--- + +# TLS Distributed Plugin + +The **TLS Distributed Plugin** provides an HTTPS implementation of the plugin interface. + +Registered as: + +```text +REGISTER(TLSDistributedPlugin, "distributed", "tls") +``` + +## Configuration Flags + +```text +--distributed_tls_read_endpoint +--distributed_tls_write_endpoint +--distributed_tls_max_attempts +``` + +## Setup Phase + +```cpp +Status setUp(); +``` + +- Builds read/write URIs +- Uses TLS request helper utilities + +## Query Retrieval + +```cpp +Status getQueries(std::string& json); +``` + +- Sends POST to read endpoint +- Returns JSON payload containing work + +## Result Submission + +```cpp +Status writeResults(const std::string& json); +``` + +- Parses JSON +- POSTs to write endpoint +- Ignores server response body + +--- + +## TLS Data Flow + +```mermaid +sequenceDiagram + participant Node + participant TLS as "TLS Distributed Plugin" + participant Server + + Node->>TLS: pullUpdates() + TLS->>Server: POST read endpoint + Server->>TLS: JSON queries + TLS->>Node: return JSON + Node->>TLS: writeResults(JSON) + TLS->>Server: POST write endpoint +``` + +--- + +# State Management + +Distributed Querying relies on persistent state to: + +- Track running queries +- Store denylist timestamps +- Maintain queued work + +This state is handled through the Database and Storage Plugins subsystem. + +--- + +# Security Model + +Key security properties: + +1. Query integrity validated via SHA-256 hashing +2. HTTPS transport in TLS plugin +3. Configurable retry attempts +4. Denylist prevents execution storms + +The denylist duration is configurable via runtime flags. + +--- + +# Integration Points + +Distributed Querying integrates with: + +- SQL Engine and Virtual Tables (local execution) +- Database and Storage Plugins (persistent state) +- Logging and Query Observability (performance tracking) +- Extensions and IPC (optional distributed plugin implementations) +- Remote HTTP Client (transport layer headers and networking) + +Sibling module reference: + +- [Remote HTTP Client](../remote-http-client/remote-http-client.md) + +--- + +# Summary + +The **Distributed Querying** module provides: + +- A structured request/response model +- Pluggable transport abstraction +- Controlled query lifecycle management +- Built-in denylisting and performance tracking +- Secure TLS-based remote orchestration + +It transforms osquery from a local query engine into a centrally orchestrated distributed telemetry system while preserving execution safety and observability guarantees. diff --git a/docs/reference/architecture/eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md b/docs/reference/architecture/eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md new file mode 100644 index 00000000000..a18e9b822dc --- /dev/null +++ b/docs/reference/architecture/eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md @@ -0,0 +1,363 @@ +# Eventing Framework And Subscriptions + +## Overview + +The **Eventing Framework And Subscriptions** module provides the core publish/subscribe infrastructure for osquery’s event-driven data model. It enables: + +- Event publishers to monitor operating system activity (e.g., file changes, process activity). +- Event subscribers to register interest in specific event types. +- Persistent storage of event data for later retrieval via virtual tables. +- Automatic lifecycle, expiration, and optimization of event data based on query schedules. + +This module acts as the bridge between: + +- The SQL Engine And Virtual Tables module (which exposes event tables to queries). +- The Database And Storage Plugins module (which persists event data). +- The Configuration And Packs module (which defines scheduled queries and event usage). + +--- + +## High-Level Architecture + +```mermaid +flowchart TD + Config["Configuration And Packs"] -->|"scheduled queries"| EventFactory["EventFactory"] + EventFactory -->|"registers"| Publisher["EventPublisherPlugin"] + EventFactory -->|"registers"| Subscriber["EventSubscriberPlugin"] + Subscriber -->|"creates"| SubscriptionObj["Subscription"] + Publisher -->|"dispatches"| Callback["EventCallback"] + Callback -->|"addBatch()"| Storage["Database And Storage Plugins"] + SQL["SQL Engine And Virtual Tables"] -->|"SELECT from _events tables"| Subscriber + Subscriber -->|"generateRows()"| SQL +``` + +### Core Roles + +- **EventFactory** – Central coordinator for publishers and subscribers. +- **EventPublisherPlugin** – Produces system events. +- **EventSubscriberPlugin** – Consumes events and exposes them as queryable tables. +- **Subscription** – Binds subscriber logic to publisher events. +- **PathSet utilities** – Optimized path pattern matching for filesystem-related subscriptions. + +--- + +## EventFactory + +The `EventFactory` is a singleton responsible for managing the lifecycle and coordination of all event publishers and subscribers. + +### Responsibilities + +- Registering and deregistering event publishers and subscribers. +- Creating and forwarding subscriptions. +- Spawning publisher run-loop threads. +- Coordinating shutdown and cleanup. +- Applying configuration updates (e.g., scheduled query analysis). + +### Publisher and Subscriber Registration + +```mermaid +flowchart TD + RegisterSub["registerEventSubscriber()"] --> SetupSub["setUp()"] + SetupSub --> InitSub["init()"] + InitSub --> RunningSub["EVENT_RUNNING"] + + RegisterPub["registerEventPublisher()"] --> SetupPub["setUp()"] + SetupPub --> ReadyPub["EVENT_SETUP"] +``` + +During registration: + +- Subscribers are optionally enabled/disabled via configuration (`events.enable_subscribers`, `events.disable_subscribers`). +- Publishers are validated by type and initialized if events are enabled. +- State transitions are enforced (`EVENT_NONE`, `EVENT_SETUP`, `EVENT_RUNNING`, `EVENT_PAUSED`, `EVENT_FAILED`). + +### Publisher Execution Model + +Each publisher runs in its own thread: + +```mermaid +flowchart TD + Delay["delay()"] --> ThreadSpawn["spawn thread per publisher"] + ThreadSpawn --> RunLoop["run(type_id)"] + RunLoop --> PublisherRun["publisher->run()"] + PublisherRun --> Pause["pause(200ms)"] + Pause --> RunLoop + RunLoop -->|"isEnding()"| TearDown["tearDown()"] +``` + +Key behaviors: + +- The factory prevents publisher restarts once started. +- A default cool-off pause avoids CPU thrashing. +- Shutdown via `end()` gracefully deregisters publishers and joins threads. + +--- + +## Subscription Model + +The `Subscription` structure binds a subscriber to a publisher. + +### Subscription Structure + +```text +Subscription + ├── subscriber_name + ├── context (SubscriptionContextRef) + └── callback (EventCallback) +``` + +Defined in: + +- `osquery.osquery.events.subscription.Subscription` + +### EventCallback + +```text +Status(const EventContextRef&, const SubscriptionContextRef&) +``` + +When an event fires: + +1. The publisher evaluates matching subscriptions. +2. The associated callback executes. +3. The subscriber stores structured rows using `addBatch()`. + +Subscriptions are usually created via: + +```cpp +EventFactory::addSubscription("PublisherType", subscription); +``` + +--- + +## EventSubscriberPlugin + +Defined in: + +- `osquery.osquery.events.eventsubscriberplugin.Context` +- `osquery.osquery.events.eventsubscriberplugin.GenerateRowsResult` + +The `EventSubscriberPlugin` class encapsulates event storage, indexing, expiration, and query-time retrieval. + +### Core Responsibilities + +- Registering subscriptions in `init()`. +- Storing events using `addBatch()`. +- Managing time-based indexing and expiration. +- Exposing events as virtual tables via `genTable()`. + +### Internal Context + +```text +Context + ├── database_namespace + ├── event_index + ├── last_query_time + └── last_event_id +``` + +The context ensures: + +- Namespace isolation between subscribers. +- Efficient indexing by time and event identifier. +- Thread-safe updates using mutexes. + +### Event Storage and Expiration + +```mermaid +flowchart TD + Callback["EventCallback"] --> AddBatch["addBatch()"] + AddBatch --> AssignID["generateEventIdentifier()"] + AssignID --> Persist["Database write"] + Persist --> IndexUpdate["update time index"] + + ExpireCheck["expireEventBatches()"] --> RemoveOld["delete expired batches"] +``` + +Expiration behavior is governed by: + +- `getEventsExpiry()` – Default from flags. +- `getEventBatchesMax()` – Max retained batches. +- `setMinExpiry()` – Adjusted dynamically based on scheduled query intervals. + +### Query-Time Retrieval + +When a query selects from an event table: + +```mermaid +flowchart TD + SQLQuery["SELECT * FROM example_events"] --> GenTable["genTable()"] + GenTable --> GenerateRows["generateRows()"] + GenerateRows --> FilterTime["apply start/stop window"] + FilterTime --> YieldRows["yield Row to SQL engine"] +``` + +The `GenerateRowsResult` structure tracks: + +- Whether the scan reached the end. +- The last processed timestamp. +- The last processed event identifier. + +This enables optimized incremental queries. + +--- + +## Configuration-Driven Expiration Logic + +One of the most advanced responsibilities of this module is aligning event retention with scheduled queries. + +### SubscriberExpirationDetails + +Defined in: + +- `osquery.osquery.events.eventfactory.SubscriberExpirationDetails` + +Structure: + +```text +SubscriberExpirationDetails + ├── max_interval + └── query_count +``` + +During `configUpdate()`: + +1. The schedule is scanned for queries referencing `_events` tables. +2. For each subscriber table: + - The maximum interval is recorded. + - The number of queries referencing it is counted. +3. The minimum expiration window is set to: + +```text +min_expiry = (max_interval * 3), rounded to next minute +``` + +This ensures: + +- Events are retained long enough for scheduled queries to observe them. +- Storage is not retained unnecessarily. + +--- + +## PathSet and Pattern Matching + +Defined in: + +- `osquery.osquery.events.pathset.Compare` + +The `PathSet` template provides a thread-safe multiset implementation for path-based matching. + +### Supported Patterns + +- `*` – Match a single path component. +- `**` – Recursive wildcard. + +Example equivalence: + +```text +/This/Path/is +/This/Path/* +/This/Path/** +``` + +### Comparison Logic + +The `Compare` functor: + +- Compares path components lexicographically. +- Treats `*` as a wildcard. +- Treats `**` as recursive match. +- Maintains ordering semantics required by `std::multiset`. + +This is especially important for filesystem event publishers that need efficient path matching across many subscriptions. + +--- + +## Concurrency Model + +```mermaid +flowchart LR + FactoryLock["factory_lock_"] --> ProtectPub["event_pubs_"] + FactoryLock --> ProtectSub["event_subs_"] + + SubscriberLock1["event_id_lock_"] --> IDGen["EventID generation"] + SubscriberLock2["event_record_lock_"] --> RecordWrite["record time bins"] + SubscriberLock3["event_query_record_"] --> QueryTracking["query usage tracking"] +``` + +Key thread-safety mechanisms: + +- Recursive locks in `EventFactory`. +- Mutex-protected event indexing. +- Atomic counters for event identifiers and expiration windows. +- Dedicated threads per publisher. + +--- + +## Integration With Other Modules + +### SQL Engine And Virtual Tables + +- Event subscribers expose data through `genTable()`. +- `_events` tables are queried like normal virtual tables. + +### Database And Storage Plugins + +- Event rows are persisted using `IDatabaseInterface`. +- Time-based indexing enables efficient windowed queries. + +### Configuration And Packs + +- Scheduled queries determine retention windows. +- Config parsers can enable/disable subscribers. + +### Logging And Query Observability + +- Events may be forwarded using `addForwarder()` and `forwardEvent()`. +- Logger plugins can receive structured event payloads. + +--- + +## End-to-End Flow + +```mermaid +sequenceDiagram + participant Config + participant Factory as EventFactory + participant Pub as EventPublisher + participant Sub as EventSubscriber + participant DB as Database + participant SQL + + Config->>Factory: configUpdate() + Factory->>Sub: setMinExpiry() + Factory->>Pub: spawn run loop + Pub->>Sub: fire event (callback) + Sub->>DB: addBatch() + SQL->>Sub: SELECT from event table + Sub->>DB: generateRows() + Sub->>SQL: yield rows +``` + +--- + +## Design Principles + +1. **Separation of Concerns** – Publishers generate, subscribers transform and persist, SQL retrieves. +2. **Config-Aware Retention** – Event expiration adapts to scheduled queries. +3. **Thread Isolation** – Each publisher runs independently. +4. **Optimized Query Windows** – Incremental scanning via time and ID indexes. +5. **Extensibility** – New publishers and subscribers can be registered dynamically. + +--- + +## Summary + +The **Eventing Framework And Subscriptions** module is the backbone of osquery’s event-driven capabilities. It provides: + +- A robust publish/subscribe infrastructure. +- Persistent and optimized event storage. +- Config-driven lifecycle management. +- Tight integration with the SQL engine and scheduler. + +Through careful coordination between `EventFactory`, `EventSubscriberPlugin`, `Subscription`, and `PathSet`, this module enables scalable, queryable system event monitoring across platforms. \ No newline at end of file diff --git a/docs/reference/architecture/extensions-and-ipc/extensions-and-ipc.md b/docs/reference/architecture/extensions-and-ipc/extensions-and-ipc.md new file mode 100644 index 00000000000..b6bf3ce6127 --- /dev/null +++ b/docs/reference/architecture/extensions-and-ipc/extensions-and-ipc.md @@ -0,0 +1,375 @@ +# Extensions And Ipc + +The **Extensions And Ipc** module provides the inter-process communication (IPC) layer and extension framework that allows osquery to be extended at runtime. It enables external processes (extensions) to: + +- Register new registry plugins (tables, config plugins, logger plugins, etc.) +- Execute SQL queries via the core engine +- Expose custom functionality to the osquery core +- Communicate securely over platform-specific IPC channels + +This module is the foundation for osquery’s pluggable architecture, separating the core daemon from externally developed functionality while maintaining a controlled and versioned API boundary. + +--- + +## 1. Purpose and Design Goals + +The Extensions And Ipc module is designed to: + +- ✅ Allow third-party or internal extensions to run as separate processes +- ✅ Provide a stable RPC interface using Apache Thrift +- ✅ Enforce SDK compatibility and version checks +- ✅ Isolate crashes or faults from the core daemon +- ✅ Support cross-platform IPC (UNIX domain sockets on POSIX, named pipes on Windows) + +At a high level, it implements: + +- An **Extension Manager** (running inside osquery core) +- One or more **Extension Processes** (external binaries) +- A **Thrift-based RPC layer** +- Health monitoring and watchdog services + +--- + +## 2. High-Level Architecture + +The Extensions And Ipc module sits between: + +- The **Registry subsystem** (plugin routing) +- The **SQL engine** (query delegation) +- The **Dispatcher and runtime threads** +- External extension processes + +### 2.1 Core–Extension Interaction Model + +```mermaid +flowchart LR + Core["osquery Core"] -->|"Starts"| Manager["Extension Manager"] + Manager -->|"Registers routes"| Registry["RegistryFactory"] + + ExtensionProc["Extension Process"] -->|"registerExtension()"| Manager + Manager -->|"Assign UUID"| ExtensionProc + + Core -->|"callExtension()"| Manager + Manager -->|"Route to UUID"| ExtensionProc + + ExtensionProc -->|"Response"| Manager + Manager -->|"PluginResponse"| Core +``` + +### 2.2 IPC and Thrift Layer + +```mermaid +flowchart TD + Client["ExtensionClient"] --> Transport["Thrift Transport"] + Transport --> Socket["UNIX Socket or Named Pipe"] + Socket --> Server["ExtensionRunner or ManagerRunner"] + Server --> Handler["ExtensionHandler or ExtensionManagerHandler"] + Handler --> Interface["ExtensionInterface / ExtensionManagerInterface"] + Interface --> Registry["RegistryFactory"] +``` + +--- + +## 3. Core Components + +### 3.1 Extension Metadata + +**Component:** `ExtensionInfo` + +Defined in `extensions.h`, this struct mirrors the Thrift `InternalExtensionInfo` type and contains: + +- `name` +- `version` +- `sdk_version` +- `min_sdk_version` + +This metadata is validated during registration and stored in an `ExtensionList` keyed by a transient `RouteUUID`. + +--- + +### 3.2 Extension Manager (Core Side) + +**Key Classes:** + +- `ExtensionManagerInterface` +- `ExtensionManagerHandler` +- `ExtensionManagerRunner` +- `ExtensionManagerWatcher` + +#### Responsibilities + +1. Accept extension registrations +2. Assign unique `RouteUUID` values (via `UuidGenerator`) +3. Validate SDK compatibility +4. Maintain active extension metadata +5. Route plugin calls to registered extensions +6. Monitor extension health + +#### Registration Flow + +```mermaid +sequenceDiagram + participant Ext as Extension Process + participant EM as Extension Manager + participant Reg as RegistryFactory + + Ext->>EM: registerExtension(info, registry) + EM->>EM: Validate name uniqueness + EM->>EM: Check SDK compatibility + EM->>Reg: addBroadcast(uuid, registry) + EM-->>Ext: Return UUID +``` + +If duplicate names or incompatible SDK versions are detected, registration fails. + +--- + +### 3.3 Extension Process (External Binary) + +**Key Classes:** + +- `ExtensionRunner` +- `ExtensionInterface` +- `ExtensionHandler` + +An extension process: + +1. Connects to the Extension Manager socket +2. Registers its broadcasted registry routes +3. Receives a `RouteUUID` +4. Starts a Thrift server loop +5. Waits for calls from the core + +Each extension serves the `Extension` Thrift service defined in the generated files. + +--- + +### 3.4 Thrift RPC Layer + +**Key Files:** + +- `impl_thrift.cpp` +- Generated files under `thrift/gen/` + +The module uses Apache Thrift to define two services: + +1. `Extension` – implemented by extension processes +2. `ExtensionManager` – implemented by osquery core + +#### Server-Side Wrappers + +- `ExtensionHandler` implements `ExtensionIf` +- `ExtensionManagerHandler` implements `ExtensionManagerIf` + +These handlers: + +- Translate Thrift structures into internal `PluginRequest`/`PluginResponse` +- Delegate logic to `ExtensionInterface` or `ExtensionManagerInterface` + +#### Client-Side Wrappers + +- `ExtensionClient` +- `ExtensionManagerClient` + +They abstract: + +- Transport creation +- Timeout configuration +- Method invocation +- Status translation + +--- + +### 3.5 Extension API Contracts + +The module defines abstract APIs: + +- `ExtensionAPI` +- `ExtensionManagerAPI` + +These interfaces enforce a separation between: + +- Thrift transport concerns +- Business logic + +This design allows alternative RPC backends in the future while preserving core logic. + +--- + +### 3.6 UUID Management + +**Component:** `UuidGenerator` + +- Generates 16-bit UUID values +- Tracks active UUIDs in a thread-safe set +- Removes UUIDs on deregistration + +UUIDs uniquely identify extension routes inside `RegistryFactory`. + +--- + +### 3.7 External SQL Plugin + +**Component:** `ExternalSQLPlugin` + +This special plugin allows the core SQL registry to delegate SQL queries to extensions. + +```mermaid +flowchart LR + SQL["SQL Engine"] --> ExternalSQL["ExternalSQLPlugin"] + ExternalSQL --> ManagerClient["ExtensionManagerClient"] + ManagerClient --> Extension["Extension Process"] +``` + +Used when tables or query logic are implemented outside the core. + +--- + +### 3.8 Watchers and Health Monitoring + +**Key Classes:** + +- `ExtensionWatcher` +- `ExtensionManagerWatcher` + +#### ExtensionWatcher + +- Runs inside extension processes +- Periodically pings the manager +- Exits fatally if the manager disappears + +#### ExtensionManagerWatcher + +- Runs inside core +- Pings all registered extensions +- Removes routes if extensions become unreachable + +```mermaid +flowchart TD + Watcher["ExtensionManagerWatcher"] --> UUIDs["Registered UUIDs"] + UUIDs --> Ping["Ping Extension"] + Ping -->|"Success"| Keep["Keep Registered"] + Ping -->|"Failure x2"| Remove["Remove Broadcast Route"] +``` + +--- + +### 3.9 Autoloading and Security + +Extensions can be autoloaded using: + +- `--extensions_autoload` +- `--extension` (shell-only) + +Security checks include: + +- File extension validation (`.ext`, `.exe` on Windows) +- Directory permission safety checks +- Ownership validation + +Unsafe or improperly permissioned binaries are rejected. + +--- + +### 3.10 IPC Channel Integration + +On POSIX systems, IPC is primarily UNIX domain sockets. Additionally, worker IPC may use pipe-based channels. + +**Component:** `GetChannelType` + +This specialization maps a `PipeChannelFactory` to its underlying `PipeChannel` type, integrating extension communication with the worker IPC framework. + +This ensures consistent abstraction across: + +- Extension IPC +- Worker-table IPC +- Internal task communication + +--- + +## 4. Lifecycle Overview + +### 4.1 Core Startup + +```mermaid +flowchart TD + Start["Core Start"] --> StartManager["startExtensionManager()"] + StartManager --> Watcher["ExtensionManagerWatcher"] + Watcher --> Runner["ExtensionManagerRunner"] + Runner --> Listen["Thrift Listen"] +``` + +### 4.2 Extension Startup + +```mermaid +flowchart TD + ExtStart["Extension Binary Start"] --> Connect["Connect to Manager Socket"] + Connect --> Register["registerExtension()"] + Register --> UUID["Receive UUID"] + UUID --> StartServer["Start ExtensionRunner"] + StartServer --> Serve["Serve Thrift Requests"] +``` + +### 4.3 Shutdown Flow + +- Extension calls `shutdown()` or receives manager failure +- Manager deregisters UUID +- `RegistryFactory::removeBroadcast()` is invoked +- Stale socket paths are cleaned via `removeStalePaths()` + +--- + +## 5. Interaction with Other Subsystems + +The Extensions And Ipc module interacts with: + +- **RegistryFactory** – route registration and broadcast +- **SQL Engine** – delegated queries +- **Dispatcher** – background service threads +- **Flags subsystem** – option propagation +- **Filesystem utilities** – socket and permission checks + +It acts as a boundary layer rather than a business-logic module. + +--- + +## 6. Error Handling and Status Model + +All RPC responses use: + +- `ExtensionStatus` +- `ExtensionResponse` +- `ExtensionCode` (SUCCESS, FAILED, FATAL) + +This ensures: + +- Consistent status propagation across process boundaries +- Clear failure semantics for SDK mismatches and duplicate registrations + +--- + +## 7. Security Considerations + +The module enforces: + +- SDK version compatibility checks +- Duplicate extension prevention +- File permission validation for autoload +- Socket path isolation per UUID +- Controlled shutdown behavior + +On Windows, named pipes are secured using explicit security descriptors. + +--- + +## 8. Summary + +The **Extensions And Ipc** module is the runtime extension backbone of osquery. It: + +- Implements a bidirectional Thrift RPC system +- Manages extension lifecycle and UUID assignment +- Routes registry plugin calls across process boundaries +- Monitors extension health +- Enforces compatibility and security constraints + +Without this module, osquery would be a static binary. With it, osquery becomes a dynamic, pluggable platform capable of safely integrating externally developed functionality at runtime. diff --git a/docs/reference/architecture/filesystem-and-path-utilities/filesystem-and-path-utilities.md b/docs/reference/architecture/filesystem-and-path-utilities/filesystem-and-path-utilities.md new file mode 100644 index 00000000000..579007ad3ba --- /dev/null +++ b/docs/reference/architecture/filesystem-and-path-utilities/filesystem-and-path-utilities.md @@ -0,0 +1,338 @@ +# Filesystem And Path Utilities + +## Overview + +The **Filesystem And Path Utilities** module provides a cross-platform abstraction layer for file operations, path handling, globbing, permissions, and filesystem metadata inspection across Linux, macOS (POSIX), and Windows systems. + +It is a foundational module used by higher-level components to: + +- Read and write files safely +- Enforce secure permission models +- Resolve glob patterns +- Inspect file metadata +- Enumerate directories and mounts +- Interact with `/proc` on Linux +- Provide consistent APIs across POSIX and Windows + +This module hides operating system differences while preserving security guarantees and performance characteristics. + +--- + +## Architectural Overview + +```mermaid +flowchart TD + Caller["Core / SQL / Extensions"] --> FSAPI["Filesystem API"] + + subgraph abstraction_layer["Cross-Platform Abstraction"] + FSAPI --> PlatformFile["PlatformFile"] + FSAPI --> Glob["platformGlob()"] + FSAPI --> Perms["platformChmod()"] + FSAPI --> Access["platformAccess()"] + FSAPI --> Stat["platformStat() / lstat()"] + end + + subgraph posix_layer["POSIX Implementation"] + PlatformFile --> POSIXFile["open/read/write/lstat"] + Glob --> POSIXGlob["glob()"] + end + + subgraph windows_layer["Windows Implementation"] + PlatformFile --> WinFile["CreateFile / ReadFile"] + Glob --> WinGlob["FindFirstFileW"] + Perms --> WinACL["ACL Manipulation"] + end +``` + +The abstraction layer ensures that callers use a unified API regardless of the underlying OS. + +--- + +## Core Components + +### 1. PlatformFile + +**Component:** `osquery.osquery.filesystem.fileops.PlatformFile` + +`PlatformFile` is the central abstraction for file I/O. + +It provides: + +- Cross-platform file opening modes (`PF_READ`, `PF_WRITE`, etc.) +- Non-blocking I/O support +- Ownership validation +- Executable checks +- Safe permission verification +- Seek and size inspection +- File time retrieval + +### Lifecycle Model + +```mermaid +flowchart TD + Open["Open PlatformFile"] --> Validate["Handle Valid?"] + Validate -->|No| Fail["Return Error"] + Validate -->|Yes| IO["Read / Write / Seek"] + IO --> Close["Destructor Closes Handle"] +``` + +Platform-specific implementations exist for: + +- POSIX (`open`, `read`, `write`, `fstat`) +- Windows (`CreateFileW`, `ReadFile`, overlapped I/O) + +--- + +### 2. File Metadata and Stat Structures + +#### POSIX +- `osquery.osquery.filesystem.posix.fileops.stat` + +Uses: +- `stat` +- `lstat` +- `fstat` + +#### Windows +- `osquery.osquery.filesystem.fileops.WINDOWS_STAT` +- `osquery.osquery.filesystem.fileops.win_stat` +- `osquery.osquery.filesystem.windows.fileops.WindowsFindFiles` + +Windows provides: +- File ID +- Volume serial +- ACL-derived ownership +- File version metadata +- Attributes (hidden, system, archive) + +```mermaid +flowchart LR + Path["File Path"] --> StatCall["platformStat()"] + StatCall -->|POSIX| POSIXStat["struct stat"] + StatCall -->|Windows| WinStat["WINDOWS_STAT"] +``` + +--- + +### 3. Globbing and Pattern Resolution + +**Core APIs:** +- `platformGlob()` +- `resolveFilePattern()` +- `replaceGlobWildcards()` + +Supports: +- `*` and `?` +- `**` recursive globs +- `%` SQL-style wildcard translation +- Brace expansion (Windows regex translation) + +Recursive globbing includes loop detection using inode tracking. + +```mermaid +flowchart TD + Pattern["Input Pattern"] --> Normalize["replaceGlobWildcards()"] + Normalize --> Expand["platformGlob()"] + Expand --> Filter["Apply GLOB_FILES / GLOB_FOLDERS"] + Filter --> Results["Resolved Paths"] +``` + +--- + +### 4. Permissions and Security Enforcement + +Security is a major responsibility of this module. + +#### Ownership Checks +- `isOwnerRoot()` +- `isOwnerCurrentUser()` + +#### Safe Permission Enforcement +- `hasSafePermissions()` +- `platformSetSafeDbPerms()` + +On Windows, safe permissions require: +- Only Administrators and SYSTEM have write privileges +- Explicit ACL validation + +On POSIX, safety approximates: +- Mode `0700` for sensitive files +- No world-writable flags + +#### Execution Safety + +The `safePermissions()` helper ensures: + +- File exists +- Not located in temporary directory +- Proper ownership +- Proper executable bits + +This protects extension loading and module execution paths. + +--- + +### 5. File Reading and Writing Utilities + +High-level helpers include: + +- `readFile()` (streaming and full-buffer versions) +- `writeTextFile()` +- `isReadable()` +- `isWritable()` +- `pathExists()` +- `removePath()` +- `movePath()` + +A configurable limit (`read_max`) prevents excessive file reads. + +```mermaid +flowchart TD + ReadCall["readFile()"] --> Open["PlatformFile"] + Open --> SizeCheck["checkFileReadLimit()"] + SizeCheck --> Loop["Block Read Loop"] + Loop --> Predicate["Callback / Buffer"] +``` + +--- + +### 6. Home Directory and System Paths + +Cross-platform resolution of: + +- Current user home directory (`getHomeDirectory()`) +- Osquery working directory (`osqueryHomeDirectory()`) +- System root (`getSystemRoot()`) + +Fallback behavior: + +- Uses environment variables first +- Falls back to system APIs +- Falls back to temp directory if needed + +--- + +## Linux-Specific Extensions + +### Mounted Filesystems + +**Component:** `osquery.osquery.filesystem.linux.mounts.MountInformation` + +Provides structured information for mounted filesystems: + +- Device path +- Filesystem type +- Mount flags +- `statfs` block and inode counts + +```mermaid +flowchart LR + getMounted["getMountedFilesystems()"] --> MountInfo["MountInformation"] + MountInfo --> StatFS["StatFsInfo"] +``` + +--- + +### /proc Parsing and Socket Inspection + +**Components:** +- `osquery.osquery.filesystem.linux.proc.SocketInfo` +- `osquery.osquery.filesystem.linux.proc.SocketProcessInfo` + +Capabilities: + +- Enumerate processes via `/proc` +- Map socket inode to process +- Decode hex-encoded network addresses +- Parse `/proc/net/*` files + +```mermaid +flowchart TD + Proc["/proc"] --> Enumerate["procEnumerateProcesses()"] + Enumerate --> FDEnum["procEnumerateProcessDescriptors()"] + FDEnum --> SocketMap["SocketInodeToProcessInfoMap"] +``` + +This enables socket tables and network observability features. + +--- + +## Windows-Specific Enhancements + +Windows includes additional capabilities: + +- NTFS ACL manipulation +- File version extraction +- Original filename extraction from PE metadata +- Overlapped (async) I/O (`AsyncEvent`) +- Named pipe detection (`socketExists()`) + +Windows globbing is implemented using: +- `FindFirstFileW` +- Regex translation for brace patterns + +--- + +## Security Model Summary + +```mermaid +flowchart TD + File["Candidate File"] --> Exists["Exists?"] + Exists -->|No| Reject["Reject"] + Exists --> Owner["Owner Root or Current User?"] + Owner -->|No| Reject + Owner --> Writable["World Writable?"] + Writable -->|Yes| Reject + Writable --> Exec["Executable Required?"] + Exec --> Accept["Safe"] +``` + +This ensures: + +- No unsafe extension loading +- No world-writable execution +- No loading from temp directories + +--- + +## Integration Within the System + +The Filesystem And Path Utilities module is used by: + +- SQL virtual tables for file metadata +- Configuration loading +- Extension management +- Logging subsystems +- Distributed query execution +- Event subscribers + +It acts as a **trusted boundary layer** between: + +- User-supplied file paths +- The operating system +- Internal execution logic + +--- + +## Key Design Principles + +1. **Cross-platform consistency** +2. **Security-first permission enforcement** +3. **Non-blocking support where possible** +4. **Recursive glob loop detection** +5. **Minimal exposure of OS-specific details** + +--- + +## Conclusion + +The **Filesystem And Path Utilities** module is a foundational cross-platform subsystem responsible for: + +- File I/O abstraction +- Secure permission validation +- Glob resolution +- Filesystem metadata access +- Linux `/proc` inspection +- Windows ACL and metadata handling + +It enables higher-level components to operate safely and consistently across heterogeneous operating systems while enforcing strict security guarantees. \ No newline at end of file diff --git a/docs/reference/architecture/logging-and-query-observability/logging-and-query-observability.md b/docs/reference/architecture/logging-and-query-observability/logging-and-query-observability.md new file mode 100644 index 00000000000..cf98d585c16 --- /dev/null +++ b/docs/reference/architecture/logging-and-query-observability/logging-and-query-observability.md @@ -0,0 +1,409 @@ +# Logging And Query Observability + +## Overview + +The **Logging And Query Observability** module is responsible for transforming raw query executions and internal status events into structured, serialized, and transport-ready log artifacts. + +It sits at the intersection of: + +- The **SQL execution layer** (see [SQL Engine And Virtual Tables](../sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md)) +- The **persistent state layer** (see [Database And Storage Plugins](../database-and-storage-plugins/database-and-storage-plugins.md)) +- The **configuration and scheduling layer** (see [Configuration And Packs](../configuration-and-packs/configuration-and-packs.md)) +- The **pluggable logging infrastructure** + +This module provides: + +- Query result diffing and snapshot handling +- Query execution metadata and performance tracking +- Structured log serialization (JSON, event-based) +- Pluggable logger interface for external sinks (TLS, filesystem, syslog, etc.) +- Status log forwarding and event streaming support + +--- + +## Architectural Position + +```mermaid +flowchart TD + Config["Configuration And Packs"] --> Scheduler["Scheduled Query"] + Scheduler --> SQ["ScheduledQuery Struct"] + SQ --> QueryObj["Query State Manager"] + QueryObj --> DB["Database And Storage Plugins"] + + QueryObj --> Diff["DiffResults Engine"] + Diff --> LogItem["QueryLogItem"] + LogItem --> Serializer["JSON Serialization"] + Serializer --> Logger["LoggerPlugin"] + + StatusLogs["Internal Status Events"] --> StatusLine["StatusLogLine"] + StatusLine --> Logger + + Logger --> External["External Logging Backend"] +``` + +The Logging And Query Observability module acts as a transformation and routing layer: + +1. Queries execute. +2. Results are compared with historical state. +3. Changes are wrapped in structured log items. +4. Logs are serialized. +5. A `LoggerPlugin` implementation forwards them upstream. + +--- + +## Core Components + +This module consists of five primary building blocks: + +- **LoggerPlugin** and **StatusLogLine** – Pluggable logging interface +- **QueryLogItem** – Structured representation of query output +- **DiffResults** – Result set comparison engine +- **QueryPerformance** – Execution metrics and telemetry +- **ScheduledQuery** – Query scheduling metadata model + +Each component addresses a specific concern in the observability pipeline. + +--- + +## LoggerPlugin And StatusLogLine + +### Purpose + +The logging subsystem provides a pluggable abstraction for forwarding: + +- Query results +- Snapshot outputs +- Event batches +- Internal status logs + +### StatusLogLine + +`StatusLogLine` represents a single internal status entry emitted by the runtime. It includes: + +- Severity (`O_INFO`, `O_WARNING`, `O_ERROR`, `O_FATAL`) +- Source file and line number +- Human-readable message +- UNIX timestamp and calendar time +- Host identifier + +This structure decouples internal logging (e.g., Glog) from external log sinks. + +### LoggerPlugin + +`LoggerPlugin` is an abstract plugin interface that allows custom logging backends. + +Key extensibility points: + +- `logString` (required) – Primary logging entrypoint +- `logStatus` – Handles structured status logs +- `logSnapshot` – Special handling for large snapshot queries +- `logEvent` / `logStringBatch` – Direct event forwarding +- `usesLogStatus` – Opt-in takeover of internal status handling +- `usesLogEvent` – Opt-in direct event streaming + +### Logger Feature Flags + +Logger plugins can advertise support for: + +- `LOGGER_FEATURE_LOGSTATUS` +- `LOGGER_FEATURE_LOGEVENT` + +This enables selective routing decisions at runtime. + +### Logger Lifecycle + +```mermaid +flowchart TD + Init["System Initialization"] --> Buffer["Buffer Early Status Logs"] + Buffer --> LoadConfig["Load Configuration"] + LoadConfig --> Discover["Discover Logger Plugin"] + Discover --> InitLogger["LoggerPlugin.init()"] + InitLogger --> Flush["Flush Buffered StatusLogLine Entries"] + Flush --> Runtime["Runtime Logging"] +``` + +Before the logger initializes, early status logs are buffered and later flushed during `init()`. + +This ensures no observability gaps during startup. + +--- + +## ScheduledQuery + +`ScheduledQuery` models the runtime attributes of a scheduled query. + +Key attributes: + +- `pack_name` – Configuration pack source +- `name` – Unique identifier +- `query` – SQL statement +- `interval` – Execution frequency +- `startup_priority` – Startup execution control +- `denylisted` – Configuration-level suppression +- `options` – Behavioral flags (e.g., snapshot, removed) + +### Behavioral Flags + +- `snapshot` → Always log full results +- `removed` → Control reporting of removed rows + +This structure originates from configuration parsing (see [Configuration And Packs](../configuration-and-packs/configuration-and-packs.md)) and feeds directly into execution and logging logic. + +--- + +## Query State Management + +### Query + +The `Query` class maintains persistent state for a scheduled query. + +Responsibilities: + +- Retrieve previous results from storage +- Compute diffs between executions +- Track query epoch +- Maintain execution counter +- Persist updated result sets + +It interacts with the database abstraction (see [Database And Storage Plugins](../database-and-storage-plugins/database-and-storage-plugins.md)). + +### Differential Execution Model + +```mermaid +flowchart LR + Old["Previous Results"] --> DiffEngine["diff()"] + New["Current Results"] --> DiffEngine + DiffEngine --> Added["Added Rows"] + DiffEngine --> Removed["Removed Rows"] +``` + +The `addNewResults` method: + +1. Loads historical state. +2. Computes a `DiffResults` structure. +3. Updates persistent storage. +4. Returns differential output for logging. + +This avoids repeatedly logging identical datasets and reduces log volume. + +--- + +## DiffResults + +`DiffResults` represents the delta between two query executions. + +Structure: + +- `added` – Rows present in new results but not old +- `removed` – Rows present in old results but not new + +### Properties + +- Move-only semantics (efficient handling of large result sets) +- Equality comparison support +- JSON serialization helpers + +### Serialization Flow + +```mermaid +flowchart TD + Diff["DiffResults"] --> Serialize["serializeDiffResults()"] + Serialize --> JSONDoc["JSON Document"] + JSONDoc --> Logger["LoggerPlugin"] +``` + +Diff serialization ensures: + +- Stable output format +- Optional numeric preservation +- Compatibility with downstream analytics pipelines + +--- + +## QueryLogItem + +`QueryLogItem` is the canonical structured representation of a query execution. + +It includes: + +- `isSnapshot` – Snapshot vs differential indicator +- `results` – `DiffResults` +- `snapshot_results` – Full results (if snapshot) +- `name` – Query name +- `identifier` – Host ID +- `time` / `calendar_time` +- `epoch` +- `counter` +- `decorations` – Arbitrary metadata + +### Equality Semantics + +Two `QueryLogItem` instances are equal if: + +- Their `DiffResults` match +- Their query names match + +This enables deduplication logic in higher-level processing. + +### Serialization Variants + +The module supports multiple output formats: + +1. Structured JSON object +2. JSON string +3. Event-style records (split per action) + +```mermaid +flowchart TD + Item["QueryLogItem"] --> JSON1["serializeQueryLogItem()"] + Item --> JSON2["serializeQueryLogItemJSON()"] + Item --> Events1["serializeQueryLogItemAsEvents()"] + Item --> Events2["serializeQueryLogItemAsEventsJSON()"] +``` + +Event-based serialization allows each row addition/removal to be emitted as an individual log record. + +--- + +## QueryPerformance + +`QueryPerformance` provides execution telemetry. + +Metrics tracked: + +- Total executions +- Last execution time +- Wall time (total and last) +- User/system CPU time +- Memory usage +- Output size + +### CSV Serialization + +Performance data is stored and transferred using a CSV representation: + +- Constructor accepts CSV +- `toCSV()` exports current state + +This lightweight format reduces overhead in persistent storage. + +### Observability Use Cases + +- Detect slow queries +- Identify resource-heavy packs +- Enforce denylisting via configuration +- Feed monitoring dashboards + +--- + +## End-To-End Query Logging Flow + +```mermaid +flowchart TD + Config["ScheduledQuery"] --> Exec["SQL Execution"] + Exec --> Results["QueryDataTyped"] + Results --> QueryState["Query.addNewResults()"] + QueryState --> Diff["DiffResults"] + Diff --> LogItem["QueryLogItem"] + LogItem --> Serialize["JSON Serialization"] + Serialize --> Logger["LoggerPlugin"] + Logger --> Sink["External Log System"] +``` + +### Snapshot Query Flow + +```mermaid +flowchart TD + SQ["ScheduledQuery snapshot=true"] --> Exec["Execute Query"] + Exec --> Full["Full Result Set"] + Full --> LogItem["QueryLogItem isSnapshot=true"] + LogItem --> Logger["logSnapshot()"] +``` + +Snapshot queries bypass diff computation and emit complete result sets. + +--- + +## Relationship With Other Modules + +### SQL Engine And Virtual Tables + +Query execution originates from the SQL engine layer: + +- Virtual table resolution +- SQLite execution +- Result materialization + +See: [SQL Engine And Virtual Tables](../sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md) + +### Database And Storage Plugins + +Persistent state for query history and counters is managed by the storage abstraction. + +See: [Database And Storage Plugins](../database-and-storage-plugins/database-and-storage-plugins.md) + +### Configuration And Packs + +Scheduled queries and behavioral flags originate from configuration packs. + +See: [Configuration And Packs](../configuration-and-packs/configuration-and-packs.md) + +### Distributed Querying + +When queries are executed via distributed frameworks, results still flow through `QueryLogItem` and the logger interface. + +See: [Distributed Querying](../distributed-querying/distributed-querying.md) + +--- + +## Design Principles + +### 1. Pluggability + +Logging is abstracted via `LoggerPlugin` to allow: + +- TLS streaming +- Filesystem logging +- Syslog integration +- Custom enterprise pipelines + +### 2. Efficiency + +- Differential logging reduces noise +- Move semantics for result sets +- CSV encoding for performance stats + +### 3. Deterministic Serialization + +All log structures have explicit JSON serializers to guarantee: + +- Backward compatibility +- Stable schema +- Predictable downstream parsing + +### 4. Observability-First Model + +Every query execution produces: + +- State delta +- Execution metadata +- Performance metrics +- Host attribution + +This ensures complete auditability of query behavior. + +--- + +## Summary + +The **Logging And Query Observability** module transforms raw SQL execution results and runtime status events into structured, serialized, and pluggable log outputs. + +It provides: + +- Differential result tracking (`DiffResults`) +- Query metadata encapsulation (`QueryLogItem`) +- Performance telemetry (`QueryPerformance`) +- Scheduling metadata (`ScheduledQuery`) +- Extensible logging backends (`LoggerPlugin`) + +By cleanly separating execution, state management, serialization, and transport, this module forms the backbone of osquery’s observability and audit pipeline. \ No newline at end of file diff --git a/docs/reference/architecture/remote-http-client/remote-http-client.md b/docs/reference/architecture/remote-http-client/remote-http-client.md new file mode 100644 index 00000000000..83b5f95de29 --- /dev/null +++ b/docs/reference/architecture/remote-http-client/remote-http-client.md @@ -0,0 +1,345 @@ +# Remote Http Client + +The **Remote Http Client** module provides a general-purpose HTTP and HTTPS client implementation used across the system for secure remote communication. Built on top of Boost.Asio and Boost.Beast, it enables synchronous-style HTTP operations backed by asynchronous networking primitives, with full TLS support, timeout handling, redirect control, and proxy configuration. + +This module acts as the foundational networking layer for components that need to communicate with remote services, such as: + +- [Distributed Querying](distributed-querying/distributed-querying.md) +- [Logging and Query Observability](logging-and-query-observability/logging-and-query-observability.md) +- [Configuration and Packs](configuration-and-packs/configuration-and-packs.md) + +It abstracts low-level socket management, TLS setup, request serialization, and response parsing into a reusable and secure HTTP client interface. + +--- + +## 1. Purpose and Responsibilities + +The Remote Http Client module is responsible for: + +- Creating and managing TCP and TLS connections +- Executing HTTP methods: `GET`, `POST`, `PUT`, `HEAD`, `DELETE` +- Managing request headers and body serialization +- Parsing HTTP responses +- Enforcing timeouts and connection lifecycle rules +- Handling TLS verification, certificates, and cipher configuration +- Supporting optional proxy routing +- Supporting redirect and keep-alive behavior + +It provides a high-level API while internally orchestrating asynchronous Boost networking operations. + +--- + +## 2. High-Level Architecture + +The module is centered around three main abstractions: + +- `Client` – Connection lifecycle and HTTP execution engine +- `HTTP_Request` – URI-aware request wrapper +- `HTTP_Response` – Response wrapper with header iteration support + +### 2.1 Architectural Overview + +```mermaid +flowchart TD + Caller["Caller Module"] --> Client["Client"] + Client --> Options["Client::Options"] + Client --> Request["HTTP_Request"] + Client --> Response["HTTP_Response"] + + Client --> Resolver["TCP Resolver"] + Client --> Socket["TCP Socket"] + Client --> TLSSocket["SSL Stream"] + Client --> Timer["Deadline Timer"] + + Request --> URI["URI Parser"] + Response --> Headers["Header Iterator"] +``` + +The `Client` orchestrates the network stack and delegates URI parsing to `HTTP_Request`, while wrapping Boost.Beast responses inside `HTTP_Response`. + +--- + +## 3. Core Components + +### 3.1 Client + +The `Client` class implements a general-purpose HTTP/HTTPS client built on: + +- `boost::asio::io_context` +- `boost::asio::ip::tcp::resolver` +- `boost::asio::ip::tcp::socket` +- `boost::asio::ssl::stream` +- `boost::beast::http` + +It exposes the following public methods: + +- `get(Request&)` +- `post(Request&, body, content_type)` +- `put(Request&, body, content_type)` +- `head(Request&)` +- `delete_(Request&)` + +Each method: +1. Initializes the request +2. Establishes or reuses a connection +3. Sends the request asynchronously +4. Waits for response or timeout +5. Returns a `Response` object + +The destructor ensures that open sockets are closed safely. + +--- + +### 3.2 Client Options + +The `Client::Options` class drives client behavior. + +It supports configuration for: + +- TLS enablement (`ssl_connection`) +- Keep-alive behavior +- Redirect following +- Peer verification enforcement +- Timeout configuration +- Cipher selection +- Certificate and private key files +- CA verify path +- Proxy hostname +- Remote hostname and port overrides + +#### Options Comparison + +The equality operator allows the `Client` to detect configuration changes: + +```mermaid +flowchart LR + OldOpts["Current Options"] --> Compare["operator=="] + NewOpts["New Options"] --> Compare + Compare -->|"Different"| Reconfigure["Reinitialize Connection"] + Compare -->|"Same"| Reuse["Reuse Existing Settings"] +``` + +This prevents unnecessary TLS reconfiguration and socket churn. + +--- + +### 3.3 HTTP_Request + +`HTTP_Request` extends Boost.Beast request objects with: + +- URI parsing support +- Host, port, path extraction +- Protocol detection +- Header insertion via operator overloading + +#### URI Handling + +The request wraps an internal `Uri` object and exposes: + +- `remoteHost()` +- `remotePort()` +- `remotePath()` +- `protocol()` + +This allows the `Client` to resolve and connect without requiring manual URL parsing. + +#### Header Injection + +Headers are added using a helper struct: + +```text +Request req("https://example.com/api"); +req << HTTP_Request::Header("Authorization", "Bearer token"); +``` + +This keeps header logic encapsulated within the request abstraction. + +--- + +### 3.4 HTTP_Response + +`HTTP_Response` extends Boost.Beast response objects and provides: + +- `status()` – numeric HTTP status +- `body()` – response body string +- `headers()` – iterable header collection + +#### Header Iteration Model + +```mermaid +flowchart TD + Response["HTTP_Response"] --> Headers["Headers"] + Headers --> Iterator["Iterator"] + Iterator --> Pair["(name, value)"] +``` + +Example usage pattern: + +```text +for (const auto& header : resp.headers()) { + header.first; + header.second; +} +``` + +This abstraction avoids exposing raw Boost iterators directly. + +--- + +## 4. Request Lifecycle + +The `Client` wraps asynchronous Boost operations into a controlled flow. + +### 4.1 End-to-End Flow + +```mermaid +sequenceDiagram + participant Caller + participant Client + participant Resolver + participant Server + + Caller->>Client: get(Request) + Client->>Resolver: async_resolve() + Resolver-->>Client: endpoints + Client->>Server: async_connect() + Client->>Server: async_write(request) + Server-->>Client: HTTP response + Client->>Server: async_read() + Client-->>Caller: Response +``` + +### 4.2 TLS Flow (HTTPS) + +If `ssl_connection` is enabled: + +```mermaid +flowchart TD + Connect["TCP Connect"] --> TLSCheck{"SSL Enabled?"} + TLSCheck -->|"Yes"| Handshake["TLS Handshake"] + TLSCheck -->|"No"| Plain["Plain HTTP"] + Handshake --> Send["Send Request"] + Plain --> Send +``` + +TLS configuration respects: + +- Cipher restrictions +- Verify path +- Server certificate pinning +- Client certificate and private key + +Legacy SSL versions and MD5 are explicitly disabled at compile time. + +--- + +## 5. Timeout and Error Handling + +The client uses a `boost::asio::deadline_timer` to enforce network timeouts. + +### 5.1 callNetworkOperation Wrapper + +The `callNetworkOperation` method: + +1. Starts the timeout timer +2. Executes the async operation +3. Waits for completion or timeout +4. Cancels the opposing operation +5. Sets the final error code + +```mermaid +flowchart TD + Start["Start Network Call"] --> Timer["Start Deadline Timer"] + Timer --> Async["Async Operation"] + Async --> Complete{"Completed?"} + Complete -->|"Yes"| CancelTimer["Cancel Timer"] + Complete -->|"No"| Timeout["Timer Fires"] + Timeout --> Abort["Abort Socket"] + CancelTimer --> Return["Return Response"] + Abort --> Return +``` + +### 5.2 TLS Short Read Handling + +During TLS shutdown, some servers may not perform proper `shutdown()` calls. The client treats certain short-read conditions as success in post-response handling to avoid false-negative errors. + +--- + +## 6. Proxy and Metadata Support + +### 6.1 Proxy Routing + +If a proxy hostname is configured: + +- Connection is established to the proxy +- Request routing logic is adapted accordingly + +This enables deployment behind corporate proxies. + +### 6.2 Cloud Metadata Authority + +The constant `kInstanceMetadataAuthority` (`169.254.169.254`) identifies the authority used by cloud metadata services (e.g., EC2, Azure). + +This allows safe and consistent access to instance metadata endpoints when required by higher-level modules. + +--- + +## 7. Interaction with Other Modules + +The Remote Http Client module acts as an infrastructure dependency for modules that require outbound HTTP communication. + +### 7.1 Distributed Querying + +The [Distributed Querying](distributed-querying/distributed-querying.md) module typically relies on HTTP transport (e.g., TLS-based distributed plugins) to: + +- Fetch remote queries +- Submit query results + +The Remote Http Client provides the secure communication backbone for these operations. + +### 7.2 Logging and Query Observability + +The [Logging and Query Observability](logging-and-query-observability/logging-and-query-observability.md) module may use HTTP/TLS transports for remote log submission. + +The client ensures: + +- TLS enforcement +- Certificate verification +- Controlled timeout behavior + +### 7.3 Configuration and Packs + +The [Configuration and Packs](configuration-and-packs/configuration-and-packs.md) module can retrieve configuration from remote endpoints via HTTP plugins. The Remote Http Client supplies: + +- Secure GET/POST support +- Redirect handling +- Proxy awareness + +--- + +## 8. Security Model + +The module enforces multiple security measures: + +- SSLv2 and SSLv3 disabled +- MD5 disabled +- Deprecated OpenSSL features disabled +- Optional strict peer verification +- Optional custom cipher suites +- Support for certificate pinning + +Security behavior is fully driven by `Client::Options`, allowing different modules to enforce different policies. + +--- + +## 9. Summary + +The **Remote Http Client** module provides a robust, TLS-capable HTTP client abstraction built on Boost.Asio and Boost.Beast. It encapsulates: + +- Connection lifecycle management +- TLS negotiation and verification +- Asynchronous networking orchestration +- Timeout enforcement +- Request and response abstraction + +By centralizing HTTP communication logic, it ensures consistent, secure, and configurable remote connectivity across the system, serving as the networking foundation for distributed querying, logging, and remote configuration workflows. diff --git a/docs/reference/architecture/sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md b/docs/reference/architecture/sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md new file mode 100644 index 00000000000..e450a4f1d9d --- /dev/null +++ b/docs/reference/architecture/sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md @@ -0,0 +1,351 @@ +# Sql Engine And Virtual Tables + +## Overview + +The **Sql Engine And Virtual Tables** module is the execution core of osquery. It embeds and configures SQLite as an in-memory analytical engine and exposes operating system data through dynamically attached virtual tables. + +This module is responsible for: + +- Managing and optimizing SQLite database instances +- Enforcing SQL security through authorizers and allowlists +- Attaching and detaching virtual tables backed by TablePlugin implementations +- Planning and introspecting queries +- Executing SQL statements and returning typed results +- Bridging SQLite’s virtual table API with osquery’s plugin system + +It acts as the execution layer between: + +- Table plugins (from the Registry) +- The scheduler and query interfaces +- Extensions providing additional tables + +--- + +## High-Level Architecture + +```mermaid +flowchart TD + Client["SQL Caller (Scheduler / CLI / Extension)"] --> SQLPlugin["SQLiteSQLPlugin"] + SQLPlugin --> DBManager["SQLiteDBManager"] + DBManager --> DBInstance["SQLiteDBInstance"] + DBInstance --> SQLiteCore[("In-Memory SQLite Engine")] + + SQLiteCore --> VTabModule["sqlite3_module (Virtual Table Module)"] + VTabModule --> VirtualTable["VirtualTable Wrapper"] + VirtualTable --> TablePlugin["TablePlugin (Registry)"] + + SQLiteCore --> Planner["QueryPlanner"] +``` + +### Flow Summary + +1. A SQL query is submitted via the SQL registry. +2. `SQLiteSQLPlugin` obtains a database connection from `SQLiteDBManager`. +3. The query is prepared and executed using `queryInternal`. +4. When a virtual table is accessed, SQLite invokes the `sqlite3_module` callbacks. +5. The virtual table implementation forwards execution to a `TablePlugin`. +6. Results are returned to SQLite and then to the caller as typed rows. + +--- + +## Core Components + +### SQLiteSQLPlugin + +Implements the internal `sql` registry plugin. + +**Responsibilities:** + +- Execute SQL queries (`query`) +- Introspect query columns (`getQueryColumns`) +- Determine scanned tables (`getQueryTables`) +- Attach/detach virtual tables dynamically + +It acts as the primary entry point for SQL execution inside osquery. + +--- + +### SQLiteDBManager + +Singleton responsible for lifecycle and concurrency management of SQLite instances. + +**Key Features:** + +- Maintains a primary in-memory SQLite database +- Creates transient database instances under contention +- Applies memory optimizations (PRAGMA tuning) +- Attaches all registered virtual tables +- Enforces table enable/disable flags + +```mermaid +flowchart LR + Caller["Caller"] --> GetConn["SQLiteDBManager.get()"] + GetConn --> PrimaryCheck{"Primary Available?"} + PrimaryCheck -->|Yes| Primary["Primary SQLiteDBInstance"] + PrimaryCheck -->|No| Transient["Transient SQLiteDBInstance"] +``` + +This design minimizes resource usage while ensuring thread safety. + +--- + +### SQLiteDBInstance + +RAII wrapper around a `sqlite3*` pointer. + +**Responsibilities:** + +- Provide safe access to SQLite handle +- Manage locking and attach mutexes +- Track affected virtual tables per query +- Clear per-query table state +- Support query result caching + +Each query receives a scoped database instance. On destruction: + +- Transient connections are closed +- Primary connections remain managed + +--- + +### Security Enforcement (Authorizer) + +The module enforces strict SQL safety using `sqliteAuthorizer`. + +**Allowlisted Actions:** + +- `SELECT`, `READ` +- Controlled `INSERT`, `UPDATE`, `DELETE` +- Virtual table creation and drop +- Limited `PRAGMA` commands + +**Explicitly Denied:** + +- `SQLITE_ATTACH` +- Any non-allowlisted opcode + +```mermaid +flowchart TD + Prepare["sqlite3_prepare_v2"] --> Authorizer["sqliteAuthorizer"] + Authorizer -->|Allowed| Continue["Execute Statement"] + Authorizer -->|Denied| Reject["SQLITE_DENY"] +``` + +This ensures osquery cannot be abused to modify arbitrary files or attach external databases. + +--- + +### QueryPlanner + +Performs query introspection using: + +- `EXPLAIN QUERY PLAN` +- `EXPLAIN` + +Used for: + +- Inferring column types for expressions +- Determining scanned tables +- Inspecting SQLite opcodes + +The planner maps SQLite opcodes to osquery `ColumnType` using `kSQLOpcodes`. + +```mermaid +flowchart TD + Query["SQL Query"] --> ExplainPlan["EXPLAIN QUERY PLAN"] + Query --> Explain["EXPLAIN"] + Explain --> OpcodeMap["kSQLOpcodes Mapping"] + OpcodeMap --> TypeInference["Apply Column Types"] +``` + +This enables accurate schema introspection even when SQLite cannot directly determine expression types. + +--- + +### SQLInternal + +A lower-level SQL execution wrapper used internally. + +**Capabilities:** + +- Executes queries bypassing registry lookup +- Returns typed results (`QueryDataTyped`) +- Detects event-based tables +- Escapes non-printable bytes +- Calculates result size + +It is used when deeper inspection of query attributes is required. + +--- + +## Virtual Table Subsystem + +The Virtual Table subsystem connects SQLite’s virtual table API with osquery’s `TablePlugin` abstraction. + +### VirtualTable + +Wraps: + +- `sqlite3_vtab` +- `VirtualTableContent` (metadata, schema, constraints) +- Associated `SQLiteDBInstance` + +It maintains: + +- Column definitions +- Aliases +- Attributes (EVENT_BASED, USER_BASED, etc.) +- Constraint tracking + +--- + +### BaseCursor + +Represents an active scan of a virtual table. + +Tracks: + +- Row set or generator +- Cursor position +- Current row +- Planner ID + +Supports both: + +- Pre-generated row sets +- Streaming generator-based tables + +--- + +### sqlite3_module Implementation + +Implements the SQLite virtual table callbacks: + +- `xCreate` +- `xBestIndex` +- `xFilter` +- `xNext` +- `xColumn` +- `xRowid` +- `xUpdate` + +```mermaid +sequenceDiagram + participant SQLite + participant Module as "sqlite3_module" + participant Table as "TablePlugin" + + SQLite->>Module: xBestIndex + Module->>SQLite: Constraint plan + + SQLite->>Module: xFilter + Module->>Table: generate(context) + Table-->>Module: Row set + Module-->>SQLite: Rows +``` + +--- + +## Constraint Optimization + +`xBestIndex` evaluates constraints provided by SQLite and: + +- Identifies indexed and required columns +- Calculates estimated cost +- Rewrites `IN` constraints for optimized handling +- Tracks used columns for projection pushdown + +If required constraints are missing: + +- Cost is set to a maximum +- Query may fail with `SQLITE_CONSTRAINT` + +This enables efficient filtering at the table implementation level. + +--- + +## Query Execution Flow + +```mermaid +flowchart TD + Start["Query Submitted"] --> Prepare["sqlite3_prepare_v2"] + Prepare --> Execute["sqlite3_step Loop"] + Execute -->|Virtual Table Access| VTab + VTab --> xBestIndex + xBestIndex --> xFilter + xFilter --> Generate["TablePlugin.generate()"] + Generate --> Rows["Return Rows"] + Rows --> Finalize["sqlite3_finalize"] + Finalize --> End["Results Returned"] +``` + +--- + +## Extension and Writable Tables + +If a table originates from an extension: + +- `xUpdate` is enabled +- INSERT / UPDATE / DELETE are forwarded +- Parameters are serialized into JSON +- Responses are validated + +This allows controlled writable virtual tables. + +--- + +## Memory and Performance Optimizations + +When opening the in-memory database: + +- `journal_mode=OFF` +- `synchronous=OFF` +- `auto_vacuum=FULL` +- Custom function extensions registered +- Authorizer installed + +Additionally: + +- SQLite soft heap limit configured +- Memory released after each query +- Transient DB instances created only under contention + +--- + +## Interaction with Other Modules + +The Sql Engine And Virtual Tables module integrates closely with: + +- Table plugins (Registry) +- Extensions (for remote virtual tables) +- Logging and query observability (for execution results) +- Eventing framework (for event-based tables) +- Core initialization (for flag configuration) + +It does not duplicate table logic; it provides the execution infrastructure. + +--- + +## Key Design Principles + +1. **Security First** – Strict SQLite authorizer and allowlists +2. **In-Memory Execution** – No persistent database files +3. **Plugin-Based Tables** – Data sources abstracted behind registry +4. **Query-Aware Optimization** – Constraint and projection pushdown +5. **Thread-Safe Resource Management** – Managed primary DB with transient fallback +6. **Extension-Friendly** – Writable virtual tables supported + +--- + +## Summary + +The **Sql Engine And Virtual Tables** module is the execution backbone of osquery. + +It transforms SQLite into a secure, extensible, in-memory analytics engine that: + +- Executes SQL safely +- Dynamically attaches system-backed virtual tables +- Optimizes query plans +- Bridges plugins and extensions with SQLite’s execution engine + +Without this module, osquery would not be able to expose system data through a unified SQL interface. From c0a8e7ee14006687481d89175919864dbe7236dc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 23:21:59 +0000 Subject: [PATCH 6/8] docs: Stage 3 - Tutorial generation (11 files) [skip ci] --- docs/development/.gitignore | 7 + docs/development/README.md | 96 ++++++ docs/development/architecture/README.md | 239 +++++++++++++++ docs/development/contributing/guidelines.md | 272 +++++++++++++++++ docs/development/security/README.md | 239 +++++++++++++++ docs/development/setup/environment.md | 200 +++++++++++++ docs/development/setup/local-development.md | 279 ++++++++++++++++++ docs/development/testing/README.md | 310 ++++++++++++++++++++ docs/getting-started/.gitignore | 7 + docs/getting-started/first-steps.md | 178 +++++++++++ docs/getting-started/introduction.md | 113 +++++++ docs/getting-started/prerequisites.md | 167 +++++++++++ docs/getting-started/quick-start.md | 177 +++++++++++ 13 files changed, 2284 insertions(+) create mode 100644 docs/development/.gitignore create mode 100644 docs/development/README.md create mode 100644 docs/development/architecture/README.md create mode 100644 docs/development/contributing/guidelines.md create mode 100644 docs/development/security/README.md create mode 100644 docs/development/setup/environment.md create mode 100644 docs/development/setup/local-development.md create mode 100644 docs/development/testing/README.md create mode 100644 docs/getting-started/.gitignore create mode 100644 docs/getting-started/first-steps.md create mode 100644 docs/getting-started/introduction.md create mode 100644 docs/getting-started/prerequisites.md create mode 100644 docs/getting-started/quick-start.md diff --git a/docs/development/.gitignore b/docs/development/.gitignore new file mode 100644 index 00000000000..a5d01be5236 --- /dev/null +++ b/docs/development/.gitignore @@ -0,0 +1,7 @@ +# VoltAgent temp files +temp/ + +# JSON intermediate files (except schema/config) +*.json +!*-schema.json +!*-config.json diff --git a/docs/development/README.md b/docs/development/README.md new file mode 100644 index 00000000000..83ce9e027c8 --- /dev/null +++ b/docs/development/README.md @@ -0,0 +1,96 @@ +# Development Documentation + +Welcome to the osquery with OpenFrame development documentation. This section covers everything you need to build, run, test, and contribute to the project. + +--- + +## Overview + +osquery is a C++ cross-platform OS instrumentation framework. The codebase is organized around a modular, plugin-driven architecture where every major subsystem is independently testable and extensible. + +The **OpenFrame** layer adds authentication and encryption components that integrate with the [Flamingo/OpenFrame MSP platform](https://openframe.ai). + +--- + +## Documentation Index + +| Guide | Description | +|---|---| +| [Environment Setup](setup/environment.md) | IDE recommendations, development tools, editor extensions | +| [Local Development](setup/local-development.md) | Clone, build, run locally, debug configuration | +| [Architecture Overview](architecture/README.md) | High-level architecture diagrams and core component descriptions | +| [Security Best Practices](security/README.md) | Auth patterns, encryption, secrets management | +| [Testing Guide](testing/README.md) | Test structure, running tests, writing new tests | +| [Contributing Guidelines](contributing/guidelines.md) | Code style, branch naming, PR process, commit format | + +--- + +## Technology Stack + +| Layer | Technology | +|---|---| +| **Language** | C++17 | +| **Build System** | CMake 3.21+ with Ninja | +| **SQL Engine** | SQLite (embedded, in-memory) | +| **IPC** | Apache Thrift (UNIX sockets / named pipes) | +| **Encryption** | OpenSSL (AES-256-GCM via OpenFrame layer) | +| **Networking** | Boost.Asio + Boost.Beast | +| **Event Systems** | inotify (Linux), FSEvents (macOS), ETW (Windows), BPF (Linux) | +| **Database** | RocksDB (persistent), ephemeral in-memory store | +| **Code Generation** | Python scripts for table schema, API, and amalgamation | +| **Testing** | Google Test + Google Mock | + +--- + +## Repository Structure + +```text +osquery/ +├── osquery/ # Core C++ source — SQL, eventing, config, logging +│ ├── core/ # Initialization, flags, watcher/worker model +│ ├── sql/ # SQLite engine, virtual tables, authorizer +│ ├── config/ # Configuration loading, packs, parsers +│ ├── events/ # Eventing framework (publishers/subscribers) +│ ├── database/ # Storage backend abstraction (RocksDB, ephemeral) +│ ├── distributed/ # Distributed query orchestration +│ ├── extensions/ # Thrift-based extension/IPC framework +│ ├── remote/ # HTTP client, TLS transport +│ ├── logger/ # Logging plugins and observability +│ └── tables/ # Virtual table implementations +├── openframe/ # OpenFrame authentication and encryption layer +├── plugins/ # Logger, config, database, and distributed plugins +├── libraries/ # CMake-managed third-party dependencies +├── tools/ # Codegen scripts, CI tools, formatting +├── tests/ # Integration test suite +└── external/ # Extension examples +``` + +--- + +## Quick Commands + +```bash +# Configure build +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo + +# Build everything +cmake --build build --parallel $(nproc) + +# Run interactive shell +./build/osquery/osqueryi + +# Run tests +cmake --build build --target osquery_tests +cd build && ctest --output-on-failure +``` + +--- + +## Getting Help + +All development discussions happen in the **OpenMSP Slack community**: + +- [Join OpenMSP Slack](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) +- [https://www.openmsp.ai/](https://www.openmsp.ai/) + +> We do not use GitHub Issues or GitHub Discussions. All support and collaboration is on Slack. diff --git a/docs/development/architecture/README.md b/docs/development/architecture/README.md new file mode 100644 index 00000000000..a7b03cb1fce --- /dev/null +++ b/docs/development/architecture/README.md @@ -0,0 +1,239 @@ +# Architecture Overview + +osquery is built as a modular, plugin-driven system. Each subsystem is independently composable, testable, and extensible. The codebase is written in C++17 and targets Linux, macOS, and Windows. + +--- + +## High-Level Architecture + +```mermaid +graph TD + CLI["osqueryi / osqueryd"] --> Core["Core Init And Runtime"] + Core --> Config["Configuration And Packs"] + Core --> SQL["SQL Engine And Virtual Tables"] + Core --> Events["Eventing Framework"] + Core --> Logging["Logging And Observability"] + Core --> DB["Database And Storage Plugins"] + Core --> Dist["Distributed Querying"] + Core --> Ext["Extensions And IPC"] + Core --> HTTP["Remote HTTP Client"] + Core --> OF["OpenFrame Auth Layer"] + Config --> SQL + Config --> Events + SQL --> Logging + Events --> DB + Dist --> SQL + Dist --> HTTP + Ext --> SQL + Ext --> DB + OF --> HTTP +``` + +--- + +## Core Components + +| Module | Location | Description | +|---|---|---| +| **Core Init And Runtime** | `osquery/core/` | Process bootstrap, flags, watchdog, shutdown | +| **SQL Engine And Virtual Tables** | `osquery/sql/` | SQLite engine, authorizer, virtual table binding | +| **Configuration And Packs** | `osquery/config/` | Config loading, packs, parsers, scheduled queries | +| **Eventing Framework** | `osquery/events/` | Pub/sub system for OS events (inotify, BPF, ETW) | +| **Logging And Observability** | `osquery/logger/` | Differential logging, JSON serialization, logger plugins | +| **Database And Storage Plugins** | `osquery/database/` | Key-value storage (RocksDB, ephemeral) | +| **Distributed Querying** | `osquery/distributed/` | Remote SQL orchestration across fleets | +| **Extensions And IPC** | `osquery/extensions/` | Apache Thrift-based extension framework | +| **Remote HTTP Client** | `osquery/remote/` | Boost.Asio/Beast HTTPS client with TLS | +| **OpenFrame Auth Layer** | `openframe/` | JWT token management, AES-256-GCM encryption | +| **Virtual Tables** | `osquery/tables/` | 300+ OS-specific table implementations | + +--- + +## Runtime Lifecycle + +```mermaid +sequenceDiagram + participant Main + participant Initializer + participant Registry + participant Config + participant Extensions + participant Events + + Main->>Initializer: Construct(argc, argv) + Initializer->>Initializer: Parse flags + Initializer->>Registry: registryAndPluginInit() + Initializer->>Extensions: startExtensionManager() + Initializer->>Config: load() + Initializer->>Events: attachEvents() + Initializer->>Initializer: start() +``` + +osquery can operate in four runtime modes: + +| Mode | Binary | Description | +|---|---|---| +| **Interactive Shell** | `osqueryi` | REPL for ad-hoc SQL queries | +| **Daemon** | `osqueryd` | Background daemon with scheduled queries | +| **Watcher** | Internal | Supervisor process that monitors the worker | +| **Extension** | External | External plugin process communicating via Thrift | + +--- + +## SQL Engine and Virtual Tables + +The heart of osquery is its SQL engine — an in-memory SQLite instance hardened with a strict authorizer. + +```mermaid +graph TD + Query["SQL Query"] --> SQLPlugin["SQLiteSQLPlugin"] + SQLPlugin --> DBManager["SQLiteDBManager"] + DBManager --> DBInst["SQLiteDBInstance"] + DBInst --> SQLiteCore["In-Memory SQLite Engine"] + SQLiteCore --> VTabModule["sqlite3_module"] + VTabModule --> VirtualTable["VirtualTable Wrapper"] + VirtualTable --> TablePlugin["TablePlugin"] + TablePlugin --> OS["Operating System Data"] +``` + +**Key properties:** +- All queries run against an in-memory SQLite database +- A strict authorizer allowlists only safe SQL opcodes +- Virtual tables are attached dynamically from the registry +- Constraint pushdown and projection optimization reduce OS calls + +--- + +## Eventing Framework + +Event-driven monitoring is handled by a publish/subscribe system: + +```mermaid +graph TD + Publisher["EventPublisher"] --> EventFactory["EventFactory"] + Subscriber["EventSubscriber"] --> EventFactory + EventFactory --> Callback["EventCallback"] + Callback --> Storage["Database Storage"] + SQL["SELECT from events tables"] --> Subscriber +``` + +| Platform | Event Technology | +|---|---| +| Linux | inotify, BPF, Audit netlink | +| macOS | FSEvents, EndpointSecurity, OpenBSM | +| Windows | ETW, USN Journal, Windows Event Log | + +--- + +## Configuration and Scheduling + +```mermaid +graph TD + Source["Config Source"] --> ConfigCore["Config Singleton"] + ConfigCore --> Parsers["ConfigParserPlugins"] + Parsers --> Schedule["Scheduled Queries"] + Schedule --> Scheduler["Query Scheduler"] + Scheduler --> SQL["SQL Engine"] + SQL --> Logging["Logging And Observability"] +``` + +Configuration sources: +- **FilesystemConfigPlugin** — local JSON file with `.d/` fragment support +- **TLS Config Plugin** — remote configuration over HTTPS +- **Extension Config Plugin** — configuration provided by an extension process + +--- + +## OpenFrame Authentication Layer + +The `openframe/` directory contains the Flamingo/OpenFrame integration: + +```mermaid +graph LR + Provider["AuthorizationManagerProvider"] --> Manager["AuthorizationManager"] + Extractor["TokenExtractor"] --> Manager + Refresher["TokenRefresher"] --> Extractor + Manager --> Token["JWT Bearer Token"] + Token --> HTTP["Remote HTTP Client"] + EncSvc["EncryptionService"] --> OpenSSL["OpenSSL AES-256-GCM"] +``` + +| Component | Responsibility | +|---|---| +| `OpenframeAuthorizationManager` | Singleton token store with provider-controlled lifecycle | +| `OpenframeAuthorizationManagerProvider` | Sole factory for the token manager | +| `OpenframeEncryptionService` | AES-256-GCM decryption via OpenSSL | +| `OpenframeTokenExtractor` | Fetches tokens from OpenFrame services | +| `OpenframeTokenRefresher` | Background thread for token renewal | + +--- + +## Extension System + +Extensions communicate with osquery core via Apache Thrift over UNIX domain sockets (Linux/macOS) or named pipes (Windows): + +```mermaid +graph LR + Core["osquery Core"] --> Manager["Extension Manager"] + Manager --> Registry["RegistryFactory"] + ExtProc["Extension Process"] --> Manager + Manager --> ExtProc +``` + +Extensions can provide: +- Custom virtual tables +- Logger plugins +- Config plugins +- Distributed plugins + +--- + +## Data Flow: Query Execution to Log + +```mermaid +sequenceDiagram + participant Scheduler + participant SQL as SQL Engine + participant DB as Database + participant Logger + + Scheduler->>SQL: Execute scheduled query + SQL->>SQL: Run against virtual tables + SQL-->>Scheduler: QueryData (current results) + Scheduler->>DB: Load previous results + DB-->>Scheduler: Previous QueryData + Scheduler->>Scheduler: Compute DiffResults + Scheduler->>Logger: logQueryLogItem(diff) + Logger->>Logger: Serialize to JSON + Logger-->>Logger: Forward to backend sink +``` + +--- + +## Key Design Decisions + +| Decision | Rationale | +|---|---| +| **In-memory SQLite** | Zero persistent SQL state; each query is a fresh execution | +| **Plugin/Registry pattern** | All major subsystems (loggers, config, tables) are hot-swappable | +| **Watcher/Worker isolation** | Worker crashes don't bring down the watchdog supervisor | +| **Thrift for extensions** | Language-agnostic, versioned, cross-platform RPC | +| **Differential logging** | Only changed rows are logged, dramatically reducing volume | +| **AES-256-GCM encryption** | OpenFrame credentials protected with authenticated encryption | + +--- + +## Reference Documentation + +For deep dives into each module, see the reference architecture docs: + +- [Core Init And Runtime](./reference/architecture/core-init-and-runtime/core-init-and-runtime.md) +- [SQL Engine And Virtual Tables](./reference/architecture/sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md) +- [Configuration And Packs](./reference/architecture/configuration-and-packs/configuration-and-packs.md) +- [Eventing Framework And Subscriptions](./reference/architecture/eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md) +- [Extensions And IPC](./reference/architecture/extensions-and-ipc/extensions-and-ipc.md) +- [Distributed Querying](./reference/architecture/distributed-querying/distributed-querying.md) +- [Remote HTTP Client](./reference/architecture/remote-http-client/remote-http-client.md) +- [Logging And Query Observability](./reference/architecture/logging-and-query-observability/logging-and-query-observability.md) +- [Database And Storage Plugins](./reference/architecture/database-and-storage-plugins/database-and-storage-plugins.md) +- [Filesystem And Path Utilities](./reference/architecture/filesystem-and-path-utilities/filesystem-and-path-utilities.md) diff --git a/docs/development/contributing/guidelines.md b/docs/development/contributing/guidelines.md new file mode 100644 index 00000000000..de082c732cb --- /dev/null +++ b/docs/development/contributing/guidelines.md @@ -0,0 +1,272 @@ +# Contributing Guidelines + +Thank you for contributing to osquery with OpenFrame! This guide covers code style, branching, commit messages, and the pull request process. + +--- + +## Community First + +All collaboration happens on the **OpenMSP Slack community** — not GitHub Issues or GitHub Discussions. + +- 💬 **Join Slack**: [OpenMSP Community](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) +- 🌐 **OpenMSP**: [https://www.openmsp.ai/](https://www.openmsp.ai/) + +Before starting significant work, please discuss your changes in the Slack community to align with the team's roadmap. + +--- + +## Code Style and Conventions + +### C++ Standards + +- Use **C++17** features where appropriate +- Follow the existing code style in each file you modify +- All new code must pass `clang-format` with the repository's `.clang-format` config + +### Formatting + +osquery enforces `clang-format`. Run it before every commit: + +```bash +# Format a single file +clang-format -i path/to/your/file.cpp + +# Format all changed files (compared to main branch) +git diff --name-only main | grep -E '\.(cpp|h)$' | xargs clang-format -i + +# Check without modifying +clang-format --dry-run --Werror path/to/your/file.cpp +``` + +### Naming Conventions + +| Item | Convention | Example | +|---|---|---| +| Classes | `PascalCase` | `EventSubscriberPlugin` | +| Methods | `camelCase` | `generateRows()` | +| Member variables | `snake_case_` (trailing underscore) | `running_` | +| Constants | `kPascalCase` | `kSQLOpcodes` | +| Macros | `UPPER_SNAKE_CASE` | `DECLARE_FLAG` | +| Namespaces | `snake_case` | `osquery` | +| Files | `snake_case.cpp` / `snake_case.h` | `event_subscriber.cpp` | + +### Code Organization + +- Keep headers (`*.h`) as minimal as possible — forward declare where possible +- Use the `osquery` namespace for all production code +- Place tests in `tests/` subdirectories alongside the source +- New virtual tables go in `osquery/tables//` + +### Include Order + +Follow this include order, with blank lines between groups: + +```cpp +// 1. Standard library +#include +#include +#include + +// 2. Third-party libraries +#include +#include + +// 3. osquery headers +#include "osquery/core/core.h" +#include "osquery/sql/sql.h" + +// 4. Local headers (same directory) +#include "my_local_header.h" +``` + +--- + +## Branch Naming + +| Type | Pattern | Example | +|---|---|---| +| Feature | `feature/` | `feature/bpf-socket-events` | +| Bug fix | `fix/` | `fix/config-refresh-race` | +| OpenFrame integration | `openframe/` | `openframe/token-refresh-retry` | +| Documentation | `docs/` | `docs/virtual-table-guide` | +| Refactor | `refactor/` | `refactor/sql-authorizer` | +| Test | `test/` | `test/events-integration` | +| Release | `release/v` | `release/v5.13.0` | + +Always branch from the latest `main`: + +```bash +git checkout main +git pull origin main +git checkout -b feature/my-new-feature +``` + +--- + +## Commit Message Format + +Use the following format for all commits: + +```text +(): + + + + +``` + +### Types + +| Type | When to Use | +|---|---| +| `feat` | New feature or capability | +| `fix` | Bug fix | +| `docs` | Documentation changes only | +| `style` | Code formatting, no logic change | +| `refactor` | Code refactoring without behavior change | +| `test` | Adding or fixing tests | +| `perf` | Performance improvement | +| `chore` | Build, CI, tooling changes | +| `openframe` | OpenFrame platform-specific changes | + +### Scopes + +| Scope | Area | +|---|---| +| `core` | Core init and runtime | +| `sql` | SQL engine and virtual tables | +| `config` | Configuration and packs | +| `events` | Eventing framework | +| `logger` | Logging and observability | +| `db` | Database and storage | +| `distributed` | Distributed querying | +| `extensions` | Extension IPC | +| `http` | Remote HTTP client | +| `openframe` | OpenFrame auth layer | +| `tables` | Virtual table implementations | + +### Examples + +```text +feat(events): add BPF socket event publisher for Linux + +Implements a new BPF-based publisher that captures socket connect/accept +events and exposes them via the bpf_socket_events virtual table. + +Closes #1234 +``` + +```text +fix(openframe): handle token refresh failure with exponential backoff + +When OpenframeTokenRefresher encounters a network error, it now retries +with exponential backoff instead of immediately stopping the refresh loop. +``` + +```text +test(config): add pack discovery query unit tests + +Adds unit tests for the discovery query evaluation logic in Pack::shouldPackExecute() +covering platform, version, and shard constraints. +``` + +--- + +## Pull Request Process + +### Before Submitting + +- [ ] Branch is up-to-date with `main` +- [ ] All tests pass: `cd build && ctest --output-on-failure` +- [ ] Code is formatted: `clang-format --dry-run --Werror` +- [ ] Copyright headers are present on new files +- [ ] New virtual tables have integration tests in `tests/integration/tables/` +- [ ] OpenFrame-specific changes include updated documentation + +### PR Title + +Use the same format as commit messages: + +```text +feat(sql): add query result caching for repeated virtual table scans +``` + +### PR Description Template + +```markdown +## Summary + + +## Changes + + +## Testing + + +## Platform Support + + +## Checklist +- [ ] Tests pass (ctest) +- [ ] clang-format applied +- [ ] Documentation updated (if applicable) +- [ ] Discussed in OpenMSP Slack (if significant change) +``` + +--- + +## Copyright Headers + +All new source files must include a copyright header. Use the format already present in existing source files: + +```cpp +/** + * Copyright (c) 2014-present, The osquery authors + * + * This source code is licensed in accordance with the terms specified in + * the LICENSE file found in the root directory of this source tree. + */ +``` + +The CI script `tools/ci/scripts/check_copyright_headers.py` enforces this on all pull requests. + +--- + +## Review Checklist for Reviewers + +When reviewing a PR: + +- [ ] Logic is correct and handles error paths +- [ ] No secrets or credentials in source +- [ ] SQL inputs validated (if applicable) +- [ ] Thread safety considered for shared state +- [ ] Platform-specific code properly guarded with `#ifdef` +- [ ] Tests added for new functionality +- [ ] No debug/temporary code left in +- [ ] Commit messages follow format convention +- [ ] Performance impact considered for hot paths (scheduler, SQL engine) + +--- + +## Adding a New Virtual Table + +1. Define the table schema in `osquery/tables//.table` +2. Run the code generator: + ```bash + python3 tools/codegen/gentable.py osquery/tables//.table + ``` +3. Implement the `generate()` method in `.cpp` +4. Register in the CMakefile for your category +5. Add an integration test in `tests/integration/tables/.cpp` +6. Test locally: + ```bash + cmake --build build --target osqueryi + ./build/osquery/osqueryi + osquery> SELECT * FROM ; + ``` + +--- + +## Code of Conduct + +Be respectful, collaborative, and constructive. All contributors are expected to maintain a professional and welcoming environment in both Slack and code reviews. diff --git a/docs/development/security/README.md b/docs/development/security/README.md new file mode 100644 index 00000000000..87ae6702cae --- /dev/null +++ b/docs/development/security/README.md @@ -0,0 +1,239 @@ +# Security Best Practices + +This guide covers security patterns used in osquery with OpenFrame, including authentication, encryption, secrets management, input validation, and common vulnerability mitigations. + +--- + +## Authentication and Authorization + +### OpenFrame JWT Token Management + +The OpenFrame layer uses a provider-controlled singleton pattern to manage JWT authentication tokens: + +```mermaid +graph LR + Provider["AuthorizationManagerProvider"] --> Manager["AuthorizationManager"] + Extractor["TokenExtractor"] --> Manager + Refresher["TokenRefresher"] --> Extractor + Manager --> Token["Bearer JWT Token"] + Token --> HTTP["Outbound HTTPS Requests"] +``` + +**Key principles:** + +- `OpenframeAuthorizationManager` is **non-copyable** (`boost::noncopyable`) — tokens cannot be accidentally duplicated +- Only `OpenframeAuthorizationManagerProvider` can construct or destroy the manager (private constructor/destructor) +- The `OpenframeTokenRefresher` runs in a dedicated background thread using `std::atomic` for lock-free lifecycle control +- Token updates and reads are centralized through `updateToken()` and `getToken()` + +**Never:** +- Store JWT tokens in plain text files or environment variables without appropriate OS-level access controls +- Log token values, even at debug level +- Pass tokens as command-line arguments + +--- + +## Encryption + +### AES-256-GCM via OpenSSL + +The `OpenframeEncryptionService` provides symmetric encryption using AES-256-GCM — an authenticated encryption scheme: + +```cpp +// Initialize with a 256-bit secret key +OpenframeEncryptionService enc("my-32-byte-secret-key-goes-here!"); + +// Decrypt a Base64-encoded AES-256-GCM ciphertext +std::string plaintext = enc.decrypt(ciphertext_base64); +``` + +**Encryption properties:** + +| Property | Value | +|---|---| +| Algorithm | AES-256-GCM | +| Key size | 32 bytes (256-bit) | +| IV (nonce) size | 12 bytes (96-bit) | +| Authentication tag | 16 bytes (128-bit) | +| Encoding | Base64 | + +**Security rules:** +- The symmetric key must be exactly 32 bytes +- GCM authentication tag is verified on every decryption — tampered ciphertext throws `std::runtime_error` +- Never reuse nonces for the same key +- Store keys using OS keystore mechanisms or secret management systems, not in source code + +--- + +## SQL Security — The Authorizer + +osquery's embedded SQLite engine includes a strict **authorizer** (`sqliteAuthorizer`) that allowlists only safe SQL operations: + +```mermaid +graph TD + Prepare["sqlite3_prepare_v2"] --> Authorizer["sqliteAuthorizer"] + Authorizer -->|"Allowed"| Continue["Execute Statement"] + Authorizer -->|"Denied"| Reject["SQLITE_DENY"] +``` + +**Explicitly allowlisted:** +- `SELECT`, `READ` operations +- Controlled `INSERT`, `UPDATE`, `DELETE` (virtual tables only) +- Virtual table creation and drop +- Limited `PRAGMA` commands + +**Explicitly denied:** +- `SQLITE_ATTACH` — cannot attach external database files +- Any non-allowlisted opcode + +This prevents osquery from being abused to read arbitrary files via SQLite's `ATTACH` mechanism or execute unsafe operations. + +--- + +## Input Validation + +### SQL Query Validation + +All SQL submitted through the distributed querying or config channels is: +1. Parsed by the SQLite authorizer before execution +2. Validated against the virtual table schema +3. Size-limited by configuration constraints + +### Configuration Validation + +The `Config` singleton enforces: +- JSON max depth limits +- JSON max document size limits +- Comment stripping (non-standard JSON is rejected) +- Hash-based change detection to prevent replay attacks + +### Extension Registration + +Extension processes are validated during registration: +- SDK version compatibility is enforced +- Duplicate extension names are rejected +- Route UUIDs are generated server-side (not client-controlled) + +--- + +## Secrets Management + +### Environment-Level Secrets + +Never embed secrets in: +- Source code +- CMake configuration +- Build scripts +- Log output + +Use OS-level secrets management: + +| Platform | Recommended Tool | +|---|---| +| Linux | systemd credentials, HashiCorp Vault, AWS Secrets Manager | +| macOS | Keychain, AWS Secrets Manager | +| Windows | Windows Credential Manager, Azure Key Vault | + +### osquery Flag Files + +Sensitive flags (TLS certificates, enrollment secrets) should be stored in a flagfile with restricted permissions: + +```bash +# Create flagfile with restricted permissions +sudo touch /etc/osquery/osquery.secret.flags +sudo chmod 600 /etc/osquery/osquery.secret.flags +sudo chown root:root /etc/osquery/osquery.secret.flags + +# Write sensitive flags +echo "--tls_server_certs=/etc/osquery/server.pem" | sudo tee -a /etc/osquery/osquery.secret.flags +echo "--enroll_secret_path=/etc/osquery/enroll.secret" | sudo tee -a /etc/osquery/osquery.secret.flags +``` + +Reference the flagfile: + +```bash +sudo ./osqueryd --flagfile=/etc/osquery/osquery.secret.flags +``` + +--- + +## TLS / HTTPS Communication + +### Remote HTTP Client Security + +The `Remote HTTP Client` module (`osquery/remote/`) enforces: +- TLS by default for all remote communication +- Peer certificate verification +- Configurable cipher suites +- Custom CA certificate paths +- Timeout enforcement (no hanging connections) + +```text +Client::Options options; +options.ssl_connection(true) + .verify_peer(true) + .ca_verify_path("/etc/ssl/certs/ca-certificates.crt") + .timeout(30); +``` + +**Never disable peer verification in production.** The `verify_peer` flag should only be `false` in isolated development environments. + +--- + +## Common Vulnerabilities and Mitigations + +| Vulnerability | Mitigation in osquery | +|---|---| +| **SQL Injection** | Authorizer blocks unsafe opcodes; only allowlisted operations execute | +| **Credential Theft** | JWT tokens non-copyable, stored in singleton with restricted access | +| **Ciphertext Tampering** | AES-256-GCM authentication tag verified on every decrypt | +| **Privilege Escalation** | Watcher/Worker model isolates query execution from the supervisor | +| **External DB Injection** | `SQLITE_ATTACH` explicitly denied by authorizer | +| **Token Replay** | `OpenframeTokenRefresher` rotates tokens on a schedule | +| **Extension Impersonation** | SDK version check and UUID server-assignment prevent spoofing | +| **Resource Exhaustion** | Watchdog enforces CPU/memory limits and respawns workers | +| **Config Tampering** | Hash-based change detection and JSON size/depth limits | + +--- + +## Security Testing + +### Running Security-Relevant Tests + +```bash +# Build with tests enabled +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DOSQUERY_BUILD_TESTS=ON +cmake --build build --parallel $(nproc) + +# Run all tests +cd build && ctest --output-on-failure + +# Run specific security-related tests +cd build && ctest -R "tls" --output-on-failure +cd build && ctest -R "config" --output-on-failure +cd build && ctest -R "sql" --output-on-failure +``` + +### Code Review Checklist for Security + +Before submitting any PR that touches security-sensitive code: + +- [ ] No secrets or credentials hardcoded +- [ ] All SQL inputs pass through the authorizer +- [ ] AES-GCM nonces are generated freshly (not reused) +- [ ] TLS peer verification is not disabled +- [ ] Error paths do not leak sensitive information in logs +- [ ] New extension APIs validate input size and type +- [ ] Thread-shared state uses proper synchronization primitives +- [ ] New config keys have size/depth validation + +--- + +## Reporting Security Issues + +Security vulnerabilities should be reported directly to the Flamingo team through the **OpenMSP Slack community**: + +- 💬 [Join OpenMSP Slack](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) +- 🌐 [https://www.openmsp.ai/](https://www.openmsp.ai/) + +> Do not open public GitHub Issues for security vulnerabilities. diff --git a/docs/development/setup/environment.md b/docs/development/setup/environment.md new file mode 100644 index 00000000000..882e48c34fb --- /dev/null +++ b/docs/development/setup/environment.md @@ -0,0 +1,200 @@ +# Development Environment Setup + +This guide covers recommended IDE configurations, development tools, editor extensions, and environment variables for contributing to osquery with OpenFrame. + +--- + +## Recommended IDEs + +### Visual Studio Code (All Platforms) + +VS Code with the C++ extension pack is the recommended cross-platform IDE for osquery development. + +**Required Extensions:** + +| Extension | ID | Purpose | +|---|---|---| +| C/C++ | `ms-vscode.cpptools` | IntelliSense, debugging, syntax highlighting | +| CMake Tools | `ms-vscode.cmake-tools` | CMake integration, build configuration | +| CMake | `twxs.cmake` | CMake syntax highlighting | +| clangd | `llvm-vs-code-extensions.vscode-clangd` | Fast code navigation, diagnostics | + +**Optional but Recommended:** + +| Extension | ID | Purpose | +|---|---|---| +| GitLens | `eamodio.gitlens` | Enhanced Git history and annotations | +| Clang-Format | `xaver.clang-format` | Auto-formatting on save | +| Error Lens | `usernamehakki.error-lens` | Inline error display | + +**Install all at once:** + +```bash +code --install-extension ms-vscode.cpptools +code --install-extension ms-vscode.cmake-tools +code --install-extension twxs.cmake +code --install-extension llvm-vs-code-extensions.vscode-clangd +code --install-extension eamodio.gitlens +``` + +**Recommended `.vscode/settings.json`:** + +```json +{ + "cmake.buildDirectory": "${workspaceFolder}/build", + "cmake.generator": "Ninja", + "cmake.buildType": "Debug", + "editor.formatOnSave": true, + "clangd.arguments": [ + "--compile-commands-dir=${workspaceFolder}/build", + "--header-insertion=iwyu", + "--clang-tidy" + ], + "C_Cpp.intelliSenseEngine": "disabled" +} +``` + +> When using `clangd`, disable the built-in IntelliSense engine to avoid conflicts. + +--- + +### CLion (JetBrains) + +CLion provides first-class CMake support. Open the project root and CLion will auto-detect the `CMakeLists.txt`. + +**Recommended Settings:** + +- Set the CMake build directory to `build/` +- Enable `clang-format` in **Settings → Editor → Code Style → C/C++** +- Use the built-in CMake tab for build configuration + +--- + +### Xcode (macOS Only) + +Generate an Xcode project from CMake: + +```bash +cmake -S . -B build-xcode -G Xcode +open build-xcode/osquery.xcodeproj +``` + +--- + +## Development Tools + +Install these tools before starting development: + +### All Platforms + +```bash +# Python tools for code generation +pip3 install jinja2 pexpect six +``` + +### Linux + +```bash +sudo apt-get install -y \ + clang-format \ + clang-tidy \ + ccache \ + valgrind \ + gdb +``` + +### macOS + +```bash +brew install \ + clang-format \ + ccache \ + llvm +``` + +### Windows + +- Install [LLVM](https://releases.llvm.org/) for `clang-format` and `clang-tidy` +- Configure `PATH` to include the LLVM `bin` directory + +--- + +## Setting Up ccache (Highly Recommended) + +`ccache` dramatically speeds up incremental rebuilds: + +```bash +# Install ccache +sudo apt-get install ccache # Linux +brew install ccache # macOS + +# Enable in CMake +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache +``` + +--- + +## Generate compile_commands.json for clangd + +For full IntelliSense support with `clangd`, generate the compilation database: + +```bash +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +# Link to project root (required by clangd) +ln -sf build/compile_commands.json compile_commands.json +``` + +--- + +## Environment Variables for Development + +| Variable | Purpose | Example | +|---|---|---| +| `OSQUERY_EXTENSIONS_SOCKET` | Extension manager socket path | `/tmp/osquery.em` | +| `OSQUERY_CONFIG_PATH` | Override default config location | `./test.conf` | +| `OSQUERY_DB_PATH` | Custom database path for development | `/tmp/osquery-dev.db` | +| `OPENSSL_ROOT_DIR` | OpenSSL installation path (Windows/macOS) | `/usr/local/opt/openssl` | + +Set in your shell profile or export before building: + +```bash +export OPENSSL_ROOT_DIR="/usr/local/opt/openssl@3" +``` + +On Windows: + +```bash +set OPENSSL_ROOT_DIR=C:\OpenSSL-Win64 +``` + +--- + +## Code Formatting + +osquery enforces formatting with `clang-format`. The configuration is in `.clang-format` at the repository root. + +```bash +# Format a single file +clang-format -i osquery/core/init.cpp + +# Check formatting without modifying +clang-format --dry-run --Werror osquery/core/init.cpp + +# Format all C++ files in a directory +find osquery/core -name "*.cpp" -o -name "*.h" | xargs clang-format -i +``` + +The CI pipeline enforces formatting. Run format checks before submitting PRs. + +--- + +## Next Steps + +Once your environment is set up, proceed to the [Local Development Guide](local-development.md) for instructions on building, running, and debugging osquery locally. diff --git a/docs/development/setup/local-development.md b/docs/development/setup/local-development.md new file mode 100644 index 00000000000..da84e7144e8 --- /dev/null +++ b/docs/development/setup/local-development.md @@ -0,0 +1,279 @@ +# Local Development Guide + +This guide walks you through cloning, building, running, and debugging osquery with OpenFrame on your local machine. + +--- + +## Clone the Repository + +```bash +git clone https://github.com/flamingo-stack/osquery.git +cd osquery +``` + +--- + +## Build Configurations + +### Debug Build (Recommended for Development) + +```bash +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +cmake --build build --parallel $(nproc) +``` + +> Debug builds include full debug symbols and disable optimizations, making debugging straightforward. + +### RelWithDebInfo (Recommended for Testing) + +```bash +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo + +cmake --build build --parallel $(nproc) +``` + +### Release Build + +```bash +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release + +cmake --build build --parallel $(nproc) +``` + +### Build with Tests Enabled + +```bash +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DOSQUERY_BUILD_TESTS=ON + +cmake --build build --parallel $(nproc) +``` + +--- + +## Running osquery Locally + +### Interactive Shell (`osqueryi`) + +```bash +./build/osquery/osqueryi +``` + +```sql +osquery> SELECT hostname, cpu_brand FROM system_info; +osquery> .tables +osquery> .schema processes +osquery> .quit +``` + +### Daemon (`osqueryd`) + +```bash +# Create a minimal development config +cat > /tmp/osquery-dev.conf <<'EOF' +{ + "options": { + "logger_plugin": "filesystem", + "logger_path": "/tmp/osquery-dev-logs", + "database_path": "/tmp/osquery-dev.db", + "schedule_splay_percent": 0 + }, + "schedule": { + "system_info": { + "query": "SELECT hostname, cpu_brand FROM system_info;", + "interval": 60 + } + } +} +EOF + +mkdir -p /tmp/osquery-dev-logs + +./build/osquery/osqueryd \ + --config_path=/tmp/osquery-dev.conf \ + --verbose \ + --ephemeral +``` + +--- + +## Development Flags + +These flags are useful during local development: + +| Flag | Purpose | +|---|---| +| `--verbose` | Enable verbose logging output | +| `--ephemeral` | Use in-memory database (no disk writes) | +| `--disable_watchdog` | Disable resource watchdog (easier debugging) | +| `--disable_events` | Disable event publishers (faster startup) | +| `--disable_logging` | Suppress all logger output | +| `--allow_unsafe` | Allow loading unsigned extensions | +| `--extensions_timeout=10` | Extension connection timeout in seconds | + +Example for a minimal debug session: + +```bash +./build/osquery/osqueryi \ + --verbose \ + --disable_events \ + --ephemeral +``` + +--- + +## Incremental Builds + +After making source changes, only rebuild changed targets: + +```bash +# Rebuild only the osqueryi binary +cmake --build build --target osqueryi + +# Rebuild only the osqueryd binary +cmake --build build --target osqueryd + +# Rebuild a specific test target +cmake --build build --target osquery_core_tests +``` + +--- + +## Debugging + +### Using GDB (Linux) + +```bash +# Build with debug symbols +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug +cmake --build build --parallel $(nproc) + +# Launch with GDB +gdb ./build/osquery/osqueryi + +# In GDB: +# (gdb) set args --verbose --ephemeral +# (gdb) break osquery::Initializer::start +# (gdb) run +``` + +### Using LLDB (macOS) + +```bash +lldb ./build/osquery/osqueryi + +# In LLDB: +# (lldb) settings set -- target.run-args "--verbose" "--ephemeral" +# (lldb) b osquery::Initializer::start +# (lldb) run +``` + +### Using VS Code Debugger + +Add to `.vscode/launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug osqueryi", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/osquery/osqueryi", + "args": ["--verbose", "--ephemeral", "--disable_events"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} +``` + +--- + +## Developing the OpenFrame Layer + +The OpenFrame components live in the `openframe/` directory: + +```text +openframe/ +├── openframe_authorization_manager.h/.cpp # JWT token lifecycle +├── openframe_authorization_manager_provider.h +├── openframe_encryption_service.h/.cpp # AES-256-GCM via OpenSSL +├── openframe_token_extractor.h/.cpp # Token acquisition +└── openframe_token_refresher.h/.cpp # Background token renewal +``` + +When modifying OpenFrame components, rebuild only the affected target: + +```bash +cmake --build build --target openframe_auth +``` + +Test the encryption service independently by linking against it in a test binary. + +--- + +## Working with Virtual Tables + +To add a new virtual table: + +1. Create schema and implementation in `osquery/tables//` +2. Register the table in the appropriate CMake target +3. Run the code generator to update schema definitions: + +```bash +python3 tools/codegen/gentable.py osquery/tables/my_category/my_table.table +``` + +4. Rebuild and test: + +```bash +cmake --build build --target osqueryi +./build/osquery/osqueryi +osquery> .tables my_table +``` + +--- + +## Log Output Locations + +| Mode | Default Log Path | +|---|---| +| Daemon (Linux) | `/var/log/osquery/` | +| Daemon (macOS) | `/var/log/osquery/` | +| Daemon (Windows) | `C:\ProgramData\osquery\log\` | +| Development override | Set `--logger_path=/tmp/my-logs` | + +--- + +## Clean Rebuild + +If you encounter stale build artifacts: + +```bash +rm -rf build/ +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug +cmake --build build --parallel $(nproc) +``` diff --git a/docs/development/testing/README.md b/docs/development/testing/README.md new file mode 100644 index 00000000000..550d7e68a8e --- /dev/null +++ b/docs/development/testing/README.md @@ -0,0 +1,310 @@ +# Testing Guide + +osquery uses **Google Test** (GTest) and **Google Mock** (GMock) for unit and integration testing. Tests are organized alongside the source code in `tests/` subdirectories within each module. + +--- + +## Test Structure and Organization + +```text +osquery/ +├── core/ +│ ├── tests/ +│ │ ├── flags_tests.cpp +│ │ ├── query_tests.cpp +│ │ ├── system_test.cpp +│ │ └── tables_tests.cpp +├── sql/ +│ └── tests/ +│ ├── sql.cpp +│ └── virtual_table.cpp +├── events/ +│ └── tests/ +│ ├── events_tests.cpp +│ └── linux/ +│ ├── inotify_tests.cpp +│ └── bpf/ +├── config/ +│ └── tests/ +│ ├── config_tests.cpp +│ └── packs.cpp +├── database/ +│ └── tests/ +├── distributed/ +│ └── tests/ +└── extensions/ + └── tests/ + +tests/ +└── integration/ + └── tables/ # Integration tests for all virtual tables + ├── processes.cpp + ├── users.cpp + ├── listening_ports.cpp + └── ... + +plugins/ +└── */tests/ # Plugin-level tests +``` + +### Test Categories + +| Category | Location | Description | +|---|---|---| +| **Unit Tests** | `osquery/*/tests/` | Fast, isolated, no OS dependencies | +| **Integration Tests** | `tests/integration/tables/` | Live table queries against the real OS | +| **Benchmark Tests** | `osquery/*/benchmarks/` | Performance measurements | +| **Extension Tests** | `osquery/extensions/tests/` | IPC and Thrift extension round-trips | +| **Plugin Tests** | `plugins/*/tests/` | Logger, config, and database plugins | + +--- + +## Building Tests + +Tests must be explicitly enabled at CMake configure time: + +```bash +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DOSQUERY_BUILD_TESTS=ON + +cmake --build build --parallel $(nproc) +``` + +--- + +## Running Tests + +### Run All Tests + +```bash +cd build +ctest --output-on-failure +``` + +### Run Tests in Parallel + +```bash +cd build +ctest --output-on-failure --parallel $(nproc) +``` + +### Run a Specific Test Suite + +```bash +cd build + +# Run core tests +ctest -R "osquery_core_tests" --output-on-failure + +# Run SQL tests +ctest -R "osquery_sql_tests" --output-on-failure + +# Run config tests +ctest -R "osquery_config_tests" --output-on-failure + +# Run events tests +ctest -R "osquery_events_tests" --output-on-failure + +# Run integration table tests +ctest -R "integration" --output-on-failure +``` + +### Run Tests by Keyword + +```bash +cd build +ctest -R "tls" --output-on-failure +ctest -R "database" --output-on-failure +ctest -R "extension" --output-on-failure +``` + +### Verbose Test Output + +```bash +cd build +ctest -V -R "osquery_core_tests" +``` + +--- + +## Writing New Tests + +### Unit Test Template + +```cpp +#include +#include + +#include "osquery/my_module/my_class.h" + +namespace osquery { + +class MyClassTest : public ::testing::Test { + protected: + void SetUp() override { + // Per-test setup + } + + void TearDown() override { + // Per-test cleanup + } +}; + +TEST_F(MyClassTest, BasicOperation) { + MyClass obj; + EXPECT_EQ(obj.doSomething(), expected_value); +} + +TEST_F(MyClassTest, HandlesEdgeCase) { + MyClass obj; + EXPECT_THROW(obj.doSomethingInvalid(), std::runtime_error); +} + +} // namespace osquery +``` + +### Virtual Table Integration Test Template + +```cpp +#include "tests/integration/tables/helper.h" + +namespace osquery { +namespace table_tests { + +class MyTableTest : public testing::Test {}; + +TEST_F(MyTableTest, test_sanity) { + // Validate table schema and basic query + auto const data = execute_query("SELECT * FROM my_table;"); + ASSERT_GT(data.size(), 0ul); + + // Validate specific column presence and types + ValidateSchema( + data, + { + {"column_one", BIGINT_TYPE, CHECK_GT, 0, CHECK_LT, INT_MAX}, + {"column_two", TEXT_TYPE, CHECK_NON_EMPTY}, + } + ); +} + +} // namespace table_tests +} // namespace osquery +``` + +### Using GMock for Dependencies + +```cpp +#include +#include "osquery/database/database.h" + +class MockDatabase : public IDatabaseInterface { + public: + MOCK_METHOD(Status, putBatch, + (const std::string& domain, + const DatabaseStringValueList& data), + (override)); + + MOCK_METHOD(Status, get, + (const std::string& domain, + const std::string& key, + std::string& value), + (const, override)); +}; + +TEST(MyServiceTest, PersistsData) { + MockDatabase db; + EXPECT_CALL(db, putBatch(testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(Status::success())); + + MyService service(db); + auto result = service.persist("key", "value"); + EXPECT_TRUE(result.ok()); +} +``` + +--- + +## Test Helpers + +The integration test suite includes shared helpers in `tests/integration/tables/helper.h`: + +| Helper | Purpose | +|---|---| +| `execute_query(sql)` | Execute SQL and return rows | +| `ValidateSchema(data, validators)` | Assert column types and value ranges | +| `CHECK_GT`, `CHECK_LT` | Numeric range validators | +| `CHECK_NON_EMPTY` | String non-emptiness check | + +--- + +## Benchmark Tests + +Benchmarks measure performance of hot paths: + +```bash +# Build benchmarks +cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DOSQUERY_BUILD_BENCHMARKS=ON +cmake --build build --parallel $(nproc) + +# Run SQL benchmarks +./build/osquery/sql/benchmarks/sql_benchmarks + +# Run events benchmarks +./build/osquery/events/benchmarks/events_benchmarks +``` + +--- + +## Coverage Requirements + +While no mandatory coverage threshold is enforced in CI currently, the project encourages: + +- **Unit tests** for all new core module functionality +- **Integration tests** for every new virtual table +- **Error path coverage** for all error handling branches +- **Mock-based tests** for components with external dependencies (network, database) + +When contributing new virtual tables, a corresponding integration test in `tests/integration/tables/` is expected. + +--- + +## Platform-Specific Tests + +Some tests only run on specific platforms: + +```cpp +// Linux-only test +#ifdef __linux__ +TEST_F(AuditTest, LinuxAuditPublisher) { + // Linux-specific audit test +} +#endif + +// macOS-only test +#ifdef __APPLE__ +TEST_F(FSEventsTest, FseventsPublisher) { + // macOS FSEvents test +} +#endif +``` + +The CMake build system automatically includes platform-appropriate test targets. + +--- + +## Continuous Integration + +Tests run automatically in CI on every pull request. The CI pipeline: + +1. Builds with `-DOSQUERY_BUILD_TESTS=ON` +2. Runs `ctest --output-on-failure` +3. Enforces `clang-format` compliance +4. Checks copyright headers (via `tools/ci/scripts/check_copyright_headers.py`) + +All tests must pass before a PR can be merged. diff --git a/docs/getting-started/.gitignore b/docs/getting-started/.gitignore new file mode 100644 index 00000000000..a5d01be5236 --- /dev/null +++ b/docs/getting-started/.gitignore @@ -0,0 +1,7 @@ +# VoltAgent temp files +temp/ + +# JSON intermediate files (except schema/config) +*.json +!*-schema.json +!*-config.json diff --git a/docs/getting-started/first-steps.md b/docs/getting-started/first-steps.md new file mode 100644 index 00000000000..3b002d66b32 --- /dev/null +++ b/docs/getting-started/first-steps.md @@ -0,0 +1,178 @@ +# First Steps + +Now that osquery is running, here are the first things to explore and configure. + +--- + +## 1. Explore Available Tables + +osquery ships with hundreds of virtual tables. Discover them with the `.tables` command in the interactive shell: + +```bash +./build/osquery/osqueryi +``` + +```sql +-- List all available tables +osquery> .tables + +-- Search for tables by name +osquery> .tables process +osquery> .tables network +osquery> .tables user + +-- Inspect the schema of any table +osquery> .schema processes +osquery> .schema listening_ports +osquery> .schema users +``` + +Tables are organized by platform: + +| Category | Example Tables | +|---|---| +| **System** | `system_info`, `uptime`, `cpu_info`, `memory_info` | +| **Processes** | `processes`, `process_open_files`, `process_open_sockets` | +| **Networking** | `listening_ports`, `interface_addresses`, `arp_cache`, `routes` | +| **Users & Groups** | `users`, `groups`, `logged_in_users`, `sudoers` | +| **Packages** | `deb_packages`, `rpm_packages`, `homebrew_packages`, `chocolatey_packages` | +| **Events** | `file_events`, `process_events`, `socket_events` | +| **Security** | `authorized_keys`, `certificates`, `secureboot`, `disk_encryption` | + +--- + +## 2. Write Your First Scheduled Query Pack + +Create a configuration pack to run queries on a schedule: + +```bash +sudo tee /etc/osquery/packs/first-steps.json <<'EOF' +{ + "queries": { + "users_snapshot": { + "query": "SELECT uid, username, shell, directory FROM users;", + "interval": 3600, + "description": "Snapshot of all local users every hour" + }, + "listening_ports": { + "query": "SELECT pid, port, protocol, address FROM listening_ports;", + "interval": 300, + "description": "Check open ports every 5 minutes" + }, + "new_processes": { + "query": "SELECT pid, name, path, cmdline, start_time FROM processes;", + "interval": 60, + "description": "Poll running processes every minute" + } + } +} +EOF +``` + +Reference it in your `osquery.conf`: + +```json +{ + "options": { + "logger_plugin": "filesystem", + "schedule_splay_percent": 10 + }, + "packs": { + "first-steps": "/etc/osquery/packs/first-steps.json" + } +} +``` + +--- + +## 3. Enable File Integrity Monitoring + +osquery can monitor filesystem changes using platform-native event APIs (inotify on Linux, FSEvents on macOS, ETW on Windows): + +```json +{ + "file_paths": { + "etc": ["/etc/%%"], + "homes": ["/root/.ssh/%%", "/home/%/.ssh/%%"], + "sensitive": ["/usr/bin/%%", "/usr/sbin/%%"] + }, + "schedule": { + "file_events": { + "query": "SELECT * FROM file_events;", + "interval": 30 + } + } +} +``` + +Then query the event table: + +```sql +osquery> SELECT time, target_path, action, md5 FROM file_events; +``` + +--- + +## 4. Connect to OpenFrame (Flamingo Platform) + +If you are part of the [OpenFrame](https://openframe.ai) / [Flamingo](https://flamingo.run) platform: + +1. Obtain your OpenFrame credentials from your platform administrator +2. Configure the TLS enrollment endpoint in your daemon flags +3. The `OpenframeAuthorizationManager` handles token lifecycle automatically +4. The `OpenframeTokenRefresher` keeps sessions active in the background + +The OpenFrame layer uses AES-256-GCM encryption via the `OpenframeEncryptionService` — all credential handling is fully automated once configured. + +Refer to your environment configuration for the specific connection details. + +--- + +## 5. Load an Extension + +osquery's extension system lets you add custom tables, loggers, and configuration plugins as separate processes: + +```bash +# Start osqueryd with extension support enabled +sudo ./build/osquery/osqueryd \ + --config_path=/etc/osquery/osquery.conf \ + --extensions_socket=/var/osquery/osquery.em \ + --extensions_autoload=/etc/osquery/extensions.load + +# In a separate terminal, run your extension +./my_extension --socket=/var/osquery/osquery.em +``` + +Extensions communicate via Apache Thrift over UNIX domain sockets (Linux/macOS) or named pipes (Windows). + +--- + +## Key Configuration Flags + +| Flag | Description | Default | +|---|---|---| +| `--config_path` | Path to the JSON configuration file | Platform-specific | +| `--logger_plugin` | Logging backend (`filesystem`, `tls`, `syslog`) | `filesystem` | +| `--schedule_splay_percent` | Random splay to distribute query load | `10` | +| `--extensions_socket` | Path to the Thrift extension socket | Platform-specific | +| `--config_refresh` | How often (seconds) to refresh remote config | `0` (disabled) | +| `--watchdog_level` | Resource limit profile (0=normal, 1=restrictive, -1=disabled) | `0` | + +--- + +## Where to Get Help + +| Resource | Link | +|---|---| +| **OpenMSP Community Slack** | [Join here](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) | +| **OpenMSP Website** | [https://www.openmsp.ai/](https://www.openmsp.ai/) | +| **Flamingo Platform** | [https://flamingo.run](https://flamingo.run) | +| **OpenFrame** | [https://openframe.ai](https://openframe.ai) | + +> All support and discussions are managed on the **OpenMSP Slack community** — not GitHub Issues or GitHub Discussions. + +--- + +## Watch: osquery in Action + +[![osquery Overview](https://img.youtube.com/vi/1UcWGiHbLVo/hqdefault.jpg)](https://www.youtube.com/watch?v=1UcWGiHbLVo) diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md new file mode 100644 index 00000000000..a15e0308dbb --- /dev/null +++ b/docs/getting-started/introduction.md @@ -0,0 +1,113 @@ +# Introduction to osquery with OpenFrame + +**osquery** is a cross-platform operating system instrumentation framework that exposes live system state and activity as relational data — enabling you to query your operating system using SQL. + +Built and extended by the [Flamingo / OpenFrame](https://openframe.ai) platform, this distribution integrates osquery's powerful SQL telemetry engine with OpenFrame's AI-driven MSP automation infrastructure. + +--- + +## What is osquery? + +osquery transforms every host into a **SQL-queryable telemetry node**. Instead of parsing flat log files or calling proprietary APIs, you write standard SQL queries to explore: + +- Running processes and open sockets +- Installed packages and browser extensions +- Kernel modules and hardware inventory +- Filesystem events and user activity +- Network interfaces and firewall rules +- Windows Registry, launchd, systemd units, and much more + +```sql +-- Which processes are listening on ports? +SELECT pid, name, port, protocol FROM listening_ports JOIN processes USING (pid); + +-- What Chrome extensions are installed? +SELECT u.username, e.name, e.version, e.permissions +FROM users u, chrome_extensions e +WHERE e.uid = u.uid; +``` + +--- + +## OpenFrame Integration + +This repository extends osquery with the **OpenFrame** authentication and encryption layer: + +- **`OpenframeAuthorizationManager`** — Secure, lifecycle-controlled JWT token management +- **`OpenframeEncryptionService`** — AES-256-GCM symmetric encryption via OpenSSL +- **`OpenframeTokenExtractor`** — Token acquisition from OpenFrame services +- **`OpenframeTokenRefresher`** — Background thread for seamless token renewal + +These components are part of the [OpenFrame platform](https://www.flamingo.run/openframe) — the unified MSP interface that integrates IT tools under a single AI-driven experience. + +--- + +## Key Features + +| Feature | Description | +|---|---| +| **SQL Querying** | Query live OS data with standard SQL via embedded SQLite | +| **Virtual Tables** | 300+ platform-specific tables across Linux, macOS, and Windows | +| **Event Monitoring** | inotify, BPF, FSEvents, ETW, audit — all exposed as queryable tables | +| **Scheduled Queries** | Pack-based query scheduling with differential result tracking | +| **Distributed Queries** | Remote SQL orchestration across fleets via TLS | +| **Extension System** | Runtime plugin model with Apache Thrift IPC | +| **OpenFrame Auth** | AES-256-GCM encrypted JWT token management for OpenFrame services | +| **Cross-Platform** | Linux (x86_64, aarch64), macOS, and Windows support | + +--- + +## Target Audience + +osquery with OpenFrame is designed for: + +- **IT Operations Teams** using Flamingo/OpenFrame for managed service delivery +- **Security Engineers** building detection and response workflows +- **Fleet Administrators** who need SQL-level visibility across endpoints +- **Developers** building extensions, plugins, or custom table providers +- **MSP Technicians** leveraging Mingo AI and Fae automation through the OpenFrame platform + +--- + +## Architecture Overview + +```mermaid +graph TD + CLI["osqueryi / osqueryd"] --> Core["Core Init And Runtime"] + Core --> Config["Configuration And Packs"] + Core --> SQL["SQL Engine And Virtual Tables"] + Core --> Events["Eventing Framework"] + Core --> Logging["Logging And Observability"] + Core --> DB["Database And Storage Plugins"] + Core --> Dist["Distributed Querying"] + Core --> Ext["Extensions And IPC"] + Core --> HTTP["Remote HTTP Client"] + Core --> OF["OpenFrame Auth Layer"] + Config --> SQL + Config --> Events + SQL --> Logging + Events --> DB + Dist --> SQL + Dist --> HTTP + Ext --> SQL + OF --> HTTP +``` + +--- + +## Getting Started + +- Review system and software prerequisites in the [Prerequisites Guide](prerequisites.md) +- Follow the [Quick Start Guide](quick-start.md) to build and run osquery in minutes +- Explore common workflows in [First Steps](first-steps.md) + +--- + +## Community and Support + +osquery with OpenFrame is part of the [Flamingo](https://flamingo.run) open-source ecosystem. + +- 💬 **Community Slack**: [OpenMSP Community](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) +- 🌐 **OpenMSP**: [https://www.openmsp.ai/](https://www.openmsp.ai/) +- 🚀 **OpenFrame Platform**: [https://openframe.ai](https://openframe.ai) +- 🦩 **Flamingo**: [https://flamingo.run](https://flamingo.run) diff --git a/docs/getting-started/prerequisites.md b/docs/getting-started/prerequisites.md new file mode 100644 index 00000000000..8e6027a2725 --- /dev/null +++ b/docs/getting-started/prerequisites.md @@ -0,0 +1,167 @@ +# Prerequisites + +Before building or deploying osquery with OpenFrame, ensure your environment meets the requirements below. + +--- + +## System Requirements + +| Tier | RAM | CPU Cores | Disk Space | +|---|---|---|---| +| **Minimum** | 24 GB | 6 cores | 50 GB | +| **Recommended** | 32 GB | 12 cores | 100 GB | + +> Building osquery from source is resource-intensive. The recommended configuration significantly reduces build times and prevents out-of-memory failures during compilation. + +--- + +## Supported Operating Systems + +| Platform | Architecture | Notes | +|---|---|---| +| Linux | x86_64, aarch64 | Ubuntu 20.04+, RHEL 8+, Debian 11+ | +| macOS | x86_64, aarch64 (Apple Silicon) | macOS 12+ recommended | +| Windows | x86_64, aarch64 | Windows 10/11, Server 2019+ | + +--- + +## Required Software + +| Tool | Minimum Version | Purpose | +|---|---|---| +| **CMake** | 3.21+ | Build system generator | +| **Python** | 3.8+ | Code generation, tooling scripts | +| **Git** | 2.x | Source control | +| **C++ Compiler** | GCC 9+ / Clang 10+ / MSVC 2019+ | Compiling C++17 sources | +| **Ninja** | 1.10+ | Fast parallel builds (recommended) | +| **OpenSSL** | 1.1.1+ | TLS support and AES-256-GCM encryption (OpenFrame) | + +--- + +## Optional but Recommended + +| Tool | Purpose | +|---|---| +| **ccache** | Speeds up incremental C++ builds significantly | +| **clang-format** | Code formatting (enforced by CI) | +| **Docker** | Isolated build environments for Linux targets | + +--- + +## Platform-Specific Notes + +### Linux + +On Debian/Ubuntu, install build essentials: + +```bash +sudo apt-get update +sudo apt-get install -y \ + build-essential \ + cmake \ + ninja-build \ + python3 \ + python3-pip \ + git \ + openssl \ + libssl-dev \ + clang-format +``` + +On RHEL/CentOS/Fedora: + +```bash +sudo dnf install -y \ + gcc-c++ \ + cmake \ + ninja-build \ + python3 \ + git \ + openssl-devel \ + clang +``` + +### macOS + +Install Xcode Command Line Tools and Homebrew tools: + +```bash +xcode-select --install +brew install cmake ninja python3 openssl git +``` + +### Windows + +1. Install [Visual Studio 2019 or 2022](https://visualstudio.microsoft.com/) with the **Desktop development with C++** workload +2. Install [CMake](https://cmake.org/download/) 3.21+ +3. Install [Git for Windows](https://git-scm.com/download/win) +4. Install [Python 3.8+](https://www.python.org/downloads/windows/) +5. Optionally install [Ninja](https://github.com/ninja-build/ninja/releases) + +--- + +## OpenFrame Platform Requirements + +If you are connecting to the [OpenFrame platform](https://openframe.ai), you also need: + +| Requirement | Description | +|---|---| +| **OpenFrame Account** | Active account on the Flamingo/OpenFrame platform | +| **Network Access** | Outbound HTTPS (port 443) to OpenFrame services | +| **AES-256 Key** | Symmetric secret key for the `OpenframeEncryptionService` | +| **JWT Token** | Authorization token managed by `OpenframeAuthorizationManager` | + +Refer to your environment configuration and OpenFrame administrator for credential details. + +--- + +## Environment Variables + +The following environment variables may be required depending on your deployment: + +| Variable | Purpose | +|---|---| +| `OSQUERY_FLAGS_FILE` | Path to a flagfile for osquery runtime configuration | +| `OSQUERY_EXTENSIONS_SOCKET` | Path to the Thrift extension manager socket | +| `OSQUERY_CONFIG_PATH` | Custom configuration file path (overrides default) | + +Set variables using your shell profile or deployment tooling. For example: + +```bash +export OSQUERY_FLAGS_FILE="/etc/osquery/osquery.flags" +export OSQUERY_EXTENSIONS_SOCKET="/var/osquery/osquery.em" +``` + +--- + +## Verifying Your Environment + +Run the following checks to confirm your build environment is ready: + +```bash +# Check CMake version +cmake --version + +# Check C++ compiler +c++ --version + +# Check Python +python3 --version + +# Check Git +git --version + +# Check OpenSSL +openssl version + +# Check Ninja (if installed) +ninja --version +``` + +All tools should report versions meeting the minimums in the table above. + +--- + +## Next Steps + +Once prerequisites are confirmed, proceed to the [Quick Start Guide](quick-start.md) to clone, build, and run osquery. diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md new file mode 100644 index 00000000000..0ab657e8ce7 --- /dev/null +++ b/docs/getting-started/quick-start.md @@ -0,0 +1,177 @@ +# Quick Start + +Get osquery with OpenFrame running in under 10 minutes. + +--- + +## TL;DR — Fastest Path + +```bash +# 1. Clone the repository +git clone https://github.com/flamingo-stack/osquery.git +cd osquery + +# 2. Configure the build +cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -G Ninja + +# 3. Build osquery (this takes 5–30 minutes depending on hardware) +cmake --build build --parallel $(nproc) + +# 4. Run the interactive shell +./build/osquery/osqueryi +``` + +> **Hardware note**: Building from source requires at minimum 24 GB RAM and 6 CPU cores. See the [Prerequisites Guide](prerequisites.md) for full system requirements. + +--- + +## Step 1: Clone the Repository + +```bash +git clone https://github.com/flamingo-stack/osquery.git +cd osquery +``` + +--- + +## Step 2: Configure the Build + +### Linux / macOS + +```bash +cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -G Ninja +``` + +### Windows (Visual Studio) + +```bash +cmake -S . -B build ^ + -DCMAKE_BUILD_TYPE=RelWithDebInfo ^ + -G "Visual Studio 17 2022" +``` + +### Common CMake Options + +| Option | Description | +|---|---| +| `-DCMAKE_BUILD_TYPE=RelWithDebInfo` | Optimized build with debug symbols (recommended) | +| `-DCMAKE_BUILD_TYPE=Debug` | Full debug build (slower, larger binaries) | +| `-DCMAKE_BUILD_TYPE=Release` | Fully optimized production build | +| `-G Ninja` | Use Ninja for faster parallel builds | +| `-DOSQUERY_BUILD_TESTS=ON` | Include test suite in the build | + +--- + +## Step 3: Build + +```bash +# Linux/macOS — use all available CPU cores +cmake --build build --parallel $(nproc) + +# Windows — use all available CPU cores +cmake --build build --parallel %NUMBER_OF_PROCESSORS% +``` + +The first build downloads and compiles all third-party dependencies. Subsequent builds with `ccache` are significantly faster. + +--- + +## Step 4: Run Your First Query + +Launch the osquery interactive shell: + +```bash +./build/osquery/osqueryi +``` + +Expected output: + +```text +Using a virtual database. Need help, type '.help' +osquery> +``` + +Now try your first query: + +```sql +osquery> SELECT hostname, cpu_brand, physical_memory FROM system_info; +``` + +Example output: + +```text ++------------------+-------------------------------+------------------+ +| hostname | cpu_brand | physical_memory | ++------------------+-------------------------------+------------------+ +| my-linux-host | Intel(R) Core(TM) i9-12900K | 34207285248 | ++------------------+-------------------------------+------------------+ +``` + +--- + +## Step 5: Explore More Tables + +```sql +-- List all running processes +osquery> SELECT pid, name, cmdline FROM processes LIMIT 10; + +-- Check listening network ports +osquery> SELECT pid, port, protocol, address FROM listening_ports LIMIT 10; + +-- List installed packages (Linux) +osquery> SELECT name, version, arch FROM deb_packages LIMIT 10; + +-- Show users on the system +osquery> SELECT uid, gid, username, shell FROM users; +``` + +--- + +## Step 6: Run as a Daemon + +To run osquery as a background daemon with scheduled queries: + +```bash +# Create a basic configuration +sudo mkdir -p /etc/osquery +sudo tee /etc/osquery/osquery.conf <<'EOF' +{ + "options": { + "logger_plugin": "filesystem", + "schedule_splay_percent": 10 + }, + "schedule": { + "system_info": { + "query": "SELECT hostname, cpu_brand, physical_memory FROM system_info;", + "interval": 3600 + } + } +} +EOF + +# Run the daemon +sudo ./build/osquery/osqueryd --config_path=/etc/osquery/osquery.conf +``` + +--- + +## Expected Results Summary + +| Command | What You Should See | +|---|---| +| `osqueryi` | Interactive SQL prompt | +| `SELECT * FROM system_info;` | Host identity, CPU, RAM details | +| `SELECT * FROM processes LIMIT 5;` | Running process list | +| `osqueryd` | Daemon starts, logs to `/var/log/osquery/` | + +--- + +## Next Steps + +After completing this quick start: + +- Follow the [First Steps Guide](first-steps.md) to explore key features and common workflows +- Review the [Prerequisites Guide](prerequisites.md) for full system and software requirements +- Explore the [Development section](../development/README.md) to understand the architecture and contribute From a9c8d2ffd97dfea2470027456a96ca8c0df43606 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 23:24:40 +0000 Subject: [PATCH 7/8] docs: Stage 4 - Repository documentation (4 files) [skip ci] --- CONTRIBUTING.md | 587 +++++++++++++++++++++++++++++------------------- LICENSE.md | 139 ++++++++++++ README.md | 349 +++++++++++++++------------- SECURITY.md | 133 ++++++++--- docs/README.md | 119 ++++++++++ 5 files changed, 910 insertions(+), 417 deletions(-) create mode 100644 LICENSE.md create mode 100644 docs/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53923bffe95..5e5092f04d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,243 +1,368 @@ -# Contributing to osquery - -We want to make contributing to osquery as simple and transparent as -possible. These guidelines explain the basics of the osquery -development process and how you can contribute. Please read these -guidelines before submitting your code as they are designed to save -you time later on when your code is under review. - -## Contributing 101 - -All contributions are submitted via pull requests (PRs) open against -the osquery's [master](https://github.com/osquery/osquery/tree/master) -branch on GitHub. After being reviewed by the _core team_ and tested -by CI, if all is well, they will be pushed to master and the -corresponding PR closed. - -You can see who the _core team_ is by viewing the [team -page](https://github.com/orgs/osquery/teams) on the osquery GitHub -organization. - -If you need help, both the core team and community members are on the osquery [Slack](https://osquery.slack.com). -Feel free to register using the following [shared invite](https://join.slack.com/t/osquery/shared_invite/zt-1wipcuc04-DBXmo51zYJKBu3_EP3xZPA). -The `#code-review` Slack channel has been set up to handle urgent review needs as well as questions about your PR. -Note: prefer to keep discussion about code changes in the GitHub pull request thread. - -The osquery team also hosts regular office hours where the community -is invited to discuss osquery development with the core team. You are -welcome to join. Office hours are announced on our Slack on the -`#officehours` channel. - -## Development Process Guidelines - -For documentation on building, testing, and formatting code, please -review the ReadTheDocs article on [building -osquery](https://osquery.readthedocs.io/en/latest/development/building/). -This CONTRIBUTING guide focuses more on concepts and high level -workflow. - -### Blueprints - -If you plan to submit a change to the osquery core, a new big feature, -or in general a change that merits discussion, start by opening a -[Blueprint](https://github.com/osquery/osquery/issues/new?template=Blueprint.md) -issue. - -A blueprint issue is a standard GitHub issue, tagged with the label -[#blueprint](https://github.com/osquery/osquery/labels/blueprint), -which describes your idea, the problem you are solving and how you -plan to implement your solution. The goal of the blueprint is to allow -both the core team and the community to discuss whether a certain -change is desirable and will be accepted, and identify possible -problems with the implementation before it even starts. - -There aren't strict guidelines on when a blueprint is needed or not, -so you should use your best judgement or just ping the osquery team on -our `#core` channel on Slack. Here are some examples of changes which -**would** benefit from a blueprint: - -* Change the basic functioning of the query scheduler -* Alter the thrift interfaces -* Reimplement the logger interface -* Add a new plugin type - -There isn't either a strict format for the blueprints, but make sure -to include what problem you are trying to solve and how you plan to -solve it. We can go from that and ask more information if -necessary. If you have code already, even if it is only a -proof-of-concept that will be dropped later, please submit it as a PR -and associate it with the blueprint by mentioning the blueprint issue -on the pull request. - -Please remember that blueprints are mostly designed to save **you** -time by preventing you from implementing code which won't be accepted -or will need to be extensively modified later on. Please use the right -[template](https://github.com/osquery/osquery/issues/new?template=Blueprint.md) -for the issue. Feel free to advertise your blueprint and ask for -feedback on Slack. - -### Pull requests - -**Do not submit multiple unrelated changes on the same PR.** A pull -request must represent a single body of work. If your work requires a -bug-fix, submit that first on a separate PR, the same goes for -refactors. If you can split your work into multiple smaller PRs please -also do so. This is of utmost importance to allow fast reviews and to -simplify regression tracking, reverts and references. - -Start by developing your feature on a [feature -branch](https://guides.github.com/introduction/flow/), possibly -formatting your code before each commit, and when ready submit a pull -request against the osquery master branch. The initial PR should -preferably **contain a single commit**. If you are unfamiliar with -GitHub or how pull requests work, GitHub has a very easy to follow -guide that teaches you [how to fork the project and submit your first -PR] (https://guides.github.com/activities/forking/). - -It is helpful if you tag the GitHub issues you are addressing on the -body of your PR description. If your PR is intended to close an issue -keywords (like `fixes` or `closes`) as defined on [GitHub -Help](https://help.github.com/articles/closing-issues-using-keywords/). - -Once you submit your PR, a formatting check and continuous integration -tests will be triggered on the CI systems for the multiple platforms -we support. If all the required checks and tests are successful the -core team will review your PR. If the tests fail or the reviewer -requests changes, please submit those changes by **appending new -commits** to your feature branch. **Avoid amending old commits** as -that makes it harder for the reviewer to track your updates. If you -need to keep your PR up-to-date with master the preferred way is to -[rebase your -branch](https://help.github.com/en/articles/about-git-rebase) on -`master` and `git push` with the `--force` option. Finally, the core -team might help you with getting your PR accepted by pushing directly -to your branch when that makes sense. - -Once both the core team and CI are happy with the PR (remember tests -need to pass for all of the supported platforms) the PR will be -squashed into a single commit and pushed to the master branch. Only -the core team can merge pull requests and therefore at least one core -team member will always review your PR, however reviews from the -community are highly encouraged and desirable. - -Finally, we try to keep only active PRs open, and we like to merge PRs as quickly as possible. -If your PR is stale we will close it, however if you want to get back to it at a certain point feel free to re-open, or comment on it. - -### A note about labels - -The core team uses labels to tag each and every pull request. If you -care about their meaning take a look at -[labels](https://github.com/osquery/osquery/labels) on -GitHub. However, only the core team can label issues and PRs, so you -don't need to care too much about this. - -### Milestones and release versions - -We currently do not use a strict release schedule and we tag new minor versions ideally every two months. -Otherwise, we may tag a release if it makes sense according to the new features implemented or if critical bug-fixes where merged. -We keep several near-future milestones open and try to tag PRs with the milestone when appropriate. - -[Milestones](https://github.com/osquery/osquery/milestones) are used for the planned minor releases. -If your PR is tagged with the next milestone you can expect it to be merged as soon as it is ready. -We may keep PRs open and wait for a major release milestone if the code changes features that are not backwards-compatible. - -### Branches and tags - -The osquery repo contains only the -[master](https://github.com/osquery/osquery/tree/master) branch which -we do our best to keep stable. We don't keep feature or release -branches. The master branch will always keep a linear history and no -merge commits are allowed. All our releases are tagged. - -## Bug reports and feature requests - -Developing code is not the only way to contribute to -osquery. Submitting bug reports and new ideas is also valuable and -appreciated. - -We use GitHub issues to track bugs and feature requests. To submit a -bug report follow the [Bug -Report](https://github.com/osquery/osquery/issues/new?template=Bug_Report.md) -template, to submit a feature request use the [Feature -Request](https://github.com/osquery/osquery/issues/new?template=Feature_Request.md) -template. - -**Please only use issues for bug reports or feature requests**. If you -have deployment questions or issues or a general question about -osquery use our Slack instead as you will have better support -there. For the fastest result, you should search the available -channels and choose the most appropriate one for your question. You -should post in the general channel as a last resort. - -**If you are using a vendor product please use the appropriate channel -as we won't be able to support vendor deployments on the non-vendor -channels.** - -## Guidelines for contributing features to osquery core - -The software housed in this repo is known as osquery core. While there -are occasional exceptions, contributions to core should abide by the -following osquery guiding principles in order to be accepted: - -1. osquery does not change the state of the system -2. osquery does not create network traffic to third parties -3. osquery binaries have a light memory footprint -4. osquery minimizes system overhead & maximizes performance -5. osquery does not 'shell out' to other binaries for data collection -6. The query schema for osquery seeks uniformity between operating systems - -For new features that do not align with the mission principles of -core, you may build outside of osquery core in separate integrated -processes called extensions: -https://osquery.readthedocs.io/en/stable/development/osquery-sdk/. - -### Does my contribution belong in Core or in an Extension? - -Belongs in Core: - -* Observes guiding principles -* Has been shared with and approved by osquery project maintainers -* Meets osquery's testing and quality standards +# Contributing to osquery — OpenFrame Edition -Belongs in an extension: - -* Might not observe the osquery core guiding principles -* Expands the scope of use for osquery beyond endpoint monitoring -* Integrates with a proprietary or esoteric tool that is not widely applicable +Thank you for contributing to osquery with OpenFrame! This guide covers everything you need to get started: code style, branching conventions, commit format, testing requirements, and the pull request process. -## Contributor License Agreement +--- -You must submit a Contributor License Agreement (CLA) before we can -accept any of your pull requests. You only need to submit one CLA for -any of osquery's open source projects. +## Community First -This is managed through the Linux Foundations's EasyCLA. It will -comment appropriately on your PR. +All collaboration happens on the **OpenMSP Slack community** — not GitHub Issues or GitHub Discussions. -## Technical Steering Committee +| Resource | Link | +|---|---| +| 💬 OpenMSP Community Slack | [Join here](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) | +| 🌐 OpenMSP Website | [https://www.openmsp.ai/](https://www.openmsp.ai/) | -As defined by the [the osquery -charter](https://github.com/osquery/foundation/blob/master/CHARTER.md), -the Technical Steering Committee (or TSC for short) is responsible for -oversight of the osquery project. +Before starting significant work, please discuss your changes in Slack to align with the team's roadmap. -The [GitHub -Team](https://github.com/orgs/osquery/teams/technical-steering-committee) -is the authoritative source, though we maintain the list here as well. +--- -Current Members (in alphabetical order): +## Development Setup -* Alessandro -- [@alessandrogario](https://github.com/alessandrogario) -* Nick -- [@muffins](https://github.com/muffins) -* seph -- [@directionless](https://github.com/directionless) -* Sharvil -- [@sharvilshah](https://github.com/sharvilshah) -* Teddy -- [@theopolis](https://github.com/theopolis) -* Victor -- [@groob](https://github.com/groob) -* Zach -- [@zwass](https://github.com/zwass) +### Hardware Requirements -The Technical Steering Commit is chaired by seph. +| Tier | RAM | CPU Cores | Disk | +|---|---|---|---| +| **Minimum** | 24 GB | 6 cores | 50 GB | +| **Recommended** | 32 GB | 12 cores | 100 GB | -## License +### Required Tools -By contributing to osquery you agree that your contributions will be licensed -in accordance with the terms specified in the [LICENSE](LICENSE) file. +| Tool | Minimum Version | Purpose | +|---|---|---| +| CMake | 3.21+ | Build system generator | +| Python | 3.8+ | Code generation scripts | +| Git | 2.x | Source control | +| C++ Compiler | GCC 9+ / Clang 10+ / MSVC 2019+ | C++17 compilation | +| Ninja | 1.10+ | Fast parallel builds | +| OpenSSL | 1.1.1+ | TLS + AES-256-GCM (OpenFrame layer) | +| clang-format | — | Code formatting (CI enforced) | + +### Quick Setup + +```bash +# Clone +git clone https://github.com/flamingo-stack/osquery.git +cd osquery + +# Configure (Debug build for development) +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DOSQUERY_BUILD_TESTS=ON + +# Build +cmake --build build --parallel $(nproc) + +# Run +./build/osquery/osqueryi +``` + +For full environment setup instructions see the [Development Documentation](./docs/development/README.md). + +--- + +## Code Style and Conventions + +### C++ Standards + +- Use **C++17** features where appropriate +- Follow the existing code style in each file you modify +- All new code must pass `clang-format` with the repository's `.clang-format` config + +### Formatting + +osquery enforces `clang-format`. Run it before every commit: + +```bash +# Format a single file +clang-format -i path/to/your/file.cpp + +# Format all changed files (compared to main branch) +git diff --name-only main | grep -E '\.(cpp|h)$' | xargs clang-format -i + +# Check without modifying +clang-format --dry-run --Werror path/to/your/file.cpp +``` + +### Naming Conventions + +| Item | Convention | Example | +|---|---|---| +| Classes | `PascalCase` | `EventSubscriberPlugin` | +| Methods | `camelCase` | `generateRows()` | +| Member variables | `snake_case_` (trailing underscore) | `running_` | +| Constants | `kPascalCase` | `kSQLOpcodes` | +| Macros | `UPPER_SNAKE_CASE` | `DECLARE_FLAG` | +| Namespaces | `snake_case` | `osquery` | +| Files | `snake_case.cpp` / `snake_case.h` | `event_subscriber.cpp` | + +### Include Order + +Follow this include order with blank lines between groups: + +```cpp +// 1. Standard library +#include +#include +#include + +// 2. Third-party libraries +#include +#include + +// 3. osquery headers +#include "osquery/core/core.h" +#include "osquery/sql/sql.h" + +// 4. Local headers (same directory) +#include "my_local_header.h" +``` + +### Code Organization + +- Keep headers (`*.h`) minimal — forward declare where possible +- Use the `osquery` namespace for all production code +- Place tests in `tests/` subdirectories alongside the source +- New virtual tables go in `osquery/tables//` + +--- + +## Branch Naming + +Always branch from the latest `main`: + +```bash +git checkout main +git pull origin main +git checkout -b feature/my-new-feature +``` + +| Type | Pattern | Example | +|---|---|---| +| Feature | `feature/` | `feature/bpf-socket-events` | +| Bug fix | `fix/` | `fix/config-refresh-race` | +| OpenFrame integration | `openframe/` | `openframe/token-refresh-retry` | +| Documentation | `docs/` | `docs/virtual-table-guide` | +| Refactor | `refactor/` | `refactor/sql-authorizer` | +| Test | `test/` | `test/events-integration` | +| Release | `release/v` | `release/v5.13.0` | + +--- + +## Commit Message Format + +```text +(): + + + + +``` + +### Types + +| Type | When to Use | +|---|---| +| `feat` | New feature or capability | +| `fix` | Bug fix | +| `docs` | Documentation changes only | +| `style` | Code formatting, no logic change | +| `refactor` | Code refactoring without behavior change | +| `test` | Adding or fixing tests | +| `perf` | Performance improvement | +| `chore` | Build, CI, tooling changes | +| `openframe` | OpenFrame platform-specific changes | + +### Scopes + +| Scope | Area | +|---|---| +| `core` | Core init and runtime | +| `sql` | SQL engine and virtual tables | +| `config` | Configuration and packs | +| `events` | Eventing framework | +| `logger` | Logging and observability | +| `db` | Database and storage | +| `distributed` | Distributed querying | +| `extensions` | Extension IPC | +| `http` | Remote HTTP client | +| `openframe` | OpenFrame auth layer | +| `tables` | Virtual table implementations | + +### Examples + +```text +feat(events): add BPF socket event publisher for Linux + +Implements a new BPF-based publisher that captures socket connect/accept +events and exposes them via the bpf_socket_events virtual table. +``` + +```text +fix(openframe): handle token refresh failure with exponential backoff + +When OpenframeTokenRefresher encounters a network error, it now retries +with exponential backoff instead of immediately stopping the refresh loop. +``` + +```text +test(config): add pack discovery query unit tests +``` + +--- + +## Testing + +Tests use **Google Test** and **Google Mock**. Always build with `-DOSQUERY_BUILD_TESTS=ON`: + +```bash +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DOSQUERY_BUILD_TESTS=ON +cmake --build build --parallel $(nproc) + +# Run all tests +cd build && ctest --output-on-failure + +# Run in parallel +cd build && ctest --output-on-failure --parallel $(nproc) + +# Run a specific suite +cd build && ctest -R "osquery_sql_tests" --output-on-failure +``` + +### Test Categories + +| Category | Location | Description | +|---|---|---| +| Unit Tests | `osquery/*/tests/` | Fast, isolated, no OS dependencies | +| Integration Tests | `tests/integration/tables/` | Live table queries against the real OS | +| Extension Tests | `osquery/extensions/tests/` | IPC and Thrift round-trips | +| Plugin Tests | `plugins/*/tests/` | Logger, config, and database plugins | + +When contributing a new virtual table, a corresponding integration test in `tests/integration/tables/` is expected. + +--- + +## Pull Request Process + +### Before Submitting + +- [ ] Branch is up-to-date with `main` +- [ ] All tests pass: `cd build && ctest --output-on-failure` +- [ ] Code is formatted: `clang-format --dry-run --Werror` +- [ ] Copyright headers are present on new files +- [ ] New virtual tables have integration tests in `tests/integration/tables/` +- [ ] OpenFrame-specific changes include updated documentation +- [ ] Discussed in OpenMSP Slack (for significant changes) + +### PR Title Format + +Use the same format as commit messages: + +```text +feat(sql): add query result caching for repeated virtual table scans +``` + +### PR Description Template + +```markdown +## Summary + + +## Changes + + +## Testing + + +## Platform Support + + +## Checklist +- [ ] Tests pass (ctest) +- [ ] clang-format applied +- [ ] Documentation updated (if applicable) +- [ ] Discussed in OpenMSP Slack (if significant change) +``` + +--- + +## Copyright Headers + +All new source files must include a copyright header: + +```cpp +/** + * Copyright (c) 2014-present, The osquery authors + * + * This source code is licensed in accordance with the terms specified in + * the LICENSE file found in the root directory of this source tree. + */ +``` + +The CI script `tools/ci/scripts/check_copyright_headers.py` enforces this on all pull requests. + +--- + +## Adding a New Virtual Table + +1. Define the table schema in `osquery/tables//.table` +2. Run the code generator: + +```bash +python3 tools/codegen/gentable.py osquery/tables//.table +``` + +3. Implement the `generate()` method in `.cpp` +4. Register in the CMakefile for your category +5. Add an integration test in `tests/integration/tables/.cpp` +6. Test locally: + +```bash +cmake --build build --target osqueryi +./build/osquery/osqueryi +osquery> SELECT * FROM ; +``` + +--- + +## Security Guidelines + +- **Never** hardcode secrets, credentials, or tokens in source code +- **Never** log JWT token values, even at debug level +- SQL inputs must pass through the SQLite authorizer — do not bypass it +- AES-GCM nonces must be generated freshly (never reused for the same key) +- TLS peer verification must **not** be disabled in production code +- New config keys must include size/depth validation +- Thread-shared state must use proper synchronization primitives + +Report security vulnerabilities directly to the Flamingo team via the **OpenMSP Slack community** — do not open public GitHub Issues for security issues. + +--- + +## Reviewer Checklist + +When reviewing a PR: + +- [ ] Logic is correct and all error paths are handled +- [ ] No secrets or credentials in source +- [ ] SQL inputs are validated through the authorizer +- [ ] Thread safety considered for shared state +- [ ] Platform-specific code is properly guarded with `#ifdef` +- [ ] Tests added for new functionality +- [ ] No debug/temporary code left in +- [ ] Commit messages follow format convention +- [ ] Performance impact considered for hot paths (scheduler, SQL engine) + +--- + +## Code of Conduct + +Be respectful, collaborative, and constructive. All contributors are expected to maintain a professional and welcoming environment in both Slack and code reviews. + +--- + +
    + Built with 💛 by the Flamingo team +
    diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000000..b50dc26d7b1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,139 @@ +# The Flamingo AI Unified License v1.0 + +**Copyright © 2025 Flamingo AI, Inc. All rights reserved.** + +--- + +## 1. Preamble + +This License governs all Flamingo AI products, including OpenFrame, OpenMSP, and the Flamingo Website. +It serves as both the **License** and the **Terms of Use**. + +Flamingo AI software includes components developed by Flamingo AI as well as upstream projects that remain under their original licenses. +This License applies **only** to Flamingo AI contributions. For upstream components, You must comply with their respective licenses (see Appendix). + +--- + +## 2. Definitions + +**2.1 “Licensor”** means Flamingo AI, Inc. + +**2.2 “Flamingo Software”** means any and all software code, binaries, object code, libraries, scripts, user interface designs, documentation, specifications, and other works of authorship created, authored, or contributed to by Flamingo AI, Inc., whether released in source or binary form, and licensed under this License. + +Flamingo Software includes: +- Core platform code developed by Flamingo AI, including orchestration layers, integration modules, APIs, user interface components, and custom agents. +- All derivative works, modifications, enhancements, bug fixes, and extensions authored by Flamingo AI. +- Contributions made by third parties that have been accepted into Flamingo AI repositories under this License. + +**Exclusions:** Flamingo Software does not include **Upstream Components**, which remain subject to their original licenses (see Appendix). + +**2.3 “Upstream Components”** means third-party software incorporated into Flamingo Software that remain governed by their original licenses. + +**2.4 “Network Use”** means making Flamingo Software available to third parties over a computer network. + +**2.5 “Commercial Managed Service”** means offering Flamingo Software, in whole or in part, as a paid hosted or managed service where the primary value is the operation of Flamingo Software. + +**2.6 “Contribution”** means any modification, patch, enhancement, or other work that You create and distribute based on Flamingo Software. + +--- + +## 3. Grant of Rights + +Subject to this License: +- You may use, copy, modify, and distribute Flamingo Software. +- You may self-host Flamingo Software for internal or external purposes. +- You may create derivative works, provided they are licensed under this License. +- You may publish modifications, which Flamingo AI may reuse freely. + +--- + +## 4. Copyleft and Contribution-Back + +**4.1 Copyleft Obligation.** All forks, modifications, or redistributions of Flamingo Software, including via Network Use, must be licensed under this License. + +**4.2 Contribution-Back.** By publishing modifications, You grant Flamingo AI an irrevocable, worldwide, royalty-free license to use, modify, and relicense such modifications. + +**4.3 Attribution.** You must preserve copyright notices, attribution to Flamingo AI, and all notices of Upstream Component licensing. + +**4.4 Upstream Compliance.** You must comply with the licenses of all Upstream Components. + +**4.5 Trademarks.** This License does not grant rights to Flamingo AI trademarks or logos, except as required for attribution. + +--- + +## 5. Restrictions + +- **5.1 No Competing SaaS.** You may not provide Flamingo Software as a Commercial Managed Service without a separate commercial license from Flamingo AI. +- **5.2 No Relicensing.** You may not relicense Flamingo Software under other terms. +- **5.3 No Removal of Attribution.** You may not remove or alter copyright or license notices. +- **5.4 Acceptable Use.** You may not use Flamingo Software, OpenMSP, or the Website for illegal activity, scraping, spamming, reverse engineering, or abusive purposes. + +--- + +## 6. Product-Specific Terms + +### 6.1 OpenFrame +- OpenFrame integrates upstream software (Fleet, MeshCentral, Osquery, Tactical RMM) and Flamingo contributions. +- Upstream components remain under their original licenses (see Appendix). +- SaaS restriction applies fully to Flamingo contributions. + +### 6.2 OpenMSP +- Covers community knowledge base and contributions. +- Contributions may be reused by Flamingo AI commercially. +- No scraping, resale, or redistribution without consent. + +### 6.3 Flamingo Website +- Website content is Flamingo AI intellectual property. +- Users must not scrape, clone, or misuse content. +- Acceptable use restrictions apply. + +--- + +## 7. DMCA and Copyright Compliance + +If You believe your copyrighted material has been used in violation of this License, You may submit a notice to: **legal@flamingo.so**. + +--- + +## 8. Warranty Disclaimer & Limitation of Liability + +**Disclaimer.** The Flamingo Software, OpenMSP, and Website are provided **“AS IS”**, without warranties of any kind. + +**Liability.** To the maximum extent permitted by law, Flamingo AI shall have **zero liability** for any damages, losses, or claims, whether in contract, tort, or otherwise. Users assume all risks of deploying and operating Flamingo Software. + +--- + +## 9. Termination + +- This License terminates immediately upon breach. +- Upon termination, all rights cease. + +--- + +## 10. Governing Law + +This License is governed by the laws of the State of Delaware, USA. + +--- + +## 11. Miscellaneous + +- If any provision is unenforceable, the remainder remains in effect. +- This License is the full agreement between You and Flamingo AI. + +--- + +## Appendix: Upstream Licenses + +The following upstream components are incorporated into Flamingo Software and remain under their original licenses. This License does not alter their terms: + +- **Tactical RMM** — Tactical RMM License v1.0 (proprietary, AmidaWare LLC). +- **Fleet** — MIT License (with documentation under CC BY-SA 4.0). +- **Osquery** — Dual-licensed under Apache-2.0 OR GPL-2.0-only. +- **MeshCentral Agent** — Apache-2.0. + +You must comply with these licenses in addition to the Flamingo AI Unified License v1.0. + +--- + +**Contact for Commercial Licensing:** legal@flamingo.so diff --git a/README.md b/README.md index 6cb4eab8219..670585d5508 100644 --- a/README.md +++ b/README.md @@ -1,209 +1,258 @@
    - - - - - - OpenFrame Logo + + + Project Logo - -

    Osquery

    - -

    Cross-platform endpoint visibility and telemetry engine, integrated with OpenFrame — SQL-powered queries for Windows, macOS, and Linux.

    - -

    - - License - - - Docs - - - Community - -

    ---- +

    + License +

    -## Quick Links -- [Overview](#overview) -- [Quick Start](#quick-start) -- [Architecture](#architecture) -- [Security](#security) -- [Contributing](#contributing) +# osquery — OpenFrame Edition ---- +**osquery** is a cross-platform operating system instrumentation framework that exposes live system state and activity as relational data — enabling you to query your operating system using SQL. -## Overview +Built and extended by the [Flamingo / OpenFrame](https://openframe.ai) platform, this distribution integrates osquery's powerful SQL telemetry engine with OpenFrame's AI-driven MSP automation infrastructure. Every host becomes a **SQL-queryable telemetry node** — no proprietary log parsers, no fragile scripts, just standard SQL. -**Osquery** is an open-source endpoint visibility and monitoring tool created by Facebook (Meta). It allows you to query operating system information using SQL queries, making it powerful for security monitoring, incident response, compliance, and infrastructure inventory. +```sql +-- Which processes are listening on ports? +SELECT pid, name, port, protocol FROM listening_ports JOIN processes USING (pid); -With Osquery, you can: -- Query system details like processes, users, network connections, file systems, and registry keys -- Monitor system changes in real-time -- Detect security threats and anomalies -- Ensure compliance with security policies -- Perform forensic investigations +-- What Chrome extensions are installed? +SELECT u.username, e.name, e.version, e.permissions +FROM users u, chrome_extensions e +WHERE e.uid = u.uid; +``` -In OpenFrame, Osquery is integrated with the **Fleet agent**, providing centralized management and query execution across all your endpoints from a single interface. +--- -**Official Documentation:** [osquery.io/docs](https://osquery.io/docs) -**GitHub Repository:** [github.com/osquery/osquery](https://github.com/osquery/osquery) +## Watch: osquery in Action + +[![osquery Overview](https://img.youtube.com/vi/1UcWGiHbLVo/hqdefault.jpg)](https://www.youtube.com/watch?v=1UcWGiHbLVo) --- -## Highlights +## Features -- Unified endpoint visibility across Windows, macOS, Linux -- Query the system state using SQL (processes, users, network, registry, etc.) -- Lightweight daemon with minimal performance overhead -- Extensible with custom tables and plugins -- Integrates with OpenFrame Gateway, Stream (Kafka), and Analytics (Pinot) -- Useful for inventory, compliance, incident response, and threat hunting -- **Automatic installation** with Fleet agent - no separate setup required +- **SQL Querying** — Query live OS data with standard SQL via an embedded, hardened SQLite engine +- **300+ Virtual Tables** — Platform-specific tables across Linux, macOS, and Windows covering processes, sockets, packages, users, registry, hardware, and more +- **Event Monitoring** — inotify, BPF, FSEvents, ETW, and OpenBSM events exposed as queryable tables +- **Scheduled Query Packs** — Configuration-driven query scheduling with differential result tracking (only changed rows are logged) +- **Distributed Fleet Queries** — Remote SQL orchestration across entire fleets via TLS +- **Extension System** — Runtime plugin model via Apache Thrift IPC — add custom tables, loggers, and config plugins as external processes +- **OpenFrame Auth Layer** — AES-256-GCM encrypted JWT token management (`OpenframeAuthorizationManager`, `OpenframeEncryptionService`, `OpenframeTokenRefresher`) for seamless integration with the Flamingo/OpenFrame MSP platform +- **Cross-Platform** — Linux (x86\_64, aarch64), macOS (Intel + Apple Silicon), and Windows x86\_64 +- **Security-First** — SQLite authorizer allowlists only safe opcodes, Watcher/Worker process isolation, peer-verified TLS everywhere --- ## Architecture -Osquery runs as an agent on endpoints, collecting data and exposing it via SQL. Integrated with OpenFrame, results flow into Gateway → Stream → Analytics. - ```mermaid -flowchart LR - subgraph OpenFrame Frontend - OUI[Openframe UI / AI agent] - end - - OUI -- osquery --> G[OpenFrame Gateway] - - subgraph OpenFrame Backend - G -- osquery from Openframe UI / AI agent --> API[(Fleet Service API)] - API --> DB[(DB)] - DB --> S[Stream] - S --> K[(Kafka)] - K --> C[(Cassandra)] - K --> P[(Pinot Analytics)] - API <-- run osquery --> G - end - - G <-- osquery --> FA[Fleet Agent] - - - style OUI fill:#FFC109,stroke:#1A1A1A,color:#FAFAFA - style G fill:#666666,stroke:#1A1A1A,color:#FAFAFA +graph TD + CLI["osqueryi / osqueryd"] --> Core["Core Init And Runtime"] + Core --> Config["Configuration And Packs"] + Core --> SQL["SQL Engine And Virtual Tables"] + Core --> Events["Eventing Framework"] + Core --> Logging["Logging And Observability"] + Core --> DB["Database And Storage Plugins"] + Core --> Dist["Distributed Querying"] + Core --> Ext["Extensions And IPC"] + Core --> HTTP["Remote HTTP Client"] + Core --> OF["OpenFrame Auth Layer"] + Config --> SQL + Config --> Events + SQL --> Logging + Events --> DB + Dist --> SQL + Dist --> HTTP + Ext --> SQL + Ext --> DB + OF --> HTTP ``` +| Module | Location | Responsibility | +|---|---|---| +| Core Init And Runtime | `osquery/core/` | Process lifecycle, flags, watcher/worker model | +| SQL Engine And Virtual Tables | `osquery/sql/` | SQLite engine, authorizer, virtual table binding | +| Configuration And Packs | `osquery/config/` | Scheduled queries, packs, decorators | +| Eventing Framework | `osquery/events/` | Publisher/subscriber event system | +| Logging And Observability | `osquery/logger/` | Differential logging, JSON serialization | +| Database And Storage Plugins | `osquery/database/` | RocksDB persistent + ephemeral in-memory stores | +| Distributed Querying | `osquery/distributed/` | Remote fleet orchestration | +| Extensions And IPC | `osquery/extensions/` | Apache Thrift-based runtime extensions | +| Remote HTTP Client | `osquery/remote/` | Boost.Asio/Beast HTTPS with strict TLS | +| OpenFrame Auth Layer | `openframe/` | JWT token management + AES-256-GCM encryption | + +--- + +## Technology Stack + +| Layer | Technology | +|---|---| +| Language | C++17 | +| Build System | CMake 3.21+ with Ninja | +| SQL Engine | SQLite (embedded, in-memory) | +| IPC | Apache Thrift (UNIX sockets / named pipes) | +| Encryption | OpenSSL (AES-256-GCM) | +| Networking | Boost.Asio + Boost.Beast | +| Event Systems | inotify, BPF, FSEvents, ETW, OpenBSM | +| Database | RocksDB (persistent), ephemeral in-memory | +| Testing | Google Test + Google Mock | + +--- + +## Hardware Requirements + +| Tier | RAM | CPU Cores | Disk | +|---|---|---|---| +| **Minimum** | 24 GB | 6 cores | 50 GB | +| **Recommended** | 32 GB | 12 cores | 100 GB | + +> Building from source is resource-intensive. The recommended configuration significantly reduces build times and prevents out-of-memory failures during compilation. + +--- + +## Supported Platforms + +| Platform | Architecture | Notes | +|---|---|---| +| Linux | x86\_64, aarch64 | Ubuntu 20.04+, RHEL 8+, Debian 11+ | +| macOS | x86\_64, aarch64 | macOS 12+ (Intel + Apple Silicon) | +| Windows | x86\_64 | Windows 10/11, Server 2019+ | + --- ## Quick Start -### Prerequisites +### 1. Install Prerequisites -**No additional installation required!** Osquery is automatically installed and configured when you deploy the Fleet agent on your endpoints. +**Linux (Debian/Ubuntu):** -Requirements: -- OpenFrame instance running with Fleet service enabled -- Access to OpenFrame UI -- Supported operating system on target endpoints: - - **Linux:** Ubuntu, Debian, CentOS, RHEL, Amazon Linux - - **macOS:** 10.14+ (Mojave and later) - - **Windows:** Windows 10, Windows Server 2016+ +```bash +sudo apt-get update +sudo apt-get install -y \ + build-essential cmake ninja-build python3 python3-pip \ + git openssl libssl-dev clang-format ccache +``` -### Installation +**macOS:** -1. **Log in to OpenFrame UI** +```bash +xcode-select --install +brew install cmake ninja python3 openssl git ccache +``` -2. **Navigate to the Devices tab** - - Click on **"Devices"** in the left sidebar - - Click **"Add Device"** or **"Enroll New Device"** button +**Windows:** -3. **Get the installation link** - - OpenFrame will generate a unique enrollment link/script for your device - - This link contains: - - Fleet agent installer - - Osquery binaries (automatically bundled) - - Your OpenFrame server configuration - - Enrollment secrets for secure authentication +1. Install [Visual Studio 2022](https://visualstudio.microsoft.com/) with the **Desktop development with C++** workload +2. Install [CMake 3.21+](https://cmake.org/download/), [Git](https://git-scm.com/download/win), [Python 3.8+](https://www.python.org/downloads/windows/) +3. Download the CLI binary directly: [openframe-cli\_windows\_amd64.zip](https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_windows_amd64.zip) -4. **Run the installation on your endpoint** - - **For Linux/macOS:** - ```bash - # The UI will provide a command similar to: - curl -sSL https://your-openframe-instance.com/api/v1/fleet/enroll?token=xxx | sudo bash - ``` +### 2. Clone and Build - **For Windows (PowerShell as Administrator):** - ```powershell - # The UI will provide a command similar to: - Invoke-WebRequest -Uri "https://your-openframe-instance.com/api/v1/fleet/enroll?token=xxx" -UseBasicParsing | Invoke-Expression - ``` +```bash +# Clone +git clone https://github.com/flamingo-stack/osquery.git +cd osquery -5. **Verify installation** - - Return to the **Devices** tab in OpenFrame UI - - Your newly enrolled device should appear within 30-60 seconds - - Status should show as **"Online"** - - Osquery will be ready to accept queries immediately +# Configure (Linux/macOS) +cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -G Ninja -### Running Your First Query +# Configure (Windows) +cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -G "Visual Studio 17 2022" -Once your device is enrolled: +# Build — uses all available CPU cores +cmake --build build --parallel $(nproc) +``` -1. **Navigate to your device** in the OpenFrame UI -2. **Go to the "Query" tab** or use the **AI Agent** interface -3. **Try a simple query:** - ```sql - SELECT * FROM system_info; - ``` -4. **View real-time results** directly in the UI +### 3. Run Your First Query -For more query examples and table schemas, visit the [Osquery Schema Documentation](https://osquery.io/schema/). +```bash +./build/osquery/osqueryi +``` + +```text +Using a virtual database. Need help, type '.help' +osquery> +``` + +```sql +osquery> SELECT hostname, cpu_brand, physical_memory FROM system_info; +osquery> SELECT pid, name, port, protocol FROM listening_ports LIMIT 10; +osquery> SELECT uid, username, shell FROM users; +osquery> .tables +``` + +### 4. Run as a Daemon + +```bash +sudo mkdir -p /etc/osquery +sudo tee /etc/osquery/osquery.conf <<'EOF' +{ + "options": { + "logger_plugin": "filesystem", + "schedule_splay_percent": 10 + }, + "schedule": { + "system_info": { + "query": "SELECT hostname, cpu_brand, physical_memory FROM system_info;", + "interval": 3600 + } + } +} +EOF + +sudo ./build/osquery/osqueryd --config_path=/etc/osquery/osquery.conf +``` --- -## Security +## OpenFrame Integration + +This distribution extends osquery with a secure authentication and encryption layer for the [OpenFrame platform](https://www.flamingo.run/openframe): -- TLS 1.2 enforced for all communication -- JWT / enrollment secrets via OpenFrame Gateway -- Minimal privileges required on endpoints +| Component | Description | +|---|---| +| `OpenframeAuthorizationManager` | Lifecycle-controlled JWT token singleton (non-copyable) | +| `OpenframeAuthorizationManagerProvider` | Sole factory and owner of the token manager | +| `OpenframeEncryptionService` | AES-256-GCM symmetric encryption via OpenSSL | +| `OpenframeTokenExtractor` | Token acquisition from OpenFrame services | +| `OpenframeTokenRefresher` | Background thread for seamless token renewal | -Found a vulnerability? Email **security@flamingo.run** instead of opening a public issue. +Obtain your OpenFrame credentials from your platform administrator. Token lifecycle is fully automated once configured. Refer to your environment configuration for connection details. --- -## Contributing +## Documentation -We welcome PRs! Please follow these guidelines: -- Use branching strategy: `feature/...`, `bugfix/...` -- Add descriptions to the **CHANGELOG** -- Run `make test` before submitting -- Keep documentation updated in `docs/` +📚 See the [Documentation](./docs/README.md) for comprehensive guides. + +- [Introduction](./docs/getting-started/introduction.md) — What is osquery + OpenFrame? +- [Prerequisites](./docs/getting-started/prerequisites.md) — System and software requirements +- [Quick Start](./docs/getting-started/quick-start.md) — Clone, build, and run +- [First Steps](./docs/getting-started/first-steps.md) — Explore tables, packs, FIM, extensions +- [Architecture Overview](./docs/development/architecture/README.md) — Module deep-dives +- [Local Development](./docs/development/setup/local-development.md) — Build configurations and debugging +- [Contributing Guidelines](./docs/development/contributing/guidelines.md) — Code style and PR process --- -## License +## Community and Support + +> We do **not** use GitHub Issues or GitHub Discussions. All support and collaboration happens on the **OpenMSP Slack community**. -This project is licensed under the **Flamingo Unified License v1.0** ([LICENSE.md](LICENSE.md)). +| Resource | Link | +|---|---| +| 💬 OpenMSP Community Slack | [Join here](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) | +| 🌐 OpenMSP Website | [https://www.openmsp.ai/](https://www.openmsp.ai/) | +| 🚀 OpenFrame Platform | [https://openframe.ai](https://openframe.ai) | +| 🦩 Flamingo | [https://flamingo.run](https://flamingo.run) | ---
    -
    - - - - -
    - Built with 💛 by the Flamingo team - - Website • - Knowledge Base • - LinkedIn • - Community -
    + Built with 💛 by the Flamingo team diff --git a/SECURITY.md b/SECURITY.md index 1cae662e540..49b47610f53 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,36 +1,97 @@ -# Security Vulnerability Disclosure - -This document contains a reference history of osquery's remediated security vulnerabilities. If you find a -vulnerability, please see CONTRIBUTING.md#how_to_report_vulnerabilities for how to submit a vulnerability report. - -## Security Issues - -This document aggregates security issues (weaknesses and vulnerabilities) affecting osquery. It tracks issues in the format: - -```text -#PRNumber Title - (Optional CVE) - Fixed in Version - Optional Reporter -``` - -There are several types of issues that do not include a CVE or reporter. -If you find a security issue and believe a CVE should be assigned, please contact a [member of the TSC](https://github.com/osquery/osquery/blob/master/CONTRIBUTING.md#technical-steering-committee) in the osquery [Slack](https://osquery.slack.com), we are happy to submit the request and provide attribution to you. -Specifically, we will use the GitHub Security Advisory features for CVE requests. -The project maintainers will tag related issues and pull requests with the [`hardening`](https://github.com/osquery/osquery/issues?q=is%3Aissue+is%3Aopen+label%3Ahardening) label. There may be changes with this label that are not directly security issues. - -If you are editing this document please feel encouraged to change this format to provide more details. This is intended to be a helpful resource so please keep content valuable and concise. - -- #6197 osquery does not validate TLS SNI hostname - CVE-2020-1887 - 4.2.0 - Timothy Britton of Apple -- #3786 Migrate from `boost::regex` to `re2` - unresolved - Ruslan Habalov and Felix Wilhelm of the Google Security Team -- #3785 `ie_extensions` susceptible to SQL injection - CVE-2017-15026 - 2.9.0 - Ruslan Habalov and Felix Wilhelm of the Google Security Team -- #3783/#3782 `safari_extensions` should not use parent paths for privilege dropping - CVE-2017-15027 - 2.9.0 - Ruslan Habalov and Felix Wilhelm of the Google Security Team -- #3781 `known_hosts` should drop privileges - CVE-2017-15028 - 2.9.0 - Ruslan Habalov and Felix Wilhelm of the Google Security Team -- #3770/#3775 `libxml2` (v2.9.5) and `libarchive` (v3.3.2) updated - 2.9.0 -- #3767 `augeas` (v1.8.1) mitigates CVE-2017-7555 - 2.9.0 - Ruslan Habalov and Felix Wilhelm of the Google Security Team -- #3133 Bad output size for TLS compression - 2.4.0 - Facebook Whitehat -- #2447 Multiple fixes to macOS `crashes` - 2.0.0 - Facebook Whitehat and zzuf -- #2330 Add size checks to `package_bom` - 2.0.0 - Facebook Whitehat -- #1598 `readFile` TOCTOU error - 1.6.0 - NCC Group -- #1596 Uncaught exception in config JSON parsing - 1.6.0 - NCC Group -- #1585 Various comparisons of integers of different signs - 1.6.0 - NCC Group -- #993 Add restricted permissions to RocksDB - 1.4.5 - Anonymous security review -- #740 Add hardening compile flags and `-fPIE` - 1.4.1 - Anonymous security review -- #300 Add restricted permissions to osqueryd logging - 1.0.4 +# Flamingo AI Privacy Policy + +**Effective Date: 2025** +**Applies to: All Flamingo AI products, including OpenFrame, OpenMSP, and the Flamingo Website** + + +## 1. Definitions + +**“Flamingo Software”** means all software products developed and released by Flamingo AI, Inc., including but not limited to OpenFrame, OpenMSP, related agents, integrations, orchestration layers, user interfaces, binaries, and documentation. + +**“OpenFrame”** refers to the self-hosted remote monitoring and management (RMM) platform released by Flamingo AI. + +**“OpenMSP”** refers to the community knowledge base, documentation, and related collaborative content contributed by users and curated by Flamingo AI. + +**“Flamingo Website”** refers to flamingo.cx and related domains controlled by Flamingo AI. + + +## 2. Scope + +This Privacy Policy explains how Flamingo AI, Inc. (“Flamingo AI”) collects, uses, and protects personal data in connection with Flamingo Software, including OpenFrame, OpenMSP, and the Flamingo Website. + + +## 3. Data Collection + +- **Self-Hosted Deployments (OpenFrame):** Flamingo AI does not access or collect customer data. All data remains fully under customer control. +- **OpenMSP (Community):** Flamingo AI collects account registration details (username, email), and any content you voluntarily submit (posts, documentation, comments). Metadata (such as IP addresses and logs) may be collected for security, fraud prevention, and moderation. +- **Website:** Limited analytics (IP addresses, device/browser data, cookies) are collected for performance, security, and improvements. +- **Support Engagements:** Customers may voluntarily provide logs or diagnostic data for troubleshooting. Such data is deleted promptly after resolution. + + +## 4. Legal Basis for Processing (GDPR) + +Flamingo AI processes personal data only when: +- You have given consent (e.g., submitting community contributions to OpenMSP, website cookies). +- Processing is necessary for the performance of a contract (e.g., account creation, support requests). +- Processing is required by law. +- Processing is necessary for legitimate interests (e.g., community moderation, improving security and services). + + +## 5. Data Subject Rights + +Depending on your location (EU/UK under GDPR, California under CCPA, or similar laws), you may have the right to: +- Access your personal data. +- Rectify inaccurate data. +- Request deletion (“right to be forgotten”). +- Restrict or object to processing. +- Request portability of your data. +- Withdraw consent at any time. +- Opt out of sale or sharing of personal data (CCPA). +- Lodge a complaint with a supervisory authority (for EU/UK residents). + +Requests can be submitted to **privacy@flamingo.so**. + + +## 6. Data Transfers + +- Flamingo AI does not transfer customer data from self-hosted deployments. +- If personal data must cross borders (e.g., for support or OpenMSP account services), Flamingo AI uses safeguards such as Standard Contractual Clauses. + + +## 7. Data Retention + +- Customer-controlled data in OpenFrame remains under customer control. +- Support data is retained only for resolution, typically deleted within 24 hours. +- Website analytics are retained no longer than necessary for stated purposes. +- OpenMSP contributions remain public unless removed by you or as part of community moderation. Account data may be deleted upon request. + + +## 8. Children’s Privacy + +Flamingo AI products are not directed to children under 16 in the EU/UK or under 13 in the US. Flamingo AI does not knowingly collect data from minors. + + +## 9. Security + +Flamingo AI implements reasonable technical and organizational safeguards, but **customers remain fully responsible for the security of their own infrastructure**. + + +## 10. Responsibility Disclaimer + +Use of Flamingo AI products is entirely at your own risk. Customers are solely responsible for compliance with applicable data protection laws when deploying Flamingo AI software or participating in OpenMSP. + + +## 11. Text Messaging Opt-In Data + +Text messaging originator opt-in data and consent will not be shared with any third parties, excluding aggregators and providers of the Text Message services. + + +## 12. Contact Information + +**Privacy Inquiries:** privacy@flamingo.so +**General Information:** info@flamingo.so + +--- + +*By using Flamingo AI products, you acknowledge and agree to this Privacy Policy.* + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..19d2db24188 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,119 @@ +# osquery — OpenFrame Edition Documentation + +Welcome to the documentation for **osquery with OpenFrame** — the cross-platform OS instrumentation framework extended with AI-driven MSP automation by [Flamingo](https://flamingo.run) and [OpenFrame](https://openframe.ai). + +--- + +## 📚 Table of Contents + +- [Getting Started](#-getting-started) +- [Development](#-development) +- [Reference Architecture](#-reference-architecture) +- [Architecture Diagrams](#-architecture-diagrams) +- [Quick Links](#-quick-links) +- [Community](#-community) + +--- + +## 🚀 Getting Started + +New to osquery? Start here. + +| Guide | Description | +|---|---| +| [Introduction](./getting-started/introduction.md) | What is osquery with OpenFrame? Features and target audience | +| [Prerequisites](./getting-started/prerequisites.md) | System requirements, supported platforms, required software | +| [Quick Start](./getting-started/quick-start.md) | Clone, build, and run osquery in under 10 minutes | +| [First Steps](./getting-started/first-steps.md) | Explore virtual tables, scheduled packs, FIM, extensions, and OpenFrame | + +**Recommended reading order:** Introduction → Prerequisites → Quick Start → First Steps + +--- + +## 🛠 Development + +Guides for contributors and developers building on or extending osquery. + +### Setup + +| Guide | Description | +|---|---| +| [Environment Setup](./development/setup/environment.md) | IDE recommendations, clangd, ccache, editor extensions | +| [Local Development](./development/setup/local-development.md) | Build configurations, debug flags, GDB/LLDB, VS Code debugging | + +### Architecture + +| Guide | Description | +|---|---| +| [Architecture Overview](./development/architecture/README.md) | High-level module breakdown, runtime lifecycle, design decisions | + +### Quality + +| Guide | Description | +|---|---| +| [Testing Guide](./development/testing/README.md) | GTest/GMock structure, running tests, writing unit and integration tests | +| [Security Best Practices](./development/security/README.md) | Auth patterns, AES-256-GCM, SQL authorizer, TLS, secrets management | + +### Contributing + +| Guide | Description | +|---|---| +| [Contributing Guidelines](./development/contributing/guidelines.md) | Code style, branch naming, commit format, PR process, virtual table creation | + +--- + +## 📖 Reference Architecture + +Deep technical documentation for each core subsystem — generated directly from source code analysis. + +| Module | Description | +|---|---| +| [Core Init And Runtime](./reference/architecture/core-init-and-runtime/core-init-and-runtime.md) | Process bootstrap, flags, watcher/worker model, watchdog | +| [SQL Engine And Virtual Tables](./reference/architecture/sql-engine-and-virtual-tables/sql-engine-and-virtual-tables.md) | SQLite engine, authorizer, virtual table binding, constraint pushdown | +| [Configuration And Packs](./reference/architecture/configuration-and-packs/configuration-and-packs.md) | Config loading, packs, schedulers, decorators, change detection | +| [Eventing Framework And Subscriptions](./reference/architecture/eventing-framework-and-subscriptions/eventing-framework-and-subscriptions.md) | Publisher/subscriber system, inotify, BPF, FSEvents, ETW | +| [Extensions And IPC](./reference/architecture/extensions-and-ipc/extensions-and-ipc.md) | Apache Thrift IPC, runtime plugin model, UUID routing | +| [Distributed Querying](./reference/architecture/distributed-querying/distributed-querying.md) | Remote SQL orchestration, TLS transport, fleet denylisting | +| [Remote HTTP Client](./reference/architecture/remote-http-client/remote-http-client.md) | Boost.Asio/Beast HTTPS client, TLS handshake, peer verification | +| [Logging And Query Observability](./reference/architecture/logging-and-query-observability/logging-and-query-observability.md) | Differential result tracking, JSON serialization, pluggable backends | +| [Database And Storage Plugins](./reference/architecture/database-and-storage-plugins/database-and-storage-plugins.md) | RocksDB persistent backend, ephemeral in-memory store, key-value interface | +| [Filesystem And Path Utilities](./reference/architecture/filesystem-and-path-utilities/filesystem-and-path-utilities.md) | Cross-platform file abstraction, glob resolution, permission enforcement | + +--- + +## 🗺 Architecture Diagrams + +Visual Mermaid diagrams for each subsystem are available in: + +```text +docs/diagrams/architecture/ +``` + +Diagrams cover all major subsystems including the SQL engine, eventing framework, configuration pipeline, distributed querying, OpenFrame auth layer, and more. Open any `.mmd` file in a Mermaid-compatible viewer. + +--- + +## 🔗 Quick Links + +| Resource | Link | +|---|---| +| [Project README](../README.md) | Main project overview, quick start, and features | +| [Contributing Guide](../CONTRIBUTING.md) | How to contribute code, docs, and virtual tables | +| [License](../LICENSE.md) | License information | + +--- + +## 💬 Community + +> We do **not** use GitHub Issues or GitHub Discussions. All support and collaboration happens on **OpenMSP Slack**. + +| Resource | Link | +|---|---| +| OpenMSP Community Slack | [Join here](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) | +| OpenMSP Website | [https://www.openmsp.ai/](https://www.openmsp.ai/) | +| OpenFrame Platform | [https://openframe.ai](https://openframe.ai) | +| Flamingo | [https://flamingo.run](https://flamingo.run) | + +--- + +*Documentation generated by [OpenFrame Doc Orchestrator](https://github.com/flamingo-stack/openframe-oss-tenant)* From 5a8090ed68c063275d86d01f1e4aea621e939193 Mon Sep 17 00:00:00 2001 From: Michael Assraf Date: Fri, 29 May 2026 00:17:59 -0400 Subject: [PATCH 8/8] Update README.md --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 670585d5508..a707452a43e 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,6 @@ FROM users u, chrome_extensions e WHERE e.uid = u.uid; ``` ---- - -## Watch: osquery in Action - -[![osquery Overview](https://img.youtube.com/vi/1UcWGiHbLVo/hqdefault.jpg)](https://www.youtube.com/watch?v=1UcWGiHbLVo) - ---- - ## Features - **SQL Querying** — Query live OS data with standard SQL via an embedded, hardened SQLite engine