pax_global_header00006660000000000000000000000064147075450020014516gustar00rootroot0000000000000052 comment=23fef4ca19ac49dd73f93362fcb51c05c3f4c580 fapolicyd-1.3.4/000077500000000000000000000000001470754500200134755ustar00rootroot00000000000000fapolicyd-1.3.4/.fmf/000077500000000000000000000000001470754500200143235ustar00rootroot00000000000000fapolicyd-1.3.4/.fmf/version000066400000000000000000000000021470754500200157230ustar00rootroot000000000000001 fapolicyd-1.3.4/.github/000077500000000000000000000000001470754500200150355ustar00rootroot00000000000000fapolicyd-1.3.4/.github/workflows/000077500000000000000000000000001470754500200170725ustar00rootroot00000000000000fapolicyd-1.3.4/.github/workflows/.rawhide-fedora-build000066400000000000000000000014051470754500200230510ustar00rootroot00000000000000name: rawhide-build on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest container: fedora:rawhide steps: - uses: actions/checkout@v2 - name: getting envinronment info run: uname -a - name: print fedora version run: cat /etc/fedora-release - name: installing dependecies run: dnf -y install dnf-plugins-core python3-dnf-plugins-core; dnf -y builddep ./fapolicyd.spec - name: generate config files run: ./autogen.sh - name: configure run: ./configure --with-rpm --with-audit --disable-shared --disable-dependency-tracking - name: build run: make - name: check run: make check - name: dist run: make dist fapolicyd-1.3.4/.github/workflows/almalinux8.yml000066400000000000000000000015651470754500200217060ustar00rootroot00000000000000name: almalinux8-build on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest container: almalinux:8 steps: - uses: actions/checkout@v2 - name: getting envinronment info run: uname -a - name: print version run: cat /etc/*release - name: installing dependencies 1 run: dnf -y install epel-release dnf-plugins-core python3-dnf-plugins-core util-linux --nogpgcheck - name: installing dependencies 2 run: dnf -y builddep ./fapolicyd.spec --enablerepo='powertools' --nogpgcheck - name: generate config files run: ./autogen.sh - name: configure run: ./configure --with-rpm --with-audit --disable-shared --disable-dependency-tracking - name: build run: make - name: check run: make check - name: dist run: make dist fapolicyd-1.3.4/.github/workflows/almalinux9.yml000066400000000000000000000015561470754500200217070ustar00rootroot00000000000000name: almalinux9-build on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest container: almalinux:9 steps: - uses: actions/checkout@v2 - name: getting envinronment info run: uname -a - name: print version run: cat /etc/*release - name: installing dependencies 1 run: dnf -y install epel-release dnf-plugins-core python3-dnf-plugins-core util-linux --nogpgcheck - name: installing dependencies 2 run: dnf -y builddep ./fapolicyd.spec --enablerepo='crb' --nogpgcheck - name: generate config files run: ./autogen.sh - name: configure run: ./configure --with-rpm --with-audit --disable-shared --disable-dependency-tracking - name: build run: make - name: check run: make check - name: dist run: make dist fapolicyd-1.3.4/.github/workflows/rockylinux8.yml000066400000000000000000000016021470754500200221130ustar00rootroot00000000000000name: rockylinux8-build on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest container: rockylinux/rockylinux:8 steps: - uses: actions/checkout@v2 - name: getting envinronment info run: uname -a - name: print version run: cat /etc/*release - name: installing dependencies 1 run: dnf -y install epel-release dnf-plugins-core python3-dnf-plugins-core util-linux --nogpgcheck - name: installing dependencies 2 run: dnf -y builddep ./fapolicyd.spec --enablerepo='powertools' --nogpgcheck - name: generate config files run: ./autogen.sh - name: configure run: ./configure --with-rpm --with-audit --disable-shared --disable-dependency-tracking - name: build run: make - name: check run: make check - name: dist run: make dist fapolicyd-1.3.4/.github/workflows/rockylinux9.yml000066400000000000000000000015731470754500200221230ustar00rootroot00000000000000name: rockylinux9-build on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest container: rockylinux/rockylinux:9 steps: - uses: actions/checkout@v2 - name: getting envinronment info run: uname -a - name: print version run: cat /etc/*release - name: installing dependencies 1 run: dnf -y install epel-release dnf-plugins-core python3-dnf-plugins-core util-linux --nogpgcheck - name: installing dependencies 2 run: dnf -y builddep ./fapolicyd.spec --enablerepo='crb' --nogpgcheck - name: generate config files run: ./autogen.sh - name: configure run: ./configure --with-rpm --with-audit --disable-shared --disable-dependency-tracking - name: build run: make - name: check run: make check - name: dist run: make dist fapolicyd-1.3.4/.github/workflows/stable-fedora-build.yml000066400000000000000000000014221470754500200234210ustar00rootroot00000000000000name: stable-fedora-build on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest container: fedora:latest steps: - uses: actions/checkout@v2 - name: getting envinronment info run: uname -a - name: print fedora version run: cat /etc/fedora-release - name: installing dependencies run: dnf -y install dnf-plugins-core python3-dnf-plugins-core util-linux; dnf -y builddep ./fapolicyd.spec - name: generate config files run: ./autogen.sh - name: configure run: ./configure --with-rpm --with-audit --disable-shared --disable-dependency-tracking - name: build run: make - name: check run: make check - name: dist run: make dist fapolicyd-1.3.4/.github/workflows/ubuntu22.yml000066400000000000000000000020511470754500200213010ustar00rootroot00000000000000name: ubuntu22-build on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest container: ubuntu:jammy steps: - uses: actions/checkout@v2 - name: getting envinronment info run: uname -a - name: print version run: cat /etc/*release - name: Ensure up to date package list run: apt update - name: installing dependencies 1 run: apt install -y autoconf automake libtool gcc libdpkg-dev libmd-dev uthash-dev liblmdb-dev libudev-dev - name: installing dependencies 2 run: apt install -y libgcrypt-dev libssl-dev libmagic-dev libcap-ng-dev libseccomp-dev make debmake debhelper - name: generate config files run: ./autogen.sh - name: configure run: ./configure --without-rpm --with-audit --disable-shared --disable-dependency-tracking --with-deb - name: build run: make - name: check run: make check - name: dist run: make dist - name: build deb run: cd deb && ./build_deb.sh fapolicyd-1.3.4/.packit.yaml000066400000000000000000000036171470754500200157210ustar00rootroot00000000000000specfile_path: fapolicyd.spec upstream_package_name: fapolicyd downstream_package_name: fapolicyd upstream_tag_template: v{version} jobs: - job: copr_build trigger: pull_request identifier: build-normal actions: post-upstream-clone: - bash -c "sed -i 's/#ELN %//' fapolicyd.spec" - bash -c "curl -L -o $(grep Source1 fapolicyd.spec | cut -d ' ' -f 2 | sed -r \"s/%\{name\}/$(grep 'Name:' fapolicyd.spec | cut -f 2 -d ' ')/g;s/%\{semodule_version\}/$(grep '%define semodule_version' fapolicyd.spec | cut -f 3 -d ' ')/g\" | sed -r 's|(.*)#/(.*)|\2 \1|')" - bash -c "curl -L -o $(grep Source2 fapolicyd.spec | cut -d ' ' -f 2 | sed -r 's|(.*)#/(.*)|\2 \1|')" - bash -c "cp uthash*.tar.gz /builddir/build/SOURCES/" - bash -c "pwd" - bash -c "ls -la" targets: - fedora-all - epel-9 - epel-8 - job: copr_build trigger: pull_request identifier: build-asan actions: post-upstream-clone: - bash -c "sed -i 's/#ASAN %//' fapolicyd.spec" - bash -c "sed -i 's/#ELN %//' fapolicyd.spec" - bash -c "curl -L -o $(grep Source1 fapolicyd.spec | cut -d ' ' -f 2 | sed -r \"s/%\{name\}/$(grep 'Name:' fapolicyd.spec | cut -f 2 -d ' ')/g;s/%\{semodule_version\}/$(grep '%define semodule_version' fapolicyd.spec | cut -f 3 -d ' ')/g\" | sed -r 's|(.*)#/(.*)|\2 \1|')" - bash -c "curl -L -o $(grep Source2 fapolicyd.spec | cut -d ' ' -f 2 | sed -r 's|(.*)#/(.*)|\2 \1|')" - bash -c "cp uthash*.tar.gz /builddir/build/SOURCES/" - bash -c "pwd" - bash -c "ls -la" targets: - fedora-all - epel-9 - epel-8 - job: tests trigger: pull_request identifier: build-normal targets: - fedora-all - epel-9 - epel-8 - job: tests trigger: pull_request identifier: build-asan targets: - fedora-all - epel-9 - epel-8 fapolicyd-1.3.4/AUTHORS000066400000000000000000000001131470754500200145400ustar00rootroot00000000000000This program was created and maintained by Steve Grubb fapolicyd-1.3.4/BUILD.md000066400000000000000000000106351470754500200146630ustar00rootroot00000000000000BUILDING ======== Building fapolicyd is reasonably straightforward on Fedora and RedHat-based Linux distributions. This document will guide in installing the build-time dependencies, configure and compile the code, and finally build the RPMs for distribution on compatible non-production systems. BUILD-TIME DEPENDENCIES (fedora and RHEL8) ------------------------------------------ * gcc * autoconf * automake * libtool * make * libudev-devel * kernel-headers * systemd-devel * libgcrypt-devel ( <= fapolicyd-1.1.3) * openssl ( >= fapolicyd-1.1.4) * rpm-devel (optional) * file * file-devel * libcap-ng-devel * libseccomp-devel * lmdb-devel * uthash-devel * python3-devel * kernel >= 4.20 (Must support FANOTIFY_OPEN_EXEC_PERM. See [1] below.) RHEL8: ENABLE CODEREADY AND INSTALL EPEL REPOS ---------------------------------------------- ```bash sudo subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm ``` INSTALL BUILD DEPENDENCIES (fedora and RHEL8) --------------------------------------------- ```bash sudo dnf install -y gcc autoconf automake libtool make libudev-devel kernel-headers systemd-devel libgcrypt-devel rpm-devel file file-devel libcap-ng-devel libseccomp-devel lmdb-devel uthash-devel python3-devel ``` CONFIGURING AND COMPILING ------------------------- To build from the repo after cloning and installing dependencies: ```bash cd fapolicyd ./autogen.sh ./configure --with-audit --disable-shared make make dist ``` This will create a tarball. You can use the new tarball with the spec file and create your own rpm. If you want to experiment without installing, just run make with no arguments. It should run fine from where it was built as long as you put the configuration files in /etc/fapolicyd (fapolicyd.rules, fapolicyd.trust, fapolicyd.conf). Note that the shipped policy expects that auditing is enabled. This is done by passing --with-audit to ./configure. The use of rpm as a trust source is now optional. You can run ./configure passing --without-rpm and it will not link against librpm. In this mode, it purely uses the file database in fapolicyd.trust. If rpm is used, then the file trust database can be used in addition to rpmdb. BUILDING THE RPMS ----------------- :exclamation: These unofficial RPMs should only be used for testing and experimentation purposes and not for production systems. :exclamation: To build the RPMs, first install the RPM development tools: ```bash sudo dnf install -y rpmdevtools ``` Then in the root of the repository where fapolicyd was built, use `rpmbuild` to build the RPMs: ```bash rpmbuild -ta fapolicyd-*.tar.gz ``` By default, the RPMs will appear in `~/rpmbuild/RPMS/$(arch)`. NOT BUILDING RPMS ----------------- If you chose to do it yourself, you need to do a couple prep steps: ``` 1) sed -i "s/%python2_path%/`readlink -f /bin/python2 | sed 's/\//\\\\\//g'`/g" rules.d/*.rules 2) sed -i "s/%python2_path%/`readlink -f /bin/python3 | sed 's/\//\\\\\//g'`/g" rules.d/*.rules 3) interpret=`readelf -e /usr/bin/bash \ | grep Requesting \ | sed 's/.$//' \ | rev | cut -d" " -f1 \ | rev` 4) sed -i "s|%ld_so_path%|`realpath $interpret`|g" rules.d/*.rules ``` This corrects the placeholders to match your current system. Then follow the rules listed above for compiling except run make install instead of make dist. CREATING RUNTIME ENVIRONMENT ---------------------------- If you are not using rpm's spec file and are doing it yourself, there are a few more steps. You need to create the necessary directories in the right spot: ``` mkdir -p /etc/fapolicyd/{rules.d,trust.d} mkdir -p /var/lib/fapolicyd/ mkdir --mode=0755 -p /usr/share/fapolicyd/ mkdir -p /usr/lib/tmpfiles.d/ mkdir --mode=0755 -p /run/fapolicyd/ cd init cp fapolicyd.bash_completion /etc/bash_completion.d/ cp fapolicyd.conf /etc/fapolicyd/ cp fapolicyd-magic.mgc /usr/share/fapolicyd/ cp fapolicyd.service /usr/lib/systemd/system/ cp fapolicyd-tmpfiles.conf /usr/lib/tmpfiles.d/fapolicyd.conf cp fapolicyd.trust /etc/fapolicyd/trust.d/ useradd -r -M -d /var/lib/fapolicyd -s /sbin/nologin -c "Application Whitelisting Daemon" fapolicyd chown root:fapolicyd /etc/fapolicyd/ chown root:fapolicyd /etc/fapolicyd/rules.d/ chown root:fapolicyd /etc/fapolicyd/trust.d/ chown root:fapolicyd /var/lib/fapolicyd/ chown root:fapolicyd /usr/share/fapolicyd/ ``` fapolicyd-1.3.4/CI/000077500000000000000000000000001470754500200137705ustar00rootroot00000000000000fapolicyd-1.3.4/CI/ci-tests.fmf000066400000000000000000000002261470754500200162150ustar00rootroot00000000000000discover: how: fmf url: https://github.com/RedHat-SP-Security/tests.git filter: component:fapolicyd & tag:CI-Tier-1 execute: how: tmt fapolicyd-1.3.4/COPYING000066400000000000000000001045131470754500200145340ustar00rootroot00000000000000 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 . fapolicyd-1.3.4/ChangeLog000066400000000000000000000213621470754500200152530ustar00rootroot000000000000001.3.4 - Fix race on fanotify fd on termination - Improve efficiency loading the rpm database into trust db 1.3.3 - Improve dpkg support (Stephen Tridgell) - Fix issue when no initial mount points (wjhunter3) - Add RuntimeDirectory to the systemd service file - Update code to limit the number group id's logged - Improve mount point detection code - Double the amount of groups a user could have (32) - Small performance improvement by not double rewinding descriptor - Improve logging: make stderr output more colourful; add timestamps (Kangie) - Identify ruleset being loaded by a sha hash of the rule file (John Wass) - Add 22-buildroot.rules to use if the machine builds software - Add --with-asan for configure 1.3.2 - Remove LimitNOFILE and instead setrlimit more carefully - Sync q_size to the documentation - Fix multiple memory leaks 1.3.1 - Fix not complete patch for filter file renaming 1.3 - Be consistent in updating and removing file system marks - Add escaping to /proc/mount entries - Revise escaping of trust files - Add LimitNOFILE to the service file - Add dpkg support (Stephen Tridgell) - Add support for runtime reloading of rules 1.2 - On shutdown when running reports, if trust db empty warn (Nobuhiro Iwamatsu) - Extend state machine to skip opens after exec until dyn linker found - Control filtering of unwanted files in rpm backend with config file - Add support for logging rule number of decision in the audit event 1.1.7 - Re-add dropped FAN_MARK_MOUNT for monitoring events (Steven Brzozowski) - Make some updates to allow running without an rpm back successful 1.1.6 - Correct the optional inclusion of code based on HAVE_DECL_FAN_MARK_FILESYSTEM 1.1.5 - If in debug mode, do not write audit events to audit system - Update filesystems we dont care about - Add --check-path to fapolicyd-cli to locate missed files - Detect trusted static apps running programs by ld.so - Add support for using FAN_MARK_FILESYSTEM to see bind mounted accesses 1.1.4 - Fix descriptor leak on enqueue failure (Steven Brzozowski) - Switch SHA256 hashing to openssl - Add --check-status to fapolicyd-cli - If fapolicyd is already running, exit - Do trust db size check on all fapolicyd updates - Add bash completions 1.1.3 - Replace snprintf integer to char conversion with uitoa function - Update the locking between the main and decision threads - Speedup sha256 hashing by mmap'ing the object - Add OOMScoreAdjust to fapolicyd.service 1.1.2 - Release the update lock if starting trust db read operations errors - CVE-2022-1117 fapolicyd incorrectly detects the run time linker - Add the btrfs to the watch_fs config option - Fix a problem tracking trusted static apps that launch other apps 1.1.1 - Reorder patterns and loopholes in rule.d - Add support for subject ppid rule matching - Add support for reloading the trust database from SIGHUP 1.1 - Add support for a rules.d directory - Add --check-config, --check-watch_fs, and --check-trustdb to fapolicyd-cli - Add libgcrypt initialization - Break up all the rules so they can be installed in rules.d - Add text/x-nftables magic - Add interpreter for s390x, ppc64le 1.0.4 - Tighten up ELF detection - Add support for multiple trust files in a trust.d directory - Add troubleshooting info for when the trust db is full - In permissive mode, allow audit events when rules say to log it - Add new rpm_sha256_only config option to the daemon - Escape whitespaces in file names put into the file trust database 1.0.3 - Add startup and shutdown syslog message - fapolicyd-cli open trustdb without locking to prevent daemon hang - If db migration fails due to unlinking problem, fail startup - Do not exit on fanotify_event read failure - Add application/javascript to Language macro 1.0.2 - Add Group ID support for rules - Add test cases for avl library - Update support for multiple copies of a trusted executable - Add support for dynamic trust updating 1.0.1 - If trust db is empty when fapolicyd-cli dumps it, say its empty - Make fapolicyd-cli buffer bigger for rule listing - Fix ignored db errors from check_trust_database - Adjust ELF x-object detection - Do device mime-type detection in-house instead of libmagic - Allow arbitrarily large group statements - Fix logging of object trust - Correct denial accounting - Add new form of LD_PRELOAD pattern detection - Fix mount reading routine to get it all - Update languages kept from /usr/share 1.0 - Add file size, IMA, and sha256 based integrity checking - Add ability to send decision results to syslog - Add ability to define the format of the syslog event - Add support for sets in rules - Add support for dumping the trustdb by fapolicyd-cli - Print a warning if rpm backend doesn't have a sha256 hash - In rpm backend, add back javascript from /usr/share 0.9.4 - Fix pattern detection in light of EXEC_PERM events - Conserve memory by dropping unneeded lists after startup - Do full reset of subject credentials when execve finishes - Drop files in /usr/share, /usr/src, and /usr/include to reduce memory use - Add error checking of the trust database - Fixed threading issue during rpm update - Add option to delete the trust database to cli, --delete-db - Add option to cli to add/delete/update the file trust database 0.9.3 - In fapolicyd-cli, add a --list option to list rules - Change lmdb to use writable mmap for startup performance improvment - Change the database to support duplicate keys - Provide a magic override file and use it during file inspection - Update rules to match new magic overrides - Add --ftype command to fapolicyd-cli - Add database statistics to usage report 0.9.2 - Split codebase into daemon, library and cli - Add Admin defined trust database - Make use of librpm optional - Updated the man pages - Setting boost, queue, user, and group on the command line are deprecated 0.9.1 - Make watched filesystems configurable - Improve ELF file classification - Expose file type in debug output - Update rules for ansible and dracut - Skip config files in database check - Expand definition of doc files - Create new rule format exposing Subj and Obj trust - Redesign the rules for trust based rules 0.9 - Convert hashes to lowercase like sha256sum outputs - Use FAN_OPEN_EXEC_PERM for subject cache management - Add static pattern detection - Performance improvements - Switch from static mounts to hotplug configuration of mount points - Dont collect documentation in trust database - When path is longer than lmdb can store, use a sha512 hash (Attila Lakatos) - Cache subject trustworthiness information after lookup (Radovan Sroka) 0.8.10 - Fix segfault for rules whose subject is number oriented - When database problem is found on startup, rebuild database - Don't flush empty caches on database rebuild - Revise default settings for better performance 0.8.9 - Systemd usage updates - File permission adjustments based on selinux policy review - Fix unterminated reads of auid & sessionid values - Deprecate ld_preload pattern until new method exists 0.8.8 - Add FAN_OPEN_EXEC_PERM Support (Matthew Bobrowski) - Man page updates - Add dnf plugin to sync database when rpms install 0.8.7 - If the path has a top level symlinked dir, retry db lookup without /usr - Fix parsing of command line options (Matthew Bobrowski) - Add more validation of mount types (Matthew Bobrowski) - Elf parser updates (Matthew Bobrowski) 0.8.6 - Update object hash calculation to better determine uniqueness - Override rpm's signal handling - Use private database as trust store - Update the rules for python 3.6 and remove systemd exclusion - Rename exec_dir rule option unpackaged to untrusted - Remove unneeded rpm code - Add support for daemon config file - Allow database size to be configurable - Add permissive setting, q_size, and q_depth to usage report 0.8.5 - Update spec file and license info 0.8.4 - Mask signals from deadman's switch - Reinstate strong umask before writing report - Use pw_gid to set the group when changing gid - Allow the use of account names for auid & uid in rules - Support group option on command line 0.8.3 - Add audit support for the linux-4.15 kernel - Don't close report descriptor in report - Fix busy loop to use poll as originally intended - Relax timing on deadman's switch 0.8.2 - Add seccomp filter support - Fix leaked descriptor in exe_type processing - Add LRU cache for subject and objects - Create fapolicyd user on install - Update systemd service file to run as user fapolicyd - Adjust inter-thread queue default size - Write statistics on shutdown - Change attribute access to hash table - Deny access to stale pid's or fd's - Add new pattern subject detection - Add executable report on shutdown - Add --no-details to suppress file/exe names on shutdown report 0.8.1 - Documentation updates - Update rules - Output how many rules are loaded in debug mode - Add user commandline option 0.8 - Initial public release fapolicyd-1.3.4/INSTALL.tmp000066400000000000000000000001411470754500200153210ustar00rootroot00000000000000Installation Instructions ************************* See README.md for build, install, testing. fapolicyd-1.3.4/Makefile.am000066400000000000000000000003261470754500200155320ustar00rootroot00000000000000 SUBDIRS = src init doc rules.d EXTRA_DIST = ChangeLog AUTHORS NEWS README.md INSTALL fapolicyd.spec dnf/fapolicyd-dnf-plugin.py autogen.sh clean-generic: rm -rf autom4te*.cache rm -f *.rej *.orig *.lang *.list fapolicyd-1.3.4/NEWS000066400000000000000000000000001470754500200141620ustar00rootroot00000000000000fapolicyd-1.3.4/README.md000066400000000000000000000645331470754500200147670ustar00rootroot00000000000000File Access Policy Daemon ========================= [![Build Status](https://travis-ci.com/linux-application-whitelisting/fapolicyd.svg?branch=master)](https://travis-ci.com/linux-application-whitelisting/fapolicyd) This is a simple application whitelisting daemon for Linux. RUNTIME DEPENDENCIES -------------------- * kernel >= 4.20 (Must support FANOTIFY_OPEN_EXEC_PERM. See [1] below.) BUILDING -------- See [BUILD.md](./BUILD.md) for build-time dependencies and instructions for building. POLICIES -------- The current design for policy is that it is split up into units of rules that are designed to work together. They are copied into /etc/fapolicyd/rules.d/ When the service starts, the systemd service file runs fagenrules which assembles the units of rules into a comprehensive policy. The policy is evaluated from top to bottom with the first match winning. You can see the assembled policy by running ``` fapolicyd-cli --list ``` Originally, there were 2 policies shipped, known-libs and restrictive. The restrictive policy was designed with these goals in mind: 1. No bypass of security by executing programs via ld.so. 2. Anything requesting execution must be trusted. 3. Elf binaries, python, and shell scripts are enabled for trusted applications/libraries. 4. Other languages are not allowed or must be enabled. It can be recreated by copying the following policy units into rules.d. The optional ones are not included unless they are needed: 20-dracut.rules 21-updaters.rules 30-patterns.rules 40-bad-elf.rules 41-shared-obj.rules 43-known-elf.rules 71-known-python.rules 72-shell.rules optional: 73-known-perl.rules optional: 74-known-ocaml.rules optional: 75-known-php.rules optional: 76-known-ruby.rules optional: 77-known-lua.rules 90-deny-execute.rules 95-allow-open.rules The known-libs policy (default) was designed with these goals in mind: 1. No bypass of security by executing programs via ld.so. 2. Anything requesting execution must be trusted. 3. Any library or interpreted application or module must be trusted. 4. Everything else is not allowed. It can be created by copying the following policy units into rules.d: 10-languages.rules 20-dracut.rules 21-updaters.rules 30-patterns.rules 40-bad-elf.rules 41-shared-obj.rules 42-trusted-elf.rules 70-trusted-lang.rules 72-shell.rules 90-deny-execute.rules 95-allow-open.rules EXPERIMENTING ------------- You can test by starting the daemon from the command line. Before starting the daemon, cp /usr/bin/ls /usr/bin/my-ls just to setup for testing. When testing new policy, its highly recommended to use the permissive mode to make sure nothing bad happens. It really is not too hard to deadlock your system. Continuing on with the tutorial, as root start the daemon as follows: ``` /usr/sbin/fapolicyd --permissive --debug ``` Then in another window do the following: 1. /usr/lib64/ld-2.29.so /usr/bin/ls 2. my-ls 3. run a python file in your home directory. 4. run a program from /tmp In permissive + debug mode you will see dec=deny which means "decision is to deny". But the program will actually be allowed to run. You can run the daemon from the command line with --debug-deny command line option. This culls the event notification to only print the denials. If this is running cleanly, then you can remove the --permissive option and get true denials. Now retest above steps and see the difference. DEBUG MODE ---------- In debug mode, you will see events such as this: ``` rule:9 dec=deny_audit perm=execute auid=1001 pid=14137 exe=/usr/bin/bash : file=/home/joe/my-ls ftype=application/x-executable ``` What this is saying is rule 9 made the ultimate Decision that was followed. The Decision is to deny access and create an audit event. The subject is the user that logged in as user id 1001. The subject's process id that is trying to perform an action is 14137. The current executable that the subject is using is bash. Bash wanted permission to execute /home/joe/my-ls which is the object. And the object is an ELF executable. Sometimes you want to list out the rules to see what rule 9 might be. You can easily do that by running: ``` fapolicyd-cli --list ``` Also, in fapolicyd.conf, there is a configuration option, syslog_format, which can be modified to output information the way you want to see it. So, if you think auid in uninteresting you can delete it. If you want to see the device information for the file being accessed, you can add it. You can also enable this information to go to syslog by changing the rules to not say audit, but instead have syslog or log appended to the allow or deny decision. WRITING RULES ------------- The authoritative source is the fapolicyd.rules man page. It is suggested that people use the known-libs set of rules. This set of rules is designed to allow anything that is trusted to execute. It's design is to stay out of your way as much as possible. All that you need to do is add unpackaged but trusted files to the trust database. See the "Managing Trust" section for more. But if you had to write rules, they follow a simple "decision permission subject : object" recipe. The idea is to write a couple things that you want to allow, and then deny everything else. For example, this is how shared libraries are handled: ``` allow perm=open all : ftype=application/x-sharedlib trust=1 deny_audit perm=open all : ftype=application/x-sharedlib ``` What this says is let any program open shared libraries if the library being opened is trusted. Otherwise, deny with an audit event any open of untrusted libraries. First and foremost, fapolicyd rules are based on trust relationships. It is not meant to be an access control system of Mandatory Access Control Policy. But you can do that. It is not recommended to do this except when necessary. Every rule that is added has to potentially be evaluated - which delays the decision. If you needed to allow admins access to ping, but deny it to everyone else, you could do that with the following rules: ``` allow perm=any gid=wheel : trust=1 path=/usr/bin/ping deny_audit perm=execute all : trust=1 path=/usr/bin/ping ``` You can similarly do this for trusted users that have to execute things in the home dir. You can create a trusted_user group, add them the group, and then write a rule allowing them to execute from their home dir. When you want to use user or group name (as a string). You have to guarantee that these names were correctly resolved. In case of systemd, you need to add a new after target 'After=nss-user-lookup.target'. To achieve that you can use `systemctl edit --full fapolicyd`, uncomment the respective line and save the change. ``` allow perm=any gid=trusted_user : ftype=%languages dir=/home deny_audit perm=any all : ftype=%languages dir=/home ``` One thing to point out, if you have lists of things that you would like to allow, use the macro set support as illustrated in this last example. This puts the list into a sorted AVL tree so that searching is cut to a minimum number of compares. One last note, the rule engine is a first match wins system. If you are adding rules to allow something but it gets denied by a rule higher up, then move your rule above the thing that denies it. But again, if you are writing rules to allow execution, you should reconsider if adding the programs to the trust database is better. RULE ORDERING ------------- Starting with 1.1, the rules should be placed in a rules.d directory under the fapolicyd configuration directory. There is a new utility, fagenrules, which will compile the rules into a single file, compiled.rules, and place the resulting file in the main config directory. If you want to migrate your existing rules, just move them as is to the rules.d directory. You cannot have both compiled.rules and fapolicyd.rules. The fagenrules will notice this and put a warning in syslog. If you use fapolicyd-cli --list, it will also notice and warn. If you do have both files, the old rules file will be used instead of the new one. This new rules.d directory is intended to make it easier to develop application specific rules that can be dropped off by automation / orchestration. This should make managing the configuration easier. The files in the rules.d directory are processed in a specific order. See the [rules.d README](rules.d/README-rules) file for more information. REPORT ------ On shutdown the daemon will write an object access report to /var/log/fapolicyd-access.log. The report is from oldest access to newest. Timestamps are not included because that would be a severe performance hit. The report gives some basic forensic information about what was being accessed. PERFORMANCE ----------- When a program opens a file or calls execve, that thread has to wait for fapolicyd to make a decision. To make a decision, fapolicyd has to lookup information about the process and the file being accessed. Each system call fapolicyd has to make slows down the system. To speed things up, fapolicyd caches everything it looks up so that subsequent access uses the cache rather than looking things up from scratch. But the cache is only so big. You are in control of it, though. You can make both subject and object caches bigger. When the program ends, it will output some performance statistic like this into /var/log/fapolicyd-access.log or the screen: ``` Permissive: false q_size: 640 Inter-thread max queue depth 7 Allowed accesses: 70397 Denied accesses: 4 Trust database max pages: 14848 Trust database pages in use: 10792 (72%) Subject cache size: 1549 Subject slots in use: 369 (23%) Subject hits: 70032 Subject misses: 455 Subject evictions: 86 (0%) Object cache size: 8191 Object slots in use: 6936 (84%) Object hits: 63465 Object misses: 17964 Object evictions: 11028 (17%) ``` In this report, you can see that the internal request queue maxed out at 7. This means that the daemon had at most 7 threads/processes waiting. This shows that it got a little backed up but was handling requests pretty quick. If this number were big, like more than 200, then increasing the q_size may be necessary. Another statistic worth looking at is the hits to evictions ratio. When a request has nowhere to put information, it has to evict something to make room. This is done by a LRU cache which naturally determines what's not getting used and makes it's memory available for re-use. In the above statistics, the subject hit ratio was 95%. The object cache was not quite as lucky. For it, we get a hit ration of 79%. This is still good, but could be better. This would suggest that for the workload on that system, the cache could be a little bigger. If the number used for the cache size is a prime number, you will get less cache churn due to collisions than if it had a common denominator. Some primes you might consider for cache size are: 1021, 1549, 2039, 4099, 6143, 8191, 10243, 12281, 16381, 20483, 24571, 28669, 32687, 40961, 49157, 57347, 65353, etc. This report can be scheduled to be written periodically by setting the configuration option `report_interval`. This option is set to `0` by default which disables the reporting interval. A positive value for this option specifies the number of seconds to wait between reports. Also, it should be mentioned that the more rules in the policy, the more rules it will have to iterate over to make a decision. As for the system performance impact, this is very workload dependent. For a typical desktop scenario, you won't notice it's running. A system that opens lots of random files for short periods of time will have more impact. Another configuration option that can affect performance is the integrity setting. If this is set to sha256, then every miss in the object cache will cause a hash to be calculated on the file being accessed. One trade-off would be to use size checking rather than sha256. This is not as secure, but it is an option if performance is problematic. MEMORY USAGE ------------ Fapolicyd uses lmdb as its trust database. The database has very fast performance because it uses the kernel virtual memory system to put the whole database in memory. If the database is sized wrongly, then fapolicyd will reserve too much memory. Don't worry too much about this. The kernel is very smart and doesn't actually allocate the memory unless its used. However, we'd like to get it right sized. Starting with the 0.9.3 version of fapolicyd, statistics about the database is output when the program shuts down. On my system, it looks like this: ``` Database max pages: 9728 Database pages in use: 7314 (75%) ``` This also correlates to the following setting in the fapolicyd.conf file: ``` db_max_size = 38 ``` This size is in megabytes. So, if you take that and multiply by 1024 * 1024, we get 39845888. A page of memory is defined as 4096. So, if we divide max_size by the page size, we get 9728 which matches the setting. Each entry in the lmdb database is 512 bytes. So, for each 4k page, we can have data on 8 trusted files. An ideal size for the database is for the statistics to come up around 75% in case you decide to install new software some day. The formula is ``` (db_max_size x percentage in use) / desired percentage = new db_max_size ``` So, working with example numbers, suppose max_size is 160 and it says it was 68% occupied. That is wasting a little space. Putting the numbers in the formula, we get (160 x 68) / 75 = 145. If you have an embedded system and are not using rpm. But instead use the file trust source and you have a list of files, then your calculation is very different. Suppose for the sake of discussion, you have 317 files that are trusted. We take that number and divide by 8. We'll round that up to 40. Take that number and multiply by 100 and divide by 75. We come up with 53.33. So, let's call it 54. This is how many pages is needed. Turning that into real memory, it's 216K. One megabyte is the smallest allocation, so you would set ``` db_max_size = 1 ``` Starting with the 0.9.4 release, the rpm backend filters most files in the /usr/share directory. It keeps anything with a with a python extension or a libexec directory. It also keeps /usr/src/kernel so that Akmod can still build drivers on a kernel update. TROUBLESHOOTING --------------- Whatever you do, DO NOT TRY TO ATTACH WITH PTRACE. Ptrace attachment sends a SIGSTOP which cannot be blocked. Since your whole system depends on fapolicyd approving access to glibc and various critical libraries, that will not happen until SIGCONT is sent. The system can deadlock if the continue signal is not sent. Using gdb will have the same results. With that in mind, let's talk about troubleshooting steps... If you are using deny_audit and you are not getting any audit events, the fix is to add 1 audit rule. It can be a rule about anything. Watches tend to be the highest performance, so maybe just add a watch for writes to etc shadow and restart the audit daemon so the rule gets loaded. ``` -w /etc/shadow -p w ``` When fapolicyd blocks something, it will generate an audit event if the Decision is deny_audit and it has been compiled with the auditing option. The audit system must have at least 1 audit rule loaded to create the full FANOTIFY event. It doesn't matter what rule. To see if you have any denials, you can run: ``` ausearch --start today -m fanotify --raw | aureport --file --summary File Summary Report =========================== total file =========================== 16 /sbin/ldconfig 1 /home/joe/./my-ls ``` You can also see which executables are involved like this: ``` ausearch --start today -m fanotify -f /sbin/ldconfig --raw | aureport -x --summary Executable Summary Report ================================= total file ================================= 16 /usr/bin/python3.7 ``` However, you probably want to know the rule that is blocking it. Unfortunately the audit system cannot tell you this unless you are using the 6.3 kernel or later. What you can do is change the decisions to deny_log. This will write the event to syslog as well as the audit log. In syslog, you will have the same output as the debug mode. The shipped rules expect that everything installed is in the trust database. If you have installed anything by unzipping it or untarring it, then you need to add the executables, libraries, and modules to the trust database. See the MANAGING THE FILE TRUST SOURCE section for instructions on how to do this. You can ask fapolicyd to include the trust information by adding trust to the end of the syslog_format configuration option. The things that you need to know to debug the policy is: * The rule triggering * The executable accessing the file * The object file type * The trust value Look at the rule that triggered and see if it makes sense that it triggered. If the rule is a catch all denial, then check if the file is in the trust db. To see the rule that is being triggered, either reproduce the problem with the daemon running in debug-deny mode or change the rules from deny_audit to deny_syslog. If you choose this method, the denials will go into syslog. To see them run: ``` journalctl -b -u fapolicyd.service ``` to list out any events since boot by the fapolicyd service. Starting with 1.1, fapolicyd-cli includes some diagnostic capabilities. | Option | What it does | |------------------------|--------------------------------------------| | --check-config | Opens fapolicyd.conf and parses it to see if there are any syntax errors in the file. | | --check-path | Check that every file in $PATH is in the trustdb. (New in 1.1.5) | | --check-status | Output internal metrics kept by the daemon. (New in 1.1.4) | | --check-trustdb | Check the trustdb against the files on disk to look for mismatches that will cause problems at run time. | | --check-watch_fs | Check the mounted file systems against the watch_fs daemon config entry to determine if any file systems need to be added to the configuration. | MANAGING TRUST -------------- Fapolicyd use lmdb as a backend database for its trusted software list. You can find this database in /var/lib/fapolicyd/. This list gets updated whenever packages are installed by dnf by a dnf plugin. If packages are installed by rpm instead of dnf, fapolicyd does not get a notification. In that case, you would also need to tell the daemon that it needs to update the trust database. This is done by running fapolicyd-cli and passing along the --update option. Also, if you add or delete files from the file trust list, fapolicyd.trust, then you will also have to run the fapolicyd-cli utility. Lmdb is a very fast database. Normally it works fine. But it does not tolerate malformed databases. When this happens, it can segfault fapolicyd. The fix is to delete the database and restart the daemon. It will then rebuild the database and work as it should. To do this, run the following command: ``` fapolicyd-cli --delete-db ``` MANAGING THE FILE TRUST SOURCE ------------------------------ Starting with 0.9.4, the fapolicyd command line utility can help you manage the file trust database. For example, suppose you have an application and its files over in /opt, you can add them all with the following command: ``` fapolicyd-cli --file add /opt/my-app/ ``` The command line utility will walk the directory tree and add all files to fapolicyd.trust. To do this, it opens each one and calculates the sha256 hash of the file and write that information to the new entry. Later if you decide to uninstall that app and you want to cleanup the list, then simply run: ``` fapolicyd-cli --file delete /opt/my-app/ ``` The command line utility will remove all files that match that directory from fapolicyd.trust. There is also a --file update extension that can update the size and hash information with what is currently on disk. Sometimes you want to see what is stored in the combined file and rpm trust database. In this case you can use the dump command ``` fapolicyd-cli --dump-db ``` which will dump which database the entry came from, path, size, and hash value. GUI --- If you need a GUI to create policy, manage trust, analyze policy, test policy, and deploy rules, you might want to checkout the [fapolicy-analyzer](https://github.com/ctc-oss/fapolicy-analyzer) project. RPM packages are in Fedora and EPEL. FAQ --- 1) Can this work with other distributions? Absolutely! There is a backend API that any trust source has to implement. This API is located in fapolicyd-backend.h. A new backend needs an init, load, and destroy function. So, someone who knows the debian package database, for example, could implement a new backend and send a pull request. We are looking for collaborators. An initial implementation for Debian distributions has been added. Run: ``` cd deb ./build_deb.sh ``` To build the `.deb` package that uses the `debdb` backend. You must add rules to `/etc/fapolicyd/rules.d/` and change configuration in `/etc/fapolicyd/fapolicyd.conf` to use `trust=debdb` after installation. Also, if the distribution is very small, you can use the file trust database file. Just add the places where libraries and applications are stored. 2) Can SE Linux or AppArmor do this instead? SE Linux is modeling how an application behaves. It is not concerned about where the application came from or whether it's known to the system. Basically, anything in /bin gets bin_t type by default which is not a very restrictive label. MAC systems serve a different purpose. Fapolicyd by design cares solely about if this is a known application/library. These are complimentary security subsystems. There is more information about application whitelisting use cases at the following NIST website: https://www.nist.gov/publications/guide-application-whitelisting 3) Does the daemon check file integrity? Version 0.9.5 and later supports 3 modes of integrity checking. The first is based on file size. In this mode, fapolicyd will take the size information from the trust db and compare it with the measured file size. This test incurs no overhead since the file size is collected when establishing uniqueness for caching purposes. It is intended to detect accidental overwrites as opposed to malicious activity where the attacker can make the file size match. The second mode is based on using IMA to calculate sha256 hashes and make them available through extended attributes. This incurs only the overhead of calling fgetxattr which is fast since there is no path name resolution. The file system must support i_version. For XFS, this is enabled by default. For other file systems, this means you need to add the iversion mount option. In either case, IMA must be setup appropriately. The third mode is where fapolicyd calculates a SHA256 hash of the file itself and compares that with what is stored in the trust db. 4) This is only looking at location. Can't this be defeated by simply moving the files to another location? Yes, this is checking to see if this is a known file. Known files have a known location. The shipped policy prevents execution from /tmp, /var/tmp, and $HOME based on the fact that no rpm package puts anything there. Also, moving a file means it's no longer "known" and will be blocked from executing. And if something were moved to overwrite it, then the hash is no longer the same and that will make it no longer trusted. 5) Does this protect against root modifications? If you are root, you can change the fapolicyd rules or simply turn off the daemon. So, this is not designed to prevent root from doing things. None of the integrity subsystems on Linux are designed to prevent root from doing things. There has to be a way of doing updates or disabling something for troubleshooting. For example, you can change IMA to ima_appraise=fix in /etc/default/grub. You can run setenforce 0 to turn off SELinux. You can also set selinux=0 or enforcing=0 for the boot prompt. The IPE integrity subsystem can be turned off via ``` echo -n 0 > "/sys/kernel/security/ipe/Ex Policy/active" ``` and so on. Since they can all be disabled, the fact that an admin can issue a service stop command is not a unique weakness. 6) How do you prevent race conditions on startup? Can something execute before the daemon takes control? One of the design goals is to take control before users can login. Users are the main problem being addressed. They can pip install apps to the home dir or do other things an admin may wish to prevent. Only root can install things that run before login. And again, root can change the rules or turn off the daemon. Another design goal is to prevent malicious apps from running. Suppose someone guesses your password and they login to your account. Perhaps they wish to ransomware your home dir. The app they try to run is not known to the system and will be stopped. Or suppose there is an exploitable service on your system. The attacker is lucky enough to pop a shell. Now they want to download privilege escalation tools or perhaps an LD_PRELOAD key logger. Since neither of these are in the trust database, they won't be allowed to run. This is really about stopping escalation or exploitation before the attacker can gain any advantage to install root kits. If we can do that, UEFI secure boot can make sure no other problems exist during boot. Wrt to the second question being asked, fapolicyd starts very early in the boot process and startup is very fast. It's running well before other login daemons. NOTES ----- * It's highly recommended to run in permissive mode while you are testing the daemon's policy. * Stracing the fapolicyd daemon WILL DEADLOCK THE SYSTEM. * About shell script restrictions...there's not much difference between running a script or someone typing things in by hand. The aim at this point is to check that any program it calls meets the policy. * Some interpreters do not immediately read all lines of input. Rather, they read content as needed until they get to end of file. This means that if they do stuff like networking or sleeping or anything that takes time, someone with the privileges to modify the file can add to it after the file's integrity has been checked. This is not unique to fapolicyd, it's simply how things work. Make sure that trusted file permissions are not excessive so that no unexpected file content modifications can occur. * If for some reason rpm database errors are detected, you may need to do the following: ``` 1. db_verify /var/lib/rpm/Packages if OK, then 2. rm -f /var/lib/rpm/__db* 3. rpm --rebuilddb ``` [1] - https://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs.git/commit/?id=66917a3130f218dcef9eeab4fd11a71cd00cd7c9 fapolicyd-1.3.4/TODO000066400000000000000000000003651470754500200141710ustar00rootroot00000000000000Userspace ========= * Allow rules to express paths using globbing (fnmatch) * add db_max_size = auto Improve reconfigure via SIGHUP to update configuration Consider adding rule testing to cli (uid, pgm, file) Support other packaging manifests fapolicyd-1.3.4/autogen.sh000077500000000000000000000002041470754500200154720ustar00rootroot00000000000000#! /bin/sh set -x -e # --no-recursive is available only in recent autoconf versions autoreconf -fv --install cp INSTALL.tmp INSTALL fapolicyd-1.3.4/configure.ac000066400000000000000000000142661470754500200157740ustar00rootroot00000000000000AC_REVISION($Revision: 1.3 $)dnl AC_INIT([fapolicyd],[1.3.4]) AC_PREREQ([2.60])dnl AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) AC_USE_SYSTEM_EXTENSIONS AC_CANONICAL_TARGET AM_INIT_AUTOMAKE(foreign subdir-objects) LT_INIT AC_SUBST(LIBTOOL_DEPS) echo . echo Checking for programs AC_PROG_CC AM_PROG_CC_C_O AC_PROG_INSTALL AC_PROG_AWK PKG_PROG_PKG_CONFIG AC_CHECK_PROG([FILE_COMM], "file", "yes", "no") if test "$FILE_COMM" = "no"; then AC_MSG_ERROR([Unable to find the file program need to build magic databases]) fi AC_CHECK_MEMBER([struct fanotify_response_info_audit_rule.rule_number], [perm=yes], [perm=no], [[#include ]]) if test $perm = "yes"; then AC_DEFINE(FAN_AUDIT_RULE_NUM, 1,[Define if kernel supports audit rule numbers]) fi echo . echo Checking compiler options AC_C_CONST AC_C_INLINE withval="" AC_ARG_WITH(debug, AS_HELP_STRING([--with-debug],[turn on debugging (default=no)]), AC_DEFINE(DEBUG,1,[Define if you want to enable runtime debug checking.]), []) AC_MSG_CHECKING(__attr_access support) AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[ #include int audit_fgets(char *buf, size_t blen, int fd) __attr_access ((__write_only__, 1, 2)); int main(void) { return 0; }]])], [ACCESS="yes"], [ACCESS="no"] ) AC_MSG_RESULT($ACCESS) AC_MSG_CHECKING(__attr_dealloc_free support) AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[ #include const char *strdup(const char *buf) __attr_dealloc_free; int main(void) { return 0; }]])], [DEALLOC="yes"], [DEALLOC="no"] ) AC_MSG_RESULT($DEALLOC) withval="" AC_ARG_WITH(asan, AS_HELP_STRING([--with-asan],[build with asan sanitizer (default=no)]), use_asan=yes,use_asan=$withval) if test x$use_asan = xyes ; then AC_LANG_PUSH([C]) CCFLAGS="-fno-omit-frame-pointer" ASAN_CFLAGS="" for CFLAG in $CCFLAGS; do echo -n "checking for $CFLAG... " TMPFLAGS="$CFLAGS" CFLAGS="$CFLAGS $CFLAG" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[ASAN_CFLAGS="$ASAN_CFLAGS $CFLAG" AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no)]) CFLAGS="$TMPFLAGS" done LLDFLAGS="-faddress-sanitizer -fsanitize=address" ASAN_LDFLAGS="" for LDFLAG in $LLDFLAGS; do echo -n "checking for $LDFLAG... " TMPLDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS $LDFLAG" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[ASAN_LDFLAGS="$ASAN_LDFLAGS $LDFLAG" AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no)]) LDFLAGS="$TMPLDFLAGS" done # how many flags should pass? just one "-fno-omit-frame-pointer" if [[ -z "$ASAN_CFLAGS" ]] || [[ "`echo "$ASAN_CFLAGS" | wc -w`" -ne 1 ]]; then AC_MSG_ERROR([ Compiler does not support asan sanitizer cflags ]) fi # how many flags should pass? just one of "-faddress-sanitizer -fsanitize=address" if [[ -z "$ASAN_LDFLAGS" ]] || [[ "`echo "$ASAN_LDFLAGS" | wc -w`" -ne 1 ]]; then AC_MSG_ERROR([ Compiler does not support asan sanitizer ldflags]) fi CFLAGS="$CFLAGS $ASAN_CFLAGS -O0" LDFLAGS="$LDFLAGS $ASAN_LDFLAGS" AC_DEFINE([USE_ASAN], [1], [Address Sanitizer is enabled]) fi AC_ARG_WITH(audit, AS_HELP_STRING([--with-audit],[turn on decision auditing (default=no)]), AC_DEFINE(USE_AUDIT,1,[Define if you want to enable decision auditing.]), []) AC_CHECK_DECLS([FAN_AUDIT], [], [], [[#include ]]) AC_CHECK_DECLS([FAN_OPEN_EXEC_PERM], [perm=yes], [perm=no], [[#include ]]) if test $perm = "no"; then AC_MSG_ERROR([FAN_OPEN_EXEC_PERM is not defined in linux/fanotify.h. It is required for the kernel to support it]) fi AC_CHECK_DECLS([FAN_MARK_FILESYSTEM], [], [], [[#include ]]) withval="" AC_ARG_WITH(rpm, AS_HELP_STRING([--with-rpm],[Use the rpm database as a trust source]), use_rpm=$withval,use_rpm=yes) if test x$use_rpm = xyes ; then AC_CHECK_LIB(rpm, rpmtsInitIterator, , [AC_MSG_ERROR([librpm not found])], -lrpm) AC_CHECK_LIB(rpmio, rpmFreeCrypto, , [AC_MSG_ERROR([librpmio not found])], -lrpmio) AC_DEFINE(USE_RPM,1,[Define if you want to use the rpm database as trust source.]) fi AM_CONDITIONAL(WITH_RPM, test x$use_rpm = xyes) withval="" AC_ARG_WITH(deb, AS_HELP_STRING([--with-deb],[Use the deb database as a trust source]), use_deb=$withval,use_deb=no) if test x$use_deb = xyes ; then AC_CHECK_LIB(dpkg, pkg_array_init_from_hash, , [AC_MSG_ERROR([libdpkg not found])], -ldpkg) AC_DEFINE(USE_DEB,1,[Define if you want to use the deb database as trust source.]) AC_CHECK_LIB(md, MD5Final, , [AC_MSG_ERROR([libmd is missing])], -lmd) fi AM_CONDITIONAL(WITH_DEB, test x$use_deb = xyes) AM_CONDITIONAL(NEED_MD5, test x$use_deb = xyes) dnl FIXME some day pass this on the command line def_systemdsystemunitdir=${prefix}/lib/systemd/system AC_SUBST([systemdsystemunitdir], [$def_systemdsystemunitdir]) echo . echo Checking for required header files AC_CHECK_HEADER(sys/fanotify.h, , [AC_MSG_ERROR( ["Couldn't find sys/fanotify.h...your kernel might not be new enough"] )]) AC_CHECK_FUNCS(fexecve, [], []) AC_CHECK_FUNCS([gettid]) AC_CHECK_HEADER(uthash.h, , [AC_MSG_ERROR( ["Couldn't find uthash.h...uthash-devel is missing"] )]) echo . echo Checking for required libraries AC_CHECK_LIB(udev, udev_device_get_devnode, , [AC_MSG_ERROR([libudev not found])], -ludev) AC_CHECK_LIB(crypto, SHA256, , [AC_MSG_ERROR([openssl libcrypto not found])], -lcrypto) AC_CHECK_LIB(magic, magic_descriptor, , [AC_MSG_ERROR([libmagic not found])], -lmagic) AC_CHECK_LIB(cap-ng, capng_change_id, , [AC_MSG_ERROR([libcap-ng not found])], -lcap-ng) AC_CHECK_LIB(seccomp, seccomp_rule_add, , [AC_MSG_ERROR([libseccomp not found])], -lseccomp) AC_CHECK_LIB(lmdb, mdb_env_create, , [AC_MSG_ERROR([liblmdb not found])], -llmdb) LD_SO_PATH if test x$use_rpm = xyes ; then RPMDB_PATH fi AC_CONFIG_FILES([Makefile src/Makefile src/tests/Makefile init/Makefile doc/Makefile rules.d/Makefile]) AC_OUTPUT echo . echo " fapolicyd Version: $VERSION Target: $target Installation prefix: $prefix Compiler: $CC Compiler flags: `echo $CFLAGS | fmt -w 50 | sed 's,^, ,'` Linker flags: `echo $LDFLAGS | fmt -w 50 | sed 's,^, ,'` __attr_access support: $ACCESS __attr_dealloc_free support: $DEALLOC " fapolicyd-1.3.4/deb/000077500000000000000000000000001470754500200142275ustar00rootroot00000000000000fapolicyd-1.3.4/deb/README.Debian000066400000000000000000000005411470754500200162700ustar00rootroot00000000000000fapolicyd for Debian This is a simple application whitelisting daemon for Linux. RUNTIME DEPENDENCIES -------------------- * kernel >= 4.20 (Must support FANOTIFY_OPEN_EXEC_PERM. See [1] below.) After configuring fapolicyd with /etc/fapolicyd/fapolicyd.conf and adding the desired set of rules to /etc/fapolicyd/rules.d/ start the fapolicyd service. fapolicyd-1.3.4/deb/build_deb.sh000077500000000000000000000005511470754500200165000ustar00rootroot00000000000000#! /bin/bash cd .. make dist cd deb cp ../fapolicyd-*.tar.gz . tar zxvf fapolicyd-*.tar.gz cd fapolicyd-*/ # Ugly work around for INSTALL.tmp # Need to figure out proper fix. mv INSTALL INSTALL.tmp cd .. tar zcvf fapolicyd-*.tar.gz fapolicyd-*/ cd fapolicyd-*/ debmake cp ../rules debian/ cp ../postinst debian/ cp ../README.Debian debian/ debuild cd .. fapolicyd-1.3.4/deb/postinst000077500000000000000000000005131470754500200160370ustar00rootroot00000000000000#! /bin/sh adduser --system --group fapolicyd mkdir -p /etc/fapolicyd/rules.d mkdir -p /etc/fapolicyd/trust.d mkdir -p /var/lib/fapolicyd mkdir -p /usr/share/fapolicyd/ mkdir -p /run/fapolicyd/ chown -R fapolicyd:fapolicyd /etc/fapolicyd/ chown fapolicyd:fapolicyd /var/lib/fapolicyd/ chown fapolicyd:fapolicyd /run/fapolicyd/ fapolicyd-1.3.4/deb/rules000077500000000000000000000003571470754500200153140ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ --with autoreconf override_dh_auto_configure: dh_auto_configure -- \ --with-audit \ --disable-shared \ --without-rpm \ --with-deb \ --prefix=/usr override_dh_autoreconf: dh_autoreconf -- ./autogen.sh fapolicyd-1.3.4/dnf/000077500000000000000000000000001470754500200142445ustar00rootroot00000000000000fapolicyd-1.3.4/dnf/fapolicyd-dnf-plugin.py000066400000000000000000000016731470754500200206400ustar00rootroot00000000000000#!/usr/bin/python3 import dnf import os import stat import sys class Fapolicyd(dnf.Plugin): name = "fapolicyd" pipe = "/run/fapolicyd/fapolicyd.fifo" file = None def __init__(self, base, cli): pass def transaction(self): if not os.path.exists(self.pipe): sys.stderr.write("Pipe does not exist (" + self.pipe + ")\n") sys.stderr.write("Perhaps fapolicy-plugin does not have enough permissions\n") sys.stderr.write("or fapolicyd is not running...\n") return if not stat.S_ISFIFO(os.stat(self.pipe).st_mode): sys.stderr.write(self.pipe + ": is not a pipe!\n") return try: self.file = open(self.pipe, "w") except PermissionError: sys.stderr.write("fapolicy-plugin does not have write permission: " + self.pipe + "\n") return self.file.write("1\n") self.file.close() fapolicyd-1.3.4/doc/000077500000000000000000000000001470754500200142425ustar00rootroot00000000000000fapolicyd-1.3.4/doc/Makefile.am000066400000000000000000000020331470754500200162740ustar00rootroot00000000000000# Makefile.am -- # Copyright 2016,2018 Red Hat Inc., Durham, North Carolina. # All Rights Reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Authors: # Steve Grubb # EXTRA_DIST = $(man_MANS) man_MANS = \ fapolicyd.8 \ fagenrules.8 \ fapolicyd-cli.8 \ fapolicyd.rules.5 \ fapolicyd.trust.5 \ fapolicyd.conf.5 \ rpm-filter.conf.5 \ fapolicyd-filter.conf.5 fapolicyd-1.3.4/doc/fagenrules.8000066400000000000000000000021521470754500200164660ustar00rootroot00000000000000.TH FAGENRULES "8" "Nov 2021" "Red Hat" "System Administration Utilities" .SH NAME fagenrules \- a script that merges component fapolicyd rule files .SH SYNOPSIS .B fagenrules .RI [ \-\-check ]\ [ \-\-load ] .SH DESCRIPTION \fBfagenrules\fP is a script that merges all component fapolicyd rules files, found in the fapolicyd rules directory, \fI/etc/fapolicyd/rules.d\fP, placing the merged file in \fI/etc/fapolicyd/compiled.rules\fP. Component fapolicyd rule files, must end in \fI.rules\fP in order to be processed. All other files in \fI/etc/fapolicyd/rules.d\fP are ignored. .P The files are concatenated in order, based on their natural sort (see -v option of ls(1)) and stripped of empty and comment (#) lines. .P The generated file is only copied to \fI/etc/fapolicyd/compiled.rules\fP, if it differs. .SH OPTIONS .TP .B \-\-check test if rules have changed and need updating without overwriting compiled.rules. .TP .B \-\-load load old or newly built rules into the daemon. .SH FILES /etc/fapolicyd/rules.d/ /etc/fapolicyd/compiled.rules .SH "SEE ALSO" .BR fapolicyd.rules (5), .BR fapolicyd-cli (8), .BR fapolicyd (8). fapolicyd-1.3.4/doc/fapolicyd-cli.8000066400000000000000000000075421470754500200170620ustar00rootroot00000000000000.TH "FAPOLICYD-CLI" "8" "Dec 2021" "Red Hat" "System Administration Utilities" .SH NAME fapolicyd-cli \- Fapolicyd CLI Tool .SH SYNOPSIS \fBfapolicyd-cli\fP [\fIoptions\fP] .SH DESCRIPTION The fapolicyd command line utility is a tool to tell the daemon that it needs to update the trust database. Normally, the daemon learns that the trust database needs updating because it uses a dnf plugin to inform it. However, you may install an rpm by hand and it can't see that a system package was installed or updated. Or perhaps the admin updates the fapolicyd.trust file and would like the changes to take effect immediately. In either of these cases, you would need to tell the daemon that it needs to do an update by running this command. .SH OPTIONS .TP .B \-h, \-\-help Prints a list of command line options. .TP .B \-\-check-config Opens fapolicyd.conf and parses it to see if there are any syntax errors in the file. .TP .B \-\-check-path Check the PATH environmental variable against the trustdb to look for file not in the trustdb which could cause problems at run time. .TP .B \-\-check-status Dump the daemon's internal performance statistics. See also the fapolicyd.conf option \fBreport_interval\fP. .TP .B \-\-check-trustdb Check the trustdb against the files on disk to look for mismatches that will cause problems at run time. .TP .B \-\-check-watch_fs Check the mounted file systems against the watch_fs daemon config entry to determine if any file systems need to be added to the configuration. .TP .B \-d, \-\-delete-db Deletes the trust database. Normally this never needs to be done. But if for some reason the trust database becomes corrupted, then the only method of recovery is to run this command. .TP .B \-D, \-\-dump-db Dumps the trust db contents for inspection. This will print the original trust source, path, file size, and SHA256 sum of the file as known by the trust source the entry came from. .TP .B \-f, \-\-file add|delete|update [path] Manage the file trust database. .RS .TP 12 .B add This command adds the file given by path to the trust database. It gets the size and calculates the required SHA256 hash. If the path is a directory, it will walk the directory tree to the bottom and add every regular file that it finds. By default, the path is appended to the end of the \fBfapolicyd.trust\fP file. .TP 12 .B delete This command deletes all entries that match from the trust database. It will try to match multiple entries so that entire directories can be deleted in one command. To ensure that you only match a directory and not a partial name, be sure to end with '/'. .TP 12 .B update This command updates the size and hash of any matching paths in the file trust database. If no path is given, then all files are updated. If an argument is passed, then only matching paths get updated. If the intent is to match against a directory, ensure that it ends with '/'. .RE .TP .B \-\-trust-file trust-file-name Use after \fBfile\fP option. Makes every command of \fBfile\fP option operate on a single trust file named \fBtrust-file-name\fP that is located inside trust.d directory. If a trust file with such a name does not exist inside trust.d directory, it is created. .TP .B \-t, \-\-ftype /path/to/file Prints the mime type of the file given. A full path must be specified. This command is intended to help get the ftype parameter of rules correct by seeing how fapolicyd will classify it. Fapolicyd may differ from the \fBfile\fP command. .TP .B \-l, \-\-list Prints a listing of the fapolicyd rules file with a rule number to aid in troubleshooting or understanding of the debug messages. .TP .B \-u, \-\-update Notifies fapolicyd to perform an update of the trust database. .TP .B \-r, \-\-reload-rules Notifies fapolicyd to perform a reload of the rules. .SH "SEE ALSO" .BR fapolicyd (8), .BR fapolicyd.rules (5), .BR fapolicyd.trust (5), and .BR fapolicyd.conf (5) .SH AUTHOR Zoltan Fridrich fapolicyd-1.3.4/doc/fapolicyd-filter.conf.5000066400000000000000000000051611470754500200205140ustar00rootroot00000000000000.TH FAPOLICYD_FILTER.CONF: "15" "June 2023" "Red Hat" "System Administration Utilities" .SH NAME fapolicyd-filter.conf \- fapolicyd filter configuration file .SH DESCRIPTION The file .I /etc/fapolicyd/fapolicyd-filter.conf contains configuration of the filter for the application allowlisting daemon. This filter specifies an allow or exclude list of files from a trust source. Valid line starts with character '+', '-' or '#' for comments. The rest of the line contains a path specification. Space can be used as indentation to add more specific filters to the previous one. Note, that only one space is required for one level of an indent. If there are multiple specifications on the same indentation level they extend the previous line with lower indentation, usually a directory. The path may be specified using the glob pattern. A directory specification has to end with a slash ‘/’. The filters are processed as follows: Starting from the up the to bottom while in case of a match the result (+/-) is set unless there is an indented block which describes more detailed specification of the parent level match. The same processing logic is applied to the inner filters definitions. If there is no match, the parent’s result is set. If there is no match at all, the default result is minus (-). If the result was a plus (+), the respective file from a trust source is imported to the TrustDB. Vice versa, if the result was a minus (-), the respective file is not imported. From a performance point of view it is better to design an indented filter because in the ideal situation each component of the path is compared only once. In contrast to it, a filter without any indentation has to contain a full path which makes the pattern more complicated and thus slower to process. The motivation behind this is to have a flexible configuration and keep the TrustDB as small as possible to make the look-ups faster. .nf .B # this is simple allow list .B - /usr/bin/some_binary1 .B - /usr/bin/some_binary2 .B + / .fi .nf .B # this is the same .B + / .B \ + usr/bin/ .B \ \ - some_binary1 .B \ \ - some_binary2 .fi .nf .B # this is similar allow list with a wildcard .B - /usr/bin/some_binary? .B + / .fi .nf .B # this is similar with another wildcard .B + / .B \ - usr/bin/some_binary* .fi .nf .B # keeps everything except usr/share except python and perl files .B # /usr/bin/ls - result is '+' .B # /usr/share/something - result is '-' .B # /usr/share/abcd.py - result is '+' .B + / .B \ - usr/share/ .B \ \ + *.py .B \ \ + *.pl .fi .SH "SEE ALSO" .BR fapolicyd (8), .BR fapolicyd-cli (1) .BR fapolicy.rules (5) and .BR glob (7) .SH AUTHOR Radovan Sroka fapolicyd-1.3.4/doc/fapolicyd.8000066400000000000000000000100111470754500200162760ustar00rootroot00000000000000.TH "FAPOLICYD" "8" "March 2022" "Red Hat" "System Administration Utilities" .SH NAME fapolicyd \- File Access Policy Daemon .SH SYNOPSIS \fBfapolicyd\fP [\fIoptions\fP] .SH DESCRIPTION \fBfapolicyd\fP is a userspace daemon that determines access rights to files based on a trust database and file or process attributes. It can be used to either blacklist or whitelist file access and execution. Configuring \fBfapolicyd\fP is done with the files in the \fI/etc/fapolicyd/\fP directory. There are three files: .B compiled.rules , .B fapolicyd.conf , and .B fapolicyd.trust. The first one contains the access policy, the second determines the daemon's configuration, and the last allows admin defined trusted files. The default rules will generate audit events whenever there is a denial. NOTE: you must have at least 1 audit rule loaded for the audit system to create the full FANOTIFY event. It doesn't matter which rule is loaded. To see if you have any denials, you can run the following command: .RS .TP 2 .B ausearch \-\-start today \-m fanotify \-i .RE or instead of \-i, you can add \-\-format text to get an easier to read audit event. .SH OPTIONS .TP .B \-\-debug leave the daemon in the foreground for debugging. Event information is written to stderr so that policy decisions can be observed. .TP .B \-\-debug\-deny leave the daemon in the foreground for debugging. Event information is written to stderr only when the decision is to deny access. .TP .B \-\-permissive the daemon will allow file access regardless of the policy decision. This is useful for debugging rules before making them permanent. .TP .B \-\-no-details when fapolicyd ends, it dumps a usage report with various statistics that may be useful for tuning performance. It can also detail which processes it knew about and files being accessed by them. This can be useful for forensics investigations. In some settings, this may not be desirable as the file names may be sensitive. Using this option removes process and file names leaving only the statistics. The default without giving this option is to generate a full report. .SH SIGNALS .TP .B SIGTERM causes fapolicyd to discontinue processing events, write it's performance report, and exit. .TP .B SIGHUP causes fapolicyd to reload the trust database. .TP .B SIGUSR1 causes fapolicyd to dump it's internal statistics to /var/run/fapolicyd.state .SH NOTES Whatever you do, DO NOT TRY TO ATTACH WITH PTRACE. Ptrace attachment sends a SIGSTOP which cannot be blocked. Since your whole system depends on fapolicyd approving access to glibc and various critical libraries, that will not happen until SIGCONT is sent. The system can deadlock if the continue signal is not sent. To get audit events, you must have auditing enabled and at least one systemcall rule loaded. Otherwise you will not get any events. If the rpmdb is set as a trust source, you should minimize the number of 32 bit packages on the system. In such cases, there may be a 32 bit and 64 file with the same pathname. Obviously only one can exist on the disk. So, this will always cause database miscompares and cause a delay in the daemon being operational. The .B compiled.rules file is the resulting merge of component rules in /etc/fapolicyd/rules.d/ See the .B fagenrules man page for more information. If you are running in the debug mode and wish to compare rule numbers reported in the output with which rule is actually triggering, you can see the rules with the corresponding number by running the following command: .nf .B fapolicyd-cli \-\-list .fi .SH FILES .B /etc/fapolicyd/fapolicyd.conf - daemon configuration .P .B /etc/fapolicyd/compiled.rules - access control rules .P .B /etc/fapolicyd/fapolicyd.trust - admin defined trusted files .P .B /var/log/fapolicyd-access.log - information about what was being accessed. .P .B /run/fapolicyd/fapolicyd.state - internal performance metrics .SH "SEE ALSO" .BR fapolicyd-cli (8), .BR fapolicyd.rules (5), .BR fapolicyd.trust (5), .BR fapolicyd-filter.conf (5), .BR fagenrules (8), and .BR fapolicyd.conf (5) .SH AUTHOR Steve Grubb fapolicyd-1.3.4/doc/fapolicyd.conf.5000066400000000000000000000201111470754500200172210ustar00rootroot00000000000000.TH FAPOLICYD.CONF: "5" "September 2022" "Red Hat" "System Administration Utilities" .SH NAME fapolicyd.conf \- fapolicyd configuration file .SH DESCRIPTION The file .I /etc/fapolicyd/fapolicyd.conf contains configuration information for the application whitelisting daemon configuration. This file allows the admin to tune the performance and actions of the fapolicyd during runtime. This file contains one configuration keyword per line, an equal sign, and then followed by appropriate configuration information. All option names and values are case insensitive. The keywords recognized are listed and described below. Each line should be limited to 160 characters or the line will be skipped. You may add comments to the file by starting the line with a '#' character. .TP .B permissive This option is either a 0 to mean send policy decisions to the kernel for enforcement. Or it can be a 1 to mean always allow the access even if policy would block it. This should only be used for policy testing and debug. The default value is 0. .TP .B nice_val This option gives fapolicyd a scheduler boost. The number can be from 0 to 20. The default value is 10. .TP .B q_size This option is used to control how big of an internal queue that fapolicyd will use. If requests come in faster than fapolicyd can answer, the queue holds the pending requests. If the do_stat_report is enabled, when fapolicyd shutsdown it will provide some statistics which includes maximum queue depth used. This information can be used to help tune performance. The default value is 800. Also note, this value means that fapolicyd gets a file descriptor for that entry. There is an rlimit cap controlled by systemd's LimitNOFILE setting for the service. You may also need to adjust it if the q_size exceeds it's value. .TP .B uid This can be a number or an account name which fapolicyd should switch to during startup. The default value is 0 because it is guaranteed to exist. But it is recommended to use the fapolicyd account if that exists. .TP .B gid This can be a number or an group name which fapolicyd should switch to during startup. The default value is 0 because it is guaranteed to exist. But it is recommended to use the fapolicyd group if that exists. .TP .B do_stat_report This option controls whether (1) or not (0) fapolicyd should create a usage statistics report on shutdown. The report is written to /var/log/fapolicyd-access.log. This report gives information about number of allowed accesses and denials. Then for both the subject and object cache, it dumps information about size, hits, misses, and evictions. The default value is 1 which means create the report. .TP .B detailed_report This option controls whether (1) or not (0) fapolicyd should add subject and object information to the usage statistics report. This would be information about the exact process or file path in the cache from most recently used to last recently used. This can be useful for forensics if an incident had occurred. But if the file names are sensitive then you may want to turn this off. The default value is 1 meaning add the details. .TP .B db_max_size This option controls how many megabytes to allow the trust database to grow to. If you have lots of packages installed, then you want to make it bigger. The default value is 50 megabytes. .TP .B subj_cache_size This option controls how many entries the subject cache holds. You want the size to be big enough that you are not getting too many evictions compared to hits. But you don't want to waste memory. Whenever there is an eviction, fapolicyd has to regenerate information about the subject and this slows performance. There are only 64k processes allowed at any time, so this would be the upper limit. The default value is 1549. .TP .B obj_cache_size This option controls how many entries the object cache holds. You want the size to be big enough that you are not getting too many evictions compared to hits. But you don't want to waste memory. Whenever there is an eviction, fapolicyd has to regenerate information about the object and this slows performance. The default value is 8191. .TP .B watch_fs This is a comma separated list of file systems that should be watched for access permission. No attempt is made to validate the file systems names. They should exactly match the name presented in the first column of /proc/mounts. If this is not configured, it will default to watching ext4, xfs, and tmpfs. .TP .B trust This is a comma separated list of trust back-ends. If this is not configured, 'rpmdb,file' is default. Fapolicyd supports \fBfile\fP back-end that reads content of /etc/fapolicyd/fapolicyd.trust and use it as a list of trusted files. The second option is \fBrpmdb\fP backend that generates list of trusted files from rpmdb. .TP .B integrity This option tells fapolicyd which integrity strategy it should use. It can be one of 4 values: .RS .TP 12 .B none This is the .IR default and does no integrity checking. .TP .B size Selecting this option will compare the size of the file with what it was knows to be. This is better than nothing and very fast since fapolicyd already collects size information during normal processing. However, an attacker could replace the file and as long as the size matches, it will not be detected. .TP .B ima Selecting this option will use a SHA256 hash that the IMA subsystem places in a file's extended attributes in addition to the size check. This means that all file systems holding executable code must support extended attributes. .TP .B sha256 Selecting this option will calculate a SHA256 hash by cryptographic means. A size check will also be performed. .RE .TP .B syslog_format This option controls how the output from the access decision is formatted. The format is a comma separated list of subject and object names from the rules. It does not allow the keyword "all". It also allows for rule, dec, and perm. The format must include a semi-colon to delineate subject from object keywords. The typical use is to place information about the access decision, then subject information, a colon, and the object information. Also note that the more things being logged, the more it will impact system performance. Also, the event written is limited to 512 bytes. Example: .nf .B syslog_format = rule,dec,perm,auid,pid,exe,:,path,ftype,trust .fi .TP .B rpm_sha256_only The option set to 1 forces the daemon to work only with SHA256 hashes. This is useful on the systems where the integrity is set to SHA256 or IMA and some rpms were originally built with e.g. SHA1. The daemon will ignore these SHA1 entries therefore they can be added manually via CLI with correct SHA256 to a trust file later. If set to 0 the daemon stores SHA1 in trustdb as well. This is compatible with older behavior which works with the integrity set to NONE and SIZE. The NONE or SIZE integrity setting considers the files installed via rpm as trusted and it does not care about their hashes at all. On the other hand the integrity set to SHA256 or IMA will never consider a file with SHA1 in trustdb as trusted. The default value is 0. .TP .B allow_filesystem_mark When this option is set to 1, it allows fapolicyd to monitor file access events on the underlying file system when they are bind mounted or are overlayed (e.g. the overlayfs). Normally they block fapolicyd from seeing events on the underlying file systems. This may or may not be desirable. For example, you might start seeing containers accessing things outside of the container but there is no source of trust for the container. In that case you probably do not want to see access from the container. Or maybe you do not use containers but want to control anything run by systemd-run when dynamic users are allowed. In that case you probably want to turn it on. Not all kernel's support this option. Therefore the default value is 0. .TP .B report_interval This option specifies a reporting interval, measured in seconds, which fapolicyd uses to schedule a recurring dump of internal performance statistics to the \fBfapolicyd.state\fP file. The default value of 0 disables interval reporting. .SH "SEE ALSO" .BR fapolicyd (8), .BR fapolicyd-cli (8) and .BR fapolicy.rules (5). .SH AUTHOR Steve Grubb fapolicyd-1.3.4/doc/fapolicyd.rules.5000066400000000000000000000255571470754500200174510ustar00rootroot00000000000000.TH FAPOLICYD.RULES: "5" "June 2022" "Red Hat" "System Administration Utilities" .SH NAME compiled.rules \- compiled fapolicyd rules to determine access rights fapolicyd.rules \- deprecated fapolicyd rules to determine access rights .SH DESCRIPTION \fBcompiled.rules\fP is a file that is compiled by .B fagenrules which contains the rules that \fBfapolicyd\fP uses to make decisions about access rights. The rules follow a simple format of: .nf .B decision perm subject : object .fi They are evaluated from top to bottom with the first rule to match being used for the access control decision. The colon is mandatory to separate subject and object since they share keywords. .SS Decision The decision is either .IR allow ", " deny ", " allow_audit ", " deny_audit ", " allow_syslog ", "deny_syslog ", " allow_log ", or " deny_log ". If the rule triggers, this is the access decision that fapolicyd will tell the kernel. If the decision is one of the audit variety, then the decision will trigger a FANOTIFY audit event with all relevant information. .B You must have at least one audit rule loaded to generate an audit event. If the decision is one of the syslog variety, then the decision will trigger writing an event into syslog. If the decision is of one the log variety, then it will create an audit event and a syslog event. Regardless of the notification, any rule with a deny in the keyword will deny access and any with an allow in the keyword will allow access. .SS Perm Perm describes what kind permission is being asked for. The permission is either .IR open ", " execute ", or " any ". If none are given, then open is assumed. .SS Subject The subject is the process that is performing actions on system resources. The fields in the rule that describe the subject are written in a name=value format. There can be one or more subject fields. Each field is and'ed with others to decide if a rule triggers. The name values can be any of the following: .RS .TP 12 .B all This matches against any subject. When used, this must be the only subject in the rule. .TP .B auid This is the login uid that the audit system assigns users when they log in to the system. Daemons have a value of -1. The given value may be numeric or the account name. .TP .B uid This is the user id that the program is running under. The given value may be numeric or the account name. .TP .B gid This is the group id that the program is running under. The given value may be numeric or the group name. .TP .B sessionid This is the numeric session id that the audit system assigns to users when they log in. Daemons have a value of -1. .TP .B pid This is the numeric process id that a program has. .TP .B ppid This is the numeric process id of the program's parent. Note that programs that are orphaned or started directly from systemd have a ppid value of 1. Kernel threads have a ppid value of 2. .TP .B trust This is a boolean describing whether it is required for the subject to be in the trust database or not. A value of 1 means its required while 0 means its not. Trust checking is extended by the integrity setting in fapolicyd.conf. When trust is used on the subject, it could be a daemon. If that daemon gets updated on disk, the trustdb will be updated to the new SHA256 hash. If the integrity setting is not none, the running daemon is not likely to be trusted unless it gets restarted. The default rules are not written in a way that this would happen. But this needs to be highlighted as it may not be obvious when writing a new rule. .TP .B comm This is the shortened command name. When an interpreter starts a program, it usually renames the program to the script rather than the interpreter. .TP .B exe This is the full path to the executable. Globbing is not supported. You may also use the special keyword \fBuntrusted\fP to match on the subject not being listed in the rpm database. .TP .B dir If you wish to match a directory, then use this by giving the full path to the directory. Its recommended to end with the / to ensure it matches a directory. There are 3 keywords that \fIdir\fP supports: \fBexecdirs\fP, \fBsystemdirs\fP, \fBuntrusted\fP. .RS .TP 12 .B execdirs The \fIexecdirs\fP option will match against the following list of directories: .RS .TP 12 /usr/ /bin/ /sbin/ /lib/ /lib64/ /usr/libexec/ .RE .TP 12 .B systemdirs The \fIsystemdirs\fP option will match against the same list as \fIexecdirs\fP but also includes /etc/. .TP 12 .B untrusted The \fIuntrusted\fP option will look up the current executable's full path in the rpm database to see if the executable is known to the system. The rule will trigger if the file in question is not in the trust database. This option is .B deprecated in favor of using obj_trust with execute permission when writing rules. .RE .TP .B ftype This option takes the mime type of a file as an argument. If you wish to check the mime type of a file while writing rules, run the following command: .nf .B fapolicyd-cli \-\-ftype /path-to-file .fi .TP .B device This option will match against the device that the executable resides on. To use it, start with /dev/ and add the target device name. .TP .B pattern There are various ways that an attacker may try to execute code that may reveal itself in the pattern of file accesses made during program startup. This rule can take one of several options depending on which access patterns is wished to be blocked. Fapolicyd is able to detect these different access patterns and provide the access decision as soon as it identifies the pattern. The pattern type can be any of: .RS .TP 12 .B normal This matches against any ELF program that is dynamically linked. .TP .B ld_so This matches against access patterns that indicate that the program is being started directly by the runtime linker. .TP .B ld_preload This matches against access patterns that indicate that the program is being started with either LD_PRELOAD or LD_AUDIT present in the environment. Note that even without this rule, you have protection against LD_PRELOAD of unknown binaries when the rules are written such that trust is used to determine if a library should be opened. In that case, the preloaded library would be denied but the application will still execute. This rule makes it so that even trusted libraries can be denied and the application will not execute. .TP .B static This matches against ELF files that are statically linked. .RE .RE .SS Object The object is the file that the subject is interacting with. The fields in the rule that describe the object are written in a name=value format. There can be one or more object fields. Each field is and'ed with others to decide if a rule triggers. The name values can be any of the following: .RS .TP 12 .B all This matches against any obbject. When used, this must be the only object in the rule. .TP .B path This is the full path to the file that will be accessed. Globbing is not supported. You may also use the special keyword \fBuntrusted\fP to match on the object not being listed in the rpm database. .TP .B dir If you wish to match on access to any file in a directory, then use this by giving the full path to the directory. Its recommended to end with the / to ensure it matches a directory. There are 3 keywords that \fIdir\fP supports: \fBexecdirs\fP, \fBsystemdirs\fP, \fBuntrusted\fP. See the \fBdir\fP option under Subject for an explanation of these keywords. .TP .B device This option will match against the device that the file being accessed resides on. To use it, start with /dev/ and add the target device name. .TP .B ftype This option matches against the mime type of the file being accessed. See \fBftype\fP under Subject for more information on determining the mime type. .TP .B trust This is a boolean describing whether it is required for the object to be in the trust database or not. A value of 1 means its required while 0 means its not. Trust checking is extended by the integrity setting in fapolicyd.conf. .TP .B sha256hash This option matches against the sha256 hash of the file being accessed. The hash in the rules should be all lowercase letters and do NOT start with 0x. Lowercase is the default output of sha256sum. .RE .SH SETS Set is a named group of values of the same type. Fapolicyd internally distinguishes between INT and STRING set types. You can define your own set and use it as a value for a specific rule attribute. The definition is in key=value syntax and starts with a set name. The set name has to start with '%' and the rest is alphanumeric or '_'. The value is a comma separated list. The set type is inherited from the first item in the list. If that can be turned into number then whole list is expected to carry numbers. One can use these sets as a value for subject and object attributes. It is also possible to use a plain list as an attribute value without previous definition. The assigned set has to match the attribute type. It is not possible set groups for TRUST and PATTERN attributes. .SS SETS EXAMPLES .nf .B # definition .b # string set .B %python=/usr/bin/python2.7,/usr/bin/python3.6 .B allow exe=%python : all trust=1 .B # .B # definition .B # number set .B %uuids=0,1000 .B allow uid=%uuids : all .fi .SH NOTES When writing rules, you should keep them focused to one goal and store them in one file. These rule files are kept in the /etc/fapolicyd/rules.d directory. During daemon startup, .B fagenrules will run and compile all these component files into one master file, compiled.rules. See the .B fagenrules man page for more information. When you are writing a rule for the execute permission, remember that the file to be executed is an .B object. For example, you type ssh into the shell. The shell calls execve on /usr/bin/ssh. At that instant in time, ssh is the object that bash is working on. However, if you are blocking execution .I from a specific program, then you would normally state the program on the subject side and use .I all for the object side. If you are writing rules that use patterns, just select .I any as the permission to be clear that this applies to anything. In reality, pattern matching ignores the permission but the suggestion is for documentation purposes. Some interpreters do not immediately read all lines of input. Rather, they read content as needed until they get to end of file. This means that if they do stuff like networking or sleeping or anything that takes time, someone with the privileges to modify the file can add to it after the file's integrity has been checked. This is not unique to fapolicyd, it's simply how things work. Make sure that trusted file permissions are not excessive so that no unexpected file content modifications can occur. .SH EXAMPLES The following rules illustrate the rule syntax. .nf .B deny_audit perm=open exe=/usr/bin/wget : dir=/tmp .B allow perm=open exe=/usr/bin/python3.7 : ftype=text/x-python trust=1 .B deny_audit perm=any pattern ld_so : all .B deny perm=any all : all .fi .SH "SEE ALSO" .BR fapolicyd (8), .B fagenrules (8), .BR fapolicyd-cli (8), and .BR fapolicyd.conf (5) .SH AUTHOR Steve Grubb fapolicyd-1.3.4/doc/fapolicyd.trust.5000066400000000000000000000032101470754500200174560ustar00rootroot00000000000000.TH FAPOLICYD.TRUST: "5" "January 2020" "Red Hat" "System Administration Utilities" .SH NAME fapolicyd.trust \- fapolicyd's file of trust .SH DESCRIPTION The file .I /etc/fapolicyd/fapolicyd.trust contains list of trusted files/binaries for the application whitelisting daemon. You may add comments to the file by starting the line with a '#' character. Each line has to contain three columns and space is a valid separator. The first column contains full path to the file, the second is size of the file in bytes and the third is valid sha256 hash. .sp The directory \fI/etc/fapolicyd/trust\&.d\fR can be used to store multiple trust files\&. This way a privileged user can split the trust database into multiple files and manage them separately through \fBfapolicyd\-cli\fR\&. Functionally, the fapolicy daemon will behave the same way as if the whole trust database has been defined inside \fBfapolicyd\&.trust\fR file\&. Syntax and semantics of trust files inside \fBtrust\&.d\fR directory are the same as for \fBfapolicyd\&.trust\fR file (described above)\&. Trust files can either be created manually inside \fBtrust\&.d\fR directory or via \fBfapolicyd\-cli\fR\& (the latter option is recommended). .SH EXAMPLE .PP .EX [root@Desktop ~]# cat /etc/fapolicyd/fapolicyd.trust /home/user/my-ls 157984 61a9960bf7d255a85811f4afcac51067b8f2e4c75e21cf4f2af95319d4ed1b87 /home/user/my-ls2 5555 61a9960bf7d255a85811f4afcac51067b8f2e4c75e21cf4f2af95319d4ed1b87 .EE .SH FILES .B /etc/fapolicyd/fapolicyd.trust - list of trusted files/binaries .SH "SEE ALSO" .BR fapolicyd (8), .BR fapolicyd-cli (8) .BR fapolicy.rules (5) and .BR fapolicy.conf (5). .SH AUTHOR Radovan Sroka fapolicyd-1.3.4/doc/rpm-filter.conf.5000066400000000000000000000006571470754500200173450ustar00rootroot00000000000000.TH FAPOLICYD.FILTER: "26" "April 2023" "Red Hat" "System Administration Utilities" .SH NAME fapolicyd-filter.conf \- fapolicyd filter configuration file .SH DESCRIPTION The file .I /etc/fapolicyd/rpm-filter.conf was migrated to .I /etc/fapolicyd/fapolicyd-filter.conf or see .BR fapolicyd-filter.conf(5). .SH "SEE ALSO" .BR fapolicyd (8), .BR fapolicyd-cli (1) .BR fapolicy.rules (5) and .BR glob (7) .SH AUTHOR Radovan Sroka fapolicyd-1.3.4/fapolicyd-selinux-var-run.patch000066400000000000000000000022701470754500200215460ustar00rootroot00000000000000From 750c5e288f8253c71a9722da960addb078aee93c Mon Sep 17 00:00:00 2001 From: Zdenek Pytela Date: Tue, 6 Feb 2024 21:17:27 +0100 Subject: [PATCH] Rename all /var/run file context entries to /run With the 1f76e522a ("Rename all /var/run file context entries to /run") selinux-policy commit, all /var/run file context entries moved to /run and the equivalency was inverted. Subsequently, changes in fapolicyd.fc need to be done, too, in a similar manner. --- fapolicyd.fc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fapolicyd-selinux-master/fapolicyd.fc b/fapolicyd-selinux-master/fapolicyd.fc index 2bdc7aa..d081dc8 100644 --- a/fapolicyd-selinux-master/fapolicyd.fc +++ b/fapolicyd-selinux-master/fapolicyd.fc @@ -8,6 +8,6 @@ /var/log/fapolicyd-access.log -- gen_context(system_u:object_r:fapolicyd_log_t,s0) -/var/run/fapolicyd(/.*)? gen_context(system_u:object_r:fapolicyd_var_run_t,s0) +/run/fapolicyd(/.*)? gen_context(system_u:object_r:fapolicyd_var_run_t,s0) -/var/run/fapolicyd\.pid -- gen_context(system_u:object_r:fapolicyd_var_run_t,s0) +/run/fapolicyd\.pid -- gen_context(system_u:object_r:fapolicyd_var_run_t,s0) -- 2.44.0 fapolicyd-1.3.4/fapolicyd-uthash-bundle.patch000066400000000000000000000021511470754500200212300ustar00rootroot00000000000000diff --git a/configure.ac b/configure.ac index 8188d13..6430e1e 100644 --- a/configure.ac +++ b/configure.ac @@ -112,9 +112,6 @@ AC_CHECK_HEADER(sys/fanotify.h, , [AC_MSG_ERROR( ["Couldn't find sys/fanotify.h...your kernel might not be new enough"] )]) AC_CHECK_FUNCS(fexecve, [], []) AC_CHECK_FUNCS([gettid]) -AC_CHECK_HEADER(uthash.h, , [AC_MSG_ERROR( -["Couldn't find uthash.h...uthash-devel is missing"] )]) - echo . echo Checking for required libraries diff --git a/src/Makefile.am b/src/Makefile.am index 25afbcd..dc308ec 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,6 +5,9 @@ AM_CPPFLAGS = \ -I${top_srcdir} \ -I${top_srcdir}/src/library +AM_CPPFLAGS += \ + -I${top_srcdir}/uthash-2.3.0/include + sbin_PROGRAMS = fapolicyd fapolicyd-cli lib_LTLIBRARIES= libfapolicyd.la diff --git a/src/library/rpm-backend.c b/src/library/rpm-backend.c index 8d5aa20..6e92f10 100644 --- a/src/library/rpm-backend.c +++ b/src/library/rpm-backend.c @@ -33,7 +33,7 @@ #include #include -#include +#include "uthash.h" #include "message.h" #include "gcc-attributes.h" fapolicyd-1.3.4/fapolicyd.spec000066400000000000000000000236331470754500200163320ustar00rootroot00000000000000#ASAN %%global asan_build 1 #ELN %%global eln_build 1 %if %{defined eln_build} %global selinuxtype targeted %global moduletype contrib %define semodule_version master %endif Summary: Application Whitelisting Daemon Name: fapolicyd Version: 1.3.3 Release: 1%{?dist} License: GPL-3.0-or-later URL: http://people.redhat.com/sgrubb/fapolicyd Source0: https://people.redhat.com/sgrubb/fapolicyd/%{name}-%{version}.tar.gz #ELN %Source1: https://github.com/linux-application-whitelisting/%{name}-selinux/archive/refs/heads/%{semodule_version}.tar.gz#/%{name}-selinux-%{semodule_version}.tar.gz # we bundle uthash for rhel9 #ELN %Source2: https://github.com/troydhanson/uthash/archive/refs/tags/v2.3.0.tar.gz#/uthash-2.3.0.tar.gz BuildRequires: gcc BuildRequires: kernel-headers BuildRequires: autoconf automake make gcc libtool BuildRequires: systemd systemd-devel openssl-devel rpm-devel file-devel file BuildRequires: libcap-ng-devel libseccomp-devel lmdb-devel BuildRequires: python3-devel #ELN %%if 0%{?fedora} > 0 BuildRequires: uthash-devel #ELN %%endif %if %{defined asan_build} BuildRequires: libasan %endif %if %{defined eln_build} Recommends: %{name}-selinux %endif Requires(pre): shadow-utils Requires(post): systemd-units Requires(preun): systemd-units Requires(postun): systemd-units # applied in CI only Patch1: fapolicyd-uthash-bundle.patch Patch2: fapolicyd-selinux-var-run.patch %description Fapolicyd (File Access Policy Daemon) implements application whitelisting to decide file access rights. Applications that are known via a reputation source are allowed access while unknown applications are not. The daemon makes use of the kernel's fanotify interface to determine file access rights. %if %{defined eln_build} %package selinux Summary: Fapolicyd selinux Group: Applications/System Requires: %{name} = %{version}-%{release} BuildRequires: selinux-policy %if 0%{?rhel} < 9 BuildRequires: selinux-policy-devel >= 3.14.3-108 %else %if 0%{?rhel} == 9 BuildRequires: selinux-policy-devel >= 38.1.2 %else BuildRequires: selinux-policy-devel >= 38.2 %endif %endif BuildArch: noarch %{?selinux_requires} %description selinux The %{name}-selinux package contains selinux policy for the %{name} daemon. %endif %prep %setup -q %if %{defined eln_build} # selinux %setup -q -D -T -a 1 %endif %if 0%{?fedora} == 0 # uthash %setup -q -D -T -a 2 %patch -P1 -p1 -b .uthash %endif %if %{defined eln_build} %if 0%{?fedora} < 40 %define selinux_var_run 1 %endif %if 0%{?rhel} < 10 %define selinux_var_run 1 %endif %if %{defined selinux_var_run} %patch -P2 -R -p1 -b .selinux %endif %endif # generate rules for python sed -i "s|%python2_path%|`readlink -f %{__python2}`|g" rules.d/*.rules sed -i "s|%python3_path%|`readlink -f %{__python3}`|g" rules.d/*.rules # Detect run time linker directly from bash interpret=`readelf -e /usr/bin/bash \ | grep Requesting \ | sed 's/.$//' \ | rev | cut -d" " -f1 \ | rev` sed -i "s|%ld_so_path%|`realpath $interpret`|g" rules.d/*.rules %build ./autogen.sh configure_flags="--with-audit --with-rpm --disable-shared" %if %{defined asan_build} configure_flags="$configure_flags --with-asan" %endif %configure $configure_flags %make_build %if %{defined eln_build} # selinux pushd %{name}-selinux-%{semodule_version} make popd # selinux %pre selinux %selinux_relabel_pre -s %{selinuxtype} %endif %install %make_install install -p -m 644 -D init/%{name}-tmpfiles.conf %{buildroot}/%{_tmpfilesdir}/%{name}.conf mkdir -p %{buildroot}/%{_localstatedir}/lib/%{name} mkdir -p %{buildroot}/run/%{name} mkdir -p %{buildroot}%{_sysconfdir}/%{name}/trust.d mkdir -p %{buildroot}%{_sysconfdir}/%{name}/rules.d # get list of file names between known-libs and restrictive from sample-rules/README-rules cat %{buildroot}/%{_datadir}/%{name}/sample-rules/README-rules \ | grep -A 100 'known-libs' \ | grep -B 100 'restrictive' \ | grep '^[0-9]' > %{buildroot}/%{_datadir}/%{name}/default-ruleset.known-libs chmod 644 %{buildroot}/%{_datadir}/%{name}/default-ruleset.known-libs %if %{defined eln_build} # selinux install -d %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype} install -m 0644 %{name}-selinux-%{semodule_version}/%{name}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype} install -d -p %{buildroot}%{_datadir}/selinux/devel/include/%{moduletype} install -p -m 644 %{name}-selinux-%{semodule_version}/%{name}.if %{buildroot}%{_datadir}/selinux/devel/include/%{moduletype}/ipp-%{name}.if %endif #cleanup find %{buildroot} \( -name '*.la' -o -name '*.a' \) -delete %define manage_default_rules default_changed=0 \ # check changed fapolicyd.rules \ if [ -e %{_sysconfdir}/%{name}/%{name}.rules ]; then \ diff %{_sysconfdir}/%{name}/%{name}.rules %{_datadir}/%{name}/%{name}.rules.known-libs >/dev/null 2>&1 || { \ default_changed=1; \ #echo "change detected in fapolicyd.rules"; \ } \ fi \ if [ -e %{_sysconfdir}/%{name}/rules.d ]; then \ default_ruleset=''; \ # get listing of default rule files in known-libs \ [ -e %{_datadir}/%{name}/default-ruleset.known-libs ] && default_ruleset=`cat %{_datadir}/%{name}/default-ruleset.known-libs`; \ # check for removed or added files \ default_count=`echo "$default_ruleset" | wc -l`; \ current_count=`ls -1 %{_sysconfdir}/%{name}/rules.d/*.rules | wc -l`; \ [ $default_count -eq $current_count ] || { \ default_changed=1; \ # echo "change detected in number of rule files d:$default_count vs c:$current_count"; \ }; \ for file in %{_sysconfdir}/%{name}/rules.d/*.rules; do \ if echo "$default_ruleset" | grep -q "`basename $file`"; then \ # compare content of the rule files \ diff $file %{_datadir}/%{name}/sample-rules/`basename $file` >/dev/null 2>&1 || { \ default_changed=1; \ # echo "change detected in `basename $file`"; \ }; \ else \ # added file detected \ default_changed=1; \ # echo "change detected in added rules file `basename $file`"; \ fi; \ done; \ fi; \ # remove files if no change against default rules detected \ [ $default_changed -eq 0 ] && rm -rf %{_sysconfdir}/%{name}/%{name}.rules %{_sysconfdir}/%{name}/rules.d/* || : \ %check make check %pre getent passwd %{name} >/dev/null || useradd -r -M -d %{_localstatedir}/lib/%{name} -s /sbin/nologin -c "Application Whitelisting Daemon" %{name} if [ $1 -eq 2 ]; then # detect changed default rules in case of upgrade %manage_default_rules fi %post # if no pre-existing rule file if [ ! -e %{_sysconfdir}/%{name}/%{name}.rules ] ; then files=`ls %{_sysconfdir}/%{name}/rules.d/ 2>/dev/null | wc -w` # Only if no pre-existing component rules if [ "$files" -eq 0 ] ; then ## Install the known libs policy for rulesfile in `cat %{_datadir}/%{name}/default-ruleset.known-libs`; do cp %{_datadir}/%{name}/sample-rules/$rulesfile %{_sysconfdir}/%{name}/rules.d/ done chgrp %{name} %{_sysconfdir}/%{name}/rules.d/* if [ -x /usr/sbin/restorecon ] ; then # restore correct label /usr/sbin/restorecon -F %{_sysconfdir}/%{name}/rules.d/* fi fagenrules >/dev/null fi fi %systemd_post %{name}.service %preun %systemd_preun %{name}.service if [ $1 -eq 0 ]; then # detect changed default rules in case of uninstall %manage_default_rules else [ -e %{_sysconfdir}/%{name}/%{name}.rules ] && rm -rf %{_sysconfdir}/%{name}/rules.d/* || : fi %postun %systemd_postun_with_restart %{name}.service %files %doc README.md %{!?_licensedir:%global license %%doc} %license COPYING %attr(755,root,%{name}) %dir %{_datadir}/%{name} %attr(755,root,%{name}) %dir %{_datadir}/%{name}/sample-rules %attr(644,root,%{name}) %{_datadir}/%{name}/default-ruleset.known-libs %attr(644,root,%{name}) %{_datadir}/%{name}/sample-rules/* %attr(644,root,%{name}) %{_datadir}/%{name}/fapolicyd-magic.mgc %attr(750,root,%{name}) %dir %{_sysconfdir}/%{name} %attr(750,root,%{name}) %dir %{_sysconfdir}/%{name}/trust.d %attr(750,root,%{name}) %dir %{_sysconfdir}/%{name}/rules.d %attr(644,root,%{name}) %{_sysconfdir}/bash_completion.d/fapolicyd.bash_completion %ghost %verify(not md5 size mtime) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/rules.d/* %ghost %verify(not md5 size mtime) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/%{name}.rules %config(noreplace) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/%{name}.conf %config(noreplace) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/%{name}-filter.conf %config(noreplace) %attr(644,root,%{name}) %{_sysconfdir}/%{name}/%{name}.trust %ghost %attr(644,root,%{name}) %{_sysconfdir}/%{name}/compiled.rules %attr(644,root,root) %{_unitdir}/%{name}.service %attr(644,root,root) %{_tmpfilesdir}/%{name}.conf %attr(755,root,root) %{_sbindir}/%{name} %attr(755,root,root) %{_sbindir}/%{name}-cli %attr(755,root,root) %{_sbindir}/fagenrules %attr(644,root,root) %{_mandir}/man8/* %attr(644,root,root) %{_mandir}/man5/* %ghost %attr(440,%{name},%{name}) %verify(not md5 size mtime) %{_localstatedir}/log/%{name}-access.log %attr(770,root,%{name}) %dir %{_localstatedir}/lib/%{name} %attr(770,root,%{name}) %dir /run/%{name} %ghost %attr(660,root,%{name}) /run/%{name}/%{name}.fifo %ghost %attr(660,%{name},%{name}) %verify(not md5 size mtime) %{_localstatedir}/lib/%{name}/data.mdb %ghost %attr(660,%{name},%{name}) %verify(not md5 size mtime) %{_localstatedir}/lib/%{name}/lock.mdb %if %{defined eln_build} %files selinux %{_datadir}/selinux/packages/%{selinuxtype}/%{name}.pp.bz2 %ghost %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{name} %{_datadir}/selinux/devel/include/%{moduletype}/ipp-%{name}.if %post selinux %selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{name}.pp.bz2 %selinux_relabel_post -s %{selinuxtype} %postun selinux if [ $1 -eq 0 ]; then %selinux_modules_uninstall -s %{selinuxtype} %{name} fi %posttrans selinux %selinux_relabel_post -s %{selinuxtype} %endif %changelog * Mon Apr 29 2024 Steve Grubb 1.3.4-1 - New release fapolicyd-1.3.4/init/000077500000000000000000000000001470754500200144405ustar00rootroot00000000000000fapolicyd-1.3.4/init/Makefile.am000066400000000000000000000012421470754500200164730ustar00rootroot00000000000000EXTRA_DIST = \ fapolicyd.service \ fapolicyd.conf \ fapolicyd-filter.conf \ fapolicyd.trust \ fapolicyd-tmpfiles.conf \ fapolicyd-magic \ fapolicyd.bash_completion \ fagenrules fapolicyddir = $(sysconfdir)/fapolicyd dist_fapolicyd_DATA = \ fapolicyd.conf \ fapolicyd-filter.conf \ fapolicyd.trust systemdservicedir = $(systemdsystemunitdir) dist_systemdservice_DATA = fapolicyd.service sbin_SCRIPTS = fagenrules completiondir = $(sysconfdir)/bash_completion.d/ dist_completion_DATA = fapolicyd.bash_completion MAGIC = fapolicyd-magic.mgc pkgdata_DATA = ${MAGIC} CLEANFILES = ${MAGIC} ${MAGIC}: $(EXTRA_DIST) file -C -m ${top_srcdir}/init/fapolicyd-magic fapolicyd-1.3.4/init/fagenrules000066400000000000000000000065361470754500200165300ustar00rootroot00000000000000#!/bin/sh # Script to concatenate rules files found in a base fapolicyd rules directory # to form a single /etc/fapolicyd/compiled.rules file suitable for loading # into the daemon # When forming the interim rules file, both empty lines and comment # lines (starting with # or #) are stripped as the source files # are processed. # # Having formed the interim rules file, the script checks if the file is empty # or is identical to the existing /etc/fapolicyd/compiled.rules and if either # of these cases are true, it does not replace the existing file. # # Variables # # DestinationFile: # Destination rules file # SourceRulesDir: # Directory location to find component rule files # TmpRules: # Temporary interim rules file # ASuffix: # Suffix for previous fapolicyd.rules file if this script replaces it. # The file is left in the destination directory with suffix with $ASuffix OldDestinationFile=/etc/fapolicyd/fapolicyd.rules DestinationFile=/etc/fapolicyd/compiled.rules SourceRulesDir=/etc/fapolicyd/rules.d TmpRules=$(mktemp /tmp/farules.XXXXXXXX) ASuffix="prev" OnlyCheck=0 LoadRules=0 RETVAL=0 usage="Usage: $0 [--check|--load]" # Delete the interim file on faults trap 'rm -f ${TmpRules}; exit 1' HUP INT QUIT PIPE TERM try_load() { pid=$(pidof fapolicyd) if [ $LoadRules -eq 1 ] && [ "x$pid" != "x" ] ; then kill -HUP "$pid" RETVAL=$? fi } while [ $# -ge 1 ] do if [ "$1" = "--check" ] ; then OnlyCheck=1 elif [ "$1" = "--load" ] ; then LoadRules=1 else echo "$usage" exit 1 fi shift done # Check environment if [ ! -d ${SourceRulesDir} ]; then echo "$0: No rules directory - ${SourceRulesDir}" rm -f "${TmpRules}" try_load exit 1 fi files=$(ls ${SourceRulesDir} 2>/dev/null | wc -w) if [ "$files" = "0" ] ; then echo "No rules in ${SourceRulesDir}" rm -f "${TmpRules}" # won't call this an error as they may not have migrated exit 0 elif [ -e ${OldDestinationFile} ] ; then echo "Error - both old and new rules exist. Delete one or the other" rm -f "${TmpRules}" exit 1 fi # Create the interim rules file ensuring its access modes protect it # from normal users and strip empty lines and comment lines. umask 0137 echo "## This file is automatically generated from $SourceRulesDir" >> "${TmpRules}" for rules in $(/bin/ls -1v ${SourceRulesDir} | grep "\.rules$") ; do cat ${SourceRulesDir}/"${rules}" done | awk ' BEGIN { rest = 0; } { if (length($0) < 1) { next; } if (match($0, "^\\s*#")) { next; } rules[rest++] = $0; } END { for (i = 0; i < rest; i++) { printf "%s\n", rules[i]; } }' >> "${TmpRules}" # If the same then quit cmp -s "${TmpRules}" ${DestinationFile} > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "$0: No change" rm -f "${TmpRules}" try_load exit $RETVAL elif [ $OnlyCheck -eq 1 ] ; then echo "$0: Rules have changed and should be updated" rm -f "${TmpRules}" exit 0 fi # Otherwise we install the new file if [ -f ${DestinationFile} ]; then cp ${DestinationFile} ${DestinationFile}.${ASuffix} fi # We copy the file so that it gets the right selinux label cp "${TmpRules}" ${DestinationFile} chmod 0644 ${DestinationFile} chgrp fapolicyd ${DestinationFile} # Restore context on MLS system. # /tmp is SystemLow & fapolicyd.rules is SystemHigh if [ -x /usr/sbin/restorecon ] ; then /usr/sbin/restorecon -F ${DestinationFile} fi rm -f "${TmpRules}" try_load exit $RETVAL fapolicyd-1.3.4/init/fapolicyd-filter.conf000066400000000000000000000010471470754500200205460ustar00rootroot00000000000000# default filter file for fedora + / - usr/include/ - usr/share/ # Python byte code + *.py? # Python text files + *.py # Some apps have a private libexec + */libexec/* # Ruby + *.rb # Perl + *.pl # System tap + *.stp # Javascript + *.js # Java archive + *.jar # M4 + *.m4 # PHP + *.php # Perl Modules + *.pm # Lua + *.lua # Java + *.class # Typescript + *.ts # Typescript JSX + *.tsx # Lisp + *.el # Compiled Lisp + *.elc - usr/src/kernel*/ + */scripts/* + */tools/objtool/* fapolicyd-1.3.4/init/fapolicyd-magic000066400000000000000000000053371470754500200174230ustar00rootroot000000000000000 string/w #!\ /usr/bin/bash Bourne-Again shell script text executable !:mime text/x-shellscript 0 string/w #!\ /usr/bin/env\ bash Bourne-Again shell script text executable !:mime text/x-shellscript 0 string/w #!\ /usr/bin/sh Shell script text executable !:mime text/x-shellscript 0 string/w #!\ /usr/bin/env\ sh Shell script text executable !:mime text/x-shellscript 0 string/wt #!\ /bin/rc Plan 9 shell script text executable !:mime text/x-plan9-shellscript 0 string/wb #!\ /usr/bin/ocamlrun Ocaml byte-compiled executable !:mime application/x-bytecode.ocaml 0 string/wt #!\ /usr/bin/lua Lua script text executable !:mime text/x-lua 0 string/wt #!\ /usr/bin/texlua LuaTex script text executable !:mime text/x-luatex 0 string/wt #!\ /usr/bin/luatex LuaTex script text executable !:mime text/x-luatex 0 string/wt #!\ /usr/bin/Rscript R script text executable !:mime text/x-R 0 string/wt #!\ /usr/bin/wish Tk windowing shell text executable !:mime text/x-tcl 0 string/wt #!\ /usr/bin/gjs Gnome Javascript text executable !:mime application/javascript 0 string/wt #!\ /usr/bin/jimsh Jim TCL text executable !:mime text/x-tcl 0 belong 0xd1f20d0a python 2.6 byte-compiled !:mime application/x-bytecode.python 0 belong 0x03f30d0a python 2.7 byte-compiled !:mime application/x-bytecode.python 0 belong 0x330d0d0a python 3.6 byte-compiled !:mime application/x-bytecode.python 0 belong 0x420d0d0a python 3.7 byte-compiled !:mime application/x-bytecode.python 0 belong 0x6f0d0d0a python 3.10 byte-compiled !:mime application/x-bytecode.python 0 search/1/wt #!\ /usr/bin/python3 Python script text executable !:strength + 15 !:mime text/x-python 0 search/1/wt #!\ /usr/bin/env\ python3 Python script text executable !:strength + 15 !:mime text/x-python 0 search/1/wt #!\ /usr/bin/python2 Python script text executable !:strength + 15 !:mime text/x-python 0 search/1/wt #!\ /usr/bin/env\ python2 Python script text executable !:strength + 15 !:mime text/x-python 0 search/1/wt #!\ /usr/bin/python Python script text executable !:strength + 15 !:mime text/x-python 0 search/1/wt #!\ /usr/bin/env\ python Python script text executable !:strength + 15 !:mime text/x-python 0 search/1/wt #!\ /usr/libexec/platform-python Python script text executable !:strength + 15 !:mime text/x-python 0 string/wt #!\ /usr/bin/guile Guile script text executable !:mime text/x-script.guile 0 string \223NUMPY NumPy data file !:mime application/x-numpy-data 0 search/1/wt #!\ /usr/bin/tclsh Tcl/Tk script text executable !:mime text/x-tcl 0 search/1/wt #!\ /usr/bin/stap Systemtap script text executable !:mime text/x-systemtap 0 string/wt #!\ /usr/sbin/nft Netfilter tables script text executable !:mime text/x-nftables fapolicyd-1.3.4/init/fapolicyd-tmpfiles.conf000066400000000000000000000000471470754500200211030ustar00rootroot00000000000000d /run/fapolicyd 0770 root fapolicyd - fapolicyd-1.3.4/init/fapolicyd.bash_completion000066400000000000000000000026061470754500200215060ustar00rootroot00000000000000# fapolicyd-cli (8) completion -*- shell-script -*- _fapolicydcli() { local cur prev opts COMPREPLY=() local cur="${COMP_WORDS[COMP_CWORD]}" local prev="${COMP_WORDS[COMP_CWORD-1]}" local opts="--check-config --check-path --check-status --check-trustdb \ --check-watch_fs --delete-db --dump-db \ --file --ftype --help --list --update --reload-rules" case $prev in --ftype) # If bash completions is installed, use it if [ -e /usr/share/bash-completion/bash_completion ] ; then _filedir return 0 else # this is almost as good compopt -o filenames 2>/dev/null COMPREPLY=( $(compgen -f -- ${cur}) ) return 0 fi ;; --file) # missing support to suggest files or dirs after these # but it's better than nothing COMPREPLY=($(compgen -W 'add delete update' -- "$cur")) return 0 ;; esac if [[ $cur == -* ]]; then COMPREPLY=($(compgen -W "${opts}" -- "$cur")) [[ ${COMPREPLY-} == *= ]] && compopt -o nospace return 0 fi } _fapolicyd() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="--debug --debug-deny --help --no-details --permissive" if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi } complete -F _fapolicydcli fapolicyd-cli complete -F _fapolicyd fapolicyd # ex: filetype=sh fapolicyd-1.3.4/init/fapolicyd.conf000066400000000000000000000010131470754500200172540ustar00rootroot00000000000000# # This file controls the configuration of the file access policy daemon. # See the fapolicyd.conf man page for explanation. # permissive = 0 nice_val = 14 q_size = 800 uid = fapolicyd gid = fapolicyd do_stat_report = 1 detailed_report = 1 db_max_size = 50 subj_cache_size = 1549 obj_cache_size = 8191 watch_fs = ext2,ext3,ext4,tmpfs,xfs,vfat,iso9660,btrfs trust = rpmdb,file integrity = none syslog_format = rule,dec,perm,auid,pid,exe,:,path,ftype,trust rpm_sha256_only = 0 allow_filesystem_mark = 0 report_interval = 0 fapolicyd-1.3.4/init/fapolicyd.service000066400000000000000000000007271470754500200200020ustar00rootroot00000000000000[Unit] Description=File Access Policy Daemon DefaultDependencies=no After=local-fs.target systemd-tmpfiles-setup.service Documentation=man:fapolicyd(8) [Service] OOMScoreAdjust=-1000 Type=forking RuntimeDirectory=fapolicyd PIDFile=/run/fapolicyd.pid ExecStartPre=/usr/sbin/fagenrules ExecStart=/usr/sbin/fapolicyd Restart=on-abnormal # Uncomment the following line if rules need user/group name lookup #After=nss-user-lookup.target [Install] WantedBy=multi-user.target fapolicyd-1.3.4/init/fapolicyd.trust000066400000000000000000000003451470754500200175170ustar00rootroot00000000000000# AUTOGENERATED FILE VERSION 2 # This file contains a list of trusted files # # FULL PATH SIZE SHA256 # /home/user/my-ls 157984 61a9960bf7d255a85811f4afcac51067b8f2e4c75e21cf4f2af95319d4ed1b87 fapolicyd-1.3.4/m4/000077500000000000000000000000001470754500200140155ustar00rootroot00000000000000fapolicyd-1.3.4/m4/dyn_linker.m4000066400000000000000000000005571470754500200164240ustar00rootroot00000000000000AC_DEFUN([LD_SO_PATH], [ bash_path=`command -v bash` xpath1=`readelf -e $bash_path | grep Requesting | sed 's/.$//' | rev | cut -d" " -f1 | rev` xpath=`realpath $xpath1` if test ! -f "$xpath" ; then AC_MSG_ERROR([Cant find the dynamic linker]) fi echo "dynamic linker is.....$xpath" AC_DEFINE_UNQUOTED(SYSTEM_LD_SO, ["$xpath"], [dynamic linker]) ]) fapolicyd-1.3.4/m4/rpm_path.m4000066400000000000000000000002321470754500200160660ustar00rootroot00000000000000AC_DEFUN([RPMDB_PATH], [ xpath=`rpm --eval '%_dbpath'` echo "rpmdb path is.....$xpath" AC_DEFINE_UNQUOTED(RPM_DB_PATH, ["$xpath"], [rpmdb path]) ]) fapolicyd-1.3.4/rules.d/000077500000000000000000000000001470754500200150515ustar00rootroot00000000000000fapolicyd-1.3.4/rules.d/10-languages.rules000066400000000000000000000007101470754500200203070ustar00rootroot00000000000000# This file contains the list of all identified languages on the system %languages=application/x-bytecode.ocaml,application/x-bytecode.python,application/java-archive,text/x-java,application/x-java-applet,application/javascript,text/javascript,text/x-awk,text/x-gawk,text/x-lisp,application/x-elc,text/x-lua,text/x-m4,text/x-nftables,text/x-perl,text/x-php,text/x-python,text/x-R,text/x-ruby,text/x-script.guile,text/x-tcl,text/x-luatex,text/x-systemtap fapolicyd-1.3.4/rules.d/20-dracut.rules000066400000000000000000000002031470754500200176210ustar00rootroot00000000000000# Carve out an exception for dracut's initramfs building allow perm=any uid=0 : dir=/var/tmp/ allow perm=any uid=0 trust=1 : all fapolicyd-1.3.4/rules.d/21-updaters.rules000066400000000000000000000002741470754500200201770ustar00rootroot00000000000000# We have to carve out an exception for the system updaters # or things go very bad (deadlock). allow perm=open exe=/usr/bin/rpm : all allow perm=open exe=%python3_path% comm=dnf : all fapolicyd-1.3.4/rules.d/22-buildroot.rules000066400000000000000000000017411470754500200203540ustar00rootroot00000000000000# Exception for software builders. # # Software builders create lots of files. Since they were all just created, # fapolicyd has never seen them before and expensive integrity checking has # to be done which will fail because they are all untrusted. Since it's single # purpose is building applications, we need to carve out a permissive domain # for it to operate in. # # The buildroot is protected by file permissions. Noone except root and the # mock account can write to the buildroot. The buildroot is wiped clean after # a build so that nothing can persist. What the following rules say is that we # are trusting the mock account to create and access anything in it's buildroot. # Otherwise, the mock account can use anything that is trusted for any purpose # anywhere else. It is still restricted by user id permissions. # # The following uid and dir should be adjusted to fit your configuration. allow perm=any uid=mock : dir=/home/mock/rpmbuild allow perm=any uid=mock trust=1 : all fapolicyd-1.3.4/rules.d/30-patterns.rules000066400000000000000000000003411470754500200202030ustar00rootroot00000000000000# This file contains the list of all patterns. Only the ld_so pattern # is enabled by default. deny_audit perm=any pattern=ld_so : all #deny_audit perm=any pattern=ld_preload : all #deny_audit perm=any pattern=static : all fapolicyd-1.3.4/rules.d/40-bad-elf.rules000066400000000000000000000001451470754500200176400ustar00rootroot00000000000000# Do not allow malformed ELF even if trusted deny_audit perm=any all : ftype=application/x-bad-elf fapolicyd-1.3.4/rules.d/41-shared-obj.rules000066400000000000000000000003701470754500200203650ustar00rootroot00000000000000# Only allow known ELF libs - this is ahead of executable because typical # executable is linked with a dozen or more libraries. allow perm=open all : ftype=application/x-sharedlib trust=1 deny_audit perm=open all : ftype=application/x-sharedlib fapolicyd-1.3.4/rules.d/42-trusted-elf.rules000066400000000000000000000001071470754500200206040ustar00rootroot00000000000000# Allow trusted programs to execute allow perm=execute all : trust=1 fapolicyd-1.3.4/rules.d/43-known-elf.rules000066400000000000000000000006171470754500200202550ustar00rootroot00000000000000# Only allow known ELF Applications allow perm=execute all : ftype=application/x-executable trust=1 deny_audit perm=execute all : ftype=application/x-executable # This is a workaround for kernel thinking this is being executed because it # occurs during the execve call for an ELF binary. We catch actual execution # in the ld_so pattern rule. allow perm=execute all : path=%ld_so_path% trust=1 fapolicyd-1.3.4/rules.d/70-trusted-lang.rules000066400000000000000000000002171470754500200207620ustar00rootroot00000000000000# Allow any program to open trusted language files allow perm=open all : ftype=%languages trust=1 deny_audit perm=any all : ftype=%languages fapolicyd-1.3.4/rules.d/71-known-python.rules000066400000000000000000000004211470754500200210220ustar00rootroot00000000000000# Only allow system python executables and libs allow perm=any all : ftype=text/x-python trust=1 allow perm=open all : ftype=application/x-bytecode.python trust=1 deny_audit perm=any all : ftype=text/x-python deny_audit perm=any all : ftype=application/x-bytecode.python fapolicyd-1.3.4/rules.d/72-shell.rules000066400000000000000000000001401470754500200174550ustar00rootroot00000000000000# Allow all shell script execution and sourcing allow perm=any all : ftype=text/x-shellscript fapolicyd-1.3.4/rules.d/73-known-perl.rules000066400000000000000000000001741470754500200204520ustar00rootroot00000000000000# Only allow system perl files allow perm=any all : ftype=text/x-perl trust=1 deny_audit perm=any all : ftype=text/x-perl fapolicyd-1.3.4/rules.d/74-known-ocaml.rules000066400000000000000000000002371470754500200206040ustar00rootroot00000000000000# Only allow system Ocaml files allow perm=any all : ftype=application/x-bytecode.ocaml trust=1 deny_audit perm=any all : ftype=application/x-bytecode.ocaml fapolicyd-1.3.4/rules.d/75-known-php.rules000066400000000000000000000001711470754500200202760ustar00rootroot00000000000000# Only allow system PHP files allow perm=any all : ftype=text/x-php trust=1 deny_audit perm=any all : ftype=text/x-php fapolicyd-1.3.4/rules.d/76-known-ruby.rules000066400000000000000000000001741470754500200204740ustar00rootroot00000000000000# Only allow system ruby files allow perm=any all : ftype=text/x-ruby trust=1 deny_audit perm=any all : ftype=text/x-ruby fapolicyd-1.3.4/rules.d/77-known-lua.rules000066400000000000000000000001711470754500200202720ustar00rootroot00000000000000# Only allow system lua files allow perm=any all : ftype=text/x-lua trust=1 deny_audit perm=any all : ftype=text/x-lua fapolicyd-1.3.4/rules.d/90-deny-execute.rules000066400000000000000000000001141470754500200207460ustar00rootroot00000000000000# Deny execution for anything untrusted deny_audit perm=execute all : all fapolicyd-1.3.4/rules.d/91-deny-lang.rules000066400000000000000000000001321470754500200202260ustar00rootroot00000000000000# Deny all languages not explicitly enabled deny_audit perm=open all : ftype=%languages fapolicyd-1.3.4/rules.d/95-allow-open.rules000066400000000000000000000001051470754500200204310ustar00rootroot00000000000000# Allow everything else to open any file allow perm=open all : all fapolicyd-1.3.4/rules.d/Makefile.am000066400000000000000000000025731470754500200171140ustar00rootroot00000000000000# Makefile.am -- # Copyright 2022-24 Red Hat Inc. # All Rights Reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor # Boston, MA 02110-1335, USA. # # Authors: # Steve Grubb # CONFIG_CLEAN_FILES = *.rej *.orig EXTRA_DIST = README-rules 10-languages.rules 20-dracut.rules \ 21-updaters.rules 22-buildroot.rules 30-patterns.rules \ 40-bad-elf.rules 41-shared-obj.rules 42-trusted-elf.rules \ 43-known-elf.rules \ 70-trusted-lang.rules 71-known-python.rules 72-shell.rules \ 73-known-perl.rules 74-known-ocaml.rules 75-known-php.rules \ 76-known-ruby.rules 77-known-lua.rules \ 90-deny-execute.rules 91-deny-lang.rules 95-allow-open.rules rulesdir = $(datadir)/fapolicyd/sample-rules dist_rules_DATA = $(EXTRA_DIST) fapolicyd-1.3.4/rules.d/README-rules000066400000000000000000000027221470754500200170640ustar00rootroot00000000000000This group of rules are meant to be used with the fagenrules program. The fagenrules program expects rules to be located in /etc/fapolicyd/rules.d/ The rules will get processed in a specific order based on their natural sort order. To make things easier to use, the files in this directory are organized into groups with the following meanings: 10 - macros 20 - loop holes 30 - patterns 40 - ELF rules 50 - user/group access rules 60 - application access rules 70 - language rules 80 - trusted execute 90 - general open access to documents that should be thought out and individual files copied to /etc/fapolicyd/rules.d/ Once you have the rules in the rules.d directory, you can load them by running fagenrules --load You can reconstruct the old policy files by including the following: fapolicyd.rules.known-libs -------------------------- 10-languages.rules 20-dracut.rules 21-updaters.rules 30-patterns.rules 40-bad-elf.rules 41-shared-obj.rules 42-trusted-elf.rules 70-trusted-lang.rules 72-shell.rules 90-deny-execute.rules 95-allow-open.rules fapolicyd.rules.restrictive --------------------------- 10-languages.rules 20-dracut.rules 21-updaters.rules 30-patterns.rules 40-bad-elf.rules 41-shared-obj.rules 43-known-elf.rules 71-known-python.rules 72-shell.rules 73-known-perl.rules (optional) 74-known-ocaml.rules (optiona) 75-known-php.rules (optional) 76-known-ruby.rules (optional) 77-known-lua.rules (optional) 90-deny-execute.rules 91-deny-lang.rules 95-allow-open.rules fapolicyd-1.3.4/src/000077500000000000000000000000001470754500200142645ustar00rootroot00000000000000fapolicyd-1.3.4/src/Makefile.am000066400000000000000000000045331470754500200163250ustar00rootroot00000000000000SUBDIRS = tests CONFIG_CLEAN_FILES = *.loT *.rej *.orig AM_CPPFLAGS = \ -I${top_srcdir} \ -I${top_srcdir}/src/library sbin_PROGRAMS = fapolicyd fapolicyd-cli lib_LTLIBRARIES= libfapolicyd.la fapolicyd_CFLAGS = -fPIE -DPIE -pthread -g -W -Wall -Wshadow -Wundef -Wno-unused-result -Wno-unused-parameter -D_GNU_SOURCE fapolicyd_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now libfapolicyd_la_SOURCES = \ library/avl.c \ library/avl.h \ library/attr-sets.c \ library/attr-sets.h \ library/backend-manager.c \ library/backend-manager.h \ library/conf.h \ library/database.c \ library/database.h \ library/daemon-config.c \ library/daemon-config.h \ library/escape.c \ library/escape.h \ library/event.c \ library/event.h \ library/fapolicyd-defs.h \ library/fapolicyd-backend.h \ library/fd-fgets.c \ library/fd-fgets.h \ library/file.c \ library/file.h \ library/file-backend.c \ library/gcc-attributes.h \ library/llist.c \ library/llist.h \ library/lru.c \ library/lru.h \ library/message.c \ library/message.h \ library/nv.h \ library/object-attr.c \ library/object-attr.h \ library/object.c \ library/object.h \ library/paths.h \ library/policy.c \ library/policy.h \ library/process.c \ library/process.h \ library/queue.c \ library/queue.h \ library/rules.c \ library/rules.h \ library/subject-attr.c \ library/subject-attr.h \ library/subject.c \ library/subject.h \ library/stack.c \ library/stack.h \ library/string-util.c \ library/string-util.h \ library/trust-file.c \ library/trust-file.h if WITH_RPM libfapolicyd_la_SOURCES += \ library/rpm-backend.c \ library/filter.c \ library/filter.h endif if WITH_DEB libfapolicyd_la_SOURCES += library/deb-backend.c fapolicyd_CFLAGS += -DLIBDPKG_VOLATILE_API fapolicyd_LDFLAGS += -ldpkg endif if NEED_MD5 libfapolicyd_la_SOURCES += \ library/md5-backend.c \ library/md5-backend.h endif fapolicyd_cli_CFLAGS = $(fapolicyd_CFLAGS) fapolicyd_cli_LDFLAGS = $(fapolicyd_LDFLAGS) libfapolicyd_la_CFLAGS = $(fapolicyd_CFLAGS) libfapolicyd_la_LDFLAGS = $(fapolicyd_LDFLAGS) -lpthread fapolicyd_SOURCES = \ daemon/fapolicyd.c \ daemon/mounts.c \ daemon/mounts.h \ daemon/notify.c \ daemon/notify.h fapolicyd_LDADD = libfapolicyd.la fapolicyd_LDFLAGS += -static fapolicyd_cli_SOURCES = \ cli/fapolicyd-cli.c \ cli/file-cli.c \ cli/file-cli.h fapolicyd_cli_LDADD = libfapolicyd.la fapolicyd-1.3.4/src/cli/000077500000000000000000000000001470754500200150335ustar00rootroot00000000000000fapolicyd-1.3.4/src/cli/fapolicyd-cli.c000066400000000000000000000532041470754500200177220ustar00rootroot00000000000000/* * fapolicy-cli.c - CLI tool for fapolicyd * Copyright (c) 2019-2022 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka * Steve Grubb * Zoltan Fridrich */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "policy.h" #include "database.h" #include "file-cli.h" #include "fapolicyd-backend.h" #include "string-util.h" #include "daemon-config.h" #include "message.h" #include "llist.h" #include "fd-fgets.h" #include "paths.h" static const char *usage = "Fapolicyd CLI Tool\n\n" "--check-config Check the daemon config for syntax errors\n" "--check-path Check files in $PATH against the trustdb for problems\n" "--check-status Dump the deamon's internal performance statistics\n" "--check-trustdb Check the trustdb against files on disk for problems\n" "--check-watch_fs Check watch_fs against currently mounted file systems\n" "-d, --delete-db Delete the trust database\n" "-D, --dump-db Dump the trust database contents\n" "-f, --file cmd path Manage the file trust database\n" "--trust-file file Use after --file to specify trust file\n" "-h, --help Prints this help message\n" "-t, --ftype file-path Prints out the mime type of a file\n" "-l, --list Prints a list of the daemon's rules with numbers\n" "-u, --update Notifies fapolicyd to perform update of database\n" "-r, --reload-rules Notifies fapolicyd to perform reload of rules\n" ; static struct option long_opts[] = { {"check-config",0, NULL, 1 }, {"check-watch_fs",0, NULL, 2 }, {"check-trustdb",0, NULL, 3 }, {"check-status",0, NULL, 4 }, {"check-path", 0, NULL, 5 }, {"delete-db", 0, NULL, 'd'}, {"dump-db", 0, NULL, 'D'}, {"file", 1, NULL, 'f'}, {"help", 0, NULL, 'h'}, {"ftype", 1, NULL, 't'}, {"list", 0, NULL, 'l'}, {"update", 0, NULL, 'u'}, {"reload-rules", 0, NULL, 'r'}, { NULL, 0, NULL, 0 } }; volatile atomic_bool stop = 0; // Library needs this unsigned int debug_mode = 0; // Library needs this unsigned int permissive = 0; // Library needs this typedef enum _reload_code { DB, RULES} reload_code; static char *get_line(FILE *f, unsigned *lineno) { char *line = NULL; size_t len = 0; while (getline(&line, &len, f) != -1) { /* remove newline */ char *ptr = strchr(line, 0x0a); if (ptr) *ptr = 0; return line; } free(line); return NULL; } static int do_delete_db(void) { if (unlink_db()) return 1; return 0; } // This function opens the trust db and iterates over the entries. // It returns a 0 on success and non-zero on failure static int do_dump_db(void) { int rc; MDB_env *env; MDB_txn *txn; MDB_dbi dbi; MDB_stat status; MDB_cursor *cursor; MDB_val key, val; rc = mdb_env_create(&env); if (rc) { fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc)); return 1; } mdb_env_set_maxdbs(env, 2); rc = mdb_env_open(env, DB_DIR, MDB_RDONLY|MDB_NOLOCK, 0660); if (rc) { fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc)); rc = 1; goto env_close; } rc = mdb_env_stat(env, &status); if (rc) { fprintf(stderr, "mdb_env_stat failed, error %d %s\n", rc, mdb_strerror(rc)); rc = 1; goto env_close; } if (status.ms_entries == 0) { printf("Trust database is empty\n"); goto env_close; // Note: rc is 0 to get here } rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); if (rc) { fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); rc = 1; goto env_close; } rc = mdb_dbi_open(txn, DB_NAME, MDB_DUPSORT, &dbi); if (rc) { fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc)); rc = 1; goto txn_abort; } rc = mdb_cursor_open(txn, dbi, &cursor); if (rc) { fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); rc = 1; goto txn_abort; } rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST); if (rc) { fprintf(stderr, "mdb_cursor_get failed, error %d %s\n", rc, mdb_strerror(rc)); rc = 1; goto txn_abort; } do { char *path, *data, sha[65]; unsigned int tsource; off_t size; const char *source; path = malloc(key.mv_size+1); if (!path) continue; memcpy(path, key.mv_data, key.mv_size); path[key.mv_size] = 0; data = malloc(val.mv_size+1); if (!data) { free(path); continue; } memcpy(data, val.mv_data, val.mv_size); data[val.mv_size] = 0; if (sscanf(data, DATA_FORMAT, &tsource, &size, sha) != 3) { free(data); free(path); continue; } source = lookup_tsource(tsource); printf("%s %s %lu %s\n", source, path, size, sha); free(data); free(path); // Try to get the duplicate. If doesn't exist, get the next one rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT_DUP); if (rc == MDB_NOTFOUND) rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT_NODUP); } while (rc == 0); rc = 0; mdb_cursor_close(cursor); mdb_close(env, dbi); txn_abort: mdb_txn_abort(txn); env_close: mdb_env_close(env); return rc; } static int do_file_add(int argc, char * const argv[]) { char full_path[PATH_MAX] = { 0 }; if (argc == 1) { if (!realpath(argv[0], full_path)) return 3; return file_append(full_path, NULL); } if (argc == 3) { if (!realpath(argv[0], full_path)) return 3; if (strcmp("--trust-file", argv[1])) return 2; return file_append(full_path, argv[2]); } return 2; } static int do_file_delete(int argc, char * const argv[]) { char full_path[PATH_MAX] = { 0 }; if (argc == 1) { if (!realpath(argv[0], full_path)) return 3; return file_delete(full_path, NULL); } if (argc == 3) { if (!realpath(argv[0], full_path)) return 3; if (strcmp("--trust-file", argv[1])) return 2; return file_delete(full_path, argv[2]); } return 2; } static int do_file_update(int argc, char * const argv[]) { char full_path[PATH_MAX] = { 0 }; if (argc == 0) return file_update("/", NULL); if (argc == 1) { if (!realpath(argv[0], full_path)) return 3; return file_update(full_path, NULL); } if (argc == 2) { if (strcmp("--trust-file", argv[0])) return 2; return file_update("/", argv[1]); } if (argc == 3) { if (!realpath(argv[0], full_path)) return 3; if (strcmp("--trust-file", argv[1])) return 2; return file_update(full_path, argv[2]); } return 2; } static int do_manage_files(int argc, char * const argv[]) { int rc = 0; if (argc < 1 || argc > 4) { fprintf(stderr, "Wrong number of arguments\n"); fprintf(stderr, "\n%s", usage); return 1; } if (!strcmp("add", argv[0])) rc = do_file_add(argc - 1, argv + 1); else if (!strcmp("delete", argv[0])) rc = do_file_delete(argc - 1, argv + 1); else if (!strcmp("update", argv[0])) rc = do_file_update(argc - 1, argv + 1); else { fprintf(stderr, "%s is not a valid option, choose one of add|delete|update\n", argv[0]); fprintf(stderr, "\n%s", usage); return 1; } switch (rc) { case 0: // no error return 0; case 2: // args error fprintf(stderr, "Wrong number of arguments\n"); fprintf(stderr, "\n%s", usage); break; case 3: // realpath error fprintf(stderr, "Can't obtain realpath from: %s\n", argv[1]); fprintf(stderr, "\n%s", usage); break; default: // file function errors break; } return 1; } static int do_ftype(const char *path) { int fd; magic_t magic_cookie; const char *ptr = NULL; struct stat sb; // We need to open in non-blocking mode because if its a // fifo, it will hang the program. fd = open(path, O_RDONLY|O_NONBLOCK); if (fd < 0) { fprintf(stderr, "Cannot open %s - %s\n", path, strerror(errno)); exit(1); } unsetenv("MAGIC"); magic_cookie = magic_open(MAGIC_MIME|MAGIC_ERROR|MAGIC_NO_CHECK_CDF| MAGIC_NO_CHECK_ELF); if (magic_cookie == NULL) { fprintf(stderr, "Unable to init libmagic"); close(fd); return 1; } if (magic_load(magic_cookie, "/usr/share/fapolicyd/fapolicyd-magic.mgc:" "/usr/share/misc/magic.mgc") != 0) { fprintf(stderr, "Unable to load magic database"); close(fd); magic_close(magic_cookie); return 1; } // Change it back to blocking if (fcntl(fd, F_SETFL, 0)) { fprintf(stderr, "Unable to make fd blocking"); close(fd); magic_close(magic_cookie); return 1; } if (fstat(fd, &sb) == 0) { uint32_t elf = 0; // Only classify if a regular file if (sb.st_mode & S_IFREG) elf = gather_elf(fd, sb.st_size); if (elf) ptr = classify_elf_info(elf, path); else { ptr = classify_device(sb.st_mode); if (ptr == NULL) ptr = magic_descriptor(magic_cookie, fd); } } else fprintf(stderr, "Failed fstat (%s)", strerror(errno)); if (ptr) { char buf[80], *str; strncpy(buf, ptr, 79); buf[79] = 0; str = strchr(buf, ';'); if (str) *str = 0; printf("%s\n", buf); } else printf("unknown\n"); close(fd); magic_close(magic_cookie); return 0; } static int do_list(void) { unsigned count = 1, lineno = 0; FILE *f = fopen(OLD_RULES_FILE, "rm"); char *buf; if (f == NULL) { f = fopen(RULES_FILE, "rm"); if (f == NULL) { fprintf(stderr, "Cannot open rules file (%s)\n", strerror(errno)); return 1; } } else { FILE *t = fopen(RULES_FILE, "rm"); if (t) { fclose(t); fclose(f); fprintf(stderr, "Error - old and new rules file detected. " "Delete one or the other.\n"); return 1; } } while ((buf = get_line(f, &lineno))) { char *str = buf; lineno++; while (*str) { if (!isblank(*str)) break; str++; } if (*str == 0) // blank line goto next_iteration; if (*str == '#') //comment line goto next_iteration; if (*str == '%') { printf("-> %s\n", buf); goto next_iteration; } printf("%u. %s\n", count, buf); count++; next_iteration: free(buf); } fclose(f); return 0; } static int do_reload(int code) { int fd = -1; struct stat s; fd = open(fifo_path, O_WRONLY); if (fd == -1) { fprintf(stderr, "Open: %s -> %s\n", fifo_path, strerror(errno)); return 1; } if (fstat(fd, &s) == -1) { fprintf(stderr, "Stat: %s -> %s\n", fifo_path, strerror(errno)); close(fd); return 1; } else { if (!S_ISFIFO(s.st_mode)) { fprintf(stderr, "File: %s exists but it is not a pipe!\n", fifo_path); close(fd); return 1; } // we will require pipe to have 0660 permissions mode_t mode = s.st_mode & ~S_IFMT; if (mode != 0660) { fprintf(stderr, "File: %s has 0%o instead of 0660 \n", fifo_path, mode); close(fd); return 1; } } ssize_t ret = 0; char str[32] = {0}; if (code == DB) { snprintf(str, 32, "%c\n", RELOAD_TRUSTDB_COMMAND); ret = write(fd, "1\n", strlen(str)); } else if (code == RULES) { snprintf(str, 32, "%c\n", RELOAD_RULES_COMMAND); ret = write(fd, "3\n", strlen(str)); } if (ret == -1) { fprintf(stderr,"Write: %s -> %s\n", fifo_path, strerror(errno)); close(fd); return 1; } if (close(fd)) { fprintf(stderr,"Close: %s -> %s\n", fifo_path, strerror(errno)); return 1; } printf("Fapolicyd was notified\n"); return 0; } static const char *bad_filesystems[] = { "autofs", "bdev", "binder", "binfmt_misc", "bpf", "cgroup", "cgroup2", "configfs", "cpuset", "debugfs", "devpts", "devtmpfs", "efivarfs", "fusectl", "fuse.gvfsd-fuse", "fuse.portal", "hugetlbfs", "mqueue", "nsfs", "overlayfs", // No source of trust for what's in this "pipefs", "proc", "pstore", "resctrl", "rpc_pipefs", "securityfs", "selinuxfs", "sockfs", "sysfs", "tracefs" }; #define FS_NAMES (sizeof(bad_filesystems)/sizeof(bad_filesystems[0])) // Returns 1 if not a real file system and 0 if its a file system we can watch static int not_watchable(const char *type) { unsigned int i; for (i = 0; i < FS_NAMES; i++) if (strcmp(bad_filesystems[i], type) == 0) return 1; return 0; } // Returns 1 on error and 0 on success. // Finding unwatched file systems is not considered an error static int check_watch_fs(void) { conf_t config; char buf[PATH_MAX * 2], device[1025], point[4097]; char type[32], mntops[128]; int fs_req, fs_passno, fd, found = 0; list_t fs, mnt; char *ptr, *saved, *tmp; set_message_mode(MSG_STDERR, DBG_YES); if (load_daemon_config(&config)) { free_daemon_config(&config); return 1; } if (config.watch_fs == NULL) { fprintf(stderr, "File systems to watch is empty"); return 1; } tmp = strdup(config.watch_fs); list_init(&fs); ptr = strtok_r(tmp, ",", &saved); while (ptr) { // we do not care about the data list_append(&fs, strdup(ptr), strdup("0")); ptr = strtok_r(NULL, ",", &saved); } free(tmp); fd = open("/proc/mounts", O_RDONLY); if (fd < 0) { fprintf(stderr, "Unable to open mounts\n"); free_daemon_config(&config); list_empty(&fs); return 1; } fd_fgets_context_t *fd_fgets_context = fd_fgets_init(); if (!fd_fgets_context) { fprintf(stderr, "Failed fd_fgets_init\n"); free_daemon_config(&config); list_empty(&fs); close(fd); return 1; } // Build the list of mount point types list_init(&mnt); do { if (fd_fgets(fd_fgets_context, buf, sizeof(buf), fd)) { sscanf(buf, "%1024s %4096s %31s %127s %d %d\n", device,point, type, mntops, &fs_req, &fs_passno); // Some file systems are not watchable if (not_watchable(type)) continue; list_append(&mnt, strdup(type), strdup("0")); } } while (!fd_fgets_eof(fd_fgets_context)); fd_fgets_destroy(fd_fgets_context); close(fd); // Now search the list we just built for (list_item_t *lptr = mnt.first; lptr; lptr = lptr->next) { // See if the file system is watched if (list_contains(&fs, lptr->index) == 0) { found = 1; printf("%s not watched\n", (char *)lptr->index); // Remove the file system so that we get 1 report char *tmpfs = strdup(lptr->index); while (list_remove(&mnt, tmpfs)) ; free(tmpfs); // Start from the beginning lptr = mnt.first; } } free_daemon_config(&config); list_empty(&fs); list_empty(&mnt); if (found == 0) printf("Nothing appears missing\n"); return 0; } // Returns 0 = everything is OK, 1 = there is a problem static int verify_file(const char *path, off_t size, const char *sha) { int fd, warn_size = 0, warn_sha = 0; struct stat sb; fd = open(path, O_RDONLY); if (fd < 0) { printf("Can't open %s (%s)\n", path, strerror(errno)); return 1; } if (fstat(fd, &sb)) { printf("Can't stat %s (%s)\n", path, strerror(errno)); close(fd); return 1; } if (sb.st_size != size) warn_size = 1; char *sha_buf = get_hash_from_fd2(fd, sb.st_size, 1); close(fd); if (sha_buf == NULL || strcmp(sha, sha_buf)) warn_sha = 1; free(sha_buf); if (warn_size || warn_sha) { printf("%s miscompares: %s %s\n", path, warn_size ? "size" : "", warn_sha ? (strlen(sha) < 64 ? "is a sha1" : "sha256") : ""); return 1; } return 0; } static int check_trustdb(void) { conf_t config; int found = 0; set_message_mode(MSG_STDERR, DBG_NO); if (load_daemon_config(&config)) { free_daemon_config(&config); return 1; } set_message_mode(MSG_QUIET, DBG_NO); int rc = walk_database_start(&config); free_daemon_config(&config); if (rc) return 1; do { unsigned int tsource; // unused off_t size; char sha[65]; char path[448]; char data[80]; // Get the entry and format it for use. walkdb_entry_t *entry = walk_database_get_entry(); snprintf(path, sizeof(path), "%.*s", (int) entry->path.mv_size, (char *) entry->path.mv_data); snprintf(data, sizeof(data), "%.*s", (int) entry->data.mv_size, (char *) entry->data.mv_data); if (sscanf(data, DATA_FORMAT, &tsource, &size, sha) != 3) { fprintf(stderr, "%s data entry is corrupted\n", path); continue; } if (verify_file(path, size, sha)) found =1 ; } while (walk_database_next()); walk_database_finish(); if (found == 0) puts("No problems found"); return 0; } static int is_link(const char *path) { struct stat sb; if (lstat(path, &sb)) { fprintf(stderr, "Can't stat %s\n", path); return -1; } if (S_ISLNK(sb.st_mode)) return 1; return 0; } // Check that the file is in the trust db static int path_found = 0; static int check_file(const char *fpath, const struct stat *sb, int typeflag_unused __attribute__ ((unused)), struct FTW *s_unused __attribute__ ((unused))) { int ret = FTW_CONTINUE; if (S_ISREG(sb->st_mode) == 0) return ret; int fd = open(fpath, O_RDONLY|O_CLOEXEC); if (fd >= 0) { struct file_info info; info.size = sb->st_size; if (check_trust_database(fpath, &info, fd) != 1) { path_found = 1; fprintf(stderr, "%s is not trusted\n", fpath); } close(fd); } return ret; } static int check_path(void) { conf_t config; char *ptr, *saved; const char *env_path = getenv("PATH"); if (env_path == NULL) { puts("PATH not found"); return 1; } set_message_mode(MSG_STDERR, DBG_NO); if (load_daemon_config(&config)) { free_daemon_config(&config); return 1; } set_message_mode(MSG_QUIET, DBG_NO); init_database(&config); char *path = strdup(env_path); ptr = strtok_r(path, ":", &saved); while (ptr) { if (is_link(path)) goto next; nftw(ptr, check_file, 1024, FTW_PHYS); next: ptr = strtok_r(NULL, ":", &saved); } stop = 1; // Need this to terminate update thread free(path); close_database(); free_daemon_config(&config); if (path_found == 0) puts("No problems found"); return 0; } static int do_status_report(void) { const char *reason = "no pid file"; fd_fgets_context_t *fd_fgets_context = fd_fgets_init(); if (!fd_fgets_context) return 1; // open pid file int pidfd = open(pidfile, O_RDONLY); if (pidfd >= 0) { char pid_buf[16]; // read contents if (fd_fgets(fd_fgets_context, pid_buf, sizeof(pid_buf), pidfd)) { int rpt_fd; unsigned int pid, tries = 0; char exe_buf[64]; // convert to integer errno = 0; pid = strtoul(pid_buf, NULL, 10); if (errno) { reason = "bad pid in pid file"; goto err_out; } // verify it really is fapolicyd if (get_program_from_pid(pid, sizeof(exe_buf), exe_buf) == NULL) { reason = "can't read proc file"; goto err_out; } if (strcmp(exe_buf, DAEMON_PATH)) { reason = "pid file doesn't point to fapolicyd"; goto err_out; } // delete the old report unlink(STAT_REPORT); // send the signal for the report kill(pid, SIGUSR1); // Access a file to provoke a response int fd = open(CONFIG_FILE, O_RDONLY); if (fd >= 0) close(fd); retry: // wait for it sleep(1); // display the report rpt_fd = open(STAT_REPORT, O_RDONLY); if (rpt_fd < 0) { if (tries < 25) { tries++; goto retry; } else { reason = "timed out waiting for report"; goto err_out; } } fd_fgets_rewind(fd_fgets_context); do { char buf[80]; if (fd_fgets(fd_fgets_context, buf, sizeof(buf), rpt_fd)) write(1, buf, strlen(buf)); } while (!fd_fgets_eof(fd_fgets_context)); close(rpt_fd); } else reason = "can't read pid file"; close(pidfd); fd_fgets_destroy(fd_fgets_context); return 0; } err_out: fd_fgets_destroy(fd_fgets_context); if (pidfd >= 0) close(pidfd); printf("Can't find fapolicyd: %s\n", reason); return 1; } int main(int argc, char * const argv[]) { int opt, option_index, rc = 1; if (argc == 1) { fprintf(stderr, "Too few arguments\n\n"); fprintf(stderr, "%s", usage); return rc; } opt = getopt_long(argc, argv, "Ddf:ht:lur", long_opts, &option_index); switch (opt) { case 'd': if (argc > 2) goto args_err; rc = do_delete_db(); break; case 'D': if (argc > 2) goto args_err; rc = do_dump_db(); break; case 'f': if (argc > 6) goto args_err; // fapolicyd-cli, -f, | operation, path ... // skip the first two args rc = do_manage_files(argc-2, argv+2); break; case 'h': printf("%s", usage); rc = 0; break; case 't': if (argc > 3) goto args_err; rc = do_ftype(optarg); break; case 'l': if (argc > 2) goto args_err; rc = do_list(); break; case 'u': if (argc > 2) goto args_err; rc = do_reload(DB); break; case 'r': if (argc > 2) goto args_err; rc = do_reload(RULES); break; // Now the pure long options case 1: { // --check-config conf_t config; if (argc > 2) goto args_err; set_message_mode(MSG_STDERR, DBG_YES); if (load_daemon_config(&config)) { free_daemon_config(&config); fprintf(stderr, "Configuration errors reported\n"); return 1; } else { printf("Daemon config is OK\n"); free_daemon_config(&config); return 0; } } break; case 2: // --check-watch_fs if (argc > 2) goto args_err; return check_watch_fs(); break; case 3: // --check-trustdb if (argc > 2) goto args_err; return check_trustdb(); break; case 4: // --check-status if (argc > 2) goto args_err; return do_status_report(); break; case 5: // --check-path if (argc > 2) goto args_err; return check_path(); break; default: printf("%s", usage); rc = 1; } return rc; args_err: fprintf(stderr, "Too many arguments\n\n"); fprintf(stderr, "%s", usage); return rc; } fapolicyd-1.3.4/src/cli/file-cli.c000066400000000000000000000066771470754500200167030ustar00rootroot00000000000000/* * file-cli.c - implementation of CLI option file * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Zoltan Fridrich */ #include "config.h" #include #include #include #include #include #include #include #include "llist.h" #include "message.h" #include "string-util.h" #include "trust-file.h" #define FTW_NOPENFD 1024 #define FTW_FLAGS (FTW_ACTIONRETVAL | FTW_PHYS) list_t add_list; static int ftw_add_list_append(const char *fpath, const struct stat *sb __attribute__ ((unused)), int typeflag, struct FTW *ftwbuf __attribute__ ((unused))) { if (typeflag == FTW_F) list_append(&add_list, strdup(fpath), NULL); return FTW_CONTINUE; } /** * Load path into add_list. If path is a directory, * loads all regular files within the directory tree * * @param path Path to load into add_list * @return 0 on success, 1 on error */ static int add_list_load_path(const char *path) { int fd = open(path, O_RDONLY); if (fd < 0) { msg(LOG_ERR, "Cannot open %s", path); return 1; } struct stat sb; if (fstat(fd, &sb)) { msg(LOG_ERR, "Cannot stat %s", path); close(fd); return 1; } close(fd); if (S_ISDIR(sb.st_mode)) nftw(path, &ftw_add_list_append, FTW_NOPENFD, FTW_FLAGS); else list_append(&add_list, strdup(path), NULL); return 0; } int file_append(const char *path, const char *fname) { set_message_mode(MSG_STDERR, DBG_NO); list_init(&add_list); if (add_list_load_path(path)) return -1; trust_file_rm_duplicates_all(&add_list); if (add_list.count == 0) { msg(LOG_ERR, "After removing duplicates, there is nothing to add"); return 1; } char *dest = fname ? fapolicyd_strcat(TRUST_DIR_PATH, fname) : TRUST_FILE_PATH; if (dest == NULL) return -1; int rc = trust_file_append(dest, &add_list); list_empty(&add_list); if (fname) free(dest); return rc ? -1 : 0; } int file_delete(const char *path, const char *fname) { set_message_mode(MSG_STDERR, DBG_NO); int count = 0; if (fname) { char *file = fapolicyd_strcat(TRUST_DIR_PATH, fname); if (file) { count = trust_file_delete_path(file, path); free(file); } } else { count = trust_file_delete_path_all(path); } if (count == 0) msg(LOG_ERR, "%s is not in the trust database", path); return !count; } int file_update(const char *path, const char *fname) { set_message_mode(MSG_STDERR, DBG_NO); int count = 0; if (fname) { char *file = fapolicyd_strcat(TRUST_DIR_PATH, fname); if (file) { count = trust_file_update_path(file, path); free(file); } } else { count = trust_file_update_path_all(path); } if (count == 0) msg(LOG_ERR, "%s is not in the trust database", path); return !count; } fapolicyd-1.3.4/src/cli/file-cli.h000066400000000000000000000050041470754500200166670ustar00rootroot00000000000000/* * file-backend.h - Header file for CLI option file * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka * Zoltan Fridrich */ #ifndef FILE_CLI_H #define FILE_CLI_H /** * Append a path into the file trust database * * @param path Path to append into the file trust database * @param fname Filename where \p path should be written. If NULL, then * \p path is written into fapolicyd.trust file. Otherwise, * write \p path into file \p fname within the trust.d directory * @return 0 on success, -1 on error and 1 if \p path already exists in * the file trust database */ int file_append(const char *path, const char *fname); /** * Delete a path from the file trust database. * It matches all occurrances so that a directory may be passed and * all parts of it get deleted * * @param path Path to delete from the file trust database * @param fname Filename from which \p path should be deleted. If NULL, then * \p path is deleted from fapolicyd.trust file. Otherwise, * deletes \p path from file \p fname within the trust.d directory * @return 0 on success, non-zero if nothing got deleted */ int file_delete(const char *path, const char *fname); /** * Update a path in the file trust database. * It matches all occurrances so that a directory may be passed and * all parts of it get updated * * @param path Path to update in the file trust database * @param fname Filename in which \p path should be updated. If NULL, then * \p path is updated in fapolicyd.trust file. Otherwise, * updates \p path in file \p fname within the trust.d directory * @return 0 on success, non-zero if nothing got updated */ int file_update(const char *path, const char *fname); #endif fapolicyd-1.3.4/src/daemon/000077500000000000000000000000001470754500200155275ustar00rootroot00000000000000fapolicyd-1.3.4/src/daemon/fapolicyd.c000066400000000000000000000402711470754500200176510ustar00rootroot00000000000000/* * fapolicyd.c - Main file for the program * Copyright (c) 2016,2018-22 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* syscall numbers */ #include /* umask */ #include #include #include #include /* PATH_MAX */ #include #ifndef HAVE_GETTID #include #endif #include "notify.h" #include "policy.h" #include "event.h" #include "escape.h" #include "fd-fgets.h" #include "file.h" #include "database.h" #include "message.h" #include "daemon-config.h" #include "conf.h" #include "queue.h" #include "gcc-attributes.h" #include "avl.h" #include "paths.h" // Global program variables unsigned int debug_mode = 0, permissive = 0; // Signal handler notifications volatile atomic_bool stop = false, hup = false, run_stats = false; // Local variables static conf_t config; // This holds info about all file systems to watch struct fs_avl { avl_tree_t index; }; // This is the data about a specific file system to watch typedef struct fs_data { avl_t avl; // This has to be first const char *fs_name; } fs_data_t; static struct fs_avl filesystems; // List of mounts being watched static mlist *m = NULL; static void usage(void) NORETURN; #ifndef HAVE_GETTID pid_t gettid(void) { return syscall(SYS_gettid); } #endif static void install_syscall_filter(void) { scmp_filter_ctx ctx; int rc = -1; ctx = seccomp_init(SCMP_ACT_ALLOW); if (ctx == NULL) goto err_out; rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(execve), 0); if (rc < 0) goto err_out; #ifdef HAVE_FEXECVE # ifdef __NR_fexecve rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(fexecve), 0); if (rc < 0) goto err_out; # endif #endif rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EIO), SCMP_SYS(sendfile), 0); if (rc < 0) goto err_out; rc = seccomp_load(ctx); err_out: if (rc < 0) msg(LOG_ERR, "Failed installing seccomp filter"); seccomp_release(ctx); } static int cmp_fs(void *a, void *b) { return strcmp(((fs_data_t *)a)->fs_name, ((fs_data_t *)b)->fs_name); } static void free_filesystem(fs_data_t *s) { free((void *)s->fs_name); free((void *)s); } static void destroy_filesystem(void) { avl_t *cur = filesystems.index.root; fs_data_t *tmp =(fs_data_t *)avl_remove(&filesystems.index, cur); if ((avl_t *)tmp != cur) msg(LOG_DEBUG, "filesystem: removal of invalid node"); free_filesystem(tmp); } static void destroy_fs_list(void) { while (filesystems.index.root) destroy_filesystem(); } static int add_filesystem(fs_data_t *f) { fs_data_t *tmp=(fs_data_t *)avl_insert(&filesystems.index,(avl_t *)(f)); if (tmp) { if (tmp != f) { msg(LOG_DEBUG, "fs_list: duplicate filesystem found"); free_filesystem(f); } return 1; } return 0; } static fs_data_t *new_filesystem(const char *fs) { fs_data_t *tmp = malloc(sizeof(fs_data_t)); if (tmp) { tmp->fs_name = fs ? strdup(fs) : strdup(""); if (add_filesystem(tmp) != 0) return NULL; } return tmp; } static fs_data_t *find_filesystem(const char *f) { fs_data_t tmp; tmp.fs_name = f; return (fs_data_t *)avl_search(&filesystems.index, (avl_t *) &tmp); } static void init_fs_list(const char *watch_fs) { if (watch_fs == NULL) { msg(LOG_ERR, "File systems to watch is empty"); exit(1); } avl_init(&filesystems.index, cmp_fs); // Now parse up list and push into avl char *ptr, *saved, *tmp = strdup(watch_fs); ptr = strtok_r(tmp, ",", &saved); while (ptr) { new_filesystem(ptr); ptr = strtok_r(NULL, ",", &saved); } free(tmp); } static void term_handler(int sig __attribute__((unused))) { stop = true; } static void coredump_handler(int sig) { if (getpid() == gettid()) { unmark_fanotify(m); unlink_fifo(); signal(sig, SIG_DFL); kill(getpid(), sig); } else { /* * Fatal signals are usually delivered to the thread generating * them, if this is not main thread, raised the signal again to * handle it there, then wait forever to die. */ kill(getpid(), sig); for (;;) pause(); } } static void hup_handler(int sig __attribute__((unused))) { hup = true; } static void usr1_handler(int sig __attribute__((unused))) { run_stats = true; } /* * This function handles the reconfiguration of the daemon * after receiving a SIGHUP signal. */ static void reconfigure(void) { set_reload_rules(); set_reload_trust_database(); // TODO: Update configuration } // This is a workaround for https://bugzilla.redhat.com/show_bug.cgi?id=643031 #define UNUSED(x) (void)(x) #ifdef USE_RPM extern int rpmsqEnable (int signum, void *handler); int rpmsqEnable (int signum, void *handler) { UNUSED(signum); UNUSED(handler); return 0; } #endif static int write_pid_file(void) { int pidfd, len; char val[16]; len = snprintf(val, sizeof(val), "%u\n", getpid()); if (len <= 0) { msg(LOG_ERR, "Pid error (%s)", strerror(errno)); return 1; } pidfd = open(pidfile, O_CREAT | O_TRUNC | O_NOFOLLOW | O_WRONLY, 0644); if (pidfd < 0) { msg(LOG_ERR, "Unable to create pidfile (%s)", strerror(errno)); return 1; } if (write(pidfd, val, (unsigned int)len) != len) { msg(LOG_ERR, "Unable to write pidfile (%s)", strerror(errno)); close(pidfd); return 1; } close(pidfd); return 0; } static int become_daemon(void) { int fd; pid_t pid; pid = fork(); switch (pid) { case 0: // Child fd = open("/dev/null", O_RDWR); if (fd < 0) return -1; if (dup2(fd, 0) < 0) { close(fd); return -1; } if (dup2(fd, 1) < 0) { close(fd); return -1; } if (dup2(fd, 2) < 0) { close(fd); return -1; } close(fd); chdir("/"); if (setsid() < 0) return -1; break; case -1: return -1; break; default: // Parent _exit(0); break; } return 0; } // Returns 1 if we care about the entry and 0 if we do not static int check_mount_entry(const char *point, const char *type) { // Some we know we don't want if (strcmp(point, "/run") == 0) return 0; if (strncmp(point, "/sys", 4) == 0) return 0; if (find_filesystem(type)) return 1; else return 0; } static void handle_mounts(int fd) { char buf[PATH_MAX * 2], device[1025], point[4097]; char type[32], mntops[128]; int fs_req, fs_passno; if (m == NULL) { m = malloc(sizeof(mlist)); mlist_create(m); } // Rewind the descriptor lseek(fd, 0, SEEK_SET); fd_fgets_context_t * fd_fgets_context = fd_fgets_init(); if (!fd_fgets_context) return; mlist_mark_all_deleted(m); do { int rc = fd_fgets(fd_fgets_context, buf, sizeof(buf), fd); // Get a line if (rc > 0) { // Parse it sscanf(buf, "%1024s %4096s %31s %127s %d %d\n", device, point, type, mntops, &fs_req, &fs_passno); unescape_shell(device, strlen(device)); unescape_shell(point, strlen(point)); // Is this one that we care about? if (check_mount_entry(point, type)) { // Can we find it in the old list? if (mlist_find(m, point)) { // Mark no change m->cur->status = NO_CHANGE; } else mlist_append(m, point); } } else if (rc < 0) // Some kind of error - stop break; } while (!fd_fgets_eof(fd_fgets_context)); fd_fgets_destroy(fd_fgets_context); // update marks fanotify_update(m); } static void usage(void) { fprintf(stderr, "Usage: fapolicyd [--debug|--debug-deny] [--permissive] " "[--no-details]\n"); exit(1); } void do_stat_report(FILE *f, int shutdown) { fprintf(f, "Permissive: %s\n", config.permissive ? "true" : "false"); fprintf(f, "q_size: %u\n", config.q_size); q_report(f); decision_report(f); database_report(f); if (shutdown) fputs("\n", f); else do_cache_reports(f); } int already_running(void) { fd_fgets_context_t * fd_fgets_context = fd_fgets_init(); if (!fd_fgets_context) return 1; int pidfd = open(pidfile, O_RDONLY); if (pidfd >= 0) { char pid_buf[16]; if (fd_fgets(fd_fgets_context, pid_buf, sizeof(pid_buf), pidfd)) { int pid; char exe_buf[80], my_path[80]; // Get our path if (get_program_from_pid(getpid(), sizeof(exe_buf), my_path) == NULL) goto err_out; // shouldn't happen, but be safe // convert pidfile to integer errno = 0; pid = strtoul(pid_buf, NULL, 10); if (errno) goto err_out; // shouldn't happen, but be safe // verify it really is fapolicyd if (get_program_from_pid(pid, sizeof(exe_buf), exe_buf) == NULL) goto good; //if pid doesn't exist, we're OK // If the path doesn't have fapolicyd in it, we're OK if (strstr(exe_buf, "fapolicyd") == NULL) goto good; if (strcmp(exe_buf, my_path) == 0) goto err_out; // if the same, we need to exit // one last sanity check in case path is unexpected // for example: /sbin/fapolicyd & /home/test/fapolicyd if (pid != getpid()) goto err_out; good: fd_fgets_destroy(fd_fgets_context); close(pidfd); unlink(pidfile); return 0; } else msg(LOG_ERR, "fapolicyd pid file found but unreadable"); err_out: // At this point, we have a pid file, let's just assume it's alive // because if 2 are running, it deadlocks the machine fd_fgets_destroy(fd_fgets_context); close(pidfd); return 1; } fd_fgets_destroy(fd_fgets_context); return 0; // pid file doesn't exist, we're good to go } int main(int argc, const char *argv[]) { struct pollfd pfd[2]; struct sigaction sa; struct rlimit limit; setlocale(LC_TIME, ""); if (argc > 1 && strcmp(argv[1], "--help") == 0) usage(); set_message_mode(MSG_STDERR, debug_mode); if (load_daemon_config(&config)) { free_daemon_config(&config); msg(LOG_ERR, "Exiting due to bad configuration"); return 1; } permissive = config.permissive; for (int i=1; i < argc; i++) { if (strcmp(argv[i], "--debug") == 0) { debug_mode = 1; set_message_mode(MSG_STDERR, DBG_YES); } else if (strcmp(argv[i], "--debug-deny") == 0) { debug_mode = 2; set_message_mode(MSG_STDERR, DBG_YES); } else if (strcmp(argv[i], "--permissive") == 0) { permissive = 1; } else if (strcmp(argv[i], "--boost") == 0) { i++; msg(LOG_ERR, "boost value on the command line is" " deprecated - ignoring"); } else if (strcmp(argv[i], "--queue") == 0) { i++; msg(LOG_ERR, "queue value on the command line is" " deprecated - ignoring"); } else if (strcmp(argv[i], "--user") == 0) { i++; msg(LOG_ERR, "user value on the command line is" " deprecated - ignoring"); } else if (strcmp(argv[i], "--group") == 0) { i++; msg(LOG_ERR, "group value on the command line is" " deprecated - ignoring"); } else if (strcmp(argv[i], "--no-details") == 0) { config.detailed_report = 0; } else { msg(LOG_ERR, "unknown command option:%s\n", argv[i]); free_daemon_config(&config); usage(); } } if (already_running()) { msg(LOG_ERR, "fapolicyd is already running"); exit(1); } // Set a couple signal handlers sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = hup_handler; sigaction(SIGHUP, &sa, NULL); sa.sa_handler = coredump_handler; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGABRT, &sa, NULL); sigaction(SIGBUS, &sa, NULL); sigaction(SIGFPE, &sa, NULL); sigaction(SIGILL, &sa, NULL); sigaction(SIGSYS, &sa, NULL); sigaction(SIGTRAP, &sa, NULL); sigaction(SIGXCPU, &sa, NULL); sigaction(SIGXFSZ, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sa.sa_handler = usr1_handler; sigaction(SIGUSR1, &sa, NULL); /* These need to be last since they are used later */ sa.sa_handler = term_handler; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); // Bump up resources limit.rlim_cur = RLIM_INFINITY; limit.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_FSIZE, &limit); getrlimit(RLIMIT_NOFILE, &limit); if (limit.rlim_max >= 16384) limit.rlim_cur = limit.rlim_max; else limit.rlim_max = limit.rlim_cur = 16834; if (setrlimit(RLIMIT_NOFILE, &limit)) msg(LOG_WARNING, "Can't increase file number rlimit - %s", strerror(errno)); else msg(LOG_INFO,"Can handle %lu file descriptors", limit.rlim_cur); // get more time slices because everything is waiting on us errno = 0; nice(-config.nice_val); if (errno) msg(LOG_WARNING, "Couldn't adjust priority (%s)", strerror(errno)); // Load the rule configuration if (load_rules(&config)) exit(1); if (!debug_mode) { if (become_daemon() < 0) { msg(LOG_ERR, "Exiting due to failure daemonizing"); exit(1); } set_message_mode(MSG_SYSLOG, DBG_NO); openlog("fapolicyd", LOG_PID, LOG_DAEMON); } // Set the exit function so there is always a fifo cleanup if (atexit(unlink_fifo)) { msg(LOG_ERR, "Cannot set exit function"); exit(1); } // Setup filesystem to watch list init_fs_list(config.watch_fs); // Write the pid file for the init system write_pid_file(); // Set strict umask (void) umask( 0117 ); if (preconstruct_fifo(&config)) { unlink(pidfile); msg(LOG_ERR, "Cannot construct a pipe"); exit(1); } // If we are not going to be root, then setup necessary capabilities if (config.uid != 0) { capng_clear(CAPNG_SELECT_BOTH); capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_DAC_OVERRIDE, CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_SYS_NICE, CAP_SYS_RESOURCE, CAP_AUDIT_WRITE, -1); if (capng_change_id(config.uid, config.gid, CAPNG_DROP_SUPP_GRP)) { msg(LOG_ERR, "Cannot change to uid %d", config.uid); exit(1); } else msg(LOG_DEBUG, "Changed to uid %d", config.uid); } // Install seccomp filter to prevent escalation install_syscall_filter(); // Setup lru caches init_event_system(&config); // Init the database if (init_database(&config)) { destroy_event_system(); destroy_rules(); destroy_fs_list(); free_daemon_config(&config); unlink(pidfile); exit(1); } // Init the file test libraries file_init(); // Initialize the file watch system pfd[0].fd = open("/proc/mounts", O_RDONLY); pfd[0].events = POLLPRI; handle_mounts(pfd[0].fd); pfd[1].fd = init_fanotify(&config, m); pfd[1].events = POLLIN; msg(LOG_INFO, "Starting to listen for events"); while (!stop) { int rc; if (hup) { hup = false; msg(LOG_DEBUG, "Got SIGHUP"); reconfigure(); } rc = poll(pfd, 2, -1); #ifdef DEBUG msg(LOG_DEBUG, "Main poll interrupted"); #endif if (rc < 0) { if (errno == EINTR) continue; else { msg(LOG_ERR, "Poll error (%s)\n", strerror(errno)); exit(1); } } else if (rc > 0) { if (pfd[1].revents & POLLIN) { handle_events(); } if (pfd[0].revents & POLLPRI) { msg(LOG_DEBUG, "Mount change detected"); handle_mounts(pfd[0].fd); } // This will always need to be here as long as we // link against librpm. Turns out that librpm masks // signals to prevent corrupted databases during an // update. Since we only do read access, we can turn // them back on. #ifdef USE_RPM sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); #endif } } msg(LOG_INFO, "shutting down..."); shutdown_fanotify(m); close(pfd[0].fd); mlist_clear(m); free(m); file_close(); close_database(); unlink(pidfile); // Reinstate the strict umask in case rpm messed with it (void) umask( 0237 ); if (config.do_stat_report) { FILE *f = fopen(REPORT, "w"); if (f == NULL) msg(LOG_WARNING, "Cannot create usage report"); else { do_stat_report(f, 1); run_usage_report(&config, f); fclose(f); } } destroy_event_system(); destroy_rules(); destroy_fs_list(); free_daemon_config(&config); return 0; } fapolicyd-1.3.4/src/daemon/mounts.c000066400000000000000000000051671470754500200172310ustar00rootroot00000000000000/* * mounts.c - Minimal linked list set of mount points * Copyright (c) 2019 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include #include #include "mounts.h" void mlist_create(mlist *m) { m->head = NULL; m->cur = NULL; } static void mlist_last(mlist *m) { register mnode* window; if (m->head == NULL) return; window = m->head; while (window->next) window = window->next; m->cur = window; } // Returns 0 on success and 1 on error int mlist_append(mlist *m, const char *p) { mnode* newnode; if (p) { newnode = malloc(sizeof(mnode)); if (newnode == NULL) return 1; newnode->path = strdup(p); newnode->status = ADD; } else return 1; newnode->next = NULL; mlist_last(m); // if we are at top, fix this up if (m->head == NULL) m->head = newnode; else // Otherwise add pointer to newnode m->cur->next = newnode; // make newnode current m->cur = newnode; return 0; } const char *mlist_first(mlist *m) { m->cur = m->head; if (m->cur == NULL) return NULL; return m->cur->path; } const char *mlist_next(mlist *m) { if (m->cur == NULL) return NULL; m->cur = m->cur->next; if (m->cur == NULL) return NULL; return m->cur->path; } void mlist_mark_all_deleted(mlist *m) { register mnode *n = m->head; while (n) { n->status = DELETE; n = n->next; } } int mlist_find(mlist *m, const char *p) { register mnode *n = m->head; while (n) { if (strcmp(p, n->path) == 0) { m->cur = n; return 1; } n = n->next; } return 0; } void mlist_clear(mlist *m) { mnode* nextnode; register mnode* current; current = m->head; while (current) { nextnode=current->next; free((void *)current->path); free((void *)current); current=nextnode; } m->head = NULL; m->cur = NULL; } fapolicyd-1.3.4/src/daemon/mounts.h000066400000000000000000000026561470754500200172360ustar00rootroot00000000000000/* * mounts.h - Header file for mounts.c * Copyright (c) 2019 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef MOUNTS_HEADER #define MOUNTS_HEADER typedef enum { NO_CHANGE, ADD, DELETE } change_t; typedef struct _mnode{ const char *path; change_t status; struct _mnode *next; // Next node pointer } mnode; typedef struct { mnode *head; // List head mnode *cur; // Pointer to current node } mlist; void mlist_create(mlist *m); const char *mlist_first(mlist *m); const char *mlist_next(mlist *m); void mlist_mark_all_deleted(mlist *l); int mlist_find(mlist *m, const char *p); int mlist_append(mlist *m, const char *p); void mlist_clear(mlist *m); #endif fapolicyd-1.3.4/src/daemon/notify.c000066400000000000000000000324001470754500200172020ustar00rootroot00000000000000/* * notify.c - functions handle recieving and enqueuing events * Copyright (c) 2016-18,2022-24 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #include "config.h" /* Needed to get O_LARGEFILE definition */ #include #include #include #include #include #include #include #include #include #include #include #include #include "policy.h" #include "event.h" #include "message.h" #include "queue.h" #include "mounts.h" #include "paths.h" #define FANOTIFY_BUFFER_SIZE 8192 #define MAX_EVENTS 4 // External variables extern volatile atomic_bool stop, run_stats; extern unsigned int permissive; // Local variables static pid_t our_pid; static struct queue *q = NULL; static pthread_t decision_thread; static pthread_t deadmans_switch_thread; static pthread_mutexattr_t decision_lock_attr; static pthread_mutex_t decision_lock; static pthread_cond_t do_decision; static pthread_condattr_t rpt_timer_attr; static volatile atomic_bool events_ready; static volatile atomic_int alive = 1; static int fd = -1; static int rpt_timer_fd = -1; static uint64_t mask; static unsigned int mark_flag; static unsigned int rpt_interval; // External functions void do_stat_report(FILE *f, int shutdown); // Local functions static void *decision_thread_main(void *arg); static void *deadmans_switch_thread_main(void *arg); int init_fanotify(const conf_t *conf, mlist *m) { const char *path; // Get inter-thread queue ready q = q_open(conf->q_size); if (q == NULL) { msg(LOG_ERR, "Failed setting up queue (%s)", strerror(errno)); exit(1); } our_pid = getpid(); fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | #ifdef USE_AUDIT FAN_ENABLE_AUDIT | #endif FAN_NONBLOCK, O_RDONLY | O_LARGEFILE | O_CLOEXEC | O_NOATIME); #ifdef USE_AUDIT // We will retry without the ENABLE_AUDIT to see if THAT is supported if (fd < 0 && errno == EINVAL) { fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK, O_RDONLY | O_LARGEFILE | O_CLOEXEC | O_NOATIME); if (fd >= 0) policy_no_audit(); } #endif if (fd < 0) { msg(LOG_ERR, "Failed opening fanotify fd (%s)", strerror(errno)); exit(1); } // Start decision thread so its ready when first event comes pthread_mutexattr_init(&decision_lock_attr); pthread_mutexattr_settype(&decision_lock_attr, PTHREAD_MUTEX_ERRORCHECK); pthread_mutex_init(&decision_lock, &decision_lock_attr); pthread_condattr_init(&rpt_timer_attr); pthread_condattr_setclock(&rpt_timer_attr, CLOCK_MONOTONIC); pthread_cond_init(&do_decision, &rpt_timer_attr); rpt_interval = conf->report_interval; events_ready = false; pthread_create(&decision_thread, NULL, decision_thread_main, NULL); pthread_create(&deadmans_switch_thread, NULL, deadmans_switch_thread_main, NULL); mask = FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM; #if defined HAVE_DECL_FAN_MARK_FILESYSTEM && HAVE_DECL_FAN_MARK_FILESYSTEM != 0 if (conf->allow_filesystem_mark) mark_flag = FAN_MARK_FILESYSTEM; else mark_flag = FAN_MARK_MOUNT; #else if (conf->allow_filesystem_mark) msg(LOG_ERR, "allow_filesystem_mark is unsupported for this kernel - ignoring"); mark_flag = FAN_MARK_MOUNT; #endif // Iterate through the mount points and add a mark path = mlist_first(m); while (path) { retry_mark: if (fanotify_mark(fd, FAN_MARK_ADD | mark_flag, mask, -1, path) == -1) { /* * The FAN_OPEN_EXEC_PERM mask is not supported by * all kernel releases prior to 5.0. Retry setting * up the mark using only the legacy FAN_OPEN_PERM * mask. */ if (errno == EINVAL && mask & FAN_OPEN_EXEC_PERM) { msg(LOG_INFO, "Kernel doesn't support OPEN_EXEC_PERM"); mask = FAN_OPEN_PERM; goto retry_mark; } msg(LOG_ERR, "Error (%s) adding fanotify mark for %s", strerror(errno), path); exit(1); } msg(LOG_DEBUG, "added %s mount point", path); path = mlist_next(m); } return fd; } void fanotify_update(mlist *m) { // Make sure fanotify_init has run if (fd < 0) return; if (m->head == NULL) return; mnode *cur = m->head, *prev = NULL, *temp; while (cur) { if (cur->status == ADD) { // We will trust that the mask was set correctly if (fanotify_mark(fd, FAN_MARK_ADD | mark_flag, mask, -1, cur->path) == -1) { msg(LOG_ERR, "Error (%s) adding fanotify mark for %s", strerror(errno), cur->path); } else { msg(LOG_DEBUG, "Added %s mount point", cur->path); } } // Now remove the deleted mount point if (cur->status == DELETE) { msg(LOG_DEBUG, "Deleted %s mount point", cur->path); temp = cur->next; if (cur == m->head) m->head = temp; else prev->next = temp; free((void *)cur->path); free((void *)cur); cur = temp; } else { prev = cur; cur = cur->next; } } m->cur = m->head; // Leave cur pointing to something valid } void unmark_fanotify(mlist *m) { const char *path = mlist_first(m); // Stop the flow of events while (path) { if (fanotify_mark(fd, FAN_MARK_FLUSH | mark_flag, 0, -1, path) == -1) msg(LOG_ERR, "Failed flushing path %s (%s)", path, strerror(errno)); path = mlist_next(m); } } void shutdown_fanotify(mlist *m) { unmark_fanotify(m); // End the thread pthread_cond_signal(&do_decision); pthread_join(decision_thread, NULL); pthread_join(deadmans_switch_thread, NULL); pthread_mutex_destroy(&decision_lock); pthread_mutexattr_destroy(&decision_lock_attr); pthread_cond_destroy(&do_decision); // Clean up q_close(q); close(rpt_timer_fd); close(fd); // Report results msg(LOG_DEBUG, "Allowed accesses: %lu", getAllowed()); msg(LOG_DEBUG, "Denied accesses: %lu", getDenied()); } void decision_report(FILE *f) { if (f == NULL) return; // Report results fprintf(f, "Allowed accesses: %lu\n", getAllowed()); fprintf(f, "Denied accesses: %lu\n", getDenied()); } static bool get_ready(void) { return events_ready; } static void set_ready(void) { events_ready = true; } static void clear_ready(void) { events_ready = false; } static void *deadmans_switch_thread_main(void *arg) { sigset_t sigs; /* This is a worker thread. Don't handle external signals. */ sigemptyset(&sigs); sigaddset(&sigs, SIGTERM); sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGUSR1); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGQUIT); pthread_sigmask(SIG_SETMASK, &sigs, NULL); do { // Are you alive decision thread? if (alive == 0 && get_ready() && !stop && q_queue_length(q) > 5) { msg(LOG_ERR, "Deadman's switch activated...killing process"); raise(SIGKILL); } // OK, prove it again. alive = 0; sleep(3); } while (!stop); return NULL; } // disable interval reports, used on unrecoverable errors static void rpt_disable(const char *why) { rpt_interval = 0; close(rpt_timer_fd); msg(LOG_INFO, "interval reports disabled; %s", why); } // initialize interval reporting static void rpt_init(struct timespec *t) { rpt_timer_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK); if (rpt_timer_fd == -1) { rpt_disable("timer create failure"); } else { t->tv_nsec = t->tv_sec = 0; struct itimerspec rpt_deadline = { {rpt_interval, 0}, {rpt_interval, 0} }; if (timerfd_settime(rpt_timer_fd, TFD_TIMER_ABSTIME, &rpt_deadline, NULL) == -1) { // settime errors are unrecoverable rpt_disable(strerror(errno)); } else { msg(LOG_INFO, "interval reports configured; %us", rpt_interval); } } } // write a stat report to file at the standard location static void rpt_write(void) { FILE *f = fopen(STAT_REPORT, "w"); if (f) { do_stat_report(f, 0); fclose(f); } } static void *decision_thread_main(void *arg) { sigset_t sigs; /* This is a worker thread. Don't handle external signals. */ sigemptyset(&sigs); sigaddset(&sigs, SIGTERM); sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGUSR1); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGQUIT); pthread_sigmask(SIG_SETMASK, &sigs, NULL); // interval reporting state int rpt_is_stale = 0; struct timespec rpt_timeout; // if an interval was configured, reports are enabled if (rpt_interval) rpt_init(&rpt_timeout); // start with a fresh report run_stats = 1; while (!stop) { int len; struct fanotify_event_metadata metadata[MAX_EVENTS]; pthread_mutex_lock(&decision_lock); while (get_ready() == 0) { // if an interval has been configured if (rpt_interval) { // check for timer expirations uint64_t expired = 0; if (read(rpt_timer_fd, &expired, sizeof(uint64_t)) == -1) { // EAGAIN expected w/nonblocking timer // any other error is unrecoverable if (errno != EAGAIN) { rpt_disable(strerror(errno)); continue; } } // timer expired or stats explicitly requested if (expired || run_stats) { // write a new report only when one of // 1. new events seen since last report // 2. explicitly requested w/run_stats if (rpt_is_stale || run_stats) { rpt_write(); run_stats = 0; rpt_is_stale = 0; } // adjust the pthread timeout to // a full interval from now if (clock_gettime(CLOCK_MONOTONIC, &rpt_timeout)) { // gettime errors are unrecoverable rpt_disable("clock failure"); continue; } rpt_timeout.tv_sec += rpt_interval; } // await a fan event, timing out at the // next report interval if (stop) break; pthread_cond_timedwait(&do_decision, &decision_lock, &rpt_timeout); } else if (run_stats) { rpt_write(); run_stats = 0; } // no interval reports, await a fan event indefinitely if (stop) break; pthread_cond_wait(&do_decision, &decision_lock); } if (stop) { msg(LOG_DEBUG, "Exiting decision thread"); pthread_mutex_unlock(&decision_lock); return NULL; } alive = 1; rpt_is_stale = 1; // Grab up to MAX_EVENTS events while locked unsigned i = 0; size_t num = q_queue_length(q); if (num > MAX_EVENTS) num = MAX_EVENTS; while (i < num) { len = q_peek(q, &metadata[i]); if (len == 0) { // Should never happen clear_ready(); // Reset to reality msg(LOG_DEBUG, "queue size is 0 but event received"); // limit processing to what we have num = i; goto out; } q_drop_head(q); if (q_queue_length(q) == 0) clear_ready(); i++; } out: pthread_mutex_unlock(&decision_lock); for (i=0; ipid); // We have to deny. This allows the kernel to free it's // memory related to this request. reply_event also closes // the descriptor, so we don't need to do it here. int decision = FAN_DENY; if (permissive) decision = FAN_ALLOW; reply_event(fd, metadata, decision, NULL); } else set_ready(); } void handle_events(void) { const struct fanotify_event_metadata *metadata; struct fanotify_event_metadata buf[FANOTIFY_BUFFER_SIZE]; ssize_t len = -2; while (len < 0) { do { len = read(fd, (void *) buf, sizeof(buf)); } while (len == -1 && errno == EINTR && stop == false); if (len == -1 && errno != EAGAIN) { // If we get this, we have no access to the file. We // cannot formulate a reply either to deny it because // we have nothing to work with. msg(LOG_ERR, "Error receiving fanotify_event (%s)", strerror(errno)); return; } if (stop) return; } // Do all the locking outside of the loop so that we do // not keep reacquiring the locks with each iteration pthread_mutex_lock(&decision_lock); metadata = (const struct fanotify_event_metadata *)buf; while (FAN_EVENT_OK(metadata, len)) { if (metadata->vers != FANOTIFY_METADATA_VERSION) { msg(LOG_ERR, "Mismatch of fanotify metadata version"); exit(1); } if (metadata->fd >= 0) { if (metadata->mask & mask) { if (metadata->pid == our_pid) reply_event(fd, metadata, FAN_ALLOW, NULL); else enqueue_event(metadata); } else { // This should never happen. Reply with deny // which releases the descriptor and kernel // memory. Continue processing what was read. reply_event(fd, metadata, FAN_DENY, NULL); } } metadata = FAN_EVENT_NEXT(metadata, len); } pthread_cond_signal(&do_decision); pthread_mutex_unlock(&decision_lock); } fapolicyd-1.3.4/src/daemon/notify.h000066400000000000000000000022541470754500200172130ustar00rootroot00000000000000/* * notify.h - Header file for notify.c * Copyright (c) 2016,2018 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef NOTIFY_HEADER #define NOTIFY_HEADER #include #include "conf.h" #include "mounts.h" int init_fanotify(const conf_t *config, mlist *m); void fanotify_update(mlist *m); void unmark_fanotify(mlist *m); void shutdown_fanotify(mlist *m); void decision_report(FILE *f); void handle_events(void); #endif fapolicyd-1.3.4/src/library/000077500000000000000000000000001470754500200157305ustar00rootroot00000000000000fapolicyd-1.3.4/src/library/attr-sets.c000066400000000000000000000165741470754500200200370ustar00rootroot00000000000000/* * attr-sets.c - Attribute sets dynamic data structure * * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #include #include #include #include "attr-sets.h" #include "message.h" #define RESIZE_BY 2 #define DEFAULT_CAPACITY 100 attr_sets_t sets; /* * this is a compare callback for avl string tree * * avl tree compare expect: * 0 when equals * <0 when a < b * >0 when a > b */ static int strcmp_cb(void * a, void * b) { return strcmp(((avl_str_data_t *)a)->str, ((avl_str_data_t *)b)->str); } /* * this is a compare callback for avl int tree * * avl tree compare expect: * 0 when equals * <0 when a < b * >0 when a > b */ static int intcmp_cb(void * a, void * b) { return ((avl_int_data_t *)a)->num - ((avl_int_data_t *)b)->num; } /* * this is a traverse callback for avl string tree * it provides directory test * e.g a->str = "/usr/bin/" a->len = 9 * b->str = "/usr/bin/ls" * strncmp return 0 with output above that means match * * avl tree traverse calls callback on each node and * it sums all return values and it returns the sum * so when I return 1 it just sums how many strncmp * returned match * with -1 I can break the recursion with first match */ static int strncmp_cb(void * a, void * b) { return strncmp( ((avl_str_data_t *)a)->str, ((avl_str_data_t *)b)->str, ((avl_str_data_t *)a)->len) ? 0 : -1; } int init_attr_sets(void) { sets.resize_factor = RESIZE_BY; // first is reserved // we are using 0th index as failure return value sets.size = 1; sets.capacity = DEFAULT_CAPACITY; sets.array = malloc(sizeof(attr_sets_entry_t) * sets.capacity); if (!sets.array) return 1; memset(sets.array, 0, sets.capacity * sizeof(attr_sets_entry_t)); return 0; } static int resize_attr_sets(void) { size_t new_capacity = sets.capacity * sets.resize_factor; attr_sets_entry_t * tmp = realloc(sets.array, sizeof(attr_sets_entry_t) * new_capacity); if (!tmp) return 1; sets.capacity = new_capacity; sets.array = tmp; return 0; } attr_sets_entry_t * get_attr_set(const size_t index) { if (index == 0) return NULL; if (index > sets.size) return NULL; return &(sets.array[index]); } size_t search_attr_set_by_name(const char * name) { // ignore 0th index for (size_t i = 1 ; i < sets.size ; i++) { const char * nname = sets.array[i].name; if (nname) { if (strcmp(nname, name) == 0) return i; } } return 0; } int add_attr_set(const char * name, const int type, size_t * index) { if (sets.size == sets.capacity) if (resize_attr_sets()) return 1; // getting last free known entry attr_sets_entry_t * entry = get_attr_set(sets.size); if (!entry) return 1; // copy string or set NULL for sure entry->name = name ? strdup(name) : NULL; entry->type = type; if (type == STRING) avl_init(&entry->tree, strcmp_cb); else if (type == INT) avl_init(&entry->tree, intcmp_cb); else { // TODO error (void)index; } *index = sets.size; sets.size++; return 0; } attr_sets_entry_t *init_standalone_set(const int type) { attr_sets_entry_t *s = malloc(sizeof(attr_sets_entry_t)); if (s) { s->name = NULL; s->type = type; if (type == STRING) avl_init(&s->tree, strcmp_cb); else avl_init(&s->tree, intcmp_cb); } return s; } int append_int_attr_set(attr_sets_entry_t * set, const int num) { if (!set) return 1; if (set->type != INT) { // trying to insert wrong type? return 1; } avl_int_data_t * data = malloc(sizeof(avl_int_data_t)); if (!data) return 1; data->num = num; avl_t * ret = avl_insert(&set->tree, (avl_t *)data); if (ret != (avl_t *)data) { // Already present in avl tree free(data); return 1; } return 0; } int append_str_attr_set(attr_sets_entry_t * set, const char * str) { if (!set) return 1; if (set->type != STRING) { // trying to insert wrong type? return 1; } avl_str_data_t * data = malloc(sizeof(avl_str_data_t)); if (!data) return 1; data->str = strdup(str); if (!data->str) { free(data); return 1; } data->len = strlen(str); avl_t * ret = avl_insert(&set->tree, (avl_t *)data); if (ret != (avl_t *)data) { // Already present in avl tree free((void *)data->str); free(data); return 1; } return 0; } int check_int_attr_set(attr_sets_entry_t * set, const int num) { avl_int_data_t data; data.num = num; // we are doing following checks on upper level //if (!set) return 1; //if (set->type != INT) // return -1; // --------------------------------------------- // valid pointer to data if found // NULL -> 0 if not return avl_search(&set->tree, (avl_t*)(&data)) ? 1 : 0; } int check_str_attr_set(attr_sets_entry_t * set, const char * str) { avl_str_data_t data; data.str = str; // we are doing following checks on upper level //if (!set) return 1; //if (set->type != STRING) // return -1; // -------------------------------------------- // valid pointer to data if found // NULL -> 0 if not return avl_search(&set->tree, (avl_t*)(&data)) ? 1 : 0; } int check_pstr_attr_set(attr_sets_entry_t * set, const char * str) { avl_str_data_t data; data.str = str; // we are doing following checks on upper level // if (!set) return 1; // if (set->type != STRING) // return -1; // -------------------------------------------- // -1 means broken recursion -> found // 0 means not found int ret = avl_traverse(&set->tree, strncmp_cb, (void*)&data); // want to be consistent if (ret == -1) return 1; return 0; } static int print_str(void * entry, void *data) { (void) data; const char * str = ((avl_str_data_t *) entry)->str; msg(LOG_DEBUG, "%s", str); return 0; } static int print_int(void * entry, void *data) { (void) data; const int num = ((avl_int_data_t *) entry)->num; msg(LOG_DEBUG, "%d", num); return 0; } void print_attr_set(attr_sets_entry_t * set) { if (!set) return; msg(LOG_DEBUG, "Set: %s", set->name); if (set->type == STRING) avl_traverse(&set->tree, print_str, NULL); if (set->type == INT) avl_traverse(&set->tree, print_int, NULL); msg(LOG_DEBUG, "--------------"); } void print_attr_sets(void) { for (size_t i = 1 ; i < sets.size ; i++) { print_attr_set(&sets.array[i]); } } void destroy_attr_set(attr_sets_entry_t * set) { if (!set) return; free(set->name); // free tree avl_t *cur; while ((cur = set->tree.root) != NULL) { void *tmp =(void *)avl_remove(&set->tree, cur); if ((avl_t *)tmp != cur) msg(LOG_DEBUG, "attr_set_entry: removal of invalid node"); if (set->type == STRING) { free((void *)((avl_str_data_t *)tmp)->str); } free(tmp); } } void destroy_attr_sets(void) { for (size_t i = 0 ; i < sets.size ; i++) { destroy_attr_set(&sets.array[i]); } free(sets.array); } fapolicyd-1.3.4/src/library/attr-sets.h000066400000000000000000000043331470754500200200320ustar00rootroot00000000000000/* * attr-sets.h - Header file for attribute sets * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #ifndef ATTR_SETS_H #define ATTR_SETS_H #include "stddef.h" #include "avl.h" typedef struct _avl_str_data { avl_t avl; size_t len; const char * str; } avl_str_data_t; typedef struct _avl_int_data { avl_t avl; int num; } avl_int_data_t; typedef struct attr_sets_entry { // optional char * name; // STRING, INT from DATA_TYPES int type; avl_tree_t tree; } attr_sets_entry_t; // variable size array typedef struct attr_sets { // allocated size size_t capacity; size_t size; size_t resize_factor; attr_sets_entry_t * array; } attr_sets_t; typedef enum _types { STRING = 1, INT, } DATA_TYPES; int init_attr_sets(void); attr_sets_entry_t * get_attr_set(const size_t index); int add_attr_set(const char * name, const int type, size_t * index); void destroy_attr_set(attr_sets_entry_t *set); void destroy_attr_sets(void); size_t search_attr_set_by_name(const char * name); attr_sets_entry_t *init_standalone_set(const int type); int append_int_attr_set(attr_sets_entry_t * set, const int num); int append_str_attr_set(attr_sets_entry_t * set, const char * str); int check_int_attr_set(attr_sets_entry_t * set, const int num); int check_str_attr_set(attr_sets_entry_t * set, const char * str); int check_pstr_attr_set(attr_sets_entry_t * set, const char * str); void print_attr_sets(void); void print_attr_set(attr_sets_entry_t * set); #endif // ATTR_SETS_H fapolicyd-1.3.4/src/library/avl.c000066400000000000000000000304531470754500200166630ustar00rootroot00000000000000//#include "config.h" #include // for NULL #include "avl.h" // Note: this file is based on this: // https://github.com/firehol/netdata/blob/master/src/avl.c // c63bdb5 on Oct 23, 2017 // // which has been moved to here (05/23/20): // https://github.com/netdata/netdata/blob/master/libnetdata/avl/avl.c // // However, its been modified to remove pthreads as this application will // only use it from a single thread. /* ------------------------------------------------------------------------- */ /* * avl_insert(), avl_remove() and avl_search() * are adaptations (by Costa Tsaousis) of the AVL algorithm found in libavl * v2.0.3, so that they do not use any memory allocations and their memory * footprint is optimized (by eliminating non-necessary data members). * * libavl - library for manipulation of binary trees. * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004 Free Software * Foundation, Inc. * GNU Lesser General Public License */ /* Search |tree| for an item matching |item|, and return it if found. Otherwise return |NULL|. */ avl_t *avl_search(const avl_tree_t *tree, avl_t *item) { avl_t *p; // assert (tree != NULL && item != NULL); for (p = tree->root; p != NULL; ) { int cmp = tree->compar(item, p); if (cmp < 0) p = p->avl_link[0]; else if (cmp > 0) p = p->avl_link[1]; else /* |cmp == 0| */ return p; } return NULL; } /* Inserts |item| into |tree| and returns a pointer to |item|'s address. If a duplicate item is found in the tree, returns a pointer to the duplicate without inserting |item|. */ avl_t *avl_insert(avl_tree_t *tree, avl_t *item) { avl_t *y, *z; /* Top node to update balance factor, and parent. */ avl_t *p, *q; /* Iterator, and parent. */ avl_t *n; /* Newly inserted node. */ avl_t *w; /* New root of rebalanced subtree. */ unsigned char dir; /* Direction to descend. */ unsigned char da[AVL_MAX_HEIGHT]; /* Cached comparison results. */ int k = 0; /* Number of cached results. */ // assert(tree != NULL && item != NULL); z = (avl_t *) &tree->root; y = tree->root; dir = 0; for (q = z, p = y; p != NULL; q = p, p = p->avl_link[dir]) { int cmp = tree->compar(item, p); if (cmp == 0) return p; if (p->avl_balance != 0) z = q, y = p, k = 0; da[k++] = dir = (cmp > 0); } n = q->avl_link[dir] = item; // tree->avl_count++; n->avl_link[0] = n->avl_link[1] = NULL; n->avl_balance = 0; if (y == NULL) return n; for (p = y, k = 0; p != n; p = p->avl_link[da[k]], k++) if (da[k] == 0) p->avl_balance--; else p->avl_balance++; if (y->avl_balance == -2) { avl_t *x = y->avl_link[0]; if (x->avl_balance == -1) { w = x; y->avl_link[0] = x->avl_link[1]; x->avl_link[1] = y; x->avl_balance = y->avl_balance = 0; } else { // assert (x->avl_balance == +1); w = x->avl_link[1]; x->avl_link[1] = w->avl_link[0]; w->avl_link[0] = x; y->avl_link[0] = w->avl_link[1]; w->avl_link[1] = y; if (w->avl_balance == -1) x->avl_balance = 0, y->avl_balance = +1; else if (w->avl_balance == 0) x->avl_balance = y->avl_balance = 0; else /* |w->avl_balance == +1| */ x->avl_balance = -1, y->avl_balance = 0; w->avl_balance = 0; } } else if (y->avl_balance == +2) { avl_t *x = y->avl_link[1]; if (x->avl_balance == +1) { w = x; y->avl_link[1] = x->avl_link[0]; x->avl_link[0] = y; x->avl_balance = y->avl_balance = 0; } else { // assert (x->avl_balance == -1); w = x->avl_link[0]; x->avl_link[0] = w->avl_link[1]; w->avl_link[1] = x; y->avl_link[1] = w->avl_link[0]; w->avl_link[0] = y; if (w->avl_balance == +1) x->avl_balance = 0, y->avl_balance = -1; else if (w->avl_balance == 0) x->avl_balance = y->avl_balance = 0; else /* |w->avl_balance == -1| */ x->avl_balance = +1, y->avl_balance = 0; w->avl_balance = 0; } } else return n; z->avl_link[y != z->avl_link[0]] = w; // tree->avl_generation++; return n; } /* Deletes from |tree| and returns an item matching |item|. Returns a null pointer if no matching item found. */ avl_t *avl_remove(avl_tree_t *tree, avl_t *item) { /* Stack of nodes. */ avl_t *pa[AVL_MAX_HEIGHT]; /* Nodes. */ unsigned char da[AVL_MAX_HEIGHT]; /* |avl_link[]| indexes. */ int k; /* Stack pointer. */ avl_t *p; /* Traverses tree to find node to delete. */ int cmp; /* Result of comparison between |item| and |p|. */ // assert (tree != NULL && item != NULL); k = 0; p = (avl_t *) &tree->root; for(cmp = -1; cmp != 0; cmp = tree->compar(item, p)) { unsigned char dir = (unsigned char)(cmp > 0); pa[k] = p; da[k++] = dir; p = p->avl_link[dir]; if(p == NULL) return NULL; } item = p; if (p->avl_link[1] == NULL) pa[k - 1]->avl_link[da[k - 1]] = p->avl_link[0]; else { avl_t *r = p->avl_link[1]; if (r->avl_link[0] == NULL) { r->avl_link[0] = p->avl_link[0]; r->avl_balance = p->avl_balance; pa[k - 1]->avl_link[da[k - 1]] = r; da[k] = 1; pa[k++] = r; } else { avl_t *s; int j = k++; for (;;) { da[k] = 0; pa[k++] = r; s = r->avl_link[0]; if (s->avl_link[0] == NULL) break; r = s; } s->avl_link[0] = p->avl_link[0]; r->avl_link[0] = s->avl_link[1]; s->avl_link[1] = p->avl_link[1]; s->avl_balance = p->avl_balance; pa[j - 1]->avl_link[da[j - 1]] = s; da[j] = 1; pa[j] = s; } } // assert (k > 0); while (--k > 0) { avl_t *y = pa[k]; if (da[k] == 0) { y->avl_balance++; if (y->avl_balance == +1) break; else if (y->avl_balance == +2) { avl_t *x = y->avl_link[1]; if (x->avl_balance == -1) { avl_t *w; // assert (x->avl_balance == -1); w = x->avl_link[0]; x->avl_link[0] = w->avl_link[1]; w->avl_link[1] = x; y->avl_link[1] = w->avl_link[0]; w->avl_link[0] = y; if (w->avl_balance == +1) x->avl_balance = 0, y->avl_balance = -1; else if (w->avl_balance == 0) x->avl_balance = y->avl_balance = 0; else /* |w->avl_balance == -1| */ x->avl_balance = +1, y->avl_balance = 0; w->avl_balance = 0; pa[k - 1]->avl_link[da[k - 1]] = w; } else { y->avl_link[1] = x->avl_link[0]; x->avl_link[0] = y; pa[k - 1]->avl_link[da[k - 1]] = x; if (x->avl_balance == 0) { x->avl_balance = -1; y->avl_balance = +1; break; } else x->avl_balance = y->avl_balance = 0; } } } else { y->avl_balance--; if (y->avl_balance == -1) break; else if (y->avl_balance == -2) { avl_t *x = y->avl_link[0]; if (x->avl_balance == +1) { avl_t *w; // assert (x->avl_balance == +1); w = x->avl_link[1]; x->avl_link[1] = w->avl_link[0]; w->avl_link[0] = x; y->avl_link[0] = w->avl_link[1]; w->avl_link[1] = y; if (w->avl_balance == -1) x->avl_balance = 0, y->avl_balance = +1; else if (w->avl_balance == 0) x->avl_balance = y->avl_balance = 0; else /* |w->avl_balance == +1| */ x->avl_balance = -1, y->avl_balance = 0; w->avl_balance = 0; pa[k - 1]->avl_link[da[k - 1]] = w; } else { y->avl_link[0] = x->avl_link[1]; x->avl_link[1] = y; pa[k - 1]->avl_link[da[k - 1]] = x; if (x->avl_balance == 0) { x->avl_balance = +1; y->avl_balance = -1; break; } else x->avl_balance = y->avl_balance = 0; } } } } // tree->avl_count--; // tree->avl_generation++; return item; } /* ------------------------------------------------------------------------- */ // below are functions by (C) Costa Tsaousis // --------------------------- // traversing int avl_walker(avl_t *node, int (*callback)(void *entry, void *data), void *data) { int total = 0, ret = 0; if(node->avl_link[0]) { ret = avl_walker(node->avl_link[0], callback, data); if(ret < 0) return ret; total += ret; } ret = callback(node, data); if(ret < 0) return ret; total += ret; if(node->avl_link[1]) { ret = avl_walker(node->avl_link[1], callback, data); if (ret < 0) return ret; total += ret; } return total; } int avl_traverse(const avl_tree_t *t, int (*callback)(void *entry, void *data), void *data) { if(t->root) return avl_walker(t->root, callback, data); else return 0; } void avl_init(avl_tree_t *t, int (*compar)(void *a, void *b)) { t->root = NULL; t->compar = compar; } /* ------------------------------------------------------------------------- */ // below are functions by (C) Steve Grubb // --------------------------- avl_t *avl_first(avl_iterator *i, avl_tree_t *t) { if (t->root == NULL || i == NULL) return NULL; i->tree = t; i->height = 0; // follow the leftmost node to its bottom avl_t *node = t->root; while (node->avl_link[0]) { i->stack[i->height] = node; i->height++; node = node->avl_link[0]; } i->current = node; return node; } avl_t *avl_next(avl_iterator *i) { if (i == NULL || i->tree == NULL) return NULL; avl_t *node = i->current; if (node == NULL) return avl_first(i, i->tree); else if (node->avl_link[1]) { i->stack[i->height] = node; i->height++; node = node->avl_link[1]; while (node->avl_link[0]) { i->stack[i->height] = node; i->height++; node = node->avl_link[0]; } } else { avl_t *tmp; do { if (i->height == 0) { i->current = NULL; return NULL; } tmp = node; i->height--; node = i->stack[i->height]; } while (tmp == node->avl_link[1]); } i->current = node; return node; } static int avl_walker2(avl_t *node, avl_tree_t *haystack) { int ret; // If the lefthand has a link, take it so that we walk to the // leftmost bottom if(node->avl_link[0]) { ret = avl_walker2(node->avl_link[0], haystack); if (ret) return ret; } // Next, check the current node avl_t *res = avl_search(haystack, node); if (res) return 1; // If the righthand has a link, take it so that we check all the // rightmost nodes, too. if(node->avl_link[1]) { ret = avl_walker2(node->avl_link[1], haystack); if (ret) return ret; } // nothing found return 0; } int avl_intersection(const avl_tree_t *needle, avl_tree_t *haystack) { // traverse the needle and search the haystack // this implies that needle should be smaller than haystack if (needle && haystack && needle->root && haystack->root) return avl_walker2(needle->root, haystack); // something is not initialized, so we cannot search return 0; } fapolicyd-1.3.4/src/library/avl.h000066400000000000000000000037131470754500200166670ustar00rootroot00000000000000#ifndef AVL_HEADER #define AVL_HEADER #include "gcc-attributes.h" /* Maximum AVL tree height. */ #ifndef AVL_MAX_HEIGHT #define AVL_MAX_HEIGHT 92 #endif /* Data structures */ /* One element of the AVL tree */ typedef struct avl { struct avl *avl_link[2]; /* Subtrees - 0 left, 1 right */ signed char avl_balance; /* Balance factor. */ } avl_t; /* An AVL tree */ typedef struct avl_tree { avl_t *root; int (*compar)(void *a, void *b); } avl_tree_t; /* Iterator state struct */ typedef struct avl_iterator { avl_tree_t *tree; avl_t *current; avl_t *stack[AVL_MAX_HEIGHT]; unsigned height; } avl_iterator; /* Public methods */ /* Insert element a into the AVL tree t * returns the added element a, or a pointer the * element that is equal to a (as returned by t->compar()) * a is linked directly to the tree, so it has to * be properly allocated by the caller. */ avl_t *avl_insert(avl_tree_t *t, avl_t *a) NEVERNULL WARNUNUSED; /* Remove an element a from the AVL tree t * returns a pointer to the removed element * or NULL if an element equal to a is not found * (equal as returned by t->compar()) */ avl_t *avl_remove(avl_tree_t *t, avl_t *a) WARNUNUSED; /* Find the element into the tree that equal to a * (equal as returned by t->compar()) * returns NULL is no element is equal to a */ avl_t *avl_search(const avl_tree_t *t, avl_t *a); /* Initialize the avl_tree_t */ void avl_init(avl_tree_t *t, int (*compar)(void *a, void *b)); /* Walk the tree and call callback at each node */ int avl_traverse(const avl_tree_t *t, int (*callback)(void *entry, void *data), void *data); /* Walk the tree down to the first node and return it */ avl_t *avl_first(avl_iterator *i, avl_tree_t *t); /* Walk the tree to the next logical node and return it */ avl_t *avl_next(avl_iterator *i); /* Given two trees, see if any in needle are contained in haystack */ int avl_intersection(const avl_tree_t *needle, avl_tree_t *haystack); #endif /* avl.h */ fapolicyd-1.3.4/src/library/backend-manager.c000066400000000000000000000063201470754500200210740ustar00rootroot00000000000000/* * backend-manager.c - backend management * Copyright (c) 2020,2022 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #include "config.h" #include #include #include #include #include #include "conf.h" #include "message.h" #include "backend-manager.h" #include "fapolicyd-backend.h" extern backend file_backend; #ifdef USE_RPM extern backend rpm_backend; #endif #ifdef USE_DEB extern backend deb_backend; #endif static backend* compiled[] = { &file_backend, #ifdef USE_RPM &rpm_backend, #endif #ifdef USE_DEB &deb_backend, #endif NULL, }; static backend_entry* backends = NULL; static int backend_push(const char *name) { long index = -1; for (long i = 0 ; compiled[i] != NULL; i++) { if (strcmp(name, compiled[i]->name) == 0) { index = i; break; } } if (index == -1) { msg(LOG_ERR, "%s backend not supported, aborting!", name); return 1; } else { backend_entry *tmp = (backend_entry *) malloc(sizeof(backend_entry)); if (!tmp) { msg(LOG_ERR, "cannot allocate %s backend", name); return 2; } tmp->backend = compiled[index]; tmp->next = NULL; if (!backends) backends = tmp; else { // Find the last entry backend_entry *cur = backends; while (cur->next) cur = cur->next; cur->next = tmp; } msg(LOG_DEBUG, "backend %s registered", name); } return 0; } static int backend_destroy(void) { backend_entry *be = backend_get_first(); backend_entry *tmp = NULL; while (be != NULL) { tmp = be; be = be->next; free(tmp); } backends = NULL; return 0; } static int backend_create(const char *trust_list) { char *ptr, *saved, *tmp = strdup(trust_list); if (!tmp) return 1; ptr = strtok_r(tmp, ",", &saved); while (ptr) { if (backend_push(ptr)) { free(tmp); return 1; } ptr = strtok_r(NULL, ",", &saved); } free(tmp); return 0; } int backend_init(const conf_t *conf) { if (backend_create(conf->trust)) return 1; for (backend_entry *be = backend_get_first(); be != NULL; be = be->next) { if (be->backend->init()) return 2; } return 0; } int backend_load(const conf_t *conf) { for (backend_entry *be = backend_get_first(); be != NULL; be = be->next) { if (be->backend->load(conf)) return 1; } return 0; } void backend_close(void) { for (backend_entry *be = backend_get_first(); be != NULL; be = be->next) { be->backend->close(); } backend_destroy(); } backend_entry* backend_get_first(void) { return backends; } fapolicyd-1.3.4/src/library/backend-manager.h000066400000000000000000000023641470754500200211050ustar00rootroot00000000000000/* * backend-manager.h - Header file for backend manager * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #ifndef BACKEND_MANAGER_H #define BACKEND_MANAGER_H #include #include "conf.h" #include "fapolicyd-backend.h" typedef struct _backend_entry { backend *backend; struct _backend_entry *next; } backend_entry; int backend_init(const conf_t *conf); int backend_load(const conf_t *conf); void backend_close(void); backend_entry* backend_get_first(void); #endif fapolicyd-1.3.4/src/library/conf.h000066400000000000000000000026771470754500200170420ustar00rootroot00000000000000/* conf.h configuration structure * Copyright 2018-20,22 Red Hat Inc. * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Radovan Sroka * */ #ifndef CONF_H #define CONF_H #include typedef enum { IN_NONE, IN_SIZE, IN_IMA, IN_SHA256 } integrity_t; typedef struct conf { unsigned int permissive; unsigned int nice_val; unsigned int q_size; uid_t uid; gid_t gid; unsigned int do_stat_report; unsigned int detailed_report; unsigned int db_max_size; unsigned int subj_cache_size; unsigned int obj_cache_size; const char *watch_fs; const char *trust; integrity_t integrity; const char *syslog_format; unsigned int rpm_sha256_only; unsigned int allow_filesystem_mark; unsigned int report_interval; } conf_t; #endif fapolicyd-1.3.4/src/library/daemon-config.c000066400000000000000000000362631470754500200206140ustar00rootroot00000000000000/* * daemon-config.c - This is a config file parser * * Copyright 2018-22 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb * Radovan Sroka * */ #include "config.h" #include "daemon-config.h" #include "message.h" #include "file.h" #include #include #include #include #include #include #include #include #include "paths.h" /* Local prototypes */ struct nv_pair { const char *name; const char *value; }; struct kw_pair { const char *name; int (*parser)(const struct nv_pair *, int, conf_t *); }; struct nv_list { const char *name; int option; }; static char *get_line(FILE *f, char *buf, unsigned size, int *lineno, const char *file); static int nv_split(char *buf, struct nv_pair *nv); static const struct kw_pair *kw_lookup(const char *val); static int permissive_parser(const struct nv_pair *nv, int line, conf_t *config); static int nice_val_parser(const struct nv_pair *nv, int line, conf_t *config); static int q_size_parser(const struct nv_pair *nv, int line, conf_t *config); static int uid_parser(const struct nv_pair *nv, int line, conf_t *config); static int gid_parser(const struct nv_pair *nv, int line, conf_t *config); static int detailed_report_parser(const struct nv_pair *nv, int line, conf_t *config); static int db_max_size_parser(const struct nv_pair *nv, int line, conf_t *config); static int subj_cache_size_parser(const struct nv_pair *nv, int line, conf_t *config); static int obj_cache_size_parser(const struct nv_pair *nv, int line, conf_t *config); static int do_stat_report_parser(const struct nv_pair *nv, int line, conf_t *config); static int watch_fs_parser(const struct nv_pair *nv, int line, conf_t *config); static int trust_parser(const struct nv_pair *nv, int line, conf_t *config); static int integrity_parser(const struct nv_pair *nv, int line, conf_t *config); static int syslog_format_parser(const struct nv_pair *nv, int line, conf_t *config); static int rpm_sha256_only_parser(const struct nv_pair *nv, int line, conf_t *config); static int fs_mark_parser(const struct nv_pair *nv, int line, conf_t *config); static int report_interval_parser(const struct nv_pair *nv, int line, conf_t *config); static const struct kw_pair keywords[] = { {"permissive", permissive_parser }, {"nice_val", nice_val_parser }, {"q_size", q_size_parser }, {"uid", uid_parser }, {"gid", gid_parser }, {"detailed_report", detailed_report_parser }, {"db_max_size", db_max_size_parser }, {"subj_cache_size", subj_cache_size_parser }, {"obj_cache_size", obj_cache_size_parser }, {"do_stat_report", do_stat_report_parser }, {"watch_fs", watch_fs_parser }, {"trust", trust_parser }, {"integrity", integrity_parser }, {"syslog_format", syslog_format_parser }, {"rpm_sha256_only", rpm_sha256_only_parser}, {"allow_filesystem_mark", fs_mark_parser }, {"report_interval", report_interval_parser }, { NULL, NULL } }; /* * Set everything to its default value */ static void clear_daemon_config(conf_t *config) { config->permissive = 0; config->nice_val = 10; config->q_size = 800; config->uid = 0; config->gid = 0; config->do_stat_report = 1; config->detailed_report = 1; config->db_max_size = 50; config->subj_cache_size = 1549; config->obj_cache_size = 8191; config->watch_fs = strdup("ext4,xfs,tmpfs"); #ifdef USE_RPM config->trust = strdup("rpmdb,file"); #else config->trust = strdup("file"); #endif config->integrity = IN_NONE; config->syslog_format = strdup("rule,dec,perm,auid,pid,exe,:,path,ftype"); config->rpm_sha256_only = 0; config->allow_filesystem_mark = 0; config->report_interval = 0; } int load_daemon_config(conf_t *config) { int fd, lineno = 1; FILE *f; char buf[160]; clear_daemon_config(config); /* open the file */ fd = open(CONFIG_FILE, O_RDONLY|O_NOFOLLOW); if (fd < 0) { if (errno != ENOENT) { msg(LOG_ERR, "Error opening config file (%s)", strerror(errno)); return 1; } msg(LOG_WARNING, "Config file %s doesn't exist, skipping", CONFIG_FILE); return 0; } /* Make into FILE struct and read line by line */ f = fdopen(fd, "rm"); if (f == NULL) { msg(LOG_ERR, "Error - fdopen failed (%s)", strerror(errno)); close(fd); return 1; } while (get_line(f, buf, sizeof(buf), &lineno, CONFIG_FILE)) { // convert line into name-value pair const struct kw_pair *kw; struct nv_pair nv; int rc = nv_split(buf, &nv); switch (rc) { case 0: // fine break; case 1: // not the right number of tokens. msg(LOG_ERR, "Wrong number of arguments for line %d in %s", lineno, CONFIG_FILE); break; case 2: // no '=' sign msg(LOG_ERR, "Missing equal sign for line %d in %s", lineno, CONFIG_FILE); break; default: // something else went wrong... msg(LOG_ERR, "Unknown error for line %d in %s", lineno, CONFIG_FILE); break; } if (nv.name == NULL) { lineno++; continue; } if (nv.value == NULL) { fclose(f); msg(LOG_ERR, "Not processing any more lines in %s", CONFIG_FILE); return 1; } /* identify keyword or error */ kw = kw_lookup(nv.name); if (kw->name == NULL) { msg(LOG_ERR, "Unknown keyword \"%s\" in line %d of %s", nv.name, lineno, CONFIG_FILE); fclose(f); return 1; } else { /* dispatch to keyword's local parser */ rc = kw->parser(&nv, lineno, config); if (rc != 0) { fclose(f); return 1; // local parser puts message out } } lineno++; } fclose(f); return 0; } static char *get_line(FILE *f, char *buf, unsigned size, int *lineno, const char *file) { int too_long = 0; while (fgets_unlocked(buf, size, f)) { /* remove newline */ char *ptr = strchr(buf, 0x0a); if (ptr) { if (!too_long) { *ptr = 0; return buf; } // Reset and start with the next line too_long = 0; *lineno = *lineno + 1; } else { // If a line is too long skip it. // Only output 1 warning if (!too_long) msg(LOG_ERR, "Skipping line %d in %s: too long", *lineno, file); too_long = 1; } } return NULL; } static char *_strsplit(char *s) { static char *str = NULL; char *ptr; if (s) str = s; else { if (str == NULL) return NULL; str++; } retry: ptr = strchr(str, ' '); if (ptr) { if (ptr == str) { str++; goto retry; } s = str; *ptr = 0; str = ptr; return s; } else { s = str; str = NULL; if (*s == 0) return NULL; return s; } } static int nv_split(char *buf, struct nv_pair *nv) { /* Get the name part */ char *ptr; nv->name = NULL; nv->value = NULL; ptr = _strsplit(buf); if (ptr == NULL) return 0; /* If there's nothing, go to next line */ if (ptr[0] == '#') return 0; /* If there's a comment, go to next line */ nv->name = ptr; /* Check for a '=' */ ptr = _strsplit(NULL); if (ptr == NULL) return 1; if (strcmp(ptr, "=") != 0) return 2; /* get the value */ ptr = _strsplit(NULL); if (ptr == NULL) return 1; nv->value = ptr; /* Make sure there's nothing else */ ptr = _strsplit(NULL); if (ptr) { /* Allow one option, but check that there's not 2 */ ptr = _strsplit(NULL); if (ptr) return 1; } /* Everything is OK */ return 0; } static const struct kw_pair *kw_lookup(const char *val) { int i = 0; while (keywords[i].name != NULL) { if (strcmp(keywords[i].name, val) == 0) break; i++; } return &keywords[i]; } void free_daemon_config(conf_t *config) { free((void*)config->watch_fs); free((void*)config->trust); free((void*)config->syslog_format); } static int unsigned_int_parser(unsigned *i, const char *str, int line) { const char *ptr = str; unsigned int j; /* check that all chars are numbers */ for (j=0; ptr[j]; j++) { if (!isdigit(ptr[j])) { msg(LOG_ERR, "Value %s should only be numbers - line %d", str, line); return 1; } } /* convert to unsigned long */ errno = 0; j = strtoul(str, NULL, 10); if (errno) { msg(LOG_ERR, "Error converting string to a number (%s) - line %d", strerror(errno), line); return 1; } *i = j; return 0; } static int permissive_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc = unsigned_int_parser(&(config->permissive), nv->value, line); if (rc == 0 && config->permissive > 1) { msg(LOG_WARNING, "permissive value reset to 1 - line %d", line); config->permissive = 1; } return rc; } static int nice_val_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc = unsigned_int_parser(&(config->nice_val), nv->value, line); if (rc == 0 && config->nice_val > 20) { msg(LOG_WARNING, "Error, nice_val is larger than 20 - line %d", line); rc = 1; } return rc; } static int q_size_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc = unsigned_int_parser(&(config->q_size), nv->value, line); if (rc == 0 && config->q_size > 10480) msg(LOG_WARNING, "q_size might be unnecessarily large - line %d", line); return rc; } static int uid_parser(const struct nv_pair *nv, int line, conf_t *config) { uid_t uid = 0; gid_t gid = 0; if (isdigit(nv->value[0])) { errno = 0; uid = strtoul(nv->value, NULL, 10); if (errno) { msg(LOG_ERR, "Error converting user value - line %d", line); return 1; } gid = uid; } else { struct passwd *pw = getpwnam(nv->value); if (pw == NULL) { msg(LOG_ERR, "user %s is unknown - line %d", nv->value, line); return 1; } uid = pw->pw_uid; gid = pw->pw_gid; endpwent(); } config->uid = uid; config->gid = gid; return 0; } static int gid_parser(const struct nv_pair *nv, int line, conf_t *config) { gid_t gid = 0; if (isdigit(nv->value[0])) { errno = 0; gid = strtoul(nv->value, NULL, 10); if (errno) { msg(LOG_ERR, "Error converting group value - line %d", line); return 1; } } else { struct group *gr ; gr = getgrnam(nv->value); if (gr == NULL) { msg(LOG_ERR, "group %s is unknown - line %d", nv->value, line); return 1; } gid = gr->gr_gid; endgrent(); } config->gid = gid; return 0; } static int detailed_report_parser(const struct nv_pair *nv, int line, conf_t *config) { return unsigned_int_parser(&(config->detailed_report), nv->value, line); } static int db_max_size_parser(const struct nv_pair *nv, int line, conf_t *config) { return unsigned_int_parser(&(config->db_max_size), nv->value, line); } static int subj_cache_size_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc=unsigned_int_parser(&(config->subj_cache_size), nv->value, line); if (rc == 0 && config->subj_cache_size > 16384) msg(LOG_WARNING, "subj_cache_size might be unnecessarily large - line %d", line); return rc; } static int obj_cache_size_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc=unsigned_int_parser(&(config->obj_cache_size), nv->value, line); if (rc == 0 && config->obj_cache_size > 32768) msg(LOG_WARNING, "obj_cache_size might be unnecessarily large - line %d", line); return rc; } static int do_stat_report_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc=unsigned_int_parser(&(config->do_stat_report), nv->value, line); if (rc == 0 && config->do_stat_report > 2) { msg(LOG_WARNING, "do_stat_report value reset to 1 - line %d", line); config->do_stat_report = 1; } return rc; } static int watch_fs_parser(const struct nv_pair *nv, int line, conf_t *config) { free((void *)config->watch_fs); config->watch_fs = strdup(nv->value); if (config->watch_fs) return 0; msg(LOG_ERR, "Could not store value line %d", line); return 1; } static int report_interval_parser(const struct nv_pair *nv, int line, conf_t *config) { return unsigned_int_parser(&(config->report_interval), nv->value, line); } static int trust_parser(const struct nv_pair *nv, int line, conf_t *config) { free((void *)config->trust); config->trust = strdup(nv->value); if (config->trust) return 0; msg(LOG_ERR, "Could not store value line %d", line); return 1; } static const struct nv_list integrity_schemes[] = { {"none", IN_NONE }, {"size", IN_SIZE }, {"ima", IN_IMA }, {"sha256", IN_SHA256 }, { NULL, 0 } }; static int integrity_parser(const struct nv_pair *nv, int line, conf_t *config) { for (int i=0; integrity_schemes[i].name != NULL; i++) { if (strcasecmp(nv->value, integrity_schemes[i].name) == 0) { config->integrity = integrity_schemes[i].option; if (config->integrity == IN_IMA) { int fd = open("/bin/sh", O_RDONLY); if (fd >= 0) { char sha[65]; int rc = get_ima_hash(fd, sha); close(fd); if (rc == 0) { msg(LOG_ERR, "IMA integrity checking selected, but the extended attributes can't be read"); return 1; } } else { msg(LOG_ERR, "IMA integrity checking selected, but can't test the shell"); return 1; } } return 0; } } msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); return 1; } static int syslog_format_parser(const struct nv_pair *nv, int line, conf_t *config) { free((void *)config->syslog_format); config->syslog_format = strdup(nv->value); if (config->syslog_format) return 0; msg(LOG_ERR, "Could not store value line %d", line); return 1; } static int rpm_sha256_only_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc = 0; #ifndef USE_RPM msg(LOG_WARNING, "rpm_sha256_only: fapolicyd was not built with rpm support, ignoring" ); #else rc = unsigned_int_parser(&(config->rpm_sha256_only), nv->value, line); if (rc == 0 && config->rpm_sha256_only > 1) { msg(LOG_WARNING, "rpm_sha256_only value reset to 0 - line %d", line); config->rpm_sha256_only = 0; } #endif return rc; } static int fs_mark_parser(const struct nv_pair *nv, int line, conf_t *config) { int rc = 0; #if defined HAVE_DECL_FAN_MARK_FILESYSTEM && HAVE_DECL_FAN_MARK_FILESYSTEM != 0 rc = unsigned_int_parser(&(config->allow_filesystem_mark), nv->value, line); if (rc == 0 && config->allow_filesystem_mark > 1) { msg(LOG_WARNING, "allow_filesystem_mark value reset to 0 - line %d", line); config->allow_filesystem_mark = 0; } #else msg(LOG_WARNING, "allow_filesystem_mark is unsupported on this kernel - ignoring"); #endif return rc; } fapolicyd-1.3.4/src/library/daemon-config.h000066400000000000000000000020341470754500200206060ustar00rootroot00000000000000/* daemon-config.h -- * Copyright 2018-20 Red Hat Inc. * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb * Radovan Sroka * */ #ifndef DAEMON_CONFIG_H #define DAEMON_CONFIG_H #include "conf.h" int load_daemon_config(conf_t *config); void free_daemon_config(conf_t *config); #endif fapolicyd-1.3.4/src/library/database.c000066400000000000000000000765511470754500200176560ustar00rootroot00000000000000/* * database.c - Trust database * Copyright (c) 2016,2018-24 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka * Marek Tamaskovic */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "database.h" #include "message.h" #include "llist.h" #include "file.h" #include "fd-fgets.h" #include "fapolicyd-backend.h" #include "backend-manager.h" #include "gcc-attributes.h" #include "paths.h" #include "policy.h" // Local defines enum { READ_DATA, READ_TEST_KEY, READ_DATA_DUP }; typedef enum { DB_NO_OP, ONE_FILE, RELOAD_DB, FLUSH_CACHE, RELOAD_RULES } db_ops_t; #define BUFFER_SIZE 4096 #define MEGABYTE (1024*1024) // Local variables static MDB_env *env; static MDB_dbi dbi; static int dbi_init = 0; static unsigned MDB_maxkeysize; static const char *data_dir = DB_DIR; static const char *db = DB_NAME; static int lib_symlink=0, lib64_symlink=0, bin_symlink=0, sbin_symlink=0; static struct pollfd ffd[1] = { {0, 0, 0} }; static integrity_t integrity; static atomic_int reload_db = 0; static pthread_t update_thread; static pthread_mutex_t update_lock; static pthread_mutex_t rule_lock; // Local functions static void *update_thread_main(void *arg); static int update_database(conf_t *config); // External variables extern volatile atomic_bool stop; extern volatile atomic_bool needs_flush; extern volatile atomic_bool reload_rules; static int is_link(const char *path) { int rc; struct stat sb; rc = lstat(path, &sb); if (rc == 0) { if (S_ISLNK(sb.st_mode)) return 1; } return 0; } const char *lookup_tsource(unsigned int tsource) { switch (tsource) { case SRC_RPM: return "rpmdb"; case SRC_DEB: return "debdb"; case SRC_FILE_DB: return "filedb"; } return "src_unknown"; } int preconstruct_fifo(const conf_t *config) { int rc; char err_buff[BUFFER_SIZE]; /* Ensure that the RUN_DIR exists */ if (mkdir(RUN_DIR, 0770) && errno != EEXIST) { msg(LOG_ERR, "Failed to create a directory %s (%s)", RUN_DIR, strerror_r(errno, err_buff, BUFFER_SIZE)); return 1; } else { /* Make sure that there is no such file/fifo */ unlink_fifo(); } rc = mkfifo(fifo_path, 0660); if (rc != 0) { msg(LOG_ERR, "Failed to create a pipe %s (%s)", fifo_path, strerror_r(errno, err_buff, BUFFER_SIZE)); return 1; } if ((ffd[0].fd = open(fifo_path, O_RDWR)) == -1) { msg(LOG_ERR, "Failed to open a pipe %s (%s)", fifo_path, strerror_r(errno, err_buff, BUFFER_SIZE)); unlink_fifo(); return 1; } if (config->gid != getgid()) { if ((fchown(ffd[0].fd, 0, config->gid))) { msg(LOG_ERR, "Failed to fix ownership of pipe %s (%s)", fifo_path, strerror_r(errno, err_buff, BUFFER_SIZE)); unlink_fifo(); close(ffd[0].fd); return 1; } } return 0; } static int init_db(const conf_t *config) { unsigned int flags = MDB_MAPASYNC|MDB_NOSYNC; #ifndef DEBUG flags |= MDB_WRITEMAP; #endif if (mdb_env_create(&env)) return 1; if (mdb_env_set_maxdbs(env, 2)) return 2; if (mdb_env_set_mapsize(env, config->db_max_size*MEGABYTE)) return 3; if (mdb_env_set_maxreaders(env, 4)) return 4; int rc = mdb_env_open(env, data_dir, flags, 0660); if (rc) { msg(LOG_ERR, "env_open error: %s", mdb_strerror(rc)); return 5; } MDB_maxkeysize = mdb_env_get_maxkeysize(env); integrity = config->integrity; msg(LOG_INFO, "fapolicyd integrity is %u", integrity); lib_symlink = is_link("/lib"); lib64_symlink = is_link("/lib64"); bin_symlink = is_link("/bin"); sbin_symlink = is_link("/sbin"); return 0; } static unsigned get_pages_in_use(void); static unsigned long pages, max_pages; static void close_db(int do_report) { if (do_report) { MDB_envinfo st; // Collect useful stats unsigned size = get_pages_in_use(); if (size == 0) { msg(LOG_DEBUG, "The trust database is empty."); } else { mdb_env_info(env, &st); max_pages = st.me_mapsize / size; msg(LOG_DEBUG, "Trust database max pages: %lu", max_pages); msg(LOG_DEBUG, "Trust database pages in use: %lu (%lu%%)", pages, max_pages ? ((100*pages)/max_pages) : 0); } } // Now close down mdb_close(env, dbi); mdb_env_close(env); } static void check_db_size(void) { MDB_envinfo st; // Collect stats unsigned long size = get_pages_in_use(); if (size == 0) { msg(LOG_WARNING, "The trust database is empty"); return; } mdb_env_info(env, &st); max_pages = st.me_mapsize / size; unsigned long percent = max_pages ? (100*pages)/max_pages : 0; if (percent > 80) msg(LOG_WARNING, "Trust database at %lu%% capacity - " "might want to increase db_max_size setting", percent); } void database_report(FILE *f) { fprintf(f, "Trust database max pages: %lu\n", max_pages); fprintf(f, "Trust database pages in use: %lu (%lu%%)\n", pages, max_pages ? ((100*pages)/max_pages) : 0); } /* * A DBI has to be associated with any new txn instance. It can be * reused within the same environment unless an abort is used. Aborts * close the data base instance. */ static int open_dbi(MDB_txn *txn) { if (!dbi_init) { int rc; if ((rc = mdb_dbi_open(txn, db, MDB_CREATE|MDB_DUPSORT, &dbi))){ msg(LOG_ERR, "%s", mdb_strerror(rc)); return rc; } dbi_init = 1; } return 0; } static void abort_transaction(MDB_txn *txn) { mdb_txn_abort(txn); dbi_init = 0; } /* * Convert path to a hash value. Used when the path exceeds the LMDB key * limit(511). Note: Returned value must be deallocated. */ static char *path_to_hash(const char *path, const size_t path_len) __attr_dealloc_free __attr_access ((__read_only__, 1, 2)); static char *path_to_hash(const char *path, const size_t path_len) { unsigned char hptr[80]; char *digest; if (path_len == 0) return NULL; SHA512((unsigned char *)path, path_len, (unsigned char *)&hptr); digest = malloc((SHA512_LEN * 2) + 1); if (digest == NULL) return digest; bytes2hex(digest, hptr, SHA512_LEN); return digest; } /* * path - key * status, file size, sha256 hash - data * status means if data is confirmed: unknown, yes, no */ static int write_db(const char *idx, const char *data) { MDB_val key, value; MDB_txn *txn; int rc, ret_val = 0; size_t len; char *hash = NULL; if (mdb_txn_begin(env, NULL, 0, &txn)) return 1; if (open_dbi(txn)) { abort_transaction(txn); return 2; } len = strlen(idx); if (len > MDB_maxkeysize) { hash = path_to_hash(idx, len); if (hash == NULL) { abort_transaction(txn); return 5; } key.mv_data = (void *)hash; key.mv_size = (SHA512_LEN * 2) + 1; } else { key.mv_data = (void *)idx; key.mv_size = len; } value.mv_data = (void *)data; value.mv_size = strlen(data); if ((rc = mdb_put(txn, dbi, &key, &value, 0))) { msg(LOG_ERR, "%s", mdb_strerror(rc)); abort_transaction(txn); ret_val = 3; goto out; } if ((rc = mdb_txn_commit(txn))) { msg(LOG_ERR, "%s", mdb_strerror(rc)); ret_val = 4; goto out; } out: if (len > MDB_maxkeysize) free(hash); return ret_val; } /* * The idea with this set of code is that we can set up ops once * and perform many read operations. This reduces the need to setup * a read lock every time and initial a whole transaction. It returns * a 0 on success and a 1 on error. */ static MDB_txn *lt_txn = NULL; static MDB_cursor *lt_cursor = NULL; static int start_long_term_read_ops(void) { int rc; if (lt_txn == NULL) { if (mdb_txn_begin(env, NULL, MDB_RDONLY, <_txn)) return 1; } if ((rc = open_dbi(lt_txn))) { msg(LOG_ERR, "open_dbi:%s", mdb_strerror(rc)); abort_transaction(lt_txn); lt_txn = NULL; return 1; } if (lt_cursor == NULL) { if ((rc = mdb_cursor_open(lt_txn, dbi, <_cursor))) { msg(LOG_ERR, "cursor_open:%s", mdb_strerror(rc)); abort_transaction(lt_txn); lt_txn = NULL; return 1; } } return 0; } /* * We are finished with read ops. Close it up. */ static void end_long_term_read_ops(void) { mdb_cursor_close(lt_cursor); lt_cursor = NULL; abort_transaction(lt_txn); lt_txn = NULL; } static unsigned get_pages_in_use(void) { MDB_stat st; start_long_term_read_ops(); mdb_stat(lt_txn, dbi, &st); end_long_term_read_ops(); pages = st.ms_leaf_pages + st.ms_branch_pages + st.ms_overflow_pages; return st.ms_psize; } // if success, the function returns positive number of entries in database // if error, it returns -1 static long get_number_of_entries(void) { MDB_stat status; start_long_term_read_ops(); mdb_stat(lt_txn, dbi, &status); end_long_term_read_ops(); return status.ms_entries; } /* * This is the long term read operation. It takes a path as input and * search for the data. It returns NULL on error or if no data found. * The returned string must be freed by the caller. */ static char *lt_read_db(const char *index, int operation, int *error) __attr_dealloc_free; static char *lt_read_db(const char *index, int operation, int *error) { int rc; char *data, *hash = NULL; MDB_val key, value; size_t len; *error = 1; // Assume an error // If the path is too long, convert to a hash len = strlen(index); if (len > MDB_maxkeysize) { hash = path_to_hash(index, len); if (hash == NULL) return NULL; key.mv_data = (void *)hash; key.mv_size = (SHA512_LEN * 2) + 1; } else { key.mv_data = (void *)index; key.mv_size = len; } value.mv_data = NULL; value.mv_size = 0; // set cursor and read first data if (operation == READ_DATA || operation == READ_TEST_KEY) { // Read the value pointed to by key if ((rc = mdb_cursor_get(lt_cursor, &key, &value, MDB_SET))) { free(hash); if (rc == MDB_NOTFOUND) { *error = 0; } else { msg(LOG_ERR, "MDB_SET: cursor_get:%s", mdb_strerror(rc)); } return NULL; } } // read next available data // READ_DATA_DUP is supposed to be used // as subsequent call just after READ_DATA if (operation == READ_DATA_DUP) { size_t nleaves; mdb_cursor_count(lt_cursor, &nleaves); if (nleaves <= 1) { free(hash); *error = 0; return NULL; } // is there a next duplicate? if ((rc = mdb_cursor_get(lt_cursor, &key, &value, MDB_NEXT_DUP))) { free(hash); if (rc == MDB_NOTFOUND) { *error = 0; } else { msg(LOG_ERR, "MDB_NEXT_DUP: cursor_get:%s", mdb_strerror(rc)); } return NULL; } } if (len > MDB_maxkeysize) free(hash); // Failure was already returned. Need to return a pointer of // some kind. Using the db name since its non-NULL. // A next step might be to check the status field to see that its // trusted. *error = 0; if (operation == READ_TEST_KEY) { return strndup(db, MDB_maxkeysize); } if ((data = malloc(value.mv_size+1))) { memcpy(data, value.mv_data, value.mv_size); data[value.mv_size] = 0; } return data; } /* * This function takes a path as input and looks it up. If found it * will delete the entry. * static int delete_entry_db(const char *index) { MDB_txn *txn; MDB_val key, value; if (mdb_txn_begin(env, NULL, 0, &txn)) return 1; if (open_dbi(txn)) { abort_transaction(txn); return 1; } // FIXME: if we ever use this function, it will need patching // to use hashes if the path is larger than MDB_maxkeysize. key.mv_data = (void *)index; key.mv_size = strlen(index); value.mv_data = NULL; value.mv_size = 0; if (mdb_del(txn, dbi, &key, &value)) { abort_transaction(txn); return 1; } if (mdb_txn_commit(txn)) return 1; return 0; }*/ // This function checks the database to see if its empty. It returns // a 0 if it has entries, 1 on empty, and -1 if an error static int database_empty(void) { MDB_stat status; if (mdb_env_stat(env, &status)) return -1; if (status.ms_entries == 0) return 1; return 0; } static int delete_all_entries_db() { int rc = 0; MDB_txn *txn; if (mdb_txn_begin(env, NULL, 0, &txn)) return 1; if (open_dbi(txn)) { abort_transaction(txn); return 2; } // 0 -> delete , 1 -> delete and close if ((rc = mdb_drop(txn, dbi, 0))) { msg(LOG_DEBUG, "mdb_drop -> %s", mdb_strerror(rc)); abort_transaction(txn); return 3; } if ((rc = mdb_txn_commit(txn))) { if (rc == MDB_MAP_FULL) msg(LOG_ERR, "db_max_size needs to be increased"); else msg(LOG_DEBUG, "mdb_txn_commit -> %s", mdb_strerror(rc)); return 4; } return 0; } static int create_database(int with_sync) { msg(LOG_INFO, "Creating trust database"); int rc = 0; for (backend_entry *be = backend_get_first() ; be != NULL ; be = be->next ) { msg(LOG_INFO,"Loading trust data from %s backend", be->backend->name); list_item_t *item = list_get_first(&be->backend->list); for (; item != NULL; item = item->next) { if ((rc = write_db(item->index, item->data))) msg(LOG_ERR, "Error (%d) writing key=\"%s\" data=\"%s\"", rc, (const char*)item->index, (const char*)item->data); } } // Flush everything to disk if (with_sync) mdb_env_sync(env, 1); // Check if database is getting full and warn check_db_size(); return rc; } // 1 -> data match // 0 -> not found // matched -> returns index of the matched duplicate static int check_data_presence(const char * index, const char * data, int * matched) { int found = 0; int error; char *read; int operation = READ_DATA; int cnt = 0; while (1) { error = 0; read = NULL; read = lt_read_db(index, operation, &error); if (error) msg(LOG_DEBUG, "Error when reading from DB!"); if (!read) break; // check strings if (strcmp(data, read) == 0) { found = 1; } free(read); cnt++; if (found) break; if (operation == READ_DATA) operation = READ_DATA_DUP; } *matched = cnt; return found; } /* * This function will compare the backend database against our copy * of the database. It returns a 1 if they do not match, 0 if they do * match, and -1 if there is an error. */ static int check_database_copy(void) { msg(LOG_INFO, "Checking if the trust database up to date"); long problems = 0; if (start_long_term_read_ops()) return -1; long backend_total_entries = 0; long backend_added_entries = 0; for (backend_entry *be = backend_get_first() ; be != NULL ; be = be->next ) { msg(LOG_INFO, "Importing trust data from %s backend", be->backend->name); backend_total_entries += be->backend->list.count; list_item_t *item = list_get_first(&be->backend->list); for (; item != NULL; item = item->next) { int matched = 0; int found = check_data_presence(item->index, item->data, &matched); if (!found) { problems++; // missing in db // recently added file if (matched == 0) { msg(LOG_DEBUG, "%s is not in the trust database", (char*)item->index); backend_added_entries++; } // updated file // data miscompare if (matched > 0) { msg(LOG_DEBUG, "Trust data miscompare for %s", (char*)item->index); } } } } end_long_term_read_ops(); long db_total_entries = get_number_of_entries(); // something wrong if (db_total_entries == -1) return -1; msg( LOG_INFO, "Entries in trust DB: %ld", db_total_entries); // Check if database is getting full and warn check_db_size(); msg( LOG_INFO, "Loaded trust info from all backends(without duplicates): %ld", backend_total_entries); // do not print 0 if (backend_added_entries > 0) msg(LOG_INFO, "New trust database entries: %ld", backend_added_entries); // db contains records that are not present in backends anymore long removed = labs(db_total_entries - (backend_total_entries - backend_added_entries) ); // do not print 0 if (removed > 0) msg(LOG_INFO, "Removed trust database entries: %ld", removed); problems += removed; if (problems) { msg(LOG_WARNING, "Found %ld problematic trust database entries", problems); return 1; } else msg(LOG_INFO, "Trust database checks OK"); return 0; } /* * This function removes the trust database files. */ int unlink_db(void) { int rc, ret_val = 0; char path[64]; snprintf(path, sizeof(path), "%s/data.mdb", data_dir); rc = unlink(path); if (rc == -1 && errno != ENOENT) { msg(LOG_ERR, "Could not unlink %s (%s)", path, strerror(errno)); ret_val = 1; } snprintf(path, sizeof(path), "%s/lock.mdb", data_dir); rc = unlink(path); if (rc == -1 && errno != ENOENT) { msg(LOG_ERR, "Could not unlink %s (%s)", path, strerror(errno)); ret_val = 1; } snprintf(path, sizeof(path), "%s/db.ver", data_dir); rc = unlink(path); if (rc == -1 && errno != ENOENT) { msg(LOG_ERR, "Could not unlink %s (%s)", path, strerror(errno)); ret_val = 1; } return ret_val; } /* * DB version 1 = unique keys (0.8 - 0.9.2) * DB version 2 = allow duplicate keys (0.9.3 - ) * * This function is used to detect if we are using version1 of the database. * If so, we have to delete the database and rebuild it. We cannot mix * database versions because lmdb doesn't do that. * Returns 0 success and 1 for failure. */ static int migrate_database(void) { int fd; char vpath[64]; snprintf(vpath, sizeof(vpath), "%s/db.ver", data_dir); fd = open(vpath, O_RDONLY); if (fd < 0) { msg(LOG_INFO, "Trust database migration will be performed."); // Then we have a version1 db since it does not track versions if (unlink_db()) return 1; // Create the new, db version tracker and write current version fd = open(vpath, O_CREAT|O_EXCL|O_WRONLY, 0640); if (fd < 0) { msg(LOG_ERR, "Failed writing db version %s", strerror(errno)); return 1; } write(fd, "2", 1); close(fd); return 0; } else { // We have a version file, read it and check the version int rc = read(fd, vpath, 2); close(fd); if ((rc > 0) && (vpath[0] == '2')) return 0; } return 1; } /* * This function is responsible for getting the database ready to use. * It will first check to see if a database is populated. If so, then * it will verify it against the backend database just in case something * has changed. If the database does not exist, then it will create one. * It returns 0 on success and a non-zero on failure. */ int init_database(conf_t *config) { int rc; msg(LOG_INFO, "Initializing the trust database"); // update_lock is used in update_database() pthread_mutex_init(&update_lock, NULL); pthread_mutex_init(&rule_lock, NULL); if (migrate_database()) return 1; if ((rc = init_db(config))) { msg(LOG_ERR, "Cannot open the trust database, init_db() (%d)", rc); return rc; } if ((rc = backend_init(config))) { msg(LOG_ERR, "Failed to load trust data from backend (%d)", rc); close_db(0); return rc; } if ((rc = backend_load(config))) { msg(LOG_ERR, "Failed to load data from backend (%d)", rc); close_db(0); return rc; } rc = database_empty(); if (rc > 0) { if ((rc = create_database(/*with_sync*/1))) { msg(LOG_ERR, "Failed to create trust database, create_database() (%d)", rc); close_db(0); return rc; } } else { // check if our internal database is synced rc = check_database_copy(); if (rc > 0) { rc = update_database(config); if (rc) msg(LOG_ERR, "Failed updating the trust database"); } } // Conserve memory by dumping the linked lists backend_close(); pthread_create(&update_thread, NULL, update_thread_main, config); return rc; } /* * This function handles the integrity check and any retries. Retries are * necessary if the system has both i686 and x86_64 packages installed. It * takes a path as input and searches for the data. It returns 0 if no * data is found or if the integrity check has failed. There is no * distinguishing which is the case since both mean you cannot trust the file. * It returns a 1 if the file is found and trustworthy. Callers have to * check the error variable before trusting it's results. */ static int read_trust_db(const char *path, int *error, struct file_info *info, int fd) { int do_integrity = 0, mode = READ_TEST_KEY; char *res; int retry = 0; char sha_xattr[65]; if (integrity != IN_NONE && info) { do_integrity = 1; mode = READ_DATA; sha_xattr[0] = 0; // Make sure we can't re-use stack value } retry_res: retry++; if (retry >= 128) { msg(LOG_ERR, "Checked 128 duplicates for %s " "and there is no match. Breaking the cycle.", path); *error = 1; return 0; } res = lt_read_db(path, mode, error); // For subjects we do a limited check because the process had to // pass some kind of trust check to even be started and we do not // have an open fd to the file. if (!do_integrity) { if (res == NULL) return 0; free(res); return 1; } else { unsigned int tsource; off_t size; char sha[65]; // record not found if (res == NULL) return 0; if (sscanf(res, DATA_FORMAT, &tsource, &size, sha) != 3) { free(res); *error = 1; return 1; } // Need to do the compare and free res free(res); // prepare for next reading if (mode != READ_DATA_DUP) mode = READ_DATA_DUP; if (integrity == IN_SIZE) { // match! if (size == info->size) { return 1; } else { goto retry_res; } } else if (integrity == IN_IMA) { int rc = 1; // read xattr only the first time if (retry == 1) rc = get_ima_hash(fd, sha_xattr); if (rc) { if ((size == info->size) && (strcmp(sha, sha_xattr) == 0)) { return 1; } else { goto retry_res; } } else { *error = 1; return 0; } } else if (integrity == IN_SHA256) { char *hash = NULL; // Calculate a hash only one time if (retry == 1) { hash = get_hash_from_fd2(fd, info->size, 1); if (hash) { strncpy(sha_xattr, hash, 64); sha_xattr[64] = 0; free(hash); } else { *error = 1; return 0; } } if ((size == info->size) && (strcmp(sha, sha_xattr) == 0)) return 1; else { goto retry_res; } } } *error = 1; return 0; } // Returns a 1 if trusted and 0 if not and -1 on error int check_trust_database(const char *path, struct file_info *info, int fd) { int retval = 0, error; int res; // this function is going to be used from decision_thread // that means we need to be sure database won't change under // our hands lock_update_thread(); if (start_long_term_read_ops()) { unlock_update_thread(); return -1; } res = read_trust_db(path, &error, info, fd); if (error) retval = -1; else if (res) retval = 1; else if (lib64_symlink || lib_symlink || bin_symlink || sbin_symlink) { // If we are on a system that symlinks the top level // directories to /usr, then let's try again without the /usr // dir. There shouldn't be many packages that have this // problem. These are sorted from most likely to least. if (strncmp(path, "/usr/", 5) == 0) { if ((lib64_symlink && strncmp(&path[5], "lib64/", 6) == 0) || (lib_symlink && strncmp(&path[5], "lib/", 4) == 0) || (bin_symlink && strncmp(&path[5], "bin/", 4) == 0) || (sbin_symlink && strncmp(&path[5], "sbin/", 5) == 0)) { // We have a symlink, retry res = read_trust_db(&path[4], &error, info, fd); if (error) retval = -1; else if (res) retval = 1; } } } end_long_term_read_ops(); unlock_update_thread(); return retval; } void close_database(void) { pthread_join(update_thread, NULL); // we can close db when we are really sure update_thread does not exist close_db(1); pthread_mutex_destroy(&update_lock); pthread_mutex_destroy(&rule_lock); backend_close(); unlink_fifo(); } void unlink_fifo(void) { unlink(fifo_path); } /* * Lock wrapper for update mutex */ void lock_update_thread(void) { pthread_mutex_lock(&update_lock); //msg(LOG_DEBUG, "lock_update_thread()"); } /* * Unlock wrapper for update mutex */ void unlock_update_thread(void) { pthread_mutex_unlock(&update_lock); //msg(LOG_DEBUG, "unlock_update_thread()"); } /* * Lock wrapper for rule mutex */ void lock_rule(void) { pthread_mutex_lock(&rule_lock); //msg(LOG_DEBUG, "lock_rule()"); } /* * Unlock wrapper for rule mutex */ void unlock_rule(void) { pthread_mutex_unlock(&rule_lock); //msg(LOG_DEBUG, "unlock_rule()"); } /* * This function reloads updated backend db into our internal database. * It returns 0 on success and non-zero on error. */ static int update_database(conf_t *config) { int rc; msg(LOG_INFO, "Updating trust database"); msg(LOG_DEBUG, "Loading trust database backends"); /* * backend loading/reloading should be done in upper level */ /* if ((rc = backend_load(config))) { msg(LOG_ERR, "Cannot open the backend database (%d)", rc); return rc; }*/ lock_update_thread(); if ((rc = delete_all_entries_db())) { msg(LOG_ERR, "Cannot delete database (%d)", rc); unlock_update_thread(); return rc; } rc = create_database(/*with_sync*/0); // signal that cache need to be flushed needs_flush = true; unlock_update_thread(); mdb_env_sync(env, 1); if (rc) { msg(LOG_ERR, "Failed to create the trust database (%d)", rc); close_db(1); return rc; } return 0; } static int handle_record(const char * buffer) { char path[2048+1]; char hash[64+1]; off_t size; // validating input int res = sscanf(buffer, "%2048s %lu %64s", path, &size, hash); msg(LOG_DEBUG, "update_thread: Parsing input buffer: %s", buffer); msg(LOG_DEBUG, "update_thread: Parsing input words(expected 3): %d", res); if (res != 3) { msg(LOG_INFO, "Corrupted data read, ignoring..."); return 1; } char data[BUFFER_SIZE]; snprintf(data, BUFFER_SIZE, DATA_FORMAT, (unsigned int)SRC_UNKNOWN, size, hash); msg(LOG_DEBUG, "update_thread: Saving %s %s", path, data); lock_update_thread(); write_db(path, data); unlock_update_thread(); return 0; } void set_reload_trust_database(void) { reload_db = 1; } static void do_reload_db(conf_t* config) { msg(LOG_INFO,"It looks like there was an update of the system... Syncing DB."); int rc; backend_close(); backend_init(config); backend_load(config); if ((rc = update_database(config))) { msg(LOG_ERR, "Cannot update trust database!"); close(ffd[0].fd); backend_close(); unlink_fifo(); exit(rc); } msg(LOG_INFO, "Updated"); // Conserve memory backend_close(); } static void *update_thread_main(void *arg) { int rc; sigset_t sigs; char buff[BUFFER_SIZE]; char err_buff[BUFFER_SIZE]; conf_t *config = (conf_t *)arg; int do_operation = DB_NO_OP;; #ifdef DEBUG msg(LOG_DEBUG, "Update thread main started"); #endif /* This is a worker thread. Don't handle external signals. */ sigemptyset(&sigs); sigaddset(&sigs, SIGTERM); sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGUSR1); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGQUIT); pthread_sigmask(SIG_SETMASK, &sigs, NULL); if (ffd[0].fd == 0) { if (preconstruct_fifo(config)) return NULL; } fcntl(ffd[0].fd, F_SETFL, O_NONBLOCK); ffd[0].events = POLLIN; while (!stop) { rc = poll(ffd, 1, 1000); if (reload_rules) { reload_rules = false; load_rule_file(); lock_rule(); do_reload_rules(config); unlock_rule(); } // got SIGHUP if (reload_db) { reload_db = 0; do_reload_db(config); } #ifdef DEBUG msg(LOG_DEBUG, "Update poll interrupted"); #endif if (rc < 0) { if (errno == EINTR) { #ifdef DEBUG msg(LOG_DEBUG, "update poll rc = EINTR"); #endif continue; } else { msg(LOG_ERR, "Update poll error (%s)", strerror_r(errno, err_buff, BUFFER_SIZE)); goto finalize; } } else if (rc == 0) { #ifdef DEBUG msg(LOG_DEBUG, "Update poll timeout expired"); #endif continue; } else { if (ffd[0].revents & POLLIN) { fd_fgets_context_t * fd_fgets_context = fd_fgets_init(); do { fd_fgets_rewind(fd_fgets_context); int res = fd_fgets(fd_fgets_context, buff, sizeof(buff), ffd[0].fd); // nothing to read if (res == -1) break; else if (res > 0) { char* end = strchr(buff, '\n'); if (end == NULL) { msg(LOG_ERR, "Too long line?"); continue; } int count = end - buff; *end = '\0'; for (int i = 0 ; i < count ; i++) { // assume file name // operation = 0 if (buff[i] == '/') { do_operation = ONE_FILE; break; } if (buff[i] == RELOAD_TRUSTDB_COMMAND) { do_operation = RELOAD_DB; break; } if (buff[i] == FLUSH_CACHE_COMMAND) { do_operation = FLUSH_CACHE; break; } if (buff[i] == RELOAD_RULES_COMMAND) { do_operation = RELOAD_RULES; break; } if (isspace(buff[i])) continue; msg(LOG_ERR, "Cannot handle data \"%s\" from pipe", buff); break; } *end = '\n'; // got "1" -> reload db if (do_operation == RELOAD_DB) { do_operation = DB_NO_OP; do_reload_db(config); } else if (do_operation == RELOAD_RULES) { do_operation = DB_NO_OP; load_rule_file(); lock_rule(); do_reload_rules(config); unlock_rule(); // got "2" -> flush cache } else if (do_operation == FLUSH_CACHE) { do_operation = DB_NO_OP; needs_flush = true; } else if (do_operation == ONE_FILE) { do_operation = DB_NO_OP; if (handle_record(buff)) continue; } } } while(!fd_fgets_eof(fd_fgets_context)); fd_fgets_destroy(fd_fgets_context); } } } finalize: close(ffd[0].fd); unlink_fifo(); return NULL; } /*********************************************************************** * This section of functions are used by the command line utility to * iterate across the database to verify each entry. It will be a read * only operation. ***********************************************************************/ static walkdb_entry_t wdb_entry; // Returns 0 on success and 1 on failure int walk_database_start(conf_t *config) { int rc; // Initialize the database if (init_db(config)) { printf("Cannot open the trust database\n"); return 1; } if (database_empty()) { printf("The trust database is empty - nothing to do\n"); return 1; } // Position to the first entry mdb_txn_begin(env, NULL, MDB_RDONLY, <_txn); if ((rc = open_dbi(lt_txn))) { puts(mdb_strerror(rc)); abort_transaction(lt_txn); return 1; } if ((rc = mdb_cursor_open(lt_txn, dbi, <_cursor))) { puts(mdb_strerror(rc)); abort_transaction(lt_txn); return 1; } if ((rc = mdb_cursor_get(lt_cursor, &wdb_entry.path, &wdb_entry.data, MDB_FIRST)) == 0) return 0; if (rc != MDB_NOTFOUND) puts(mdb_strerror(rc)); return 1; } walkdb_entry_t *walk_database_get_entry(void) { return &wdb_entry; } // Returns 1 on success and 0 in error int walk_database_next(void) { int rc; if ((rc = mdb_cursor_get(lt_cursor, &wdb_entry.path, &wdb_entry.data, MDB_NEXT)) == 0) return 1; if (rc != MDB_NOTFOUND) puts(mdb_strerror(rc)); return 0; } void walk_database_finish(void) { mdb_cursor_close(lt_cursor); abort_transaction(lt_txn); close_db(0); } fapolicyd-1.3.4/src/library/database.h000066400000000000000000000034711470754500200176520ustar00rootroot00000000000000/* * database.h - Header file for trust database * Copyright (c) 2018-22 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #ifndef DATABASE_HEADER #define DATABASE_HEADER #include #include "conf.h" #include "file.h" typedef struct { MDB_val path; MDB_val data; } walkdb_entry_t; void lock_update_thread(void); void unlock_update_thread(void); const char *lookup_tsource(unsigned int tsource); int preconstruct_fifo(const conf_t *config); int init_database(conf_t *config); int check_trust_database(const char *path, struct file_info *info, int fd); void set_reload_trust_database(void); void close_database(void); void database_report(FILE *f); int unlink_db(void); void unlink_fifo(void); void lock_rule(void); void unlock_rule(void); // Database verification functions int walk_database_start(conf_t *config); walkdb_entry_t *walk_database_get_entry(void); int walk_database_next(void); void walk_database_finish(void); #define RELOAD_TRUSTDB_COMMAND '1' #define FLUSH_CACHE_COMMAND '2' #define RELOAD_RULES_COMMAND '3' #endif fapolicyd-1.3.4/src/library/deb-backend.c000066400000000000000000000131451470754500200202170ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "conf.h" #include "fapolicyd-backend.h" #include "file.h" #include "llist.h" #include "message.h" #include "md5-backend.h" static const char kDebBackend[] = "debdb"; static int deb_init_backend(void); static int deb_load_list(const conf_t *); static int deb_destroy_backend(void); backend deb_backend = { kDebBackend, deb_init_backend, deb_load_list, deb_destroy_backend, /* list initialization */ {0, 0, NULL}, }; // ================================================================ // These functions are copied from dpkg source v1.21.1 // For some reason they segfault when i call :/ int parse_filehash_buffer(struct varbuf *buf, struct pkginfo *pkg, struct pkgbin *pkgbin) { char *thisline, *nextline; const char *pkgname = pkg_name(pkg, pnaw_nonambig); const char *buf_end = buf->buf + buf->used; for (thisline = buf->buf; thisline < buf_end; thisline = nextline) { struct fsys_namenode *namenode; char *endline, *hash_end, *filename; endline = memchr(thisline, '\n', buf_end - thisline); if (endline == NULL) { msg(LOG_ERR, "control file '%s' for package '%s' is " "missing final newline\n", HASHFILE, pkgname); return 1; } /* The md5sum hash has a constant length. */ hash_end = thisline + kMd5HexSize; filename = hash_end + 2; if (filename + 1 > endline) { msg(LOG_ERR, "control file '%s' for package '%s' is " "missing value\n", HASHFILE, pkgname); return 1; } if (hash_end[0] != ' ' || hash_end[1] != ' ') { msg(LOG_ERR, "control file '%s' for package '%s' is " "missing value separator\n", HASHFILE, pkgname); return 1; } hash_end[0] = '\0'; /* Where to start next time around. */ nextline = endline + 1; /* Strip trailing ‘/’. */ if (endline > thisline && endline[-1] == '/') endline--; *endline = '\0'; if (endline == thisline) { msg(LOG_ERR, "control file '%s' for package '%s' " "contains empty filename\n", HASHFILE, pkgname); return 1; } /* Add the file to the list. */ namenode = fsys_hash_find_node(filename, 0); namenode->newhash = nfstrsave(thisline); } return 0; } void parse_filehash2(struct pkginfo *pkg, struct pkgbin *pkgbin) { const char *hashfile; struct varbuf buf = VARBUF_INIT; struct dpkg_error err = DPKG_ERROR_INIT; hashfile = pkg_infodb_get_file(pkg, pkgbin, HASHFILE); if (file_slurp(hashfile, &buf, &err) < 0 && err.syserrno != ENOENT) msg(LOG_ERR, "loading control file '%s' for package '%s'", HASHFILE, pkg_name(pkg, pnaw_nonambig)); if (buf.used > 0) parse_filehash_buffer(&buf, pkg, pkgbin); varbuf_destroy(&buf); } // End of functions copied from dpkg. // ======================================================================= static int deb_load_list(const conf_t *conf) { const char *control_file = "md5sums"; list_empty(&deb_backend.list); struct _hash_record *hashtable = NULL; struct _hash_record **hashtable_ptr = &hashtable; struct pkg_array array; pkg_array_init_from_hash(&array); msg(LOG_INFO, "Computing hashes for %d packages.", array.n_pkgs); fsys_hash_reset(); for (int i = 0; i < array.n_pkgs; i++) { struct pkginfo *package = array.pkgs[i]; if (package->status != PKG_STAT_INSTALLED) { continue; } printf("\x1b[2K\rPackage %d / %d : %s", i + 1, array.n_pkgs, package->set->name); if (pkg_infodb_has_file(package, &package->installed, control_file)) pkg_infodb_get_file(package, &package->installed, control_file); ensure_packagefiles_available(package); // Should not need this copy of code ... parse_filehash2(package, &package->installed); // This is causing segfault in linked lib :/ // parse_filehash(package, &package->installed); // ensure_diversions(); struct fsys_namenode_list *file = package->files; if (!file) { // Package does not have any files. continue; } // Loop over all files in the package, adding them to debdb. while (file) { struct fsys_namenode *namenode = file->namenode; // Get the hash and path of the file. const char *hash = (namenode->newhash == NULL) ? namenode->oldhash : namenode->newhash; const char *path = (namenode->divert && !namenode->divert->camefrom) ? namenode->divert->useinstead->name : namenode->name; if (hash != NULL) { add_file_to_backend_by_md5(path, hash, hashtable_ptr, SRC_DEB, &deb_backend); } file = file->next; } } struct _hash_record *item, *tmp; HASH_ITER(hh, hashtable, item, tmp) { HASH_DEL(hashtable, item); free((void *)item->key); free((void *)item); } pkg_array_destroy(&array); return 0; } static int deb_init_backend(void) { dpkg_program_init(kDebBackend); list_init(&deb_backend.list); msg(LOG_INFO, "Loading debdb backend"); enum modstatdb_rw status = msdbrw_readonly; status = modstatdb_open(msdbrw_readonly); if (status != msdbrw_readonly) { msg(LOG_ERR, "Could not open database for reading. Status %d", status); return 1; } return 0; } static int deb_destroy_backend(void) { dpkg_program_done(); list_empty(&deb_backend.list); modstatdb_shutdown(); return 0; } fapolicyd-1.3.4/src/library/escape.c000066400000000000000000000075341470754500200173450ustar00rootroot00000000000000/* * escape.c - Source file for escaping capability * Copyright (c) 2021,23 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #include "escape.h" #include #include #include #include "message.h" static const char sh_set[] = "\"'`$\\!()| "; /* * this function checks whether escaping is needed and if yes * it returns positive value and this value represents the size * of the string after escaping */ size_t check_escape_shell(const char *input) { unsigned int len = strlen(input); size_t i = 0, cnt = 0; while (i < len) { // \000 if (input[i] < 32) cnt += 4; // \\ \/ else if (strchr(sh_set, input[i])) cnt += 2; // non escaped char else cnt++; i++; } // if no escaped char if (cnt == len) return 0; return cnt; } #define MAX_SIZE 8192 static char escape_buffer[MAX_SIZE]; char *escape_shell(const char *input, const size_t expected_size) { if(!input) return NULL; if (expected_size >= MAX_SIZE) return NULL; size_t len = strlen(input); unsigned int i = 0, j = 0; while (i < len) { if ((unsigned char)input[i] < 32) { escape_buffer[j++] = ('\\'); escape_buffer[j++] = ('0' + ((input[i] & 0300) >> 6)); escape_buffer[j++] = ('0' + ((input[i] & 0070) >> 3)); escape_buffer[j++] = ('0' + (input[i] & 0007)); } else if (strchr(sh_set, input[i])) { escape_buffer[j++] = ('\\'); escape_buffer[j++] = input[i]; } else escape_buffer[j++] = input[i]; i++; } escape_buffer[j] = '\0'; /* terminate string */ return escape_buffer; } #define isoctal(a) (((a) & ~7) == '0') void unescape_shell(char *s, const size_t len) { size_t sz = 0; char *buf = s; while (*s) { if (*s == '\\' && sz + 3 < len && isoctal(s[1]) && isoctal(s[2]) && isoctal(s[3])) { *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7); s += 4; sz += 4; } else if (*s == '\\' && sz + 2 < len) { *buf++ = s[1]; s += 2; sz += 2; } else { *buf++ = *s++; sz++; } } *buf = '\0'; } #define IS_HEX(X) (isxdigit(X) > 0 && !(islower(X) > 0)) static char asciiHex2Bits(char X) { char base = 0; if (X >= '0' && X <= '9') { base = '0'; } else if (X >= 'A' && X <= 'F') { base = 'A' - 10; } return (X - base) & 0X00FF; } // unescape old format of a trust file // it makes code backwards compatible char *unescape(const char *input) { char buffer[4096 + 1] = {0}; size_t input_len = strlen(input); size_t pos = 0; for (size_t i = 0; i < input_len; i++ ) { if (input[i] == '%') { if (i+2 < input_len && (IS_HEX(input[i+1]) && IS_HEX(input[i+2])) ) { char c = asciiHex2Bits(input[i+1]); char d = asciiHex2Bits(input[i+2]); if (pos >=(sizeof(buffer) - 1)) return NULL; buffer[pos++] = (c << 4) + d; i += 2; } else { msg(LOG_WARNING, "Input %s does not have a valid escape sequence, " "unable to unescape, copying char by char", input); // if not vaid sequence, copy char by char if (pos >=(sizeof(buffer) - 1)) return NULL; buffer[pos++] = input[i]; } } else { if (pos >=(sizeof(buffer) - 1)) return NULL; buffer[pos++] = input[i]; } } return strdup(buffer); } fapolicyd-1.3.4/src/library/escape.h000066400000000000000000000023211470754500200173370ustar00rootroot00000000000000/* * escape.h - Header file for escaping capability * Copyright (c) 2021,23 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #ifndef ESCAPE_H #define ESCAPE_H #include "gcc-attributes.h" char *escape_shell(const char *, const size_t) __attr_access ((__read_only__, 1, 2)); size_t check_escape_shell(const char *); void unescape_shell(char *s, const size_t len) __attr_access ((__read_write__, 1, 2)); char *unescape(const char *input) __attr_dealloc_free; #endif fapolicyd-1.3.4/src/library/event.c000066400000000000000000000344671470754500200172330ustar00rootroot00000000000000/* * event.c - Functions to access event attributes * Copyright (c) 2016,2018-20,2023 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #include "config.h" #include #include #include #include #include #include #include #include #include "event.h" #include "database.h" #include "file.h" #include "lru.h" #include "message.h" #include "policy.h" #define ALL_EVENTS (FAN_ALL_EVENTS|FAN_OPEN_PERM|FAN_ACCESS_PERM| \ FAN_OPEN_EXEC_PERM) static Queue *subj_cache = NULL; static Queue *obj_cache = NULL; volatile atomic_bool needs_flush = false; // Return 0 on success and 1 on error int init_event_system(const conf_t *config) { subj_cache=init_lru(config->subj_cache_size, (void (*)(void *))subject_clear, "Subject"); if (!subj_cache) return 1; obj_cache = init_lru(config->obj_cache_size, (void (*)(void *))object_clear, "Object"); if (!obj_cache) return 1; return 0; } static int flush_cache(void) { if (obj_cache->count == 0) return 0; const unsigned int size = obj_cache->total; msg(LOG_DEBUG, "Flushing object cache"); destroy_lru(obj_cache); obj_cache = init_lru(size, (void (*)(void *))object_clear, "Object"); if (!obj_cache) return 1; msg(LOG_DEBUG, "Flushed"); return 0; } void destroy_event_system(void) { destroy_lru(subj_cache); destroy_lru(obj_cache); } // Return 0 on success and 1 on error int new_event(const struct fanotify_event_metadata *m, event_t *e) { subject_attr_t subj; QNode *q_node; unsigned int key, rc, evict = 1, skip_path = 0; s_array *s; o_array *o; struct proc_info *pinfo; struct file_info *finfo; if (needs_flush) { flush_cache(); needs_flush = false; } // Transfer things from fanotify structs to ours e->pid = m->pid; e->fd = m->fd; e->type = m->mask & ALL_EVENTS; e->num = 0; key = compute_subject_key(subj_cache, m->pid); q_node = check_lru_cache(subj_cache, key); s = (s_array *)q_node->item; // get proc fingerprint pinfo = stat_proc_entry(m->pid); if (pinfo == NULL) return 1; // Check the subject to see if its what its supposed to be if (s) { rc = compare_proc_infos(pinfo, s->info); // EXEC_PERM causes 2 events for every execute. First is an // execute request. This is followed by an open request of // the same file. So, if we are collecting and perm is open, // that means this is the second step, open. We also need // be sure we are the same process. We skip collecting path // because it was collected on perm = execute. if ((s->info->state == STATE_COLLECTING) && (e->type & FAN_OPEN_PERM) && !rc) { skip_path = 1; s->info->state = STATE_REOPEN; // special branch after ld_so exec // next opens will go fall trough if (s->info->path1 && (strcmp(s->info->path1, SYSTEM_LD_SO) == 0)) s->info->state = STATE_DEFAULT_REOPEN; } // If not same proc or we detect execution, evict evict = rc || e->type & FAN_OPEN_EXEC_PERM; // We need to reset everything now that execve has finished if (s->info->state == STATE_STATIC_PARTIAL && !rc) { // If the static app itself launches an app right // away, go back to collecting. if (e->type & FAN_OPEN_EXEC_PERM) s->info->state = STATE_COLLECTING; else { s->info->state = STATE_STATIC; skip_path = 1; } evict = 0; subject_reset(s, EXE); subject_reset(s, COMM); subject_reset(s, EXE_TYPE); subject_reset(s, SUBJ_TRUST); } // Static has to sequence through a state machine to get to // the point where we can do a full subject reset. Still // in execve at this point. if ((s->info->state == STATE_STATIC_REOPEN) && (e->type & FAN_OPEN_PERM) && !rc) { s->info->state = STATE_STATIC_PARTIAL; evict = 0; skip_path = 1; } // If we've seen the reopen and its an execute and process // has an interpreter and we're the same process, don't evict // and don't collect the path since reopen interp will. The // !skip_path is to prevent the STATE_REOPEN change above from // falling into this. if ((s->info->state == STATE_REOPEN) && !skip_path && (e->type & FAN_OPEN_EXEC_PERM) && (s->info->elf_info & HAS_INTERP) && !rc) { s->info->state = STATE_DEFAULT_REOPEN; evict = 0; skip_path = 1; } // this is how STATE_REOPEN and // STATE_DEFAULT_REOPEN differs // in STATE_REOPEN path is always skipped if ((s->info->state == STATE_REOPEN) && !skip_path && (e->type & FAN_OPEN_PERM) && !rc) { skip_path = 1; } if (evict) { lru_evict(subj_cache, key); q_node = check_lru_cache(subj_cache, key); s = (s_array *)q_node->item; } else if (s->cnt == 0) msg(LOG_DEBUG, "cached subject has cnt of 0"); } if (evict) { // If empty, setup the subject with what we currently have e->s = malloc(sizeof(s_array)); subject_create(e->s); subj.type = PID; subj.val = e->pid; subject_add(e->s, &subj); // give custody of the list to the cache q_node->item = e->s; ((s_array *)q_node->item)->info = pinfo; // If this is the first time we've seen this process // and its doing a file open, its likely to be a running // process. That means we should not do pattern detection. if (!s && (e->type & FAN_OPEN_PERM)) pinfo->state = STATE_NORMAL; } else { // Use the one from the cache e->s = s; clear_proc_info(pinfo); free(pinfo); } // Init the object // get file fingerprint rc = 1; finfo = stat_file_entry(m->fd); if (finfo == NULL) return 1; // Just using inodes don't give a good key. It needs // conditioning to use more slots in the cache. unsigned long magic = finfo->inode + finfo->time.tv_nsec + finfo->size; key = compute_object_key(obj_cache, magic); q_node = check_lru_cache(obj_cache, key); o = (o_array *)q_node->item; if (o) { rc = compare_file_infos(finfo, o->info); if (rc) { lru_evict(obj_cache, key); q_node = check_lru_cache(obj_cache, key); o = (o_array *)q_node->item; } } if (rc) { // If empty, setup the object with what we currently have e->o = malloc(sizeof(o_array)); object_create(e->o); // give custody of the list to the cache q_node->item = e->o; ((o_array *)q_node->item)->info = finfo; } else { // Use the one from the cache e->o = o; free(finfo); } // Setup pattern info pinfo = e->s->info; if (pinfo && !skip_path && pinfo->state < STATE_FULL) { object_attr_t *on = get_obj_attr(e, PATH); if (on) { const char *file = on->o; if (pinfo->path1 == NULL) { // In this step, we gather info on what is // being asked permission to execute. pinfo->path1 = strdup(file); pinfo->elf_info = gather_elf(e->fd, e->o->info->size); // pinfo->state = STATE_COLLECTING;Just for clarity } else if (pinfo->path2 == NULL) { pinfo->path2 = strdup(file); pinfo->state = STATE_PARTIAL; } else { // This third look is needed because the first // two are still the old process as far as // procfs is concerned. Reset things that could // change based on the new process name. pinfo->state = STATE_FULL; subject_reset(e->s, EXE); subject_reset(e->s, COMM); subject_reset(e->s, EXE_TYPE); subject_reset(e->s, SUBJ_TRUST); } } } return 0; } /* * This function will search the list for a nv pair of the right type. * If not found, it will create the type and return it. */ subject_attr_t *get_subj_attr(event_t *e, subject_type_t t) { subject_attr_t subj; subject_attr_t *sn; s_array *s = e->s; sn = subject_access(s, t); if (sn) return sn; // One not on the list, look it up and make one subj.type = t; subj.str = NULL; switch (t) { case AUID: subj.val = get_program_auid_from_pid(e->pid); break; case UID: subj.val = get_program_uid_from_pid(e->pid); break; case SESSIONID: subj.val = get_program_sessionid_from_pid(e->pid); break; case PID: subj.val = e->pid; break; case PPID: subj.val = get_program_ppid_from_pid(e->pid); break; case GID: subj.set = get_gid_set_from_pid(e->pid); break; case COMM: { char buf[21], *ptr; ptr = get_comm_from_pid(e->pid, sizeof(buf), buf); if (ptr) subj.str = strdup(buf); else subj.str = strdup("?"); } break; // If these 2 ever get separated, update subject_add // and subject_access in subject.c case EXE: case EXE_DIR: { char buf[PATH_MAX+1], *ptr; ptr = get_program_from_pid(e->pid, sizeof(buf), buf); if (ptr) subj.str = strdup(buf); else subj.str = strdup("?"); } break; case EXE_TYPE: { char buf[128], *ptr; ptr = get_type_from_pid(e->pid, sizeof(buf), buf); if (ptr) subj.str = strdup(buf); else subj.str = strdup("?"); } break; case EXE_DEVICE: // FIXME: write real code for this subj.str = strdup("?"); break; case SUBJ_TRUST: { subject_attr_t *exe = get_subj_attr(e, EXE); subj.val = 0; if (exe) { if (exe->str) { int res = check_trust_database(exe->str, NULL, 0); // ignore -1 if (res == 1) subj.val = 1; else subj.val = 0; } } } break; default: return NULL; } if (subject_add(e->s, &subj) == 0) { sn = subject_access(e->s, t); return sn; } // free .str/set only when it was really used // otherwise invalid free is possible if (t == GID) destroy_attr_set(subj.set); else if (t >= COMM) free(subj.str); return NULL; } /* * This function will search the list for a nv pair of the right type. * If not found, it will create the type and return it. */ object_attr_t *get_obj_attr(event_t *e, object_type_t t) { char buf[PATH_MAX+1], *ptr; object_attr_t obj; object_attr_t *on; o_array *o = e->o; on = object_access(o, t); if (on) return on; // One not on the list, look it up and make one obj.type = t; obj.o = NULL; obj.val = 0; switch (t) { case PATH: case ODIR: // Try to avoid looking up the path if we have it on = object_find_file(o); if (on) obj.o = strdup(on->o); else { ptr = get_file_from_fd(e->fd, e->pid, sizeof(buf), buf); if (ptr) obj.o = strdup(buf); else obj.o = strdup("?"); } break; case DEVICE: ptr = get_device_from_stat(o->info->device, sizeof(buf), buf); if (ptr) obj.o = strdup(buf); else obj.o = strdup("?"); break; case FTYPE: { object_attr_t *path = get_obj_attr(e, PATH); ptr = get_file_type_from_fd(e->fd, o->info, path ? path->o : "?", sizeof(buf), buf); if (ptr) obj.o = strdup(buf); else obj.o = strdup("?"); } break; case SHA256HASH: obj.o = get_hash_from_fd2(e->fd, o->info->size, 1); break; case OBJ_TRUST: { object_attr_t *path = get_obj_attr(e, PATH); if (path && path->o) { int res = check_trust_database(path->o, o->info, e->fd); // ignore -1 if (res == 1) obj.val = 1; else obj.val = 0; } } break; case FMODE: default: obj.o = NULL; return NULL; } if (object_add(e->o, &obj) == 0) { on = object_access(e->o, t); return on; } free(obj.o); return NULL; } static void print_queue_stats(FILE *f, const Queue *q) { fprintf(f, "%s cache size: %u\n", q->name, q->total); fprintf(f, "%s slots in use: %u (%u%%)\n", q->name, q->count, q->total ? (100*q->count)/q->total : 0); fprintf(f, "%s hits: %lu\n", q->name, q->hits); fprintf(f, "%s misses: %lu\n", q->name, q->misses); fprintf(f, "%s evictions: %lu (%lu%%)\n", q->name, q->evictions, q->hits ? (100*q->evictions)/q->hits : 0); } void run_usage_report(const conf_t *config, FILE *f) { time_t t; QNode *q_node; if (f == NULL) return; if (config->detailed_report) { t = time(NULL); fprintf(f, "File access attempts from oldest to newest as of %s\n", ctime(&t)); fprintf(f, "\tFILE\t\t\t\t\t\t ATTEMPTS\n"); fprintf(f, "---------------------------------------------------------------------------\n" ); if (obj_cache->count == 0) { fprintf(f, "(none)\n"); return; } q_node = obj_cache->end; while (q_node) { unsigned int len; const char *file; o_array *o = (o_array *)q_node->item; object_attr_t *on = object_find_file(o); if (on == NULL) goto next_obj; file = on->o; if (file == NULL) goto next_obj; len = strlen(file); if (len > 62) fprintf(f, "%s\t%lu\n", file, q_node->uses); else fprintf(f, "%-62s\t%lu\n", file, q_node->uses); next_obj: q_node = q_node->prev; } fprintf(f, "\n---\n\n"); } print_queue_stats(f, obj_cache); fprintf(f, "\n\n"); if (config->detailed_report) { fprintf(f, "Active processes oldest to most recently active as of %s\n", ctime(&t)); fprintf(f, "\tEXE\tCOMM\t\t\t\t\t ATTEMPTS\n"); fprintf(f, "---------------------------------------------------------------------------\n" ); if (subj_cache->count == 0) { fprintf(f, "(none)\n"); return; } q_node = subj_cache->end; while (q_node) { unsigned int len; char *exe, *comm, *text; subject_attr_t *se, *sc; s_array *s = (s_array *)q_node->item; se = subject_find_exe(s); if (se == NULL) goto next_subj; exe = se->str; if (exe == NULL) goto next_subj; sc = subject_find_comm(s); if (sc == NULL) comm = "?"; else comm = sc->str ? sc->str : "?"; if (asprintf(&text, "%s (%s)", exe, comm) < 0) { fprintf(f, "?\n"); goto next_subj; } len = strlen(text); if (len > 62) fprintf(f, "%s\t%lu\n", text, q_node->uses); else fprintf(f,"%-62s\t%lu\n", text, q_node->uses); free(text); next_subj: q_node = q_node->prev; } fprintf(f, "\n---\n\n"); } print_queue_stats(f, subj_cache); fprintf(f, "\n"); } void do_cache_reports(FILE *f) { print_queue_stats(f, subj_cache); print_queue_stats(f, obj_cache); } fapolicyd-1.3.4/src/library/event.h000066400000000000000000000030271470754500200172240ustar00rootroot00000000000000/* * event.h - Header file for event.c * Copyright (c) 2016,2018-19,2023 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #ifndef EVENT_HEADER #define EVENT_HEADER #include #include #include #include "subject.h" #include "object.h" #include "conf.h" typedef struct ev { pid_t pid; int fd; int type; unsigned num; s_array *s; o_array *o; } event_t; int init_event_system(const conf_t *config); void destroy_event_system(void); int new_event(const struct fanotify_event_metadata *m, event_t *e); subject_attr_t *get_subj_attr(event_t *e, subject_type_t t); object_attr_t *get_obj_attr(event_t *e, object_type_t t); void run_usage_report(const conf_t *config, FILE *f); void do_cache_reports(FILE *f); #endif fapolicyd-1.3.4/src/library/fapolicyd-backend.h000066400000000000000000000025141470754500200214420ustar00rootroot00000000000000/* * fapolicyd-backend.h - Header file for database backend interface * Copyright (c) 2020-23 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #ifndef FAPOLICYD_BACKEND_HEADER #define FAPOLICYD_BACKEND_HEADER #include "conf.h" #include "llist.h" // If this gets extended, please put the new items at the end. typedef enum { SRC_UNKNOWN, SRC_RPM, SRC_FILE_DB, SRC_DEB } trust_src_t; // source, size, sha #define DATA_FORMAT "%u %lu %64s" typedef struct _backend { const char * name; int (*init)(void); int (*load)(const conf_t *); int (*close)(void); list_t list; } backend; #endif fapolicyd-1.3.4/src/library/fapolicyd-defs.h000066400000000000000000000020541470754500200207730ustar00rootroot00000000000000/* * fapolicyd-defs.h - Header file for defines & enums that cause loops * Copyright (c) 2019 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef FAPOLICYD_DEFS_HEADER #define FAPOLICYD_DEFS_HEADER typedef enum { OPEN_ACC, EXEC_ACC , ANY_ACC } access_t; typedef enum { RULE_FMT_ORIG, RULE_FMT_COLON } rformat_t; #endif fapolicyd-1.3.4/src/library/fd-fgets.c000066400000000000000000000065301470754500200175770ustar00rootroot00000000000000/* fd-fgets.c -- * Copyright 2019,2020 Red Hat Inc. * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include "fd-fgets.h" fd_fgets_context_t * fd_fgets_init(void) { fd_fgets_context_t *ctx = malloc(sizeof(fd_fgets_context_t)); if (!ctx) return NULL; memset(ctx->buffer, 0, sizeof(ctx->buffer)); ctx->current = ctx->buffer; ctx->eptr = ctx->buffer+(2*FD_FGETS_BUF_SIZE); ctx->eof = 0; return ctx; } void fd_fgets_destroy(fd_fgets_context_t *ctx) { free(ctx); } int fd_fgets_eof(fd_fgets_context_t *ctx) { return ctx->eof; } void fd_fgets_rewind(fd_fgets_context_t *ctx) { ctx->eof = 0; } int fd_fgets(fd_fgets_context_t *ctx, char *buf, size_t blen, int fd) { int complete = 0; size_t line_len = 0; char *line_end = NULL; assert(blen != 0); /* See if we have more in the buffer first */ if (ctx->current != ctx->buffer) { line_end = strchr(ctx->buffer, '\n'); if (line_end == NULL && (size_t)(ctx->current - ctx->buffer) >= blen-1) line_end = ctx->current-1; //enough to fill blen,point to end } /* Otherwise get some new bytes */ if (line_end == NULL && ctx->current != ctx->eptr && !ctx->eof) { ssize_t len; /* Use current since we may be adding more */ do { len = read(fd, ctx->current, ctx->eptr - ctx->current); } while (len < 0 && errno == EINTR); if (len < 0) return -1; if (len == 0) ctx->eof = 1; else ctx->current[len] = 0; ctx->current += len; /* Start from beginning to see if we have one */ line_end = strchr(ctx->buffer, '\n'); } /* See what we have */ if (line_end) { /* Include the last character (usually newline) */ line_len = (line_end+1) - ctx->buffer; /* Make sure we are within the right size */ if (line_len > blen-1) line_len = blen-1; complete = 1; } else if (ctx->current == ctx->eptr) { /* We are full but no newline */ line_len = blen-1; complete = 1; } else if (ctx->current >= ctx->buffer+blen-1) { /* Not completely full, no newline, but enough to fill buf */ line_len = blen-1; complete = 1; } if (complete) { size_t remainder_len; /* Move to external buf and terminate it */ memcpy(buf, ctx->buffer, line_len); buf[line_len] = 0; remainder_len = ctx->current - (ctx->buffer + line_len); if (remainder_len > 0) { /* We have a few leftover bytes to move */ memmove(ctx->buffer, ctx->buffer+line_len, remainder_len); ctx->current = ctx->buffer+remainder_len; } else { /* Got the whole thing, just reset */ ctx->current = ctx->buffer; } *(ctx->current) = 0; } return complete; } fapolicyd-1.3.4/src/library/fd-fgets.h000066400000000000000000000031571470754500200176060ustar00rootroot00000000000000/* fd-fgets.h -- a replacement for glibc's fgets * Copyright 2019,2020,2022 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb */ #ifndef FD_FGETS_HEADER #define FD_FGETS_HEADER #include #include "gcc-attributes.h" #ifndef __attr_access # define __attr_access(x) #endif #ifndef FD_FGETS_BUF_SIZE # define FD_FGETS_BUF_SIZE 8192 #endif typedef struct fd_fgets_context { char buffer[2*FD_FGETS_BUF_SIZE+1]; char *current; char *eptr; int eof; } fd_fgets_context_t; void fd_fgets_destroy(fd_fgets_context_t *ctx); fd_fgets_context_t * fd_fgets_init(void) __attribute_malloc__ __attr_dealloc (fd_fgets_destroy, 1); int fd_fgets_eof(fd_fgets_context_t *ctx); void fd_fgets_rewind(fd_fgets_context_t *ctx); int fd_fgets(fd_fgets_context_t *ctx, char *buf, size_t blen, int fd) __attr_access ((__write_only__, 2, 3)); #endif fapolicyd-1.3.4/src/library/file-backend.c000066400000000000000000000032151470754500200204010ustar00rootroot00000000000000/* * file-backend.c - file backend * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka * Steve Grubb * Zoltan Fridrich */ #include "config.h" #include #include "fapolicyd-backend.h" #include "llist.h" #include "message.h" #include "trust-file.h" static int file_init_backend(void); static int file_load_list(const conf_t *conf); static int file_destroy_backend(void); backend file_backend = { "file", file_init_backend, file_load_list, file_destroy_backend, { 0, 0, NULL }, }; static int file_load_list(const conf_t *conf) { msg(LOG_DEBUG, "Loading file backend"); list_empty(&file_backend.list); trust_file_load_all(&file_backend.list); return 0; } static int file_init_backend(void) { list_init(&file_backend.list); return 0; } static int file_destroy_backend(void) { list_empty(&file_backend.list); return 0; } fapolicyd-1.3.4/src/library/file.c000066400000000000000000000505011470754500200170140ustar00rootroot00000000000000/* * file.c - functions for accessing attributes of files * Copyright (c) 2016,2018-23 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "file.h" #include "message.h" #include "process.h" // For elf info bit mask // Local defines #define IMA_XATTR_DIGEST_NG 0x04 // security/integrity/integrity.h // Local variables static struct udev *udev; magic_t magic_cookie; struct cache { dev_t device; const char *devname; }; static struct cache c = { 0, NULL }; // Local declarations static ssize_t safe_read(int fd, char *buf, size_t size) __attr_access ((__write_only__, 2, 3)); static char *get_program_cwd_from_pid(pid_t pid, size_t blen, char *buf) __attr_access ((__write_only__, 3, 2)); static void resolve_path(const char *pcwd, char *path, size_t len) __attr_access ((__write_only__, 2, 3)); // readelf -l path-to-app | grep 'Requesting' | cut -d':' -f2 | tr -d ' ]'; static const char *interpreters[] = { "/lib64/ld-linux-x86-64.so.2", "/lib/ld-linux.so.2", // i686 "/usr/lib64/ld-linux-x86-64.so.2", "/usr/lib/ld-linux.so.2", // i686 "/lib/ld.so.2", "/lib/ld-linux-armhf.so.3", // fedora armv7hl "/lib/ld-linux-aarch64.so.1", // fedora aarch64 "/lib/ld64.so.1", // rhel8 s390x "/lib64/ld64.so.2", // rhel8 ppc64le }; #define MAX_INTERPS (sizeof(interpreters)/sizeof(interpreters[0])) // Define a convience function to rewind a descriptor to the beginning static inline void rewind_fd(int fd) { lseek(fd, 0, SEEK_SET); } // Initialize what we can now so that its not done each call void file_init(void) { // Setup udev udev = udev_new(); // Setup libmagic unsetenv("MAGIC"); magic_cookie = magic_open(MAGIC_MIME|MAGIC_ERROR|MAGIC_NO_CHECK_CDF| MAGIC_NO_CHECK_ELF); if (magic_cookie == NULL) { msg(LOG_ERR, "Unable to init libmagic"); exit(1); } // Load our overrides and the default magic definitions if (magic_load(magic_cookie, "/usr/share/fapolicyd/fapolicyd-magic.mgc:/usr/share/misc/magic.mgc") != 0) { msg(LOG_ERR, "Unable to load magic database"); exit(1); } } // Release memory during shutdown void file_close(void) { udev_unref(udev); magic_close(magic_cookie); free((void *)c.devname); } struct file_info *stat_file_entry(int fd) { struct stat sb; if (fstat(fd, &sb) == 0) { struct file_info *info = malloc(sizeof(struct file_info)); if (info == NULL) return info; info->device = sb.st_dev; info->inode = sb.st_ino; info->mode = sb.st_mode; info->size = sb.st_size; // Try to get the modified time. If its zero, then it // hasn't been modified. Revert to create time if no // modifications have been done. if (sb.st_mtim.tv_sec) info->time.tv_sec = sb.st_mtim.tv_sec; else info->time.tv_sec = sb.st_ctim.tv_sec; if (sb.st_mtim.tv_nsec) info->time.tv_nsec = sb.st_mtim.tv_nsec; else info->time.tv_nsec = sb.st_ctim.tv_nsec; return info; } return NULL; } // Returns 0 if equal and 1 if not equal int compare_file_infos(const struct file_info *p1, const struct file_info *p2) { if (p1 == NULL || p2 == NULL) return 1; // Compare in the order to find likely mismatch first //msg(LOG_DEBUG, "inode %ld %ld", p1->inode, p2->inode); if (p1->inode != p2->inode) { //msg(LOG_DEBUG, "mismatch INODE"); return 1; } if (p1->time.tv_nsec != p2->time.tv_nsec) { //msg(LOG_DEBUG, "mismatch NANO"); return 1; } if (p1->time.tv_sec != p2->time.tv_sec) { //msg(LOG_DEBUG, "mismatch SEC"); return 1; } if (p1->size != p2->size) { //msg(LOG_DEBUG, "mismatch BLOCKS"); return 1; } if (p1->device != p2->device) { //msg(LOG_DEBUG, "mismatch DEV"); return 1; } return 0; } static char *get_program_cwd_from_pid(pid_t pid, size_t blen, char *buf) { char path[32]; ssize_t path_len; snprintf(path, sizeof(path), "/proc/%d/cwd", pid); path_len = readlink(path, buf, blen - 1); if (path_len < 0) return NULL; if ((size_t)path_len < blen) buf[path_len] = 0; else buf[blen-1] = 0; return buf; } // If we had to build a path because it started out relative, // then put the pieces together and get the conanical name static void resolve_path(const char *pcwd, char *path, size_t len) { char tpath[PATH_MAX+1]; int tlen = strlen(pcwd); // Start with current working directory strncpy(tpath, pcwd, PATH_MAX); if (tlen >= PATH_MAX) { tlen=PATH_MAX-1; tpath[PATH_MAX] = 0; } // Add the relative path strncat(tpath, path, (PATH_MAX-1) - tlen); tpath[PATH_MAX] = 0; // Ask for it to be resolved if (realpath(tpath, path) == NULL) { strncpy(path, tpath, len); path[len - 1] = 0; } } char *get_file_from_fd(int fd, pid_t pid, size_t blen, char *buf) { char procfd_path[32]; ssize_t path_len; if (blen == 0) return NULL; snprintf(procfd_path, sizeof(procfd_path)-1, "/proc/self/fd/%d", fd); path_len = readlink(procfd_path, buf, blen - 1); if (path_len < 0) return NULL; if ((size_t)path_len < blen) buf[path_len] = 0; else buf[blen-1] = 0; // If this does not start with a '/' we have a relative path if (buf[0] != '/') { char pcwd[PATH_MAX+1]; pcwd[0] = 0; get_program_cwd_from_pid(pid, sizeof(pcwd), pcwd); resolve_path(pcwd, buf, blen); } return buf; } char *get_device_from_stat(unsigned int device, size_t blen, char *buf) { struct udev_device *dev; const char *node; if (c.device) { if (c.device == device) { strncpy(buf, c.devname, blen-1); buf[blen-1] = 0; return buf; } } // Create udev_device from the dev_t obtained from stat dev = udev_device_new_from_devnum(udev, 'b', device); node = udev_device_get_devnode(dev); if (node == NULL) { udev_device_unref(dev); return NULL; } strncpy(buf, node, blen-1); buf[blen-1] = 0; udev_device_unref(dev); // Update saved values free((void *)c.devname); c.device = device; c.devname = strdup(buf); return buf; } const char *classify_elf_info(uint32_t elf, const char *path) { const char *ptr; if (elf & HAS_ERROR) ptr = "application/x-bad-elf"; else if (elf & HAS_EXEC) ptr = "application/x-executable"; else if (elf & HAS_REL) ptr = "application/x-object"; else if (elf & HAS_CORE) ptr = "application/x-coredump"; else if (elf & HAS_INTERP) { // dynamic app ptr = "application/x-executable"; // libc and pthread actually have an interpreter?!? // Need to carve out an exception to reclassify them. const char *p = path; if (!strncmp(p, "/usr", 4)) p += 4; if (!strncmp(p, "/lib", 4)) { p += 4; if (!strncmp(p, "64", 2)) p += 2; if (!strncmp(p, "/libc-2", 7) || !strncmp(p, "/libc.so", 8) || !strncmp(p, "/libpthread-2", 13)) ptr = "application/x-sharedlib"; } } else { if (elf & HAS_DYNAMIC) { // shared obj if (elf & HAS_DEBUG) ptr = "application/x-executable"; else ptr = "application/x-sharedlib"; } else return NULL; } // TODO: add HAS_BAD_INTERP, HAS_EXE_STACK, HAS_RWE_LOAD to // classify BAD_ELF based on system policy return ptr; } /* * This function classifies the descriptor if it's not a regular file. * This is needed because libmagic tries to read it and comes up with * application/x-empty instead. This function will return NULL if the * file is not a device. Otherwise a pointer to its mime type. */ const char *classify_device(mode_t mode) { const char *ptr = NULL; switch (mode & S_IFMT) { case S_IFCHR: ptr = "inode/chardevice"; break; case S_IFBLK: ptr = "inode/blockdevice"; break; case S_IFIFO: ptr = "inode/fifo"; break; case S_IFSOCK: ptr = "inode/socket"; break; } return ptr; } // This function will determine the mime type of the passed file descriptor. // If it returns NULL, then an error of some kind happed. Otherwise it // fills in "buf" and returns a pointer to it. char *get_file_type_from_fd(int fd, const struct file_info *i, const char *path, size_t blen, char *buf) { const char *ptr; // libmagic is unpredictable in determining elf files. // We need to do it ourselves for consistency. if (i->mode & S_IFREG) { uint32_t elf = gather_elf(fd, i->size); if (elf) { ptr = classify_elf_info(elf, path); if (ptr == NULL) return (char *)ptr; return strncpy(buf, ptr, blen-1); } } // Take a look to see if its a device ptr = classify_device(i->mode); if (ptr) return strncpy(buf, ptr, blen-1); // Do the normal classification ptr = magic_descriptor(magic_cookie, fd); if (ptr) { char *str; strncpy(buf, ptr, blen-1); buf[blen-1] = 0; str = strchr(buf, ';'); if (str) *str = 0; } else return NULL; return buf; } // This function converts byte array into asciie hex char *bytes2hex(char *final, const unsigned char *buf, unsigned int size) { unsigned int i; char *ptr = final; const char *hex = "0123456789abcdef"; if (final == NULL) return final; for (i=0; i>4]; /* Upper nibble */ *ptr++ = hex[buf[i] & 0x0F]; /* Lower nibble */ } *ptr = 0; return final; } // This function wraps read(2) so its signal-safe static ssize_t safe_read(int fd, char *buf, size_t size) { ssize_t len; do { len = read(fd, buf, size); } while (len < 0 && errno == EINTR); return len; } /* * Given a fd, calculate the hash by accessing size bytes of the file. * Calculate SHA256 by default or compute MD5. * Returns a char pointer of the hash which the caller must free. * If a size of 0 is passed, it will return a NULL pointer. * If there is an error with mmap, it will also return a NULL pointer. */ static const char *degenerate_hash_sha = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; static const char *degenerate_hash_md5 = "d41d8cd98f00b204e9800998ecf8427e"; char *get_hash_from_fd2(int fd, size_t size, const int is_sha) { unsigned char *mapped; char *digest = NULL; if (size == 0) { if (is_sha) return strdup(degenerate_hash_sha); return strdup(degenerate_hash_md5); } mapped = mmap(0, size, PROT_READ, MAP_PRIVATE|MAP_POPULATE, fd, 0); if (mapped != MAP_FAILED) { const int digest_length = is_sha ? SHA256_DIGEST_LENGTH : 16; // Just use the larger one as buffer. unsigned char hptr[SHA256_DIGEST_LENGTH]; if (is_sha) { SHA256(mapped, size, (unsigned char *)&hptr); } else { #ifdef USE_DEB MD5(mapped, size, (unsigned char *)&hptr); #else ; #endif } munmap(mapped, size); digest = malloc((SHA256_LEN * 2) + 1); // Convert to ASCII string bytes2hex(digest, hptr, digest_length); } return digest; } // This function returns 0 on error and 1 if successful int get_ima_hash(int fd, char *sha) { unsigned char tmp[34]; if (fgetxattr(fd, "security.ima", tmp, sizeof(tmp)) < 0) { msg(LOG_DEBUG, "Can't read ima xattr"); return 0; } // Let's check what we got if (tmp[0] != IMA_XATTR_DIGEST_NG) { msg(LOG_DEBUG, "Wrong ima xattr type"); return 0; } if (tmp[1] != HASH_ALGO_SHA256) { msg(LOG_DEBUG, "Wrong ima hash algorithm"); return 0; } // Looks like it what we want... bytes2hex(sha, &tmp[2], SHA256_LEN); return 1; } static unsigned char e_ident[EI_NIDENT]; static int read_preliminary_header(int fd) { ssize_t rc = safe_read(fd, (char *)e_ident, EI_NIDENT); if (rc == EI_NIDENT) return 0; return 1; } static Elf32_Ehdr *read_header32(int fd, Elf32_Ehdr *ptr) { memcpy(ptr->e_ident, e_ident, EI_NIDENT); ssize_t rc = safe_read(fd, (char *)ptr + EI_NIDENT, sizeof(Elf32_Ehdr) - EI_NIDENT); if (rc == (sizeof(Elf32_Ehdr) - EI_NIDENT)) return ptr; return NULL; } static Elf64_Ehdr *read_header64(int fd, Elf64_Ehdr *ptr) { memcpy(ptr->e_ident, e_ident, EI_NIDENT); ssize_t rc = safe_read(fd, (char *)ptr + EI_NIDENT, sizeof(Elf64_Ehdr) - EI_NIDENT); if (rc == (sizeof(Elf64_Ehdr) - EI_NIDENT)) return ptr; return NULL; } /** * Check interpreter provided as an argument obtained from the ELF against * known fixed locations in the file hierarchy. */ static int check_interpreter(const char *interp) { unsigned i; for (i = 0; i < MAX_INTERPS; i++) { if (strcmp(interp, interpreters[i]) == 0) return 0; } return 1; } // size is the file size from fstat done when event was received uint32_t gather_elf(int fd, off_t size) { uint32_t info = 0; if (read_preliminary_header(fd)) goto rewind_out; if (strncmp((char *)e_ident, ELFMAG, 4)) goto rewind_out; info |= IS_ELF; if (e_ident[EI_CLASS] == ELFCLASS32) { unsigned i, type; Elf32_Phdr *ph_tbl = NULL; Elf32_Ehdr hdr_buf; Elf32_Ehdr *hdr = read_header32(fd, &hdr_buf); if (hdr == NULL) { info |= HAS_ERROR; goto rewind_out; } type = hdr->e_type & 0xFFFF; if (type == ET_EXEC) info |= HAS_EXEC; else if (type == ET_REL) info |= HAS_REL; else if (type == ET_CORE) info |= HAS_CORE; // Look for program header information // We want to do a basic size check to make sure unsigned long sz = (unsigned)hdr->e_phentsize * (unsigned)hdr->e_phnum; // Program headers are meaning for executable & shared obj only if (sz == 0 && type == ET_REL) goto done32_obj; /* Verify the entry size is right */ if ((unsigned)hdr->e_phentsize != sizeof(Elf32_Phdr) || (unsigned)hdr->e_phnum == 0) { info |= HAS_ERROR; goto rewind_out; } if (sz > ((unsigned long)size - sizeof(Elf32_Ehdr))) { info |= HAS_ERROR; goto rewind_out; } ph_tbl = malloc(sz); if (ph_tbl == NULL) goto err_out32; if ((unsigned int)lseek(fd, (off_t)hdr->e_phoff, SEEK_SET) != hdr->e_phoff) goto err_out32; // Read in complete table if ((unsigned int)safe_read(fd, (char *)ph_tbl, sz) != sz) goto err_out32; // Check for rpath record for (i = 0; i < hdr->e_phnum; i++) { if (ph_tbl[i].p_type == PT_LOAD) { info |= HAS_LOAD; // If we have RWE flags, something is wrong if (ph_tbl[i].p_flags == (PF_X|PF_W|PF_R)) info |= HAS_RWE_LOAD; } if (ph_tbl[i].p_type == PT_PHDR) info |= HAS_PHDR; // Obtain program interpreter from ELF object file if (ph_tbl[i].p_type == PT_INTERP) { uint32_t len; char interp[23]; uint32_t filesz = ph_tbl[i].p_filesz; uint32_t offset = ph_tbl[i].p_offset; info |= HAS_INTERP; if ((unsigned int) lseek(fd, offset, SEEK_SET) != offset) goto err_out32; len = (filesz < 23 ? filesz : 23); if ((unsigned int) safe_read(fd, (char *) interp, len) != len) goto err_out32; // Explictly terminate the string if (len == 0) interp[0] = 0; else interp[len - 1] = '\0'; // Perform ELF interpreter validation if (check_interpreter(interp)) info |= HAS_BAD_INTERP; } if (ph_tbl[i].p_type == PT_GNU_STACK) { // If we have Execute flags, something is wrong if (ph_tbl[i].p_flags & PF_X) info |= HAS_EXE_STACK; } if (ph_tbl[i].p_type == PT_DYNAMIC) { unsigned int j = 0; unsigned int num; info |= HAS_DYNAMIC; if (ph_tbl[i].p_filesz > size) goto err_out32; Elf64_Dyn *dyn_tbl = malloc(ph_tbl[i].p_filesz); if((unsigned int)lseek(fd, ph_tbl[i].p_offset, SEEK_SET) != ph_tbl[i].p_offset) { free(dyn_tbl); goto err_out32; } num = ph_tbl[i].p_filesz / sizeof(Elf64_Dyn); if (num > 1000) { free(dyn_tbl); goto err_out32; } if ((unsigned int)safe_read(fd, (char *)dyn_tbl, ph_tbl[i].p_filesz) != ph_tbl[i].p_filesz) { free(dyn_tbl); goto err_out32; } while (j < num) { if (dyn_tbl[j].d_tag == DT_NEEDED) { // intentional } /* else if (dyn_tbl[j].d_tag == DT_RUNPATH) info |= HAS_RPATH; else if (dyn_tbl[j].d_tag == DT_RPATH) info |= HAS_RPATH; */ else if (dyn_tbl[j].d_tag == DT_DEBUG) { info |= HAS_DEBUG; break; } j++; } free(dyn_tbl); } // if (info & HAS_RPATH) // break; } goto done32; err_out32: info |= HAS_ERROR; done32: free(ph_tbl); done32_obj: ; // fix an 'error label at end of compound statement' } else if (e_ident[EI_CLASS] == ELFCLASS64) { unsigned i, type; Elf64_Phdr *ph_tbl; Elf64_Ehdr hdr_buf; Elf64_Ehdr *hdr = read_header64(fd, &hdr_buf); if (hdr == NULL) { info |= HAS_ERROR; goto rewind_out; } type = hdr->e_type & 0xFFFF; if (type == ET_EXEC) info |= HAS_EXEC; else if (type == ET_REL) info |= HAS_REL; else if (type == ET_CORE) info |= HAS_CORE; // Look for program header information // We want to do a basic size check to make sure unsigned long sz = (unsigned)hdr->e_phentsize * (unsigned)hdr->e_phnum; // Program headers are meaning for executable & shared obj only if (sz == 0 && type == ET_REL) goto done64_obj; /* Verify the entry size is right */ if ((unsigned)hdr->e_phentsize != sizeof(Elf64_Phdr) || (unsigned)hdr->e_phnum == 0) { info |= HAS_ERROR; goto rewind_out; } if (sz > ((unsigned long)size - sizeof(Elf64_Ehdr))) { info |= HAS_ERROR; goto rewind_out; } ph_tbl = malloc(sz); if (ph_tbl == NULL) goto err_out64; if ((unsigned int)lseek(fd, (off_t)hdr->e_phoff, SEEK_SET) != hdr->e_phoff) goto err_out64; // Read in complete table if ((unsigned int)safe_read(fd, (char *)ph_tbl, sz) != sz) goto err_out64; // Check for rpath record for (i = 0; i < hdr->e_phnum; i++) { if (ph_tbl[i].p_type == PT_LOAD) { info |= HAS_LOAD; // If we have RWE flags, something is wrong if (ph_tbl[i].p_flags == (PF_X|PF_W|PF_R)) info |= HAS_RWE_LOAD; } if (ph_tbl[i].p_type == PT_PHDR) info |= HAS_PHDR; // Obtain program interpreter from ELF object file if (ph_tbl[i].p_type == PT_INTERP) { uint64_t len; char interp[33]; uint64_t filesz = ph_tbl[i].p_filesz; uint64_t offset = ph_tbl[i].p_offset; info |= HAS_INTERP; if ((unsigned int) lseek(fd, offset, SEEK_SET) != offset) goto err_out64; len = (filesz < 33 ? filesz : 33); if ((unsigned int) safe_read(fd, (char *) interp, len) != len) goto err_out64; /* Explicitly terminate the string */ if (len == 0) interp[0] = 0; else interp[len - 1] = '\0'; // Perform ELF interpreter validation if (check_interpreter(interp)) info |= HAS_BAD_INTERP; } if (ph_tbl[i].p_type == PT_GNU_STACK) { // If we have Execute flags, something is wrong if (ph_tbl[i].p_flags & PF_X) info |= HAS_EXE_STACK; } if (ph_tbl[i].p_type == PT_DYNAMIC) { unsigned int j = 0; unsigned int num; info |= HAS_DYNAMIC; if (ph_tbl[i].p_filesz>(long unsigned int)size) goto err_out64; Elf64_Dyn *dyn_tbl = malloc(ph_tbl[i].p_filesz); if ((unsigned int)lseek(fd, ph_tbl[i].p_offset, SEEK_SET) != ph_tbl[i].p_offset) { free(dyn_tbl); goto err_out64; } num = ph_tbl[i].p_filesz / sizeof(Elf64_Dyn); if (num > 1000) { free(dyn_tbl); goto err_out64; } if ((unsigned int)safe_read(fd, (char *)dyn_tbl, ph_tbl[i].p_filesz) != ph_tbl[i].p_filesz) { free(dyn_tbl); goto err_out64; } while (j < num) { if (dyn_tbl[j].d_tag == DT_NEEDED) { // intentional } /* else if (dyn_tbl[j].d_tag == DT_RUNPATH) info |= HAS_RPATH; else if (dyn_tbl[j].d_tag == DT_RPATH) info |= HAS_RPATH; */ else if (dyn_tbl[j].d_tag == DT_DEBUG) { info |= HAS_DEBUG; break; } j++; } free(dyn_tbl); } // if (info & HAS_RPATH) // break; } goto done64; err_out64: info |= HAS_ERROR; done64: free(ph_tbl); done64_obj: ; // fix an 'error label at end of compound statement' } else // Invalid ELF class info |= HAS_ERROR; rewind_out: rewind_fd(fd); return info; } fapolicyd-1.3.4/src/library/file.h000066400000000000000000000041771470754500200170310ustar00rootroot00000000000000/* * file.h - Header file for file.c * Copyright (c) 2016,2018-20,2022 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef FILE_HEADER #define FILE_HEADER #include #include #include #include "gcc-attributes.h" // Information we will cache to identify the same executable struct file_info { dev_t device; ino_t inode; mode_t mode; off_t size; struct timespec time; }; #define SHA256_LEN 32 #define SHA512_LEN 64 void file_init(void); void file_close(void); struct file_info *stat_file_entry(int fd) __attr_dealloc_free; int compare_file_infos(const struct file_info *p1, const struct file_info *p2); char *get_file_from_fd(int fd, pid_t pid, size_t blen, char *buf) __attr_access ((__write_only__, 4, 3)); char *get_device_from_stat(unsigned int device, size_t blen, char *buf) __attr_access ((__write_only__, 3, 2)); const char *classify_device(mode_t mode); const char *classify_elf_info(uint32_t elf, const char *path); char *get_file_type_from_fd(int fd, const struct file_info *i, const char *path, size_t blen, char *buf) __attr_access ((__write_only__, 5, 4)); char *bytes2hex(char *final, const unsigned char *buf, unsigned int size) __attr_access ((__read_only__, 2, 3)); char *get_hash_from_fd2(int fd, size_t size, int is_sha) __attr_dealloc_free; int get_ima_hash(int fd, char *sha); uint32_t gather_elf(int fd, off_t size); #endif fapolicyd-1.3.4/src/library/filter.c000066400000000000000000000267071470754500200173750ustar00rootroot00000000000000/* * filter.c - filter for a trust source * Copyright (c) 2023 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #include "filter.h" #include #include #include #include #include "llist.h" #include "stack.h" #include "message.h" #include "string-util.h" #pragma GCC optimize("O3") #define OLD_FILTER_FILE "/etc/fapolicyd/rpm-filter.conf" #define FILTER_FILE "/etc/fapolicyd/fapolicyd-filter.conf" filter_t *global_filter = NULL; static filter_t *filter_create_obj(void); static void filter_destroy_obj(filter_t *_filter); // init fuction of this module int filter_init(void) { global_filter = filter_create_obj(); if (global_filter == NULL) return 1; return 0; } // destroy funtion of this module void filter_destroy(void) { filter_destroy_obj(global_filter); global_filter = NULL; } // alocate new filter object and fill with the defaults static filter_t *filter_create_obj(void) { filter_t *filter = malloc(sizeof(filter_t)); if (filter) { filter->type = NONE; filter->path = NULL; filter->len = 0; filter->matched = 0; filter->processed = 0; list_init(&filter->list); } return filter; } // free all nested filters static void filter_destroy_obj(filter_t *_filter) { if (_filter == NULL) return; filter_t *filter = _filter; stack_t stack; stack_init(&stack); stack_push(&stack, filter); while (!stack_is_empty(&stack)) { filter = (filter_t*)stack_top(&stack); if (filter->processed) { (void)free(filter->path); // asume that item->data is NULL list_empty(&filter->list); (void)free(filter); stack_pop(&stack); continue; } list_item_t *item = list_get_first(&filter->list); for (; item != NULL ; item = item->next) { filter_t *next_filter = (filter_t*)item->data; // we can use list_empty() later // we dont want to free filter right now // it will freed after popping item->data = NULL; stack_push(&stack, next_filter); } filter->processed = 1; } stack_destroy(&stack); } // create struct and push it to the top of stack static void stack_push_vars(stack_t *_stack, int _level, int _offset, filter_t *_filter) { if (_stack == NULL) return; stack_item_t *item = malloc(sizeof(stack_item_t)); if (item == NULL) return; item->level = _level; item->offset = _offset; item->filter = _filter; stack_push(_stack, item); } // pop stack_item_t and free it static void stack_pop_vars(stack_t *_stack) { if (_stack == NULL) return; stack_item_t * item = (stack_item_t*)stack_top(_stack); free(item); stack_pop(_stack); } // pop all the stack_item_t and free them static void stack_pop_all_vars(stack_t *_stack) { if (_stack == NULL) return; while (!stack_is_empty(_stack)) stack_pop_vars(_stack); } // reset filter to default, pop top and free static void stack_pop_reset(stack_t *_stack) { if (_stack == NULL) return; stack_item_t *stack_item = (stack_item_t*)stack_top(_stack); free(stack_item); stack_pop(_stack); } // reset and pop all the stack_item_t static void stack_pop_all_reset(stack_t *_stack) { if (_stack == NULL) return; while (!stack_is_empty(_stack)) stack_pop_reset(_stack); } // this funtion gets full path and checks it against filter // returns 1 for keeping the file and 0 for dropping it int filter_check(const char *_path) { if (_path == NULL) { msg(LOG_ERR, "filter_check: path is NULL, something is wrong!"); return 0; } filter_t *filter = global_filter; char *path = strdupa(_path); size_t path_len = strlen(path); size_t offset = 0; // Create a stack to store the filters that need to be checked stack_t stack; stack_init(&stack); int res = 0; int level = 0; stack_push_vars(&stack, level, offset, filter); while(!stack_is_empty(&stack)) { int matched = 0; filter->processed = 1; // this is starting branch of the algo // assuming that in root filter filter->path is NULL if (filter->path == NULL) { list_item_t *item = list_get_first(&filter->list); // push all the descendants to the stack for (; item != NULL ; item = item->next) { filter_t *next_filter = (filter_t*)item->data; stack_push_vars(&stack, level+1, offset, next_filter); } // usual branch, start with processing } else { // wildcard contition char *is_wildcard = strpbrk(filter->path, "?*["); if (is_wildcard) { int count = 0; char *filter_lim, *filter_old_lim; filter_lim = filter_old_lim = filter->path; char *path_lim, *path_old_lim; path_lim = path_old_lim = path+offset; // there can be wildcard in the dir name as well // we need to count how many chars can be eaten by wildcard while(1) { filter_lim = strchr(filter_lim, '/'); path_lim = strchr(path_lim, '/'); if (filter_lim) { count++; filter_old_lim = filter_lim; filter_lim++; } else break; if (path_lim) { path_old_lim = path_lim; path_lim++; } else break; } // put 0 after the last / char tmp = '\0'; if (count && *(filter_old_lim+1) == '\0') { tmp = *(path_old_lim+1); *(path_old_lim+1) = '\0'; } // check fnmatch matched = !fnmatch(filter->path, path+offset, 0); // and set back if (count && *(filter_old_lim+1) == '\0') *(path_old_lim+1) = tmp; if (matched) { offset = path_old_lim - path+offset; } } else { // match normal path or just specific part of it matched = !strncmp(path+offset, filter->path, filter->len); if (matched) offset += filter->len; } if (matched) { level++; filter->matched = 1; // if matched we need ot push descendants to the stack list_item_t *item = list_get_first(&filter->list); // if there are no descendants and it is a wildcard then it's a match if (item == NULL && is_wildcard) { // if '+' ret 1 and if '-' ret 0 res = filter->type == ADD ? 1 : 0; goto end; } // no descendants, and already compared whole path string so its a match if (item == NULL && path_len == offset) { // if '+' ret 1 and if '-' ret 0 res = filter->type == ADD ? 1 : 0; goto end; } // push descendants to the stack for (; item != NULL ; item = item->next) { filter_t *next_filter = (filter_t*)item->data; stack_push_vars(&stack, level, offset, next_filter); } } } stack_item_t * stack_item = NULL; // popping processed filters from the top of the stack do { if (stack_item) { filter = stack_item->filter; offset = stack_item->offset; level = stack_item->level; // assuimg that nothing has matched on the upper level so it's a directory match if (filter->matched && filter->path[filter->len-1] == '/') { res = filter->type == ADD ? 1 : 0; goto end; } // reset processed flag stack_pop_reset(&stack); } stack_item = (stack_item_t*)stack_top(&stack); } while(stack_item && stack_item->filter->processed); if (!stack_item) break; filter = stack_item->filter; offset = stack_item->offset; level = stack_item->level; } end: // Clean up the stack stack_pop_all_reset(&stack); stack_destroy(&stack); return res; } // load filter configuration file and fill the filter structure int filter_load_file(void) { int res = 0; FILE *stream = fopen(OLD_FILTER_FILE, "r"); if (stream == NULL) { stream = fopen(FILTER_FILE, "r"); if (stream == NULL) { msg(LOG_ERR, "Cannot open filter file %s", FILTER_FILE); return 1; } } else { msg(LOG_INFO, "Using old filter file: %s, use the new one: %s", OLD_FILTER_FILE, FILTER_FILE); msg(LOG_INFO, "Consider 'mv %s %s'", OLD_FILTER_FILE, FILTER_FILE); } ssize_t nread; size_t len = 0; char * line = NULL; long line_number = 0; int last_level = 0; stack_t stack; stack_init(&stack); stack_push_vars(&stack, last_level, 0, global_filter); while ((nread = getline(&line, &len, stream)) != -1) { line_number++; if (line[0] == '\0' || line[0] == '\n') { free(line); line = NULL; continue; } // get rid of the new line char char * new_line = strchr(line, '\n'); if (new_line) { *new_line = '\0'; len--; } int level = 1; char * rest = line; filter_type_t type = NONE; for (size_t i = 0 ; i < len ; i++) { switch (line[i]) { case ' ': level++; continue; case '+': type = ADD; break; case '-': type = SUB; break; case '#': type = COMMENT; break; default: type = BAD; break; } // continue with next char // skip + and space rest = fapolicyd_strtrim(&(line[i+2])); break; } // ignore comment if (type == COMMENT) { free(line); line = NULL; continue; } // if something bad return error if (type == BAD) { msg(LOG_ERR, "filter_load_file: cannot parse line number %ld, \"%s\"", line_number, line); free(line); line = NULL; goto bad; } filter_t * filter = filter_create_obj(); if (filter) { filter->path = strdup(rest); filter->len = strlen(filter->path); filter->type = type; } // comparing level of indetantion between the last line and the current one last_level = ((stack_item_t*)stack_top(&stack))->level; if (level == last_level) { // since we are at the same level as filter before // we need to pop the previous filter from the top stack_pop_vars(&stack); // pushing filter to the list of top's children list list_prepend(&((stack_item_t*)stack_top(&stack))->filter->list, NULL, (void*)filter); // pushing filter to the top of the stack stack_push_vars(&stack, level, 0, filter); } else if (level == last_level + 1) { // this filter has higher level tha privious one // we wont do pop just push // pushing filter to the list of top's children list list_prepend(&((stack_item_t*)stack_top(&stack))->filter->list, NULL, (void*)filter); // pushing filter to the top of the stack stack_push_vars(&stack, level, 0, filter); } else if (level < last_level){ // level of indentation dropped // we need to pop // +1 is meant for getting rid of the current level so we can again push for (int i = 0 ; i < last_level - level + 1; i++) { stack_pop_vars(&stack); } // pushing filter to the list of top's children list list_prepend(&((stack_item_t*)stack_top(&stack))->filter->list, NULL, (void*)filter); // pushing filter to the top of the stack stack_push_vars(&stack, level, 0, filter); } else { msg(LOG_ERR, "filter_load_file: paring error line: %ld, \"%s\"", line_number, line); filter_destroy_obj(filter); free(line); line = NULL; goto bad; } } if (line) { free(line); line = NULL; } goto good; bad: res = 1; good: fclose(stream); stack_pop_all_vars(&stack); stack_destroy(&stack); if (global_filter->list.count == 0) { msg(LOG_ERR, "filter_load_file: no valid filter provided in %s", FILTER_FILE); } return res; } fapolicyd-1.3.4/src/library/filter.h000066400000000000000000000025701470754500200173720ustar00rootroot00000000000000/* * filter.h - Header for a filter implementation * Copyright (c) 2023 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #ifndef FILTER_H_ #define FILTER_H_ #include #include #include "llist.h" typedef enum filter_type { NONE, ADD, SUB, COMMENT, BAD, } filter_type_t; typedef struct _filter { filter_type_t type; char * path; size_t len; int processed; int matched; list_t list; } filter_t; typedef struct _stack_item { int level; int offset; filter_t *filter; } stack_item_t; int filter_init(void); void filter_destroy(void); int filter_check(const char *path); int filter_load_file(void); #endif // FILTER_H_ fapolicyd-1.3.4/src/library/gcc-attributes.h000066400000000000000000000010671470754500200210250ustar00rootroot00000000000000#ifndef GCC_ATTRIBUTES_H #define GCC_ATTRIBUTES_H #define NEVERNULL __attribute__ ((returns_nonnull)) #define WARNUNUSED __attribute__ ((warn_unused_result)) #define NORETURN __attribute__ ((noreturn)) // These macros originate in sys/cdefs.h. These are stubs in case undefined. #include // any major header brings cdefs.h #ifndef __attr_access # define __attr_access(x) #endif #ifndef __attr_dealloc # define __attr_dealloc(dealloc, argno) # define __attr_dealloc_free #endif #ifndef __attribute_malloc__ # define __attribute_malloc__ #endif #endif fapolicyd-1.3.4/src/library/llist.c000066400000000000000000000062341470754500200172300ustar00rootroot00000000000000/* * llist.c - Linked list as a temporary memory storage * for trust database data * Copyright (c) 2016,2018 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka * Zoltan Fridrich */ #include #include #include #include "message.h" #include "llist.h" #pragma GCC optimize("O3") void list_init(list_t *list) { list->count = 0; list->first = NULL; list->last = NULL; } list_item_t *list_get_first(const list_t *list) { return list->first; } static list_item_t * create_item(const char *index, const char *data) { list_item_t *item = malloc(sizeof(list_item_t)); if (!item) { msg(LOG_ERR, "Malloc failed"); return item; } item->index = index; item->data = data; item->next = NULL; return item; } int list_prepend(list_t *list, const char *index, const char *data) { list_item_t *item = create_item(index, data); item->next = list->first; list->first = item; ++list->count; return 0; } int list_append(list_t *list, const char *index, const char *data) { list_item_t *item = create_item(index, data); if (list->first) { list->last->next = item; list->last = item; } else { list->first = item; list->last = item; } ++list->count; return 0; } void list_destroy_item(list_item_t **item) { free((void *)(*item)->index); free((void *)(*item)->data); free((*item)); *item = NULL; } void list_empty(list_t *list) { if (!list->first) return; list_item_t *actual = list->first; list_item_t *next = NULL; for (; actual; actual = next) { next = actual->next; list_destroy_item(&actual); } list_init(list); } // Return 1 if the list contains the string, 0 otherwise int list_contains(list_t *list, const char *str) { for (list_item_t *lptr = list->first; lptr; lptr = lptr->next) { if (!strcmp(str, lptr->index)) return 1; } return 0; } // Return 1 if an item was removed, 0 otherwise int list_remove(list_t *list, const char *str) { list_item_t *lptr, *prev = NULL; for (lptr = list->first; lptr; lptr = lptr->next) { if (!strcmp(str, lptr->index)) { if (prev) prev->next = lptr->next; else list->first = lptr->next; if (!lptr->next) list->last = prev; --list->count; list_destroy_item(&lptr); return 1; } prev = lptr; } return 0; } void list_merge(list_t *dest, list_t *src) { if (!dest->last) { *dest = *src; } else { dest->last->next = src->first; dest->count += src->count; } list_init(src); } fapolicyd-1.3.4/src/library/llist.h000066400000000000000000000031221470754500200172260ustar00rootroot00000000000000/* * temporary_db.h - Header file for linked list * Copyright (c) 2018 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka * Zoltan Fridrich */ #ifndef LLIST_H #define LLIST_H typedef struct item { const void *index; const void *data; struct item *next; } list_item_t; typedef struct list_header { long count; struct item *first; struct item *last; } list_t; void list_init(list_t *list); list_item_t *list_get_first(const list_t *list); int list_prepend(list_t *list, const char *index, const char *data); int list_append(list_t *list, const char *index, const char *data); void list_destroy_item(list_item_t **item); void list_empty(list_t *list); int list_contains(list_t *list, const char *str); int list_remove(list_t *list, const char *str); void list_merge(list_t *dest, list_t *src); #endif fapolicyd-1.3.4/src/library/lru.c000066400000000000000000000204141470754500200166770ustar00rootroot00000000000000/* * lru.c - LRU cache implementation * Copyright (c) 2016,2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include "lru.h" #include "message.h" #include "gcc-attributes.h" //#define DEBUG // Local declarations static void dequeue(Queue *queue); // The Queue Node will store the 'item' being cached static QNode *new_QNode(void) __attr_dealloc_free; static QNode *new_QNode(void) { QNode *temp = malloc(sizeof(QNode)); if (temp == NULL) return temp; temp->item = NULL; temp->uses = 1; // Setting to 1 because its being used // Initialize prev and next as NULL temp->prev = temp->next = NULL; return temp; } static Hash *create_hash(unsigned int hsize) { unsigned int i; Hash *hash = malloc(sizeof(Hash)); if (hash == NULL) return hash; hash->array = malloc(hsize * sizeof(QNode*)); if (hash->array == NULL) { free(hash); return NULL; } // Initialize all hash entries as empty for (i = 0; i < hsize; i++) hash->array[i] = NULL; return hash; } static void destroy_hash(Hash *hash) { free(hash->array); free(hash); } static void dump_queue_stats(const Queue *q) { msg(LOG_DEBUG, "%s cache size: %u", q->name, q->total); msg(LOG_DEBUG, "%s slots in use: %u (%u%%)", q->name, q->count, q->total ? (100*q->count)/q->total : 0); msg(LOG_DEBUG, "%s hits: %lu", q->name, q->hits); msg(LOG_DEBUG, "%s misses: %lu", q->name, q->misses); msg(LOG_DEBUG, "%s evictions: %lu (%lu%%)", q->name, q->evictions, q->hits ? (100*q->evictions)/q->hits : 0); } static Queue *create_queue(unsigned int qsize, const char *name) { Queue *queue = malloc(sizeof(Queue)); if (queue == NULL) return queue; // The queue is empty queue->count = 0; queue->hits = 0; queue->misses = 0; queue->evictions = 0; queue->front = queue->end = NULL; // Number of slots that can be stored in memory queue->total = qsize; queue->name = name; return queue; } static void destroy_queue(Queue *queue) { dump_queue_stats(queue); // Some static analysis scanners try to flag this as a use after // free accessing queue->end. This is a false positive. It is freed. // However, static analysis apps are incapable of seeing that in // remove_node, end is updated to a prior node as part of detaching // the current end node. while (queue->count) dequeue(queue); free(queue); } static unsigned int are_all_slots_full(const Queue *queue) { return queue->count == queue->total; } static unsigned int queue_is_empty(const Queue *queue) { return queue->end == NULL; } #ifdef DEBUG static void sanity_check_queue(Queue *q, const char *id) { unsigned int i; QNode *n; if (q == NULL) { msg(LOG_DEBUG, "%s - q is NULL", id); abort(); } n = q->front; if (n == NULL) return; // Walk bottom to top i = 0; while (n->next) { if (n->next->prev != n) { msg(LOG_DEBUG, "%s - corruption found %u", id, i); abort(); } if (i == q->count) { msg(LOG_DEBUG, "%s - forward loop found %u", id, i); abort(); } i++; n = n->next; } // Walk top to bottom n = q->end; while (n->prev) { if (n->prev->next != n) { msg(LOG_DEBUG, "%s - Corruption found %u", id, i); abort(); } if (i == 0) { msg(LOG_DEBUG, "%s - backward loop found %u", id, i); abort(); } i--; n = n->prev; } } #else #define sanity_check_queue(a, b) do {} while(0) #endif static void insert_before(Queue *queue, QNode *node, QNode *new_node) { sanity_check_queue(queue, "1 insert_before"); if (queue == NULL || node == NULL || new_node == NULL) return; new_node->prev = node->prev; new_node->next = node; if (node->prev == NULL) queue->front = new_node; else node->prev->next = new_node; node->prev = new_node; sanity_check_queue(queue, "2 insert_before"); } static void insert_beginning(Queue *queue, QNode *new_node) { sanity_check_queue(queue, "1 insert_beginning"); if (queue == NULL || new_node == NULL) return; if (queue->front == NULL) { queue->front = new_node; queue->end = new_node; new_node->prev = NULL; new_node->next = NULL; } else insert_before(queue, queue->front, new_node); sanity_check_queue(queue, "2 insert_beginning"); } static void remove_node(Queue *queue, const QNode *node) { // If we are at the beginning sanity_check_queue(queue, "1 remove_node"); if (node->prev == NULL) { queue->front = node->next; if (queue->front) queue->front->prev = NULL; goto out; } else { if (node->prev->next != node) { msg(LOG_ERR, "Linked list corruption detected %s", queue->name); abort(); } node->prev->next = node->next; } // If we are at the end if (node->next == NULL) { queue->end = node->prev; if (queue->end) queue->end->next = NULL; } else { if (node->next->prev != node) { msg(LOG_ERR, "Linked List corruption detected %s", queue->name); abort(); } node->next->prev = node->prev; } out: sanity_check_queue(queue, "2 remove_node"); } // Remove from the end of the queue static void dequeue(Queue *queue) { if (queue_is_empty(queue)) return; QNode *temp = queue->end; remove_node(queue, queue->end); queue->cleanup(temp->item); free(temp->item); free(temp); // decrement the total of full slots by 1 queue->count--; } // Remove front of the queue because its a mismatch void lru_evict(Queue *queue, unsigned int key) { if (queue_is_empty(queue)) return; Hash *hash = queue->hash; QNode *temp = queue->front; hash->array[key] = NULL; remove_node(queue, queue->front); queue->cleanup(temp->item); free(temp->item); free(temp); // decrement the total of full slots by 1 queue->count--; queue->evictions++; } // Make a new entry with item to be assigned later // and setup the hash key static void enqueue(Queue *queue, unsigned int key) { QNode *temp; Hash *hash = queue->hash; // If all slots are full, remove the page at the end if (are_all_slots_full(queue)) { // remove page from hash hash->array[key] = NULL; dequeue(queue); } // Create a new node with given page total, // And add the new node to the front of queue temp = new_QNode(); insert_beginning(queue, temp); hash->array[key] = temp; // increment number of full slots queue->count++; } // This function is called needing an item from cache. // There are two scenarios: // 1. Item is not in cache, so add it to the front of the queue // 2. Item is in cache, we move the item to front of queue QNode *check_lru_cache(Queue *queue, unsigned int key) { QNode *reqPage; Hash *hash = queue->hash; // Check for out of bounds key if (key >= queue->total) { return NULL; } reqPage = hash->array[key]; // item is not in cache, make new spot for it if (reqPage == NULL) { enqueue(queue, key); queue->misses++; // item is there but not at front. Move it } else if (reqPage != queue->front) { remove_node(queue, reqPage); reqPage->next = NULL; reqPage->prev = NULL; insert_beginning(queue, reqPage); // Increment cached object metrics queue->front->uses++; queue->hits++; } else queue->hits++; return queue->front; } Queue *init_lru(unsigned int qsize, void (*cleanup)(void *), const char *name) { Queue *q = create_queue(qsize, name); if (q == NULL) return q; q->cleanup = cleanup; q->hash = create_hash(qsize); return q; } void destroy_lru(Queue *queue) { if (queue == NULL) return; destroy_hash(queue->hash); destroy_queue(queue); } unsigned int compute_subject_key(const Queue *queue, unsigned int pid) { if (queue) return pid % queue->total; else return 0; } unsigned long compute_object_key(const Queue *queue, unsigned long num) { if (queue) return num % queue->total; else return 0; } fapolicyd-1.3.4/src/library/lru.h000066400000000000000000000042671470754500200167140ustar00rootroot00000000000000/* * lru.h - Header file for lru.c * Copyright (c) 2016 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef LRU_HEADER #define LRU_HEADER #include "gcc-attributes.h" // Queue is implemented using double linked list typedef struct QNode { struct QNode *prev; struct QNode *next; unsigned long uses; void *item; // the data in the cache } QNode; // Collection of pointers to Queue Nodes typedef struct Hash { unsigned int size; // how many entries QNode **array; // an array of queue nodes } Hash; // FIFO of Queue Nodes typedef struct Queue { unsigned int count; // Number of filled slots unsigned int total; // total number of slots unsigned long hits; // Number of times object was in cache unsigned long misses;// number of times object was not in cache unsigned long evictions;// number of times cached object was not usable QNode *front; QNode *end; Hash *hash; const char *name; // Used for reporting void (*cleanup)(void *); // Function to call when releasing memory } Queue; void destroy_lru(Queue *queue); Queue *init_lru(unsigned int qsize, void (*cleanup)(void *), const char *name) __attribute_malloc__ __attr_dealloc (destroy_lru, 1); void lru_evict(Queue *queue, unsigned int key); QNode *check_lru_cache(Queue *q, unsigned int key); unsigned int compute_subject_key(const Queue *queue, unsigned int pid); unsigned long compute_object_key(const Queue *queue, unsigned long num); #endif fapolicyd-1.3.4/src/library/md5-backend.c000066400000000000000000000077461470754500200201640ustar00rootroot00000000000000/* * md5-backend.c - functions for adding files to the trust database * based on MD5 hashes. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Stephen Tridgell * Matt Jolly */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "file.h" #include "fapolicyd-backend.h" #include "message.h" #include "md5-backend.h" /* * Given a path to a file with an expected MD5 digest, add * the file to the trust database if it matches. * * Dpkg does not provide sha256 sums or file sizes to verify against. * The only source for verification is MD5. The logic implemented is: * 1) Calculate the MD5 sum and compare to the expected hash. If it does * not match, abort. * 2) Calculate the SHA256 and file size on the local files. * 3) Add to database. * * Security considerations: * An attacker would need to craft a file with a MD5 hash collision. * While MD5 is considered broken, this is still some effort. * This function would compute a sha256 and file size on the attackers * crafted file so they do not secure this backend. */ int add_file_to_backend_by_md5(const char *path, const char *expected_md5, struct _hash_record **hashtable, trust_src_t trust_src, backend *dstbackend) { #ifdef DEBUG msg(LOG_DEBUG, "Adding %s", path); msg(LOG_DEBUG, "\tExpected MD5: %s", expected_md5); #endif int fd = open(path, O_RDONLY|O_NOFOLLOW); struct stat path_stat; if (fd < 0) { if (errno != ELOOP) // Don't report symlinks as a warning msg(LOG_WARNING, "Could not open %si, %s", path, strerror(errno)); return 1; } if (fstat(fd, &path_stat)) { close(fd); msg(LOG_WARNING, "fstat file %s failed %s", path, strerror(errno)); return 1; } // If its not a regular file, skip. if (!S_ISREG(path_stat.st_mode)) { close(fd); msg(LOG_DEBUG, "Not regular file %s", path); return 1; } size_t file_size = path_stat.st_size; #ifdef DEBUG msg(LOG_DEBUG, "\tFile size: %zu", file_size); #endif char *md5_digest = get_hash_from_fd2(fd, file_size, 0); if (md5_digest == NULL) { close(fd); msg(LOG_ERR, "MD5 digest returned NULL"); return 1; } if (strcmp(md5_digest, expected_md5) != 0) { msg(LOG_WARNING, "Skipping %s: hash mismatch. Got %s, expected %s", path, md5_digest, expected_md5); close(fd); free(md5_digest); return 1; } free(md5_digest); // It's OK so create a sha256 of the file char *sha_digest = get_hash_from_fd2(fd, file_size, 1); close(fd); if (sha_digest == NULL) { msg(LOG_ERR, "Sha digest returned NULL"); return 1; } char *data; if (asprintf(&data, DATA_FORMAT, trust_src, file_size, sha_digest) == -1) { data = NULL; } free(sha_digest); if (data) { // Getting rid of the duplicates. struct _hash_record *rcd = NULL; char key[kMaxKeyLength]; snprintf(key, kMaxKeyLength - 1, "%s %s", path, data); HASH_FIND_STR(*hashtable, key, rcd); if (!rcd) { rcd = (struct _hash_record *)malloc(sizeof(struct _hash_record)); rcd->key = strdup(key); HASH_ADD_KEYPTR(hh, *hashtable, rcd->key, strlen(rcd->key), rcd); list_append(&dstbackend->list, strdup(path), data); } else { free((void *)data); } return 0; } return 1; } fapolicyd-1.3.4/src/library/md5-backend.h000066400000000000000000000024021470754500200201510ustar00rootroot00000000000000/* * md5-backend.h - header file for md5-backend.c * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Stephen Tridgell * Matt Jolly */ #ifndef MD5_BACKEND_HEADER #define MD5_BACKEND_HEADER #include #include "fapolicyd-backend.h" struct _hash_record { const char *key; UT_hash_handle hh; }; static const int kMaxKeyLength = 4096; static const int kMd5HexSize = 32; int add_file_to_backend_by_md5(const char *path, const char *expected_md5, struct _hash_record **hashtable, trust_src_t trust_src, backend *dstbackend); #endif fapolicyd-1.3.4/src/library/message.c000066400000000000000000000055621470754500200175300ustar00rootroot00000000000000/* * message.c - function to syslog or write to stderr * Copyright (c) 2016 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include "message.h" /* The message mode refers to where informational messages go 0 - stderr, 1 - syslog, 2 - quiet. The default is quiet. */ static message_t message_mode = MSG_QUIET; static debug_message_t debug_message = DBG_NO; void set_message_mode(message_t mode, debug_message_t debug) { message_mode = mode; debug_message = debug; } void msg(int priority, const char *fmt, ...) { va_list ap; if (message_mode == MSG_QUIET) return; if (priority == LOG_DEBUG && debug_message == DBG_NO) return; va_start(ap, fmt); if (message_mode == MSG_SYSLOG) vsyslog(priority, fmt, ap); else { // For stderr we'll include the log level, use ANSI escape // codes to colourise the it, and prefix lines with the time // and date. const char *color; const char *level; switch (priority) { case LOG_EMERG: color = "\x1b[31m"; level = "EMERGENCY"; break; /* Red */ case LOG_ALERT: color = "\x1b[35m"; level = "ALERT"; break; /* Magenta */ case LOG_CRIT: color = "\x1b[33m"; level = "CRITICAL"; break; /* Yellow */ case LOG_ERR: color = "\x1b[31m"; level = "ERROR"; break; /* Red */ case LOG_WARNING: color = "\x1b[33m"; level = "WARNING"; break; /* Yellow */ case LOG_NOTICE: color = "\x1b[32m"; level = "NOTICE"; break; /* Green */ case LOG_INFO: color = "\x1b[36m"; level = "INFO"; break; /* Cyan */ case LOG_DEBUG: color = "\x1b[34m"; level = "DEBUG"; break; /* Blue */ default: color = "\x1b[0m"; level = "UNKNOWN"; break; /* Reset */ } time_t rawtime; struct tm timeinfo; char buffer[80]; time(&rawtime); // localtime is not threadsafe, use _r version for safety (void) localtime_r(&rawtime, &timeinfo); strftime(buffer, sizeof(buffer), "%x %T [ ", &timeinfo); fputs(buffer, stderr); fputs(color, stderr); fputs(level, stderr); fputs("\x1b[0m ]: ", stderr); vfprintf(stderr, fmt, ap); fputc('\n', stderr); fflush(stderr); } va_end(ap); } fapolicyd-1.3.4/src/library/message.h000066400000000000000000000023671470754500200175350ustar00rootroot00000000000000/* * message.h - Header file for message.c * Copyright (c) 2016 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef MESSAGE_HEADER #define MESSAGE_HEADER #include typedef enum { MSG_STDERR, MSG_SYSLOG, MSG_QUIET } message_t; typedef enum { DBG_NO, DBG_YES } debug_message_t; void set_message_mode(message_t mode, debug_message_t debug); void msg(int priority, const char *fmt, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))); #else ; #endif #endif fapolicyd-1.3.4/src/library/nv.h000066400000000000000000000022021470754500200165200ustar00rootroot00000000000000/* * nv.h - Header file for name value struct * Copyright (c) 2016 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef NV_HEADER #define NV_HEADER #define SUBJ_START 0 #define OBJ_START 16 typedef struct nv { unsigned int value; const char *name; }nv_t; typedef struct nv_list { const char *name; int item; }nvlist_t; #endif fapolicyd-1.3.4/src/library/object-attr.c000066400000000000000000000032501470754500200203120ustar00rootroot00000000000000/* * object-attr.c - abstract object attribute access * Copyright (c) 2016,2019 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #include "config.h" #include // For NULL #include #include "object-attr.h" static const nv_t table[] = { { ALL_OBJ, "all" }, { PATH, "path" }, { ODIR, "dir" }, { DEVICE, "device" }, { FTYPE, "ftype" }, { OBJ_TRUST, "trust"}, { SHA256HASH, "sha256hash" }, { FMODE, "mode" }, }; #define MAX_OBJECTS (sizeof(table)/sizeof(table[0])) int obj_name_to_val(const char *name) { unsigned int i = 0; while (i < MAX_OBJECTS) { if (strcmp(name, table[i].name) == 0) return table[i].value; i++; } return -1; } const char *obj_val_to_name(unsigned int v) { if (v < OBJ_START || v > OBJ_END) return NULL; unsigned int index = v - OBJ_START; if (index < MAX_OBJECTS) return table[index].name; return NULL; } fapolicyd-1.3.4/src/library/object-attr.h000066400000000000000000000027051470754500200203230ustar00rootroot00000000000000/* * object-attr.h - Header file for object-attr.c * Copyright (c) 2016 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #ifndef OBJECT_ATTR_HEADER #define OBJECT_ATTR_HEADER #include #include "nv.h" #include "attr-sets.h" typedef enum { ALL_OBJ = OBJ_START, PATH, ODIR, DEVICE, FTYPE, OBJ_TRUST, SHA256HASH, FMODE } object_type_t; #define OBJ_END FMODE typedef struct o { object_type_t type; int val; // holds trust value char *o; // Everything is a string union { size_t gr_index; attr_sets_entry_t * set; }; } object_attr_t; int obj_name_to_val(const char *name); const char *obj_val_to_name(unsigned int v); #endif fapolicyd-1.3.4/src/library/object.c000066400000000000000000000053571470754500200173540ustar00rootroot00000000000000/* * object.c - Minimal linked list set of object attributes * Copyright (c) 2016 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include "policy.h" #include "object.h" #include "message.h" //#define DEBUG void object_create(o_array *a) { int i; a->obj = malloc(sizeof(object_attr_t *) * ((OBJ_END-OBJ_START)+1)); for (i = 0; i < OBJ_END - OBJ_START; i++) a->obj[i] = NULL; a->cnt = 0; a->info = NULL; } #ifdef DEBUG static void sanity_check_array(o_array *a, const char *id) { int i; unsigned int num = 0; for (i = 0; i < OBJ_END - OBJ_START; i++) if (a->obj[i]) num++; if (num != a->cnt) { msg(LOG_DEBUG, "%s - array corruption %u!=%u", id, num, a->cnt); abort(); } } #else #define sanity_check_array(a, b) do {} while(0) #endif object_attr_t *object_access(const o_array *a, object_type_t t) { sanity_check_array(a, "object_access"); if (t >= OBJ_START && t <= OBJ_END) return a->obj[t - OBJ_START]; else return NULL; } // Returns 1 on failure and 0 on success int object_add(o_array *a, const object_attr_t *obj) { object_attr_t *newnode; sanity_check_array(a, "object_add 1"); if (obj) { if (obj->type >= OBJ_START && obj->type <= OBJ_END) { newnode = malloc(sizeof(object_attr_t)); if (newnode == NULL) return 1; newnode->type = obj->type; newnode->o = obj->o; newnode->val = obj->val; } else return 1; } else return 1; a->obj[obj->type - OBJ_START] = newnode; a->cnt++; return 0; } object_attr_t *object_find_file(const o_array *a) { sanity_check_array(a, "object_find_file"); if (a->obj[PATH - OBJ_START]) return a->obj[PATH - OBJ_START]; else return a->obj[ODIR - OBJ_START]; } void object_clear(o_array *a) { int i; object_attr_t *current; if (a == NULL) return; for (i = 0; i < OBJ_END - OBJ_START; i++) { current = a->obj[i]; if (current == NULL) continue; free(current->o); free(current); } free(a->info); free(a->obj); a->cnt = 0; } fapolicyd-1.3.4/src/library/object.h000066400000000000000000000030031470754500200173430ustar00rootroot00000000000000/* * object.h - Header file for object.c * Copyright (c) 2016,2019 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef OBJECT_HEADER #define OBJECT_HEADER #include "object-attr.h" #include "file.h" /* This is the linked list head. Only data elements that are 1 per * event goes here. */ typedef struct { object_attr_t **obj; // Object array unsigned int cnt; // How many items in this list struct file_info *info; // unique file fingerprint } o_array; void object_create(o_array *a); object_attr_t *object_access(const o_array *a, object_type_t t); int object_add(o_array *a, const object_attr_t *obj); object_attr_t *object_find_file(const o_array *a); void object_clear(o_array *a); static inline int type_is_obj(int type) {if (type >= OBJ_START) return 1; else return 0;} #endif fapolicyd-1.3.4/src/library/paths.h000066400000000000000000000030661470754500200172250ustar00rootroot00000000000000/* globals.h - Constant paths used throughout fapolicyd * Copyright 2022 Red Hat Inc. * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb * */ #ifndef GLOBALS_H #define GLOBALS_H #define DAEMON_PATH "/usr/sbin/fapolicyd" #define CONFIG_FILE "/etc/fapolicyd/fapolicyd.conf" #define OLD_RULES_FILE "/etc/fapolicyd/fapolicyd.rules" #define RULES_FILE "/etc/fapolicyd/compiled.rules" #define TRUST_DIR_PATH "/etc/fapolicyd/trust.d/" #define TRUST_FILE_PATH "/etc/fapolicyd/fapolicyd.trust" #define DB_DIR "/var/lib/fapolicyd" #define DB_NAME "trust.db" #define REPORT "/var/log/fapolicyd-access.log" #define RUN_DIR "/run/fapolicyd/" #define STAT_REPORT "/run/fapolicyd/fapolicyd.state" #define fifo_path "/run/fapolicyd/fapolicyd.fifo" #define pidfile "/run/fapolicyd.pid" #endif fapolicyd-1.3.4/src/library/policy.c000066400000000000000000000354531470754500200174050ustar00rootroot00000000000000/* * policy.c - functions that encapsulate the notion of a policy * Copyright (c) 2016,2019-24 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #include "attr-sets.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "database.h" #include "escape.h" #include "file.h" #include "rules.h" #include "policy.h" #include "nv.h" #include "message.h" #include "gcc-attributes.h" #include "string-util.h" #include "paths.h" #define MAX_SYSLOG_FIELDS 21 #define NGID_LIMIT 32 static llist rules; static unsigned long allowed = 0, denied = 0; static nvlist_t fields[MAX_SYSLOG_FIELDS]; static unsigned int num_fields; extern volatile atomic_bool stop; volatile atomic_bool reload_rules = false; static const nv_t table[] = { { NO_OPINION, "no-opinion" }, { ALLOW, "allow" }, { DENY, "deny" }, #ifdef USE_AUDIT { ALLOW_AUDIT, "allow_audit" }, { DENY_AUDIT, "deny_audit" }, #endif { ALLOW_SYSLOG, "allow_syslog" }, { DENY_SYSLOG, "deny_syslog" }, { ALLOW_LOG, "allow_log" }, { DENY_LOG, "deny_log" } }; extern unsigned int debug_mode; extern unsigned int permissive; #define MAX_DECISIONS (sizeof(table)/sizeof(table[0])) // These are the constants for things not subj or obj #define F_RULE 30 #define F_DECISION 31 #define F_PERM 32 #define F_COLON 33 #ifdef FAN_AUDIT_RULE_NUM struct fan_audit_response { struct fanotify_response r; struct fanotify_response_info_audit_rule a; }; #endif #define WB_SIZE 512 static char *working_buffer = NULL; // This function returns 1 on success and 0 on failure static int parsing_obj; static int lookup_field(const char *ptr) { if (strcmp("rule", ptr) == 0) { fields[num_fields].name = strdup(ptr); fields[num_fields].item = F_RULE; goto success; } else if (strcmp("dec", ptr) == 0) { fields[num_fields].name = strdup(ptr); fields[num_fields].item = F_DECISION; goto success; } else if (strcmp("perm", ptr) == 0) { fields[num_fields].name = strdup(ptr); fields[num_fields].item = F_PERM; goto success; } else if (strcmp(":", ptr) == 0) { fields[num_fields].name = strdup(ptr); fields[num_fields].item = F_COLON; parsing_obj = 1; goto success; } if (parsing_obj == 0) { int ret_val = subj_name_to_val(ptr, RULE_FMT_COLON); if (ret_val >= 0) { if (ret_val == ALL_SUBJ || ret_val == PATTERN || ret_val > EXE) { msg(LOG_ERR, "%s cannot be used in syslog_format", ptr); } else { fields[num_fields].name = strdup(ptr); fields[num_fields].item = ret_val; goto success; } } } else { int ret_val = obj_name_to_val(ptr); if (ret_val >= 0) { if (ret_val == ALL_OBJ) { msg(LOG_ERR, "%s cannot be used in syslog_format", ptr); } else { fields[num_fields].name = strdup(ptr); fields[num_fields].item = ret_val; goto success; } } } return 0; success: num_fields++; return 1; } // This function returns 1 on success, 0 on failure static int parse_syslog_format(const char *syslog_format) { char *ptr, *saved, *tformat; int rc = 1; if (strchr(syslog_format, ':') == NULL) { msg(LOG_ERR, "syslog_format does not have a ':'"); return 0; } num_fields = 0; parsing_obj = 0; tformat = strdup(syslog_format); // Must be delimited by comma ptr = strtok_r(tformat, ",", &saved); while (ptr && rc && num_fields < MAX_SYSLOG_FIELDS) { rc = lookup_field(ptr); if (rc == 0) msg(LOG_ERR, "Field %s invalid for syslog_format", ptr); ptr = strtok_r(NULL, ",", &saved); } free(tformat); return rc; } int dec_name_to_val(const char *name) { unsigned int i = 0; while (i < MAX_DECISIONS) { if (strcmp(name, table[i].name) == 0) return table[i].value; i++; } return -1; } static const char *dec_val_to_name(unsigned int v) { unsigned int i = 0; while (i < MAX_DECISIONS) { if (v == table[i].value) return table[i].name; i++; } return NULL; } static FILE *open_file(void) { int fd; FILE *f; // Now open the file and load them one by one. We default to // opening the old file first in case there are both fd = open(OLD_RULES_FILE, O_NOFOLLOW|O_RDONLY); if (fd < 0) { // See if the new rules exist fd = open(RULES_FILE, O_NOFOLLOW|O_RDONLY); if (fd < 0) { msg(LOG_ERR, "Error opening rules file (%s)", strerror(errno)); return NULL; } } struct stat sb; if (fstat(fd, &sb)) { msg(LOG_ERR, "Failed to stat rule file %s", strerror(errno)); close(fd); return NULL; } char *sha_buf = get_hash_from_fd2(fd, sb.st_size, 1); if (sha_buf) { msg(LOG_INFO, "Ruleset identity: %s", sha_buf); free(sha_buf); } else { msg(LOG_WARNING, "Failed to hash rule identity %s", strerror(errno)); } f = fdopen(fd, "r"); if (f == NULL) { msg(LOG_ERR, "Error - fdopen failed (%s)", strerror(errno)); } return f; } // Returns 0 on success and 1 on error static int _load_rules(const conf_t *_config, FILE *f) { int rc, lineno = 1; char *line = NULL; size_t len = 0; if (rules_create(&rules)) return 1; msg(LOG_DEBUG, "Loading rule file:"); while (getline(&line, &len, f) != -1) { char *ptr = strchr(line, 0x0a); if (ptr) *ptr = 0; msg(LOG_DEBUG, "%s", line); rc = rules_append(&rules, line, lineno); if (rc) { free(line); return 1; } lineno++; } free(line); rules_regen_sets(&rules); if (rules.cnt == 0) { msg(LOG_INFO, "No rules in file - exiting"); return 1; } else { msg(LOG_DEBUG, "Loaded %u rules", rules.cnt); } rc = parse_syslog_format(_config->syslog_format); if (!rc || num_fields == 0) return 1; return 0; } int load_rules(const conf_t *_config) { if (init_attr_sets()) return 1; FILE * f = open_file(); if (f == NULL) { destroy_attr_sets(); return 1; } int res = _load_rules(_config, f); fclose(f); if (res) { destroy_attr_sets(); return 1; } return 0; } void destroy_rules(void) { unsigned int i = 0; rules_clear(&rules); while (i < num_fields) { free((void *)fields[i].name); i++; } destroy_attr_sets(); if (stop) free(working_buffer); } void set_reload_rules(void) { reload_rules = true; } static FILE * ff = NULL; int load_rule_file(void) { if (ff) { fclose(ff); ff = NULL; } ff = open_file(); if (ff == NULL) return 1; return 0; } int do_reload_rules(const conf_t *_config) { destroy_rules(); if (init_attr_sets()) return 1; _load_rules(_config, ff); fclose(ff); ff = NULL; return 0; } static char *format_value(int item, unsigned int num, decision_t results, event_t *e) __attr_dealloc_free; static char *format_value(int item, unsigned int num, decision_t results, event_t *e) { char *out = NULL; if (item >= F_RULE) { switch (item) { case F_RULE: if (asprintf(&out, "%u", num+1) < 0) out = NULL; break; case F_DECISION: if (asprintf(&out, "%s", dec_val_to_name(results)) < 0) out = NULL; break; case F_PERM: if (asprintf(&out, "%s", e->type & FAN_OPEN_EXEC_PERM ? "execute" : "open") < 0) out = NULL; break; case F_COLON: if (asprintf(&out, ":") < 0) out = NULL; break; } } else if (item >= OBJ_START) { object_attr_t *obj = get_obj_attr(e, item); if (item != OBJ_TRUST) { char * str = obj ? obj->o : "?"; size_t need_escape = check_escape_shell(str); if (need_escape) // need_escape contains potential size of escaped string str = escape_shell(str, need_escape); if (asprintf(&out, "%s", str ? str : "??") < 0) out = NULL; } else { if (asprintf(&out, "%d", obj ? (obj->val ? 1 : 0) : 9) < 0) out = NULL; } } else { subject_attr_t *subj = get_subj_attr(e, item); if (item < GID) { if (asprintf(&out, "%d", subj ? subj->val : -2) < 0) out = NULL; } else if (item >= COMM) { char * str = subj ? subj->str : "?"; size_t need_escape = check_escape_shell(str); if (need_escape) // need_escape contains potential size of escaped string str = escape_shell(str, need_escape); if (asprintf(&out, "%s", str ? str : "??") < 0) out = NULL; } else { // GID only log first 32 out = malloc(NGID_LIMIT*12); if (out && subj->set) { char buf[12]; char *ptr = out; int cnt = 0; avl_iterator i; avl_int_data_t *grp; for (grp = (avl_int_data_t *) avl_first(&i, &(subj->set->tree)); grp && cnt < NGID_LIMIT; grp=(avl_int_data_t *)avl_next(&i)) { if (ptr == out) snprintf(buf, sizeof(buf), "%d", grp->num); else snprintf(buf, sizeof(buf), ",%d", grp->num); ptr = stpcpy(ptr, buf); cnt++; } } else if (out) strcpy(out, "?"); } } return out; } // This is like memccpy except it returns the pointer to the NIL byte so // that we are positioned for the next concatenation. Also, since we know // we are always looking for NIL, just hard code it. static void *fmemccpy(void* restrict dst, const void* restrict src, ssize_t n) { if (n <= 0) return dst; const char *s = src; char *ret = dst; for ( ; n; ++ret, ++s, --n) { *ret = *s; if ((unsigned char)*ret == (unsigned char)'\0') return ret; } return ret; } static void log_it2(unsigned int num, decision_t results, event_t *e) { int mode = results & SYSLOG ? LOG_INFO : LOG_DEBUG; unsigned int i; int dsize; char *p1, *p2, *val; if (working_buffer == NULL) { working_buffer = malloc(WB_SIZE); if (working_buffer == NULL) { msg(LOG_ERR, "No working buffer for logging"); return; } } dsize = WB_SIZE; p1 = p2 = working_buffer; // Dummy assignment for p1 to quiet warnings for (i = 0; i < num_fields && dsize; i++) { if (dsize < WB_SIZE) { // This is skipped first pass, p1 is initialized below p2 = fmemccpy(p1, " ", dsize); dsize -= p2 - p1; } p1 = fmemccpy(p2, fields[i].name, dsize); dsize -= p1 - p2; if (fields[i].item != F_COLON) { p2 = fmemccpy(p1, "=", dsize); dsize -= p2 - p1; val = format_value(fields[i].item, num, results, e); p1 = fmemccpy(p2, val ? val : "?", dsize); dsize -= p1 - p2; free(val); } } working_buffer[WB_SIZE-1] = 0; // Just in case msg(mode, "%s", working_buffer); } decision_t process_event(event_t *e) { decision_t results = NO_OPINION; /* populate the event struct and iterate over the rules */ rules_first(&rules); lnode *r = rules_get_cur(&rules); int cnt = 0; while (r) { //msg(LOG_INFO, "process_event: rule %d", cnt); results = rule_evaluate(r, e); // If a rule has an opinion, stop and use it if (results != NO_OPINION) break; r = rules_next(&rules); cnt++; } // Output some information if debugging on or syslogging requested if ( (results & SYSLOG) || (debug_mode == 1) || (debug_mode > 1 && (results & DENY)) ) log_it2(r ? r->num : 0xFFFFFFFF, results, e); // Record which rule (rules are 1 based when listed by the cli tool) if (r) e->num = r->num + 1; // If we are not in permissive mode, return any decision if (results != NO_OPINION) return results; return ALLOW; } #ifdef FAN_AUDIT_RULE_NUM static int test_info_api(int fd) { int rc; struct fan_audit_response f; f.r.fd = FAN_NOFD; f.r.response = FAN_DENY | FAN_INFO; f.a.hdr.type = FAN_RESPONSE_INFO_AUDIT_RULE; f.a.hdr.pad = 0; f.a.hdr.len = sizeof(struct fanotify_response_info_audit_rule); f.a.rule_number = 0; f.a.subj_trust = 2; f.a.obj_trust = 2; rc = write(fd, &f, sizeof(struct fan_audit_response)); msg(LOG_DEBUG, "Rule number API supported %s", rc < 0 ? "no" : "yes"); if (rc < 0) return 0; else return 1; } #endif #ifdef USE_RPM int push_fd_to_buffer(int); extern volatile atomic_bool ongoing_rpm_operation; extern const char *rpm_dir_path; extern ssize_t rpm_dir_path_len; #endif void reply_event(int fd, const struct fanotify_event_metadata *metadata, unsigned reply, event_t *e) { #ifdef FAN_AUDIT_RULE_NUM static int use_new = 2; if (use_new == 2) use_new = test_info_api(fd); if (reply & FAN_AUDIT && use_new) { struct fan_audit_response f; subject_attr_t *sn; object_attr_t *obj; f.r.fd = metadata->fd; f.r.response = reply | FAN_INFO; f.a.hdr.type = FAN_RESPONSE_INFO_AUDIT_RULE; f.a.hdr.pad = 0; f.a.hdr.len = sizeof(struct fanotify_response_info_audit_rule); if (e) f.a.rule_number = e->num; else f.a.rule_number = 0; // Subj trust is rare. See if we have it. if (e && (sn = subject_access(e->s, SUBJ_TRUST))) { f.a.subj_trust = sn->val; } else f.a.subj_trust = 2; // All objects have a trust value if (e && (obj = get_obj_attr(e, OBJ_TRUST))) { f.a.obj_trust = obj->val; } else f.a.obj_trust = 2; write(fd, &f, sizeof(struct fan_audit_response)); return; } #endif struct fanotify_response response; response.fd = metadata->fd; response.response = reply; write(fd, &response, sizeof(struct fanotify_response)); #ifdef USE_RPM if (ongoing_rpm_operation) { object_attr_t *obj; char *path = NULL; if (e && (obj = get_obj_attr(e, PATH))) { path = obj->o; } if (path && !strncmp(path, rpm_dir_path, rpm_dir_path_len)) { if(push_fd_to_buffer(metadata->fd)) close(metadata->fd); return; // no close for now } } #endif close(metadata->fd); } void make_policy_decision(const struct fanotify_event_metadata *metadata, int fd, uint64_t mask) { event_t e; int decision; if (new_event(metadata, &e)) decision = FAN_DENY; else { lock_rule(); decision = process_event(&e); unlock_rule(); } if ((decision & DENY) == DENY) denied++; else allowed++; if (metadata->mask & mask) { // if in debug mode, do not allow audit events if (debug_mode) decision &= ~AUDIT; // If permissive, always allow and honor the audit bit // if not in debug mode if (permissive) reply_event(fd, metadata,FAN_ALLOW | (decision & AUDIT), &e); else reply_event(fd, metadata, decision & FAN_RESPONSE_MASK, &e); } } unsigned long getAllowed(void) { return allowed; } unsigned long getDenied(void) { return denied; } void policy_no_audit(void) { rules_unsupport_audit(&rules); } fapolicyd-1.3.4/src/library/policy.h000066400000000000000000000040521470754500200174010ustar00rootroot00000000000000/* * policy.h - Header file for policy.c * Copyright (c) 2016,2020,2023 Red Hat * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef POLICY_HEADER #define POLICY_HEADER #include #include "event.h" #ifdef USE_AUDIT #if HAVE_DECL_FAN_AUDIT #define AUDIT FAN_AUDIT #else #define AUDIT 0x0010 #define FAN_ENABLE_AUDIT 0x00000040 #endif #else #define AUDIT 0x0 #endif #define SYSLOG 0x0020 #define FAN_RESPONSE_MASK (FAN_ALLOW|FAN_DENY|FAN_AUDIT) typedef enum { NO_OPINION = 0, ALLOW = FAN_ALLOW, DENY = FAN_DENY, #ifdef USE_AUDIT ALLOW_AUDIT = FAN_ALLOW | AUDIT, DENY_AUDIT = FAN_DENY | AUDIT, #endif ALLOW_SYSLOG = FAN_ALLOW | SYSLOG, DENY_SYSLOG = FAN_DENY | SYSLOG, ALLOW_LOG = FAN_ALLOW | AUDIT | SYSLOG, DENY_LOG = FAN_DENY | AUDIT | SYSLOG } decision_t; int dec_name_to_val(const char *name); int load_rules(const conf_t *config); int load_rule_file(void); int do_reload_rules(const conf_t *config); void set_reload_rules(void); decision_t process_event(event_t *e); void reply_event(int fd, const struct fanotify_event_metadata *metadata, unsigned reply, event_t *e); void make_policy_decision(const struct fanotify_event_metadata *metadata, int fd, uint64_t mask); unsigned long getAllowed(void); unsigned long getDenied(void); void policy_no_audit(void); void destroy_rules(void); #endif fapolicyd-1.3.4/src/library/process.c000066400000000000000000000200661470754500200175560ustar00rootroot00000000000000/* * process.c - functions to access attributes of processes * Copyright (c) 2016,2020-22 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include //#ifdef HAVE_STDIO_EXT_H # include //#endif #include #include #include #include #include #include #include #include #include "process.h" #include "file.h" #define BUFSZ 12 // Largest unsigned int is 10 characters long /* * This is an optimized integer to string conversion. It only * does base 10 which is exactly what you need to access per * process files in the proc file system. It is about 30% faster * than snprint. */ static const char *uitoa(unsigned int j) { static char buf[BUFSZ]; if (j == 0) return "0"; char *ptr = &buf[BUFSZ - 1]; *ptr = 0; do { *--ptr = '0' + (j % 10); j /= 10; } while (j); return ptr; } static char ppath[40] = "/proc/"; static inline const char *proc_path(unsigned int pid, const char *file) { char *p = stpcpy(ppath + 6, uitoa(pid)); if (file) stpcpy(p, file); return ppath; } struct proc_info *stat_proc_entry(pid_t pid) { struct stat sb; const char *path = proc_path(pid, NULL); if (stat(path, &sb) == 0) { struct proc_info *info = malloc(sizeof(struct proc_info)); if (info == NULL) return info; info->pid = pid; info->device = sb.st_dev; info->inode = sb.st_ino; info->time.tv_sec = sb.st_ctim.tv_sec; info->time.tv_nsec = sb.st_ctim.tv_nsec; // Make all paths empty info->path1 = NULL; info->path2 = NULL; info->state = STATE_COLLECTING; info->elf_info = 0; return info; } return NULL; } void clear_proc_info(struct proc_info *info) { free(info->path1); free(info->path2); info->path1 = NULL; info->path2 = NULL; } // Returns 0 if equal and 1 if not equal int compare_proc_infos(const struct proc_info *p1, const struct proc_info *p2) { if (p1 == NULL || p2 == NULL) return 1; // Compare in the order to find likely mismatch first if (p1->inode != p2->inode) return 1; if (p1->pid != p2->pid) return 1; if (p1->time.tv_nsec != p2->time.tv_nsec) return 1; if (p1->time.tv_sec != p2->time.tv_sec) return 1; if (p1->device != p2->device) return 1; return 0; } char *get_comm_from_pid(pid_t pid, size_t blen, char *buf) { ssize_t rc; int fd; const char *path = proc_path(pid, "/comm"); fd = open(path, O_RDONLY|O_CLOEXEC); if (fd >= 0) { char *ptr; rc = read(fd, buf, blen); close(fd); if (rc < 0) return NULL; if ((size_t)rc < blen) buf[rc] = 0; else buf[blen-1] = 0; // Trim the newline ptr = strchr(buf, 0x0A); if (ptr) *ptr = 0; } else // FIXME: this should be NULL snprintf(buf, blen-1, "Error-getting-comm(errno=%d,pid=%d)", errno, pid); return buf; } char *get_program_from_pid(pid_t pid, size_t blen, char *buf) { ssize_t path_len; if (blen == 0) return NULL; const char *path = proc_path(pid, "/exe"); path_len = readlink(path, buf, blen - 1); if (path_len <= 0) { if (errno == ENOENT) return get_comm_from_pid(pid, blen, buf); snprintf(buf, blen, "Error-getting-exe(errno=%d,pid=%d)", errno, pid); return buf; } size_t len; if ((size_t)path_len < blen) len = path_len; else len = blen - 1; buf[len] = '\0'; // some binaries can be deleted after execution // then we need to delete the suffix so they are // trusted even after deletion // strlen(" deleted") == 10 if (buf[len-1] == ')' && len > 10) { if (strcmp(&buf[len - 10], " (deleted)") == 0) buf[len - 10] = '\0'; } return buf; } char *get_type_from_pid(pid_t pid, size_t blen, char *buf) { int fd; const char *path = proc_path(pid, "/exe"); fd = open(path, O_RDONLY|O_NOATIME|O_CLOEXEC); if (fd >= 0) { const char *ptr; extern magic_t magic_cookie; struct stat sb; // Most of the time, the process will be ELF. // We can identify it much faster than libmagic. if (fstat(fd, &sb) == 0) { uint32_t elf = gather_elf(fd, sb.st_size); if (elf) { ptr = classify_elf_info(elf, path); close(fd); if (ptr == NULL) return (char *)ptr; return strncpy(buf, ptr, blen-1); } } ptr = magic_descriptor(magic_cookie, fd); close(fd); if (ptr) { char *str; strncpy(buf, ptr, blen); buf[blen-1] = 0; str = strchr(buf, ';'); if (str) *str = 0; } else return NULL; return buf; } return NULL; } uid_t get_program_auid_from_pid(pid_t pid) { ssize_t rc; int fd; const char *path = proc_path(pid, "/loginuid"); fd = open(path, O_RDONLY|O_CLOEXEC); if (fd >= 0) { char buf[16]; uid_t auid; rc = read(fd, buf, sizeof(buf)-1); close(fd); if (rc > 0) { buf[rc] = 0; // manually terminate, read doesn't errno = 0; auid = strtol(buf, NULL, 10); if (errno == 0) return auid; } } return -1; } int get_program_sessionid_from_pid(pid_t pid) { ssize_t rc; int fd; const char *path = proc_path(pid, "/sessionid"); fd = open(path, O_RDONLY|O_CLOEXEC); if (fd >= 0) { char buf[16]; int ses; rc = read(fd, buf, sizeof(buf)-1); close(fd); if (rc > 0) { buf[rc] = 0; // manually terminate, read doesn't errno = 0; ses = strtol(buf, NULL, 10); if (errno == 0) return ses; } } return -1; } pid_t get_program_ppid_from_pid(pid_t pid) { char buf[128]; int ppid = -1; FILE *f; const char *path = proc_path(pid, "/status"); f = fopen(path, "rt"); if (f) { __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, 128, f)) { if (memcmp(buf, "PPid:", 4) == 0) { sscanf(buf, "PPid: %d ", &ppid); break; } } fclose(f); } return ppid; } uid_t get_program_uid_from_pid(pid_t pid) { char buf[128]; int uid = -1; FILE *f; const char *path = proc_path(pid, "/status"); f = fopen(path, "rt"); if (f) { __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, 128, f)) { if (memcmp(buf, "Uid:", 4) == 0) { sscanf(buf, "Uid: %d ", &uid); break; } } fclose(f); } return uid; } attr_sets_entry_t *get_gid_set_from_pid(pid_t pid) { char buf[128]; int gid = -1; FILE *f; attr_sets_entry_t *set = init_standalone_set(INT); if (set) { const char *path = proc_path(pid, "/status"); f = fopen(path, "rt"); if (f) { __fsetlocking(f, FSETLOCKING_BYCALLER); while (fgets(buf, 128, f)) { if (memcmp(buf, "Gid:", 4) == 0) { sscanf(buf, "Gid: %d ", &gid); append_int_attr_set(set, gid); break; } } char *data; int offset; while (fgets(buf, 128, f)) { if (memcmp(buf, "Groups:", 7) == 0) { data = buf + 7; while (sscanf(data," %d%n", &gid, &offset) == 1){ data += offset; append_int_attr_set(set, gid); } break; } } fclose(f); } } return set; } // Returns 0 if environ is clean, 1 if problems, -1 on error int check_environ_from_pid(pid_t pid) { int rc = -1; char *line = NULL; size_t len = 0; FILE *f; const char *path = proc_path(pid, "/environ"); f = fopen(path, "rt"); if (f) { __fsetlocking(f, FSETLOCKING_BYCALLER); while (getline(&line, &len, f) != -1) { char *match = strstr(line, "LD_PRELOAD"); if (!match) match = strstr(line, "LD_AUDIT"); if (match) { rc = 1; break; } } fclose(f); if (rc == -1) rc = 0; free(line); } return rc; } fapolicyd-1.3.4/src/library/process.h000066400000000000000000000062011470754500200175560ustar00rootroot00000000000000/* * process.h - Header file for process.c * Copyright (c) 2016,2019-22 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef PROCESS_HEADER #define PROCESS_HEADER #include #include #include "attr-sets.h" #include "gcc-attributes.h" typedef enum { STATE_COLLECTING=0, // initial state - execute STATE_REOPEN, // anticipating open perm next, always skips the path STATE_DEFAULT_REOPEN, // reopen after dyn. linker exec, never skips the path STATE_STATIC_REOPEN, // static app aniticipating STATE_PARTIAL, // second path collected STATE_STATIC_PARTIAL, // second path collected STATE_FULL, // third path seen - decision time STATE_NORMAL, // normal pattern STATE_NOT_ELF, // not elf, ignore STATE_LD_SO, // app started by ld.so STATE_STATIC, // app is static STATE_BAD_ELF, // app is elf but malformed STATE_LD_PRELOAD // app has LD_PRELOAD or LD_AUDIT set } state_t; // This is used to determine what kind of elf file we are looking at. // HAS_LOAD but no HAS_DYNAMIC is staticly linked app. Normally you see both. #define IS_ELF 0x00001 #define HAS_ERROR 0x00002 // #define HAS_RPATH 0x00004 #define HAS_DYNAMIC 0x00008 #define HAS_LOAD 0x00010 #define HAS_INTERP 0x00020 #define HAS_BAD_INTERP 0x00040 #define HAS_EXEC 0x00080 #define HAS_CORE 0x00100 #define HAS_REL 0x00200 #define HAS_DEBUG 0x00400 #define HAS_RWE_LOAD 0x00800 #define HAS_PHDR 0x01000 #define HAS_EXE_STACK 0x02000 // Information we will cache to identify the same executable struct proc_info { pid_t pid; dev_t device; ino_t inode; struct timespec time; state_t state; char *path1; char *path2; uint32_t elf_info; }; struct proc_info *stat_proc_entry(pid_t pid) __attr_dealloc_free; void clear_proc_info(struct proc_info *info); int compare_proc_infos(const struct proc_info *p1, const struct proc_info *p2); char *get_comm_from_pid(pid_t pid, size_t blen, char *buf) __attr_access ((__write_only__, 3, 2)); char *get_program_from_pid(pid_t pid, size_t blen, char *buf) __attr_access ((__write_only__, 3, 2)); char *get_type_from_pid(pid_t pid, size_t blen, char *buf) __attr_access ((__write_only__, 3, 2)); uid_t get_program_auid_from_pid(pid_t pid); int get_program_sessionid_from_pid(pid_t pid); pid_t get_program_ppid_from_pid(pid_t pid); uid_t get_program_uid_from_pid(pid_t pid); attr_sets_entry_t *get_gid_set_from_pid(pid_t pid); int check_environ_from_pid(pid_t pid); #endif fapolicyd-1.3.4/src/library/queue.c000066400000000000000000000075401470754500200172260ustar00rootroot00000000000000/* * queue.c - a simple queue implementation * Copyright 2016,2018,2022 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "queue.h" #include "message.h" /* Queue implementation */ static unsigned int max_depth; /* Initialize a queue */ struct queue *q_open(size_t num_entries) { struct queue *q; int saved_errno; size_t sz, entry_size = sizeof(struct fanotify_event_metadata); if (num_entries == 0 || num_entries > UINT32_MAX || entry_size < 1 /* for trailing NUL */ /* to allocate "struct queue" including its buffer*/ || entry_size > UINT32_MAX - sizeof(struct queue)) { errno = EINVAL; return NULL; } if (num_entries > SIZE_MAX / sizeof(*q->memory)) { errno = EINVAL; return NULL; } q = malloc(sizeof(*q) + entry_size); if (q == NULL) return NULL; q->memory = NULL; q->num_entries = num_entries; q->entry_size = entry_size; q->queue_head = 0; q->queue_length = 0; max_depth = 0; sz = num_entries * sizeof(*q->memory); q->memory = malloc(sz); if (q->memory == NULL) goto err; memset(q->memory, 0, sz); return q; err: saved_errno = errno; free(q); errno = saved_errno; return NULL; } void q_close(struct queue *q) { if (q->memory != NULL) { size_t i; for (i = 0; i < q->num_entries; i++) free(q->memory[i]); free(q->memory); } msg(LOG_DEBUG, "Inter-thread max queue depth %u", max_depth); free(q); } void q_report(FILE *f) { fprintf(f, "Inter-thread max queue depth: %u\n", max_depth); } /* add DATA to Q */ int q_append(struct queue *q, const struct fanotify_event_metadata *data) { size_t entry_index; unsigned char *copy; if (q->queue_length == q->num_entries) { errno = ENOSPC; return -1; } entry_index = (q->queue_head + q->queue_length) % q->num_entries; if (q->memory != NULL) { if (q->memory[entry_index] != NULL) { errno = EIO; /* This is _really_ unexpected. */ return -1; } copy = malloc(sizeof(struct fanotify_event_metadata)); if (copy == NULL) return -1; memcpy(copy, data, sizeof(struct fanotify_event_metadata)); } else copy = NULL; if (copy != NULL) q->memory[entry_index] = copy; q->queue_length++; if (q->queue_length > max_depth) max_depth = q->queue_length; return 0; } int q_peek(const struct queue *q, struct fanotify_event_metadata *data) { if (q->queue_length == 0) return 0; if (q->memory != NULL && q->memory[q->queue_head] != NULL) { struct fanotify_event_metadata *d = q->memory[q->queue_head]; memcpy(data, d, sizeof(struct fanotify_event_metadata)); return 1; } return 0; } /* drop head of Q */ int q_drop_head(struct queue *q) { if (q->queue_length == 0) { errno = EINVAL; return -1; } if (q->memory != NULL) { free(q->memory[q->queue_head]); q->memory[q->queue_head] = NULL; } q->queue_head++; if (q->queue_head == q->num_entries) q->queue_head = 0; q->queue_length--; return 0; } fapolicyd-1.3.4/src/library/queue.h000066400000000000000000000043011470754500200172230ustar00rootroot00000000000000/* * queue.h -- a queue abstraction * Copyright 2016,2018,2022 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef QUEUE_HEADER #define QUEUE_HEADER #include #include #include #include #include "gcc-attributes.h" struct queue { /* NULL if !Q_IN_MEMORY. [i] contains a memory copy of the queue entry * "i", if known - it may be NULL even if entry exists. */ void **memory; size_t num_entries; size_t entry_size; size_t queue_head; atomic_size_t queue_length; unsigned char buffer[]; /* Used only locally within q_peek() */ }; /* Close Q. */ void q_close(struct queue *q); /* Open a queue for use */ struct queue *q_open(size_t num_entries) __attribute_malloc__ __attr_dealloc (q_close, 1); /* Write out q_depth */ void q_report(FILE *f); /* Add DATA to tail of Q. Return 0 on success, -1 on error and set errno. */ int q_append(struct queue *q, const struct fanotify_event_metadata *data); /* Peek at head of Q, storing it into BUF of SIZE. Return 1 if an entry * exists, 0 if queue is empty. On error, return -1 and set errno. */ int q_peek(const struct queue *q, struct fanotify_event_metadata *data); /* Drop head of Q and return 0. On error, return -1 and set errno. */ int q_drop_head(struct queue *q); /* Return the number of entries in Q. */ static inline size_t q_queue_length(const struct queue *q) { return q->queue_length; } #endif fapolicyd-1.3.4/src/library/rpm-backend.c000066400000000000000000000205721470754500200202650ustar00rootroot00000000000000/* * rpm-backend.c - rpm backend * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "message.h" #include "gcc-attributes.h" #include "fapolicyd-backend.h" #include "llist.h" #include "filter.h" static int rpm_init_backend(void); static int rpm_load_list(const conf_t *); static int rpm_destroy_backend(void); volatile atomic_bool ongoing_rpm_operation = 0; backend rpm_backend = { "rpmdb", rpm_init_backend, rpm_load_list, rpm_destroy_backend, /* list initialization */ { 0, 0, NULL }, }; #ifdef RPM_DB_PATH const char *rpm_dir_path = RPM_DB_PATH; #else const char *rpm_dir_path = "/usr/lib/sysimage/rpm"; #endif ssize_t rpm_dir_path_len = -1; static size_t fd_buffer_size = 0; static size_t fd_buffer_pos = 0; static int *fd_buffer = NULL; #define MIN_BUFFER_SIZE 512 static int init_fd_buffer(void) { struct rlimit limit; getrlimit(RLIMIT_NOFILE, &limit); fd_buffer_size = limit.rlim_cur / 4; if (fd_buffer_size < MIN_BUFFER_SIZE) fd_buffer_size = MIN_BUFFER_SIZE; fd_buffer = malloc(fd_buffer_size * sizeof(int)); if (!fd_buffer) return 1; for(size_t i = 0 ; i < fd_buffer_size; i++) fd_buffer[i] = -1; msg(LOG_DEBUG, "FD buffer size set to: %ld", fd_buffer_size); return 0; } int push_fd_to_buffer(int fd) { if (!fd_buffer) { msg(LOG_ERR, "FD buffer already freed!"); return 1; } if (fd_buffer_pos < fd_buffer_size) { msg(LOG_DEBUG, "Pushing to FD buffer(%ld), ocupancy: %ld", fd_buffer_size, fd_buffer_pos); fd_buffer[fd_buffer_pos++] = fd; return 0; } msg(LOG_ERR, "FD buffer full"); return 1; } static void close_fds_in_buffer(void) { if (fd_buffer_pos) msg(LOG_DEBUG, "Closing FDs from buffer, size: %ld", fd_buffer_pos); for (size_t i = 0 ; i < fd_buffer_pos ; i++) { close(fd_buffer[i]); fd_buffer[i] = -1; } fd_buffer_pos = 0; } static void destroy_fd_buffer(void) { if (!fd_buffer) return; free(fd_buffer); fd_buffer = NULL; fd_buffer_size = -1; } static rpmts ts = NULL; static rpmdbMatchIterator mi = NULL; static int init_rpm(void) { return rpmReadConfigFiles ((const char *)NULL, (const char *)NULL); } static Header h = NULL; static int get_next_package_rpm(void) { // If this is the first time, create a package iterator if (mi == NULL) { ts = rpmtsCreate(); mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0); if (mi == NULL) return 0; } if (h) // Decrement reference count, and free memory headerFree(h); h = rpmdbNextIterator(mi); if (h == NULL) return 0; // No more packages, done // Increment reference count headerLink(h); return 1; } static rpmfi fi = NULL; static int get_next_file_rpm(void) { // If its the first time, make file iterator if (fi == NULL) fi = rpmfiNew(NULL, h, RPMTAG_BASENAMES, RPMFI_KEEPHEADER); if (fi) { if (rpmfiNext(fi) == -1) { // No more files, cleanup iterator rpmfiFree(fi); fi = NULL; return 0; } } return 1; } // Like strdup, but sets a minimum size for safety static inline char *strmdup(const char *s, size_t min) __attr_dealloc_free; static inline char *strmdup(const char *s, size_t min) { char *new; size_t len = strlen(s) + 1; new = malloc(len < min ? min : len); if (new == NULL) return NULL; return (char *)memcpy(new, s, len); } static const char *get_file_name_rpm(void) { return strmdup(rpmfiFN(fi), 7); } static rpm_loff_t get_file_size_rpm(void) { return rpmfiFSize(fi); } static char *get_sha256_rpm(void) { return rpmfiFDigestHex(fi, NULL); } static int is_dir_link_rpm(void) { mode_t mode = rpmfiFMode(fi); if (S_ISDIR(mode) || S_ISLNK(mode)) return 1; return 0; } /* We don't want doc files in the database */ static int is_doc_rpm(void) { if (rpmfiFFlags(fi) & (RPMFILE_DOC|RPMFILE_README| RPMFILE_GHOST|RPMFILE_LICENSE|RPMFILE_PUBKEY)) return 1; return 0; } /* Config files can have a changed hash. We want them in the db since * they are trusted. */ static int is_config_rpm(void) { if (rpmfiFFlags(fi) & (RPMFILE_CONFIG|RPMFILE_MISSINGOK|RPMFILE_NOREPLACE)) return 1; return 0; } static void close_rpm(void) { rpmfiFree(fi); fi = NULL; headerFree(h); h = NULL; rpmdbFreeIterator(mi); mi = NULL; rpmtsFree(ts); ts = NULL; rpmFreeCrypto(); rpmFreeRpmrc(); rpmFreeMacros(NULL); rpmlogClose(); } struct _hash_record { const char * key; UT_hash_handle hh; }; extern unsigned int debug_mode; static int rpm_load_list(const conf_t *conf) { int rc; unsigned int msg_count = 0; unsigned int tsource = SRC_RPM; // empty list before loading list_empty(&rpm_backend.list); // hash table struct _hash_record *hashtable = NULL; ongoing_rpm_operation = 1; msg(LOG_INFO, "Loading rpmdb backend"); if ((rc = init_rpm())) { msg(LOG_ERR, "init_rpm() failed (%d)", rc); return rc; } // Loop across the rpm database while (get_next_package_rpm()) { // Loop across the packages while (get_next_file_rpm()) { // We do not want directories or symlinks in the // database. Multiple packages can own the same // directory and that causes problems in the size info. if (is_dir_link_rpm()) continue; // We do not want any documentation in the database if (is_doc_rpm()) continue; // We do not want any configuration files in database if (is_config_rpm()) continue; // Get specific file information const char *file_name = get_file_name_rpm(); rpm_loff_t sz = get_file_size_rpm(); const char *sha = get_sha256_rpm(); char *data; if (file_name == NULL) continue; // should we drop a path? if (!filter_check(file_name)) { free((void *)file_name); free((void *)sha); continue; } if (strlen(sha) != 64) { // Limit this to 5 if production if (debug_mode || msg_count++ < 5) { msg(LOG_WARNING, "No SHA256 for %s", file_name); } // skip the entry if there is no sha256 if (conf && conf->rpm_sha256_only) { free((void *)file_name); free((void *)sha); continue; } } if (asprintf( &data, DATA_FORMAT, tsource, sz, sha) == -1) { data = NULL; } if (data) { // getting rid of the duplicates struct _hash_record *rcd = NULL; char key[4096]; snprintf(key, 4095, "%s %s", file_name, data); HASH_FIND_STR( hashtable, key, rcd ); if (!rcd) { rcd = (struct _hash_record*) malloc(sizeof(struct _hash_record)); rcd->key = strdup(key); HASH_ADD_KEYPTR( hh, hashtable, rcd->key, strlen(rcd->key), rcd ); list_append(&rpm_backend.list, file_name, data); } else { free((void*)file_name); free((void*)data); } } else { free((void*)file_name); } free((void *)sha); } } close_rpm(); ongoing_rpm_operation = 0; close_fds_in_buffer(); // cleaning up struct _hash_record *item, *tmp; HASH_ITER( hh, hashtable, item, tmp) { HASH_DEL( hashtable, item ); free((void*)item->key); free((void*)item); } return 0; } static int rpm_init_backend(void) { if (rpm_dir_path_len == -1) { rpm_dir_path_len = strlen(rpm_dir_path); if(init_fd_buffer()) { return 1; } } if (filter_init()) return 1; if (filter_load_file()) { filter_destroy(); return 1; } list_init(&rpm_backend.list); return 0; } extern volatile atomic_bool stop; static int rpm_destroy_backend(void) { filter_destroy(); list_empty(&rpm_backend.list); // for sure close_fds_in_buffer(); if (stop) destroy_fd_buffer(); return 0; } fapolicyd-1.3.4/src/library/rules.c000066400000000000000000000742251470754500200172400ustar00rootroot00000000000000/* * rules.c - Minimal linked list set of rules * Copyright (c) 2016,2018,2019-20 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #include "config.h" #include #include #include #include #include #include #include #include "attr-sets.h" #include "policy.h" #include "rules.h" #include "nv.h" #include "message.h" #include "file.h" // This seems wrong #include "database.h" #include "subject-attr.h" #include "object-attr.h" #include "string-util.h" //#define DEBUG #define UNUSED 0xFF // Pattern detection #define SYSTEM_LD_CACHE "/etc/ld.so.cache" #define PATTERN_NORMAL_STR "normal" #define PATTERN_NORMAL_VAL 0 #define PATTERN_LD_SO_STR "ld_so" #define PATTERN_LD_SO_VAL 1 #define PATTERN_STATIC_STR "static" #define PATTERN_STATIC_VAL 2 #define PATTERN_LD_PRELOAD_STR "ld_preload" #define PATTERN_LD_PRELOAD_VAL 3 int rules_create(llist *l) { l->head = NULL; l->cur = NULL; l->cnt = 0; return 0; } void rules_first(llist *l) { l->cur = l->head; } static void rules_last(llist *l) { register lnode* window; if (l->head == NULL) return; window = l->head; while (window->next) window = window->next; l->cur = window; } lnode *rules_next(llist *l) { if (l->cur == NULL) return NULL; l->cur = l->cur->next; return l->cur; } #ifdef DEBUG static void sanity_check_node(lnode *n, const char *id) { unsigned int j, cnt; if (n == NULL) { msg(LOG_DEBUG, "node is NULL"); abort(); } if (n->s_count > MAX_FIELDS) { msg(LOG_DEBUG, "%s - node s_count is out of range %u", id, n->s_count); abort(); } if (n->o_count > MAX_FIELDS) { msg(LOG_DEBUG, "%s - node o_count is out of range %u", id, n->o_count); abort(); } if (n->s_count) { cnt = 0; for (j = 0; j < MAX_FIELDS; j++) { if (n->s[j].type != UNUSED) { cnt++; if (n->s[j].type < SUBJ_START || n->s[j].type > SUBJ_END) { msg(LOG_DEBUG, "%s - subject type is out of range %d", id, n->s[j].type); abort(); } } } if (cnt != n->s_count) { msg(LOG_DEBUG, "%s - subject cnt mismatch %u!=%u", id, cnt, n->s_count); abort(); } } if (n->o_count) { cnt = 0; for (j = 0; j < MAX_FIELDS; j++) { if (n->o[j].type != UNUSED) { cnt++; if (n->o[j].type < OBJ_START || n->o[j].type > OBJ_END) { msg(LOG_DEBUG, "%s - object type is out of range %d", id, n->o[j].type); abort(); } } } if (cnt != n->o_count) { msg(LOG_DEBUG, "%s - object cnt mismatch %u!=%u", id, cnt, n->o_count); abort(); } } } #else #define sanity_check_node(a, b) do {} while(0) #endif #ifdef DEBUG static void sanity_check_list(llist *l, const char *id) { unsigned int i; lnode *n = l->head; if (n == NULL) return; if (l->cnt == 0) { msg(LOG_DEBUG, "%s - zero length cnt found", id); abort(); } i = 1; while (n->next) { if (i == l->cnt) { msg(LOG_DEBUG, "%s - forward loop found %u", id, i); abort(); } sanity_check_node(n, id); i++; n = n->next; } if (i != l->cnt) { msg(LOG_DEBUG, "%s - count mismatch %u!=%u", id, i, l->cnt); abort(); } } #else #define sanity_check_list(a, b) do {} while(0) #endif /* * If subject is trusted function returns true, false otherwise. */ static bool is_subj_trusted(event_t *e) { subject_attr_t *trusted = get_subj_attr(e, SUBJ_TRUST); if (!trusted) return 0; return trusted->val; } /* * If object is trusted function returns true, false otherwise. */ static bool is_obj_trusted(event_t *e) { object_attr_t *trusted = get_obj_attr(e, OBJ_TRUST); if (!trusted) return 0; return trusted->val; } /* * It takes something like "% set " and it returns "set" */ static char * parse_set_name(char * buf) { // replace % with space buf[0] = ' '; char * name = fapolicyd_strtrim(buf); if (!name) return NULL; // little validation for (int i = 0 ; name[i] ; i++) { if (!(isalnum(name[i]) || name[i] == '_' )) { return NULL; } } return buf; } #define GROUP_NAME_SIZE 64 static int assign_subject(lnode *n, int type, const char *ptr2, int lineno) { size_t index = 0; // assign the subject unsigned int i = n->s_count; sanity_check_node(n, "assign_subject - 1"); n->s[i].type = type; char *ptr, *saved, *tmp = strdup(ptr2); if (tmp == NULL) { msg(LOG_ERR, "memory allocation error in line %d", lineno); return 1; } // use already defined set if (tmp[0] == '%') { char * defined_set = parse_set_name(tmp); if (!defined_set) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "cannot obtain set name from \'%s\'", lineno, tmp); goto free_and_error; } index = search_attr_set_by_name(defined_set); if (!index) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "set \'%s\' was not defined before", lineno, defined_set); goto free_and_error; } attr_sets_entry_t * set = get_attr_set(index); if (!set) goto free_and_error; // we cannot assign any set to these attributes if (type == SUBJ_TRUST || type == PATTERN) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "cannot assign any set to %s", lineno, subj_val_to_name(type, RULE_FMT_COLON)); goto free_and_error; } // numbers if (type <= PPID && set->type != INT ) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "cannot assign %%%s which has STRING type to INT", lineno, defined_set); goto free_and_error; } // strings if (type >= COMM && set->type != STRING) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "cannot assign %%%s which has STRING type to INT", lineno, defined_set); goto free_and_error; } n->s[i].gr_index = index; goto finalize; } // for debug output char name[GROUP_NAME_SIZE]; memset(name, 0, GROUP_NAME_SIZE); snprintf(name, GROUP_NAME_SIZE-1, "_rule-line-%d-subj-%s", lineno, subj_val_to_name(type, RULE_FMT_COLON)); switch(n->s[i].type) { case ALL_SUBJ: break; // numbers -> multiple value case AUID: case UID: case SESSIONID: case GID: case PID: case PPID: { if (add_attr_set(name, INT, &index)) { goto free_and_error; } attr_sets_entry_t * set = get_attr_set(index); if (!set) goto free_and_error; ptr = strtok_r(tmp, ",", &saved); while (ptr) { // starts with a digit or minus sign if (isdigit(*ptr) || *ptr == '-') { errno = 0; long val = strtol(ptr, NULL, 10); if (errno) { msg(LOG_ERR, "Error converting val (%s) in line %d", ptr, lineno); goto free_and_error; } else if (append_int_attr_set(set, (int)val)) { goto free_and_error; } // Support names for auid and uid entries } else if (n->s[i].type == AUID || n->s[i].type == UID) { struct passwd *pw = getpwnam(ptr); if (pw == NULL) { msg(LOG_ERR, "user %s is unknown", ptr); goto free_and_error; } int val = pw->pw_uid; endpwent(); if (append_int_attr_set(set, val)) goto free_and_error; } else if (n->s[i].type == GID) { struct group *gr = getgrnam(ptr); if (gr == NULL) { msg(LOG_ERR, "group %s is unknown", ptr); goto free_and_error; } int val = gr->gr_gid; endgrent(); if (append_int_attr_set(set, val)) goto free_and_error; } ptr = strtok_r(NULL, ",", &saved); } n->s[i].gr_index = index; break; } // case // single value exception case PATTERN: { if (strchr(tmp, ',')) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "pattern can handle only single value", lineno); goto free_and_error; } if (strcmp(tmp, PATTERN_LD_SO_STR) == 0) { n->s[i].val = PATTERN_LD_SO_VAL; } else if (strcmp(tmp, PATTERN_STATIC_STR) == 0) { n->s[i].val = PATTERN_STATIC_VAL; } else if (strcmp(tmp, PATTERN_LD_PRELOAD_STR) == 0) { n->s[i].val = PATTERN_LD_PRELOAD_VAL; } else { msg(LOG_ERR, "Unknown pattern value %s in line %d", tmp, lineno); goto free_and_error; } break; } // case // single value exception case SUBJ_TRUST: { if (strchr(tmp, ',')) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "trust can handle only single value", lineno); goto free_and_error; } errno = 0; long val = strtol(tmp, NULL, 10); if (errno) { msg(LOG_ERR, "Error converting val (%s) in line %d", tmp, lineno); goto free_and_error; } else { if (val != 1 && val != 0) { msg(LOG_ERR, "rules: line:%d: assign_subject: " "trust can be set to 1 or 0", lineno); goto free_and_error; } n->s[i].val = val; } break; } // case // regular strings -> multiple value case COMM: case EXE: case EXE_DIR: case EXE_TYPE: case EXE_DEVICE: { if (add_attr_set(name, STRING, &index)) { goto free_and_error; } attr_sets_entry_t * set = get_attr_set(index); if (!set) goto free_and_error; ptr = strtok_r(tmp, ",", &saved); while (ptr) { append_str_attr_set(set, ptr); ptr = strtok_r(NULL, ",", &saved); } n->s[i].gr_index = index; break; } // case // should not happen default: { msg(LOG_ERR, "assign_subject: fatal error " "-> this should not happen!"); goto free_and_error; } // case } // switch finalize: n->s_count++; free(tmp); sanity_check_node(n, "assign_subject - 2"); return 0; free_and_error: free(tmp); return 1; } static int assign_object(lnode *n, int type, const char *ptr2, int lineno) { // assign the object unsigned int i = n->o_count; size_t index = 0; sanity_check_node(n, "assign_object - 1"); n->o[i].type = type; char *ptr, *saved, *tmp = strdup(ptr2); if (tmp == NULL) { msg(LOG_ERR, "memory allocation error in line %d", lineno); return 1; } // use already defined set if (tmp[0] == '%') { char * defined_set = parse_set_name(tmp); if (!defined_set) { msg(LOG_ERR, "rules: line:%d: assign_object: " "cannot obtain set name from \'%s\'", lineno, tmp); goto free_and_error; } index = search_attr_set_by_name(defined_set); if (!index) { msg(LOG_ERR, "rules: line:%d: assign_object: " "set \'%s\' was not defined before", lineno, defined_set); goto free_and_error; } attr_sets_entry_t * set = get_attr_set(index); if (!set) goto free_and_error; // we cannot assign any set to these attributes if (type == OBJ_TRUST) { msg(LOG_ERR, "rules: line:%d: assign_object: " "cannot assign any set to %s", lineno, obj_val_to_name(type)); goto free_and_error; } // strings if (set->type != STRING) { msg(LOG_ERR, "rules: line:%d: assign_object: " "cannot assign INT set %s to the STRING attribute", lineno, defined_set); goto free_and_error; } n->o[i].gr_index = index; goto finalize; } // for debug output char name[GROUP_NAME_SIZE]; memset(name, 0, GROUP_NAME_SIZE); snprintf(name, GROUP_NAME_SIZE-1, "_rule-line-%d-obj-%s", lineno, obj_val_to_name(type)); switch(n->o[i].type) { case ALL_OBJ: break; case OBJ_TRUST: { if (strchr(tmp, ',')) { msg(LOG_ERR, "rules: line:%d: assign_object: " "trust can handle only single value", lineno); goto free_and_error; } errno = 0; long val = strtol(tmp, NULL, 10); if (errno) { msg(LOG_ERR, "Error converting val (%s) in line %d", tmp, lineno); goto free_and_error; } else { if (val != 1 && val != 0) { msg(LOG_ERR, "rules: line:%d: assign_object: " "trust can be set to 1 or 0", lineno); goto free_and_error; } n->o[i].val = val; } break; } // case case ODIR: case PATH: case DEVICE: case FTYPE: case SHA256HASH: case FMODE: { if (add_attr_set(name, STRING, &index)) { goto free_and_error; } attr_sets_entry_t * set = get_attr_set(index); if (!set) goto free_and_error; ptr = strtok_r(tmp, ",", &saved); while (ptr) { append_str_attr_set(set, ptr); ptr = strtok_r(NULL, ",", &saved); } n->o[i].gr_index = index; break; } // case // should not happen default: { msg(LOG_ERR, "assign_object: fatal error " "-> this should not happen!"); goto free_and_error; } // case } // switch finalize: n->o_count++; free(tmp); sanity_check_node(n, "assign_object - 2"); return 0; free_and_error: free(tmp); return 1; } static int parse_new_format(lnode *n, int lineno) { int state = 0; // 0 == subj, 1 == obj char *ptr; while ((ptr = strtok(NULL, " "))) { int type; char *ptr2 = strchr(ptr, '='); if (ptr2) { *ptr2 = 0; ptr2++; if (state == 0) { type = subj_name_to_val(ptr, 2); if (type == -1) { msg(LOG_ERR, "Field type (%s) is unknown in line %d", ptr, lineno); return 1; } if (assign_subject(n, type, ptr2, lineno) == 3) return -1; } else { type = obj_name_to_val(ptr); if (type == -1) { msg(LOG_ERR, "Field type (%s) is unknown in line %d", ptr, lineno); return 2; } else assign_object(n, type, ptr2, lineno); } } else if (state == 0 && strcmp(ptr, ":") == 0) state = 1; else if (strcmp(ptr, "all") == 0) { if (state == 0) { type = ALL_SUBJ; assign_subject(n, type, "", lineno); } else { type = ALL_OBJ; assign_object(n, type, "", lineno); } } else { msg(LOG_ERR, "'=' is missing for field %s, in line %d", ptr, lineno); return 5; } } return 0; } static int parse_set_line(const char * line, int lineno) { if (!line) return -1; // for sure char * l = strdup(line); if (!l) { return 1; } char * sep = strchr(l, '='); if (!sep) { msg(LOG_ERR, "rules.conf:%d: parse_set_line: " "Cannot parse line, no separator \"=\"", lineno); goto free_and_error; } else { *sep = '\0'; } char * name = parse_set_name(l); if (!name) { msg(LOG_ERR, "rules.conf:%d: parse_set_line: " "Cannot parse name of the set", lineno); goto free_and_error; } size_t index = 0; index = search_attr_set_by_name(name); // looking for non zero result if (index) { msg(LOG_ERR, "rules.conf:%d: parse_set_line: " "set %s was already defined!", lineno, name); goto free_and_error; } char *ptr, *saved, *tmp = sep + 1; int type = STRING; if (isdigit(tmp[0])) type = INT; if (add_attr_set(name, type, &index)) { goto free_and_error; } attr_sets_entry_t * set = get_attr_set(index); if (!set) goto free_and_error; ptr = strtok_r(tmp, ",", &saved); while (ptr) { if (type == STRING) { if (append_str_attr_set(set, ptr)) goto free_and_error; } else if (type == INT) { errno = 0; long val = strtol(ptr, NULL, 10); if (errno) { msg(LOG_ERR, "Error converting val (%s) in line %d", ptr, lineno); goto free_and_error; } else if (append_int_attr_set(set, (int)val)) goto free_and_error; } ptr = strtok_r(NULL, ",", &saved); } free(l); return -1; free_and_error: free(l); return 1; } /* * This function take a whole rule as input and parses it up. * Returns: -1 nothing, 0 OK, >0 error */ static int nv_split(char *buf, lnode *n, int lineno) { char *ptr, *ptr2; rformat_t format = RULE_FMT_ORIG; if (strchr(buf, ':')) format = RULE_FMT_COLON; n->format = format; ptr = strtok(buf, " "); if (ptr == NULL) return -1; /* If there's nothing, go to next line */ if (ptr[0] == '#') return -1; /* If there's a comment, go to next line */ if (ptr[0] == '%') return parse_set_line(ptr, lineno); // Load decision n->d = dec_name_to_val(ptr); if ((int)n->d == -1) { msg(LOG_ERR, "Invalid decision (%s) in line %d", ptr, lineno); return 1; } // Default access permission is open n->a = OPEN_ACC; while ((ptr = strtok(NULL, " "))) { int type; ptr2 = strchr(ptr, '='); if (ptr2) { *ptr2 = 0; ptr2++; if (format == RULE_FMT_COLON) { if (strcmp(ptr, "perm") == 0) { if (strcmp(ptr2, "execute") == 0) n->a = EXEC_ACC; else if (strcmp(ptr2, "any") == 0) n->a = ANY_ACC; else if (strcmp(ptr2, "open")) { msg(LOG_ERR, "Access permission (%s) is unknown in line %d", ptr2, lineno); return 2; } } else { type = subj_name_to_val(ptr, 2); if (type == -1) { msg(LOG_ERR, "Field type (%s) is unknown in line %d", ptr, lineno); return 1; } if (assign_subject(n, type, ptr2, lineno) == 3) return -1; } parse_new_format(n, lineno); goto finish_up; } type = subj_name_to_val(ptr, format); if (type == -1) { type = obj_name_to_val(ptr); if (type == -1) { msg(LOG_ERR, "Field type (%s) is unknown in line %d", ptr, lineno); return 3; } else assign_object(n, type, ptr2, lineno); } else if (assign_subject(n, type, ptr2, lineno) == 3) return -1; } else if (strcmp(ptr, "all") == 0) { if (n->s_count == 0) { type = ALL_SUBJ; assign_subject(n, type, "", lineno); } else if (n->o_count == 0) { type = ALL_OBJ; assign_object(n, type, "", lineno); } else { msg(LOG_ERR, "All can only be used in place of a subject or object"); return 4; } } else { msg(LOG_ERR, "'=' is missing for field %s, in line %d", ptr, lineno); return 5; } } finish_up: // do one last sanity check for missing subj or obj if (n->s_count == 0) { msg(LOG_ERR, "Subject is missing in line %d", lineno); return 6; } if (n->o_count == 0) { msg(LOG_ERR, "Object is missing in line %d", lineno); return 7; } return 0; } // This function take a whole rule as input and passes it to nv_split. // Returns 0 if success and 1 on rule failure. int rules_append(llist *l, char *buf, unsigned int lineno) { lnode* newnode; sanity_check_list(l, "rules_append - 1"); if (buf) { // parse up the rule unsigned int i; newnode = malloc(sizeof(lnode)); if (newnode == NULL) return 1; memset(newnode, 0, sizeof(lnode)); newnode->s_count = 0; newnode->o_count = 0; for (i=0; is[i].type = UNUSED; newnode->o[i].type = UNUSED; } int rc = nv_split(buf, newnode, lineno); if (rc) { free(newnode); if (rc < 0) return 0; else return 1; } } else return 1; newnode->next = NULL; rules_last(l); // if we are at top, fix this up if (l->head == NULL) l->head = newnode; else // Otherwise add pointer to newnode l->cur->next = newnode; // make newnode current l->cur = newnode; newnode->num = l->cnt; l->cnt++; sanity_check_list(l, "rules_append - 2"); return 0; } // In this table, the number is string length static const nv_t dirs[] = { { 5, "/etc/"}, { 5, "/usr/"}, { 5, "/bin/"}, { 6, "/sbin/"}, { 5, "/lib/"}, { 7, "/lib64/"}, {13, "/usr/libexec/"} }; #define NUM_DIRS (sizeof(dirs)/sizeof(dirs[0])) // Returns 0 if no match, 1 if a match static int check_dirs(unsigned int i, const char *path) { // Iterate across the lists looking for a match. // If we match, stop iterating and return a decision. for (; i < NUM_DIRS; i++) { // Check to see if we even care about this path if (strncmp(path, dirs[i].name, dirs[i].value) == 0) return 1; } return 0; } /* * Notes about elf program startup * =============================== * The run time linker will do the folowing: * 1) kernel loads executable * 2) kernel attaches ld-2.2x.so to executable memory and turns over execution * 3) rtl loads LD_AUDIT libs * 4) rtl loads LD_PRELOAD libs * 5) rtl next loads /etc/ld.so.preload libs * * Then for each dependency: * Call into LD_AUDIT la_objsearch() to modify path/name and try * 1) RPATH in object * 2) RPATH in executable * 3) LD_LIBRARY_PATH: for each path, iterate permutations of * tls, x86_64, haswell, & plain path * 4) RUNPATH in object * 5) Try the name as found in the object * 6) Consult /etc/ld.so.cache * 7) Try default path (can't find where string table is) * * LD_AUDIT modules can add arbitrary early file system actions because * the may also call open. They can also trigger loading another copy of * libc.so.6. * * Patterns * ======== * Normal: * exe=/usr/bin/bash file=/usr/bin/ls * exe=/usr/bin/bash file=/usr/lib64/ld-2.27.so * exe=/usr/bin/ls file=/etc/ld.so.cache * exe=/usr/bin/ls file=/usr/lib64/libselinux.so.1 * * runtime linker started: * exe=/usr/bin/bash file=/usr/lib64/ld-2.27.so * exe=/usr/bin/bash file=/usr/bin/ls * exe=/usr/lib64/ld-2.27.so file=/etc/ld.so.cache * exe=/usr/lib64/ld-2.27.so file=/usr/lib64/libselinux.so.1 * * LD_PRELOAD=libaudit no LD_LIBRARY_PATH: * exe=/usr/bin/bash file=/usr/bin/ls * exe=/usr/bin/bash file=/usr/lib64/ld-2.27.so * exe=/usr/bin/ls file=/usr/lib64/libaudit.so.1.0.0 * exe=/usr/bin/ls file=/etc/ld.so.cache * exe=/usr/bin/ls file=/usr/lib64/libselinux.so.1 * * LD_PRELOAD=libaudit with LD_LIBRARY_PATH: * exe=/usr/bin/bash file=/usr/bin/ls * exe=/usr/bin/bash file=/usr/lib64/ld-2.28.so * exe=/usr/bin/ls file=/usr/lib64/libaudit.so.1.0.0 * exe=/usr/bin/ls file=/usr/lib64/libselinux.so.1 * * /etc/ld.so.preload: * exe=/usr/bin/bash file=/usr/bin/ls * exe=/usr/bin/bash file=/usr/lib64/ld-2.27.so * exe=/usr/bin/ls file=/etc/ld.so.preload * exe=/usr/bin/ls file=/usr/lib64/libaudit.so.1.0.0 * * This means only first two can be counted on. Looking for ld.so.cache * is no good because its almost the last option. */ // Returns 0 if no match, 1 if a match, -1 on error static int subj_pattern_test(const subject_attr_t *s, event_t *e) { int rc = 0; struct proc_info *pinfo = e->s->info; // At this point, we have only 1 or 2 paths. if (pinfo->state < STATE_FULL) { // if it's not an elf file, we're done if (pinfo->elf_info == 0) { pinfo->state = STATE_NOT_ELF; clear_proc_info(pinfo); } // If its a static, make a decision. EXEC_PERM will cause // a follow up open request. We change state here and will // go all the way to static on the open request. else if ((pinfo->elf_info & IS_ELF) && (pinfo->state == STATE_COLLECTING) && ((pinfo->elf_info & HAS_DYNAMIC) == 0)) { pinfo->state = STATE_STATIC_REOPEN; goto make_decision; } else if (pinfo->state == STATE_STATIC_PARTIAL) goto make_decision; else if ((e->type & FAN_OPEN_EXEC_PERM) && pinfo->path1 && strcmp(pinfo->path1, SYSTEM_LD_SO) == 0) { pinfo->state = STATE_LD_SO; goto make_decision; } // otherwise, we don't have enough info for a decision return rc; } // Do the analysis if (pinfo->state == STATE_FULL) { if (pinfo->elf_info & HAS_ERROR) { pinfo->state = STATE_BAD_ELF; clear_proc_info(pinfo); return -1; } // Pattern detection is only static or not, ld.so started or // not. That means everything else is normal. if (strcmp(pinfo->path1, SYSTEM_LD_SO) == 0) // First thing is ld.so when its used - detected above pinfo->state = STATE_LD_SO; else // To get here, pgm matched path1 pinfo->state = STATE_NORMAL; } // Make a decision make_decision: switch (s->val) { case PATTERN_NORMAL_VAL: if (pinfo->state == STATE_NORMAL) rc = 1; break; case PATTERN_LD_SO_VAL: if (pinfo->state == STATE_LD_SO) rc = 1; break; case PATTERN_STATIC_VAL: if ((pinfo->state == STATE_STATIC_REOPEN) || (pinfo->state == STATE_STATIC_PARTIAL) || (pinfo->state == STATE_STATIC)) rc = 1; break; case PATTERN_LD_PRELOAD_VAL: { int env = check_environ_from_pid(pinfo->pid); if (env == 1) { pinfo->state = STATE_LD_PRELOAD; rc = 1; } } break; } // Done with the paths clear_proc_info(pinfo); return rc; } // Returns 0 if no match, 1 if a match static int check_access(const lnode *r, const event_t *e) { access_t perm; if (r->a == ANY_ACC) return 1; if (e->type & FAN_OPEN_EXEC_PERM) perm = EXEC_ACC; else perm = OPEN_ACC; return r->a == perm; } // Returns 0 if no match, 1 if a match, -1 on error static int check_subject(lnode *r, event_t *e) { unsigned int cnt = 0; sanity_check_node(r, "check_subject"); while (cnt < r->s_count) { unsigned int type = r->s[cnt].type; subject_attr_t *subj = NULL; // optimize get_subj_attr call if possible if (type == ALL_SUBJ) { cnt++; continue; } else { subj = get_subj_attr(e, type); } if (subj == NULL && type != PATTERN) { cnt++; continue; } switch(type) { // numbers -> multiple value case AUID: case UID: case SESSIONID: case PID: case PPID: { if (!check_int_attr_set(r->s[cnt].set, subj->val)) return 0; break; } // case // GID is unique in that process can have multiple and // rules can have multiple case GID: if (!avl_intersection(&(r->s[cnt].set->tree), &(subj->set->tree))) return 0; break; // single value exception case PATTERN: { int rc = subj_pattern_test(&(r->s[cnt]), e); if (rc == 0) return 0; // If there was an error, consider it // a match since deny is likely if (rc == -1) return 1; break; } // case // single value exception case SUBJ_TRUST: { if (subj->val != r->s[cnt].val) return 0; break; } // case // regular strings -> multiple value // simple presence check case EXE: { if (check_str_attr_set(r->s[cnt].set, "untrusted")) if (is_subj_trusted(e)) return 0; } // case // fall through case COMM: case EXE_TYPE: case EXE_DEVICE: { if (!subj->str) { break; } if (!check_str_attr_set(r->s[cnt].set, subj->str)) return 0; break; } // case case EXE_DIR: { if (!subj->str) { break; } if (check_str_attr_set(r->s[cnt].set, "execdirs")) if (check_dirs(1, subj->str)) return 0; if (check_str_attr_set(r->s[cnt].set, "systemdirs")) if (check_dirs(0, subj->str)) return 0; // DEPRECATED if (check_str_attr_set(r->s[cnt].set, "untrusted")) if (is_subj_trusted(e)) return 0; // check partial match (via strncmp) // subdir test if (!check_pstr_attr_set(r->s[cnt].set, subj->str)) return 0; break; } // case default: return -1; } // switch cnt++; } return 1; } // Returns 0 if no match, 1 if a match static decision_t check_object(lnode *r, event_t *e) { unsigned int cnt = 0; sanity_check_node(r, "check_object"); while (cnt < r->o_count) { unsigned int type = r->o[cnt].type; object_attr_t *obj = NULL; // optimize get_obj_attr call if possible if (type == ALL_OBJ) { cnt++; continue; } else { obj = get_obj_attr(e, type); } if (obj == NULL) { cnt++; continue; } switch(type) { case OBJ_TRUST: { // obj->val holds (0|1) as int if (obj->val != r->o[cnt].val) return 0; break; } // case case FTYPE: { if (check_str_attr_set(r->o[cnt].set, "any")) break; } // fall through case PATH: // skip if fall through if (type == PATH) { if (r->s[cnt].type == EXE || r->s[cnt].type == EXE_DIR) if (check_str_attr_set(r->s[cnt].set, "untrusted")) if (is_obj_trusted(e)) return 0; } // fall through case DEVICE: case SHA256HASH: case FMODE: { if (!obj->o) { // Treat errors as a denial for sha256 if (type == SHA256HASH) return 0; break; } if (!check_str_attr_set(r->o[cnt].set, obj->o)) return 0; break; } // case case ODIR: { if (!obj->o) { break; } if (check_str_attr_set(r->o[cnt].set, "execdirs")) if (check_dirs(1, obj->o)) return 0; if (check_str_attr_set(r->o[cnt].set, "systemdirs")) if (check_dirs(0, obj->o)) return 0; // DEPRECATED if (check_str_attr_set(r->o[cnt].set, "untrusted")) if (is_obj_trusted(e)) return 0; // check partial match (via strncmp) // subdir test if (!check_pstr_attr_set(r->o[cnt].set, obj->o)) return 0; break; } // case // should not happen default: { return -1; } // case } // switch cnt++; } return 1; } decision_t rule_evaluate(lnode *r, event_t *e) { int d; // Check access permission d = check_access(r, e); if (d == 0) // No match return NO_OPINION; // Check the subject d = check_subject(r, e); if (d == 0) // No match return NO_OPINION; // Check the object d = check_object(r, e); if (d == 0) // No match return NO_OPINION; return r->d; } void rules_unsupport_audit(const llist *l) { #ifdef USE_AUDIT register lnode *current = l->head; int warn = 0; while (current) { if (current->d & AUDIT) warn = 1; current->d &= ~AUDIT; current=current->next; } if (warn) { msg(LOG_WARNING, "Rules with audit events are not supported by the kernel"); msg(LOG_NOTICE, "Converting rules to non-audit rules"); } #endif } // Function iterates over rules and theirs subjects and objects // and it is setting direct pointer to set instead of // set index. // Rules are created with set index because internal set // array structure can be reallocated when it grows. // We can safely call this function once right after // rules.conf was processed. void rules_regen_sets(llist *l) { lnode *nextnode; lnode *current = l->head; while (current) { unsigned int i; nextnode=current->next; i = 0; while (i < current->s_count) { int type = current->s[i].type; if (type == ALL_SUBJ || type == SUBJ_TRUST || type == PATTERN) { i++; continue; } size_t index = current->s[i].gr_index; attr_sets_entry_t * set = get_attr_set(index); if (!set) { i++; continue; } current->s[i].set = set; i++; } i = 0; while (i < current->o_count) { int type = current->o[i].type; if (type == ALL_OBJ || type == OBJ_TRUST) { i++; continue; } size_t index = current->o[i].gr_index; attr_sets_entry_t * set = get_attr_set(index); if (!set) { i++; continue; } current->o[i].set = set; i++; } current=nextnode; } } void rules_clear(llist *l) { lnode *nextnode; register lnode *current = l->head; while (current) { nextnode=current->next; free(current); current=nextnode; } l->head = NULL; l->cur = NULL; l->cnt = 0; } fapolicyd-1.3.4/src/library/rules.h000066400000000000000000000037251470754500200172420ustar00rootroot00000000000000/* * rules.h - Header file for rules.c * Copyright (c) 2016-17,2019 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef RULES_HEADER #define RULES_HEADER #include "policy.h" #include "subject-attr.h" #include "object-attr.h" #include "event.h" #define MAX_FIELDS 8 /* This is one node of the linked list. Any data elements that are per * rule goes here. */ typedef struct _lnode{ decision_t d; access_t a; unsigned int num; rformat_t format; unsigned int s_count; unsigned int o_count; subject_attr_t s[MAX_FIELDS]; object_attr_t o[MAX_FIELDS]; struct _lnode *next; // Next node pointer } lnode; /* This is the linked list head. Only data elements that are 1 per * event goes here. */ typedef struct { lnode *head; // List head lnode *cur; // Pointer to current node unsigned int cnt; // How many items in this list } llist; int rules_create(llist *l); void rules_first(llist *l); lnode *rules_next(llist *l); static inline lnode *rules_get_cur(const llist *l) { return l->cur; } int rules_append(llist *l, char *buf, unsigned int lineno); decision_t rule_evaluate(lnode *r, event_t *e); void rules_unsupport_audit(const llist *l); void rules_regen_sets(llist* l); void rules_clear(llist* l); #endif fapolicyd-1.3.4/src/library/stack.c000066400000000000000000000036451470754500200172110ustar00rootroot00000000000000/* * stack.c - generic stack impementation * Copyright (c) 2023 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #include "stack.h" #include // init of the stack struct void stack_init(stack_t *_stack) { if (_stack == NULL) return; list_init(_stack); } // free all the resources from the stack void stack_destroy(stack_t *_stack) { if (_stack == NULL) return; list_empty(_stack); } // push to the top of the stack void stack_push(stack_t *_stack, void *_data) { if (_stack == NULL) return; list_prepend(_stack, NULL, (void *)_data); } // pop the the top without returning what was on the top void stack_pop(stack_t *_stack) { if (_stack == NULL) return; list_item_t *first = _stack->first; _stack->first = first->next; first->data = NULL; list_destroy_item(&first); _stack->count--; return; } // function returns 1 if stack is emtpy 0 if it's not int stack_is_empty(stack_t *_stack) { if (_stack == NULL) return -1; if (_stack->count == 0) return 1; return 0; } // return top of the stack without popping const void *stack_top(stack_t *_stack) { if (_stack == NULL) return NULL; return _stack->first ? _stack->first->data : NULL; } fapolicyd-1.3.4/src/library/stack.h000066400000000000000000000023071470754500200172100ustar00rootroot00000000000000/* * stack.h - header for generic stack implementation * Copyright (c) 2023 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka */ #ifndef STACK_H_ #define STACK_H_ #include "llist.h" typedef list_t stack_t; void stack_init(stack_t *_stack); void stack_destroy(stack_t *_stack); void stack_push(stack_t *_stack, void *_data); void stack_pop(stack_t *_stack); int stack_is_empty(stack_t *_stack); const void *stack_top(stack_t *_stack); #endif // STACK_H_ fapolicyd-1.3.4/src/library/string-util.c000066400000000000000000000031641470754500200203610ustar00rootroot00000000000000/* * string-util.c - useful string functions * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka * Zoltan Fridrich */ #include #include #include #include #include "string-util.h" char *fapolicyd_strtrim(char *s) { char *cp1; char *cp2; if (!s) return NULL; // skip leading spaces, via cp1 for (cp1=s; isspace(*cp1); cp1++ ); // shift left remaining chars, via cp2 for (cp2=s; *cp1; cp1++, cp2++) *cp2 = *cp1; // mark new end of string for s *cp2-- = 0; // replace trailing spaces with '\0' while ( cp2 > s && isspace(*cp2) ) *cp2-- = 0; return s; } char *fapolicyd_strcat(const char *s1, const char *s2) { size_t s1_len = strlen(s1); size_t s2_len = strlen(s2); char *r = malloc(s1_len + s2_len + 1); if (r == NULL) return NULL; strcpy(r, s1); strcat(r, s2); return r; } fapolicyd-1.3.4/src/library/string-util.h000066400000000000000000000024531470754500200203660ustar00rootroot00000000000000/* * string-util.h - Header file for string-util * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Radovan Sroka * Zoltan Fridrich */ #ifndef STRING_UTIL_H #define STRING_UTIL_H #include "gcc-attributes.h" char *fapolicyd_strtrim(char *s); /** * Concatenates two NULL terminated strings * * @param s1 First NULL terminated string * @param s2 Second NULL terminated string * @return Dynamically allocated NULL terminated string s1||s2 */ char *fapolicyd_strcat(const char *s1, const char *s2) __attr_dealloc_free; #endif fapolicyd-1.3.4/src/library/subject-attr.c000066400000000000000000000052521470754500200205070ustar00rootroot00000000000000/* * subject-attr.c - functions to abstract subject attributes * Copyright (c) 2016,2019,2022 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb * Radovan Sroka */ #include "config.h" #include // For NULL #include #include "subject-attr.h" /* * Table1 is used for looking up rule fields written in the old format. * Do not add anything new here. */ static const nv_t table1[] = { { ALL_SUBJ, "all" }, { AUID, "auid" }, { UID, "uid" }, { SESSIONID, "sessionid" }, { PID, "pid" }, { PATTERN, "pattern" }, { COMM, "comm" }, { EXE, "exe" }, { EXE_DIR, "exe_dir" }, { EXE_TYPE, "exe_type" }, { EXE_DEVICE, "exe_device" }, }; #define MAX_SUBJECTS1 (sizeof(table1)/sizeof(table1[0])) /* * Table2 is used for looking up rule fields written in the new format */ static const nv_t table2[] = { { ALL_SUBJ, "all" }, { AUID, "auid" }, { UID, "uid" }, { SESSIONID, "sessionid" }, { PID, "pid" }, { PPID, "ppid" }, { PATTERN, "pattern" }, { SUBJ_TRUST, "trust" }, { GID, "gid" }, { COMM, "comm" }, { EXE, "exe" }, { EXE_DIR, "dir" }, { EXE_TYPE, "ftype" }, { EXE_DEVICE, "device" }, }; #define MAX_SUBJECTS2 (sizeof(table2)/sizeof(table2[0])) int subj_name_to_val(const char *name, rformat_t format) { unsigned int i = 0; if (format == RULE_FMT_ORIG) { while (i < MAX_SUBJECTS1) { if (strcmp(name, table1[i].name) == 0) return table1[i].value; i++; } } else { while (i < MAX_SUBJECTS2) { if (strcmp(name, table2[i].name) == 0) return table2[i].value; i++; } } return -1; } const char *subj_val_to_name(unsigned int v, rformat_t format) { if (v > SUBJ_END) return NULL; unsigned int index = v - SUBJ_START; if (format == RULE_FMT_ORIG) { if (index < MAX_SUBJECTS1) return table1[index].name; } else { if (index < MAX_SUBJECTS2) return table2[index].name; } return NULL; } fapolicyd-1.3.4/src/library/subject-attr.h000066400000000000000000000030221470754500200205050ustar00rootroot00000000000000/* * subject-attr.h - Header file for subject-attr.c * Copyright (c) 2016,2019-20,2022 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef SUBJECT_ATTR_HEADER #define SUBJECT_ATTR_HEADER #include #include "nv.h" #include "fapolicyd-defs.h" #include "attr-sets.h" // Top is numbers, bottom is strings typedef enum { ALL_SUBJ = SUBJ_START, AUID, UID, SESSIONID, PID, PPID, PATTERN, SUBJ_TRUST, GID, COMM, EXE, EXE_DIR, EXE_TYPE, EXE_DEVICE } subject_type_t; #define SUBJ_END EXE_DEVICE typedef struct s { subject_type_t type; union { int val; char *str; size_t gr_index; attr_sets_entry_t * set; }; } subject_attr_t; int subj_name_to_val(const char *name, rformat_t format); const char * subj_val_to_name(unsigned v, rformat_t format); #endif fapolicyd-1.3.4/src/library/subject.c000066400000000000000000000101011470754500200175240ustar00rootroot00000000000000/* * subject.c - Minimal linked list set of subject attributes * Copyright (c) 2016 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include "policy.h" #include "subject.h" #include "message.h" //#define DEBUG void subject_create(s_array *a) { int i; a->subj = malloc(sizeof(subject_attr_t *) * ((SUBJ_END-SUBJ_START)+1)); for (i = 0; i < SUBJ_END - SUBJ_START; i++) a->subj[i] = NULL; a->cnt = 0; a->info = NULL; } #ifdef DEBUG static void sanity_check_array(const s_array *a, const char *id) { int i; unsigned int num = 0; if (a == NULL) { msg(LOG_DEBUG, "%s - array is NULL", id); abort(); } for (i = 0; i < SUBJ_END - SUBJ_START; i++) if (a->subj[i]) num++; if (num != a->cnt) { msg(LOG_DEBUG, "%s - array corruption %u!=%u", id, num, a->cnt); abort(); } } #else #define sanity_check_array(a, b) do {} while(0) #endif subject_attr_t *subject_access(const s_array *a, subject_type_t t) { sanity_check_array(a, "subject_access"); // These store the same info, see get_subj_attr in event.c if (t == EXE_DIR) t = EXE; if (t >= SUBJ_START && t <= SUBJ_END) return a->subj[t - SUBJ_START]; else return NULL; } // Returns 1 on failure and 0 on success int subject_add(s_array *a, const subject_attr_t *subj) { subject_attr_t* newnode; subject_type_t t; sanity_check_array(a, "subject_add 1"); if (subj) { t = subj->type; // These store the same info, see get_subj_attr in event.c if (t == EXE_DIR) t = EXE; if (t >= SUBJ_START && t <= SUBJ_END) { newnode = malloc(sizeof(subject_attr_t)); if (newnode == NULL) return 1; newnode->type = t; if (subj->type >= COMM) newnode->str = subj->str; else if (subj->type == GID) newnode->set = subj->set; else newnode->val = subj->val; } else return 1; } else return 1; a->subj[t - SUBJ_START] = newnode; a->cnt++; sanity_check_array(a, "subject_add 2"); return 0; } subject_attr_t *subject_find_exe(const s_array *a) { sanity_check_array(a, "subject_find_exe"); if (a->subj[EXE - SUBJ_START]) return a->subj[EXE - SUBJ_START]; return NULL; } subject_attr_t *subject_find_comm(const s_array *a) { sanity_check_array(a, "subject_find_comm"); if (a->subj[COMM - SUBJ_START]) return a->subj[COMM - SUBJ_START]; return NULL; } void subject_clear(s_array* a) { int i; subject_attr_t *current; if (a == NULL) return; sanity_check_array(a, "subject_clear"); for (i = 0; i < SUBJ_END - SUBJ_START; i++) { current = a->subj[i]; if (current == NULL) continue; if (current->type == GID) { destroy_attr_set(current->set); free(current->set); } else if (current->type >= COMM) free(current->str); free(current); } clear_proc_info(a->info); free(a->info); free(a->subj); a->cnt = 0; } void subject_reset(s_array *a, subject_type_t t) { if (a == NULL) return; sanity_check_array(a, "subject_reset1"); if (t >= SUBJ_START && t <= SUBJ_END) { subject_attr_t *current = a->subj[t - SUBJ_START]; if (current == NULL) return; if (current->type == GID) { destroy_attr_set(current->set); free(current->set); } else if (current->type >= COMM) free(current->str); free(current); a->subj[t - SUBJ_START] = NULL; a->cnt--; sanity_check_array(a, "subject_reset2"); } } fapolicyd-1.3.4/src/library/subject.h000066400000000000000000000032241470754500200175410ustar00rootroot00000000000000/* * subject.h - Header file for subject.c * Copyright (c) 2016 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb */ #ifndef SUBJECT_HEADER #define SUBJECT_HEADER #include "subject-attr.h" #include "process.h" /* This is the attribute array. Only data elements that are 1 per * event goes here. */ typedef struct { subject_attr_t **subj; // Subject array unsigned int cnt; // How many items in this list struct proc_info *info; // unique proc fingerprint } s_array; void subject_create(s_array *a); subject_attr_t *subject_access(const s_array *a, subject_type_t t); int subject_add(s_array *a, const subject_attr_t *subj); subject_attr_t *subject_find_exe(const s_array *a); subject_attr_t *subject_find_comm(const s_array *a); void subject_reset(s_array *a, subject_type_t t); void subject_clear(s_array* a); static inline int type_is_subj(int type) {if (type < OBJ_START) return 1; else return 0;} #endif fapolicyd-1.3.4/src/library/trust-file.c000066400000000000000000000254701470754500200202020ustar00rootroot00000000000000/* * trust-file.c - Functions for working with trust files * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Zoltan Fridrich * Radovan Sroka */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "fapolicyd-backend.h" #include "file.h" #include "llist.h" #include "message.h" #include "trust-file.h" #include "escape.h" #include "paths.h" #define BUFFER_SIZE 4096+1+1+1+10+1+64+1 #define FILE_READ_FORMAT "%4096s %lu %64s" // path size SHA256 #define FILE_WRITE_FORMAT "%s %lu %s\n" // path size SHA256 #define FTW_NOPENFD 1024 #define FTW_FLAGS (FTW_ACTIONRETVAL | FTW_PHYS) #define HEADER_OLD "# AUTOGENERATED FILE VERSION 2\n" #define HEADER0 "# AUTOGENERATED FILE VERSION 3\n" #define HEADER1 "# This file contains a list of trusted files\n" #define HEADER2 "#\n" #define HEADER3 "# FULL PATH SIZE SHA256\n" #define HEADER4 "# /home/user/my-ls 157984 61a9960bf7d255a85811f4afcac51067b8f2e4c75e21cf4f2af95319d4ed1b87\n" list_t _list; char *_path; int _count; /** * Take a path and create a string that is ready to be written to the disk. * * @param path Path to create a string from * @return Data string ready to be written to the disk or NULL on error */ static char *make_data_string(const char *path) { int fd = open(path, O_RDONLY); if (fd < 0) { msg(LOG_ERR, "Cannot open %s", path); return NULL; } // Get the size struct stat sb; if (fstat(fd, &sb)) { msg(LOG_ERR, "Cannot stat %s", path); close(fd); return NULL; } // Get the hash char *hash = get_hash_from_fd2(fd, sb.st_size, 1); close(fd); if (!hash) { msg(LOG_ERR, "Cannot hash %s", path); return NULL; } char *line; /* * formated data to be saved * source size sha256 * path is stored as lmdb index */ int count = asprintf(&line, DATA_FORMAT, 0, sb.st_size, hash); free(hash); if (count < 0) { msg(LOG_ERR, "Cannot format entry for %s", path); return NULL; } return line; } /** * Write a list into a file * * @param list List to write into a file * @param dest Destination file * @return 0 on success, 1 on error */ static int write_out_list(list_t *list, const char *dest) { FILE *f = fopen(dest, "w"); if (!f) { msg(LOG_ERR, "Cannot delete %s", dest); list_empty(list); return 1; } size_t hlen; hlen = strlen(HEADER0); fwrite(HEADER0, hlen, 1, f); hlen = strlen(HEADER1); fwrite(HEADER1, hlen, 1, f); hlen = strlen(HEADER2); fwrite(HEADER2, hlen, 1, f); hlen = strlen(HEADER3); fwrite(HEADER3, hlen, 1, f); hlen = strlen(HEADER4); fwrite(HEADER4, hlen, 1, f); for (list_item_t *lptr = list->first; lptr; lptr = lptr->next) { char buf[BUFFER_SIZE + 1]; const char *data = (char *)(lptr->data); const char *path = (char *)lptr->index; /* * + 2 because we are omitting source number * "0 12345 ..." * 0 -> filedb source */ hlen = snprintf(buf, sizeof(buf), "%s %s\n", path, data + 2); fwrite(buf, hlen, 1, f); } fclose(f); return 0; } int trust_file_append(const char *fpath, list_t *list) { list_t content; list_init(&content); int rc = trust_file_load(fpath, &content); // if trust file does not exist, we ignore it as it will be created while writing if (rc == 2) { // exit on parse error, we dont want invalid entries to be autoremoved return 1; } for (list_item_t *lptr = list->first; lptr; lptr = lptr->next) { lptr->data = make_data_string(lptr->index); } list_merge(&content, list); write_out_list(&content, fpath); list_empty(&content); return 0; } #define DELIM ' ' #define MAX_DELIMS 2 static int parse_line_backwards(char *line, char *path, unsigned long *size, char *sha) { if (line == NULL || path == NULL || size == NULL || sha == NULL) return -1; size_t len = strlen(line); int count = 0; char *delims[MAX_DELIMS] = {0}; int stripped = 0; for (int i = len - 1 ; i >= 0 ; i--) { if (!stripped) { if (isspace(line[i])) line[i] = '\0'; else { stripped = 1; } } if (count == MAX_DELIMS) break; if (line[i] == DELIM) { delims[count++] = &line[i]; } } if (count != MAX_DELIMS) return -1; for (int i = 0 ; i < count ; i++) { *(delims[i]) = '\0'; } // save sha to arg // right from the last delimiter to the end of the line size_t sha_size = &line[len-1] - delims[0]+1; if (sha_size > 65) sha_size = 65; memcpy(sha, delims[0]+1, sha_size); sha[65-1] = '\0'; // save size to arg char number[1024]; size_t number_size = delims[0] - delims[1]+1; memcpy(number, delims[1]+1, number_size); char *endptr; *size = strtol(number, &endptr, 10); // save path to arg size_t path_size = delims[1] - line; if (path_size >= 4097) path_size = 4096; memcpy(path, line, path_size); path[path_size] = '\0'; return 0; } /** * @brief Load trust file into list * * @param fpath Full path to trust file * @param list Trust file will be loaded into this list * @return 0 on success, 1 if file can't be open, 2 on parsing error */ int trust_file_load(const char *fpath, list_t *list) { char buffer[BUFFER_SIZE]; int escaped = 0; long line = 0; FILE *file = fopen(fpath, "r"); if (!file) return 1; while (fgets(buffer, BUFFER_SIZE, file)) { char name[4097], sha[65], *index = NULL, *data = NULL; unsigned long sz; unsigned int tsource = SRC_FILE_DB; line++; if (iscntrl(buffer[0]) || buffer[0] == '#') { if (line == 1 && strncmp(buffer, HEADER_OLD, strlen(HEADER_OLD)) == 0) escaped = 1; continue; } if (parse_line_backwards(buffer, name, &sz, sha)) { msg(LOG_WARNING, "Can't parse %s", buffer); fclose(file); return 2; } if (asprintf(&data, DATA_FORMAT, tsource, sz, sha) == -1) data = NULL; // if old format unescape index = escaped ? unescape(name) : strdup(name); if (index == NULL) { msg(LOG_ERR, "Could not unescape %s from %s", name, fpath); free(data); continue; } if (data == NULL) { free(index); continue; } if (list_contains(list, index)) { msg(LOG_WARNING, "%s contains a duplicate %s", fpath, index); free(index); free(data); continue; } if (list_append(list, index, data)) { free(index); free(data); } } fclose(file); return 0; } int trust_file_delete_path(const char *fpath, const char *path) { list_t list; list_init(&list); int rc = trust_file_load(fpath, &list); switch (rc) { case 1: msg(LOG_ERR, "Cannot open %s", fpath); return 0; case 2: list_empty(&list); return -1; default: break; } int count = 0; size_t path_len = strlen(path); list_item_t *lptr = list.first, *prev = NULL, *tmp; while (lptr) { if (!strncmp(lptr->index, path, path_len)) { ++count; tmp = lptr->next; if (prev) prev->next = lptr->next; else list.first = lptr->next; if (!lptr->next) list.last = prev; --list.count; list_destroy_item(&lptr); lptr = tmp; continue; } prev = lptr; lptr = lptr->next; } if (count) write_out_list(&list, fpath); list_empty(&list); return count; } int trust_file_update_path(const char *fpath, const char *path) { list_t list; list_init(&list); int rc = trust_file_load(fpath, &list); switch (rc) { case 1: msg(LOG_ERR, "Cannot open %s", fpath); return 0; case 2: list_empty(&list); return -1; default: break; } int count = 0; size_t path_len = strlen(path); for (list_item_t *lptr = list.first; lptr; lptr = lptr->next) { if (!strncmp(lptr->index, path, path_len)) { free((char *)lptr->data); lptr->data = make_data_string(lptr->index); ++count; } } if (count) write_out_list(&list, fpath); list_empty(&list); return count; } int trust_file_rm_duplicates(const char *fpath, list_t *list) { list_t trust_file; list_init(&trust_file); int rc = trust_file_load(fpath, &trust_file); switch (rc) { case 1: msg(LOG_ERR, "Cannot open %s", fpath); return -1; case 2: list_empty(&trust_file); return -1; default: break; } for (list_item_t *lptr = trust_file.first; lptr; lptr = lptr->next) { list_remove(list, lptr->index); if (list->count == 0) break; } list_empty(&trust_file); return 0; } static int ftw_load(const char *fpath, const struct stat *sb __attribute__ ((unused)), int typeflag, struct FTW *ftwbuf __attribute__ ((unused))) { if (typeflag == FTW_F) trust_file_load(fpath, &_list); return FTW_CONTINUE; } static int ftw_delete_path(const char *fpath, const struct stat *sb __attribute__ ((unused)), int typeflag, struct FTW *ftwbuf __attribute__ ((unused))) { if (typeflag == FTW_F) _count += trust_file_delete_path(fpath, _path); return FTW_CONTINUE; } static int ftw_update_path(const char *fpath, const struct stat *sb __attribute__ ((unused)), int typeflag, struct FTW *ftwbuf __attribute__ ((unused))) { if (typeflag == FTW_F) _count += trust_file_update_path(fpath, _path); return FTW_CONTINUE; } static int ftw_rm_duplicates(const char *fpath, const struct stat *sb __attribute__ ((unused)), int typeflag, struct FTW *ftwbuf __attribute__ ((unused))) { if (_list.count == 0) return FTW_STOP; if (typeflag == FTW_F) trust_file_rm_duplicates(fpath, &_list); return FTW_CONTINUE; } void trust_file_load_all(list_t *list) { list_empty(&_list); trust_file_load(TRUST_FILE_PATH, &_list); nftw(TRUST_DIR_PATH, &ftw_load, FTW_NOPENFD, FTW_FLAGS); list_merge(list, &_list); } int trust_file_delete_path_all(const char *path) { _path = strdup(path); _count = trust_file_delete_path(TRUST_FILE_PATH, path); nftw(TRUST_DIR_PATH, &ftw_delete_path, FTW_NOPENFD, FTW_FLAGS); free(_path); return _count; } int trust_file_update_path_all(const char *path) { _path = strdup(path); _count = trust_file_update_path(TRUST_FILE_PATH, path); nftw(TRUST_DIR_PATH, &ftw_update_path, FTW_NOPENFD, FTW_FLAGS); free(_path); return _count; } void trust_file_rm_duplicates_all(list_t *list) { list_empty(&_list); list_merge(&_list, list); trust_file_rm_duplicates(TRUST_FILE_PATH, &_list); nftw(TRUST_DIR_PATH, &ftw_rm_duplicates, FTW_NOPENFD, FTW_FLAGS); list_merge(list, &_list); } fapolicyd-1.3.4/src/library/trust-file.h000066400000000000000000000030321470754500200201750ustar00rootroot00000000000000/* * trust-file.h - Header for managing trust files * Copyright (c) 2020 Red Hat Inc. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Zoltan Fridrich */ #ifndef TRUST_FILE_H #define TRUST_FILE_H #include "llist.h" #define TRUST_FILE_PATH "/etc/fapolicyd/fapolicyd.trust" #define TRUST_DIR_PATH "/etc/fapolicyd/trust.d/" int trust_file_append(const char *fpath, list_t *list); int trust_file_load(const char *fpath, list_t *list); int trust_file_update_path(const char *fpath, const char *path); int trust_file_delete_path(const char *fpath, const char *path); int trust_file_rm_duplicates(const char *fpath, list_t *list); void trust_file_load_all(list_t *list); int trust_file_update_path_all(const char *path); int trust_file_delete_path_all(const char *path); void trust_file_rm_duplicates_all(list_t *list); #endif fapolicyd-1.3.4/src/tests/000077500000000000000000000000001470754500200154265ustar00rootroot00000000000000fapolicyd-1.3.4/src/tests/Makefile.am000066400000000000000000000031151470754500200174620ustar00rootroot00000000000000# Copyright 2020 Red Hat Inc. # All Rights Reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Authors: # Steve Grubb # CONFIG_CLEAN_FILES = *.orig *.cur check_PROGRAMS = avl_test gid_proc_test AM_CPPFLAGS = -I${top_srcdir}/src/library/ avl_test_SOURCES = avl_test.c ${top_srcdir}/src/library/avl.c gid_proc_test_SOURCES = gid_proc_test.c gid_proc_test_LDADD = ${top_builddir}/src/.libs/libfapolicyd.la if WITH_DEB check_PROGRAMS += deb_test deb_test_CFLAGS = -fPIE -DPIE -pthread -g -W -Wall -Wshadow -Wundef -Wno-unused-result -Wno-unused-parameter -D_GNU_SOURCE -DLIBDPKG_VOLATILE_API deb_test_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now,-ldpkg deb_test_LDADD = ${top_builddir}/src/.libs/libfapolicyd.la deb_test_SOURCES = \ deb_test.c \ ${top_srcdir}/src/library/file.c \ ${top_srcdir}/src/library/backend-manager.c \ ${top_srcdir}/src/library/deb-backend.c endif TESTS = $(check_PROGRAMS) fapolicyd-1.3.4/src/tests/avl_test.c000066400000000000000000000052041470754500200174140ustar00rootroot00000000000000#include #include #include #include "avl.h" typedef struct _avl_int_data { avl_t avl; int num; } avl_int_data_t; static avl_tree_t tree; static int intcmp_cb(void *a, void *b) { return ((avl_int_data_t *)a)->num - ((avl_int_data_t *)b)->num; } int append(int num) { avl_int_data_t *data = malloc(sizeof(avl_int_data_t)); if (data == NULL) return 0; data->num = num; avl_t *ret = avl_insert(&tree, (avl_t *)data); if (ret != (avl_t *)data) { free(data); return 1; } return 0; } int node_remove(int num) { avl_int_data_t node, *n; node.num = num; n = (avl_int_data_t *)avl_remove(&tree, (avl_t *)&node); if (n) { if (n->num != num) error(1, 0, "Remove wrong item %d looking for %d", n->num, num); else { free(n); return 0; } } else error(1, 0, "Remove didn't find %d", num); return 0; } // https://stackoverflow.com/questions/3955680/how-to-check-if-my-avl-tree-implementation-is-correct int main(void) { avl_int_data_t *node; avl_init(&tree, intcmp_cb); append(2); append(1); // force a 1L rotation append(3); node = (avl_int_data_t *)tree.root; if (node->num != 2) error(1, 0, "tree not balanced 1"); // pop the top off to force a rebalance node_remove(2); node = (avl_int_data_t *)tree.root; if (node->num != 3) error(1, 0, "tree not balanced 2"); node_remove(1); append(2); // tree should be 3-2, then add a 1 to force a 1R rotation append(1); node = (avl_int_data_t *)tree.root; if (node->num != 2) error(1, 0, "tree not balanced 3"); node_remove(3); node_remove(2); append(3); // tree should be 1-3, now force a 2L rotation append(2); node = (avl_int_data_t *)tree.root; if (node->num != 2) error(1, 0, "tree not balanced 4"); node_remove(1); node_remove(2); append(1); // tree should be 3-1, now force a 2R rotation append(2); node = (avl_int_data_t *)tree.root; if (node->num != 2) error(1, 0, "tree not balanced 5"); node_remove(1); node_remove(2); node_remove(3); if (tree.root != NULL) error(1, 0, "root not NULL when tree should be empty 1"); // Now let's test the iterator functions append(2); append(5); append(1); append(4); append(3); int i = 1; avl_iterator k; for (node = (avl_int_data_t *)avl_first(&k, &tree); node; node = (avl_int_data_t *)avl_next(&k)) { if (node->num != i) error(1, 0, "Iteration expected %d, got %d", i, node->num); else printf("Iterator %d\n", node->num); i++; } node_remove(1); node_remove(2); node_remove(3); node_remove(4); node_remove(5); if (tree.root != NULL) error(1, 0, "root not NULL when tree should be empty 2"); return 0; } fapolicyd-1.3.4/src/tests/deb_test.c000066400000000000000000000014331470754500200173640ustar00rootroot00000000000000#include #include #include "backend-manager.h" #include "conf.h" #include "config.h" #include "message.h" #ifdef USE_RPM unsigned int debug_mode; #endif int main(int argc, char* const argv[]) { set_message_mode(MSG_STDERR, DBG_YES); conf_t conf; conf.trust = "debdb"; backend_init(&conf); backend_load(&conf); msg(LOG_INFO, "\nDone loading."); backend_entry* debdb_entry = backend_get_first(); backend* debdb = NULL; if (debdb_entry != NULL) { debdb = debdb_entry->backend; } else { msg(LOG_ERR, "ERROR: No backends registered."); } if (debdb == NULL) { msg(LOG_ERR, "ERROR: debdb not registered"); } if (strcmp(conf.trust, debdb->name) != 0) { msg(LOG_ERR, "ERROR: debdb bad name"); } backend_close(); return 0; } fapolicyd-1.3.4/src/tests/gid_proc_test.c000066400000000000000000000026551470754500200204270ustar00rootroot00000000000000#include #include #include #include #include #include #include "attr-sets.h" #include "process.h" int main(void) { int res, num, i, check_intersect = 0; gid_t gid, gids[NGROUPS_MAX]; attr_sets_entry_t *groups = get_gid_set_from_pid(getpid()); gid = getgid(); res = check_int_attr_set(groups, gid); if (!res) error(1, 0, "Group %d not found", gid); num = getgroups(NGROUPS_MAX, gids); if (num < 0) error(1, 0, "Too many groups"); for (i = 0; itree), &(groups->tree)); if (res) error(1, 0, "Negative AVL intersection failed"); printf("Doing Positive AVL intersection\n"); append_int_attr_set(g, gid); res = avl_intersection(&(g->tree), &(groups->tree)); if (!res) error(1, 0, "Positive AVL intersection failed"); destroy_attr_set(g); free(g); } destroy_attr_set(groups); free(groups); return 0; }