commit ad064fea9e2ca52054674e35e3709a1fff17b0b3 Author: 樱檩殇雪 <2773800761@qq.com> Date: Mon Mar 17 02:48:59 2025 +0800 add susfs-dev branch files diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/kernel/.clang-format b/kernel/.clang-format new file mode 100644 index 00000000..10dc5a9a --- /dev/null +++ b/kernel/.clang-format @@ -0,0 +1,548 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 4. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +#AlignEscapedNewlines: Left # Unknown to clang-format-4.0 +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + #AfterExternBlock: false # Unknown to clang-format-5.0 + BeforeCatch: false + BeforeElse: false + IndentBraces: false + #SplitEmptyFunction: true # Unknown to clang-format-4.0 + #SplitEmptyRecord: true # Unknown to clang-format-4.0 + #SplitEmptyNamespace: true # Unknown to clang-format-4.0 +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +#CompactNamespaces: false # Unknown to clang-format-4.0 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +#FixNamespaceComments: false # Unknown to clang-format-4.0 + +# Taken from: +# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ +# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ +# | sort | uniq +ForEachMacros: + - 'apei_estatus_for_each_section' + - 'ata_for_each_dev' + - 'ata_for_each_link' + - '__ata_qc_for_each' + - 'ata_qc_for_each' + - 'ata_qc_for_each_raw' + - 'ata_qc_for_each_with_internal' + - 'ax25_for_each' + - 'ax25_uid_for_each' + - '__bio_for_each_bvec' + - 'bio_for_each_bvec' + - 'bio_for_each_bvec_all' + - 'bio_for_each_integrity_vec' + - '__bio_for_each_segment' + - 'bio_for_each_segment' + - 'bio_for_each_segment_all' + - 'bio_list_for_each' + - 'bip_for_each_vec' + - 'bitmap_for_each_clear_region' + - 'bitmap_for_each_set_region' + - 'blkg_for_each_descendant_post' + - 'blkg_for_each_descendant_pre' + - 'blk_queue_for_each_rl' + - 'bond_for_each_slave' + - 'bond_for_each_slave_rcu' + - 'bpf_for_each_spilled_reg' + - 'btree_for_each_safe128' + - 'btree_for_each_safe32' + - 'btree_for_each_safe64' + - 'btree_for_each_safel' + - 'card_for_each_dev' + - 'cgroup_taskset_for_each' + - 'cgroup_taskset_for_each_leader' + - 'cpufreq_for_each_entry' + - 'cpufreq_for_each_entry_idx' + - 'cpufreq_for_each_valid_entry' + - 'cpufreq_for_each_valid_entry_idx' + - 'css_for_each_child' + - 'css_for_each_descendant_post' + - 'css_for_each_descendant_pre' + - 'device_for_each_child_node' + - 'dma_fence_chain_for_each' + - 'do_for_each_ftrace_op' + - 'drm_atomic_crtc_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane_state' + - 'drm_atomic_for_each_plane_damage' + - 'drm_client_for_each_connector_iter' + - 'drm_client_for_each_modeset' + - 'drm_connector_for_each_possible_encoder' + - 'drm_for_each_bridge_in_chain' + - 'drm_for_each_connector_iter' + - 'drm_for_each_crtc' + - 'drm_for_each_encoder' + - 'drm_for_each_encoder_mask' + - 'drm_for_each_fb' + - 'drm_for_each_legacy_plane' + - 'drm_for_each_plane' + - 'drm_for_each_plane_mask' + - 'drm_for_each_privobj' + - 'drm_mm_for_each_hole' + - 'drm_mm_for_each_node' + - 'drm_mm_for_each_node_in_range' + - 'drm_mm_for_each_node_safe' + - 'flow_action_for_each' + - 'for_each_active_dev_scope' + - 'for_each_active_drhd_unit' + - 'for_each_active_iommu' + - 'for_each_aggr_pgid' + - 'for_each_available_child_of_node' + - 'for_each_bio' + - 'for_each_board_func_rsrc' + - 'for_each_bvec' + - 'for_each_card_auxs' + - 'for_each_card_auxs_safe' + - 'for_each_card_components' + - 'for_each_card_dapms' + - 'for_each_card_pre_auxs' + - 'for_each_card_prelinks' + - 'for_each_card_rtds' + - 'for_each_card_rtds_safe' + - 'for_each_card_widgets' + - 'for_each_card_widgets_safe' + - 'for_each_cgroup_storage_type' + - 'for_each_child_of_node' + - 'for_each_clear_bit' + - 'for_each_clear_bit_from' + - 'for_each_cmsghdr' + - 'for_each_compatible_node' + - 'for_each_component_dais' + - 'for_each_component_dais_safe' + - 'for_each_comp_order' + - 'for_each_console' + - 'for_each_cpu' + - 'for_each_cpu_and' + - 'for_each_cpu_not' + - 'for_each_cpu_wrap' + - 'for_each_dapm_widgets' + - 'for_each_dev_addr' + - 'for_each_dev_scope' + - 'for_each_displayid_db' + - 'for_each_dma_cap_mask' + - 'for_each_dpcm_be' + - 'for_each_dpcm_be_rollback' + - 'for_each_dpcm_be_safe' + - 'for_each_dpcm_fe' + - 'for_each_drhd_unit' + - 'for_each_dss_dev' + - 'for_each_efi_memory_desc' + - 'for_each_efi_memory_desc_in_map' + - 'for_each_element' + - 'for_each_element_extid' + - 'for_each_element_id' + - 'for_each_endpoint_of_node' + - 'for_each_evictable_lru' + - 'for_each_fib6_node_rt_rcu' + - 'for_each_fib6_walker_rt' + - 'for_each_free_mem_pfn_range_in_zone' + - 'for_each_free_mem_pfn_range_in_zone_from' + - 'for_each_free_mem_range' + - 'for_each_free_mem_range_reverse' + - 'for_each_func_rsrc' + - 'for_each_hstate' + - 'for_each_if' + - 'for_each_iommu' + - 'for_each_ip_tunnel_rcu' + - 'for_each_irq_nr' + - 'for_each_link_codecs' + - 'for_each_link_cpus' + - 'for_each_link_platforms' + - 'for_each_lru' + - 'for_each_matching_node' + - 'for_each_matching_node_and_match' + - 'for_each_member' + - 'for_each_mem_region' + - 'for_each_memblock_type' + - 'for_each_memcg_cache_index' + - 'for_each_mem_pfn_range' + - '__for_each_mem_range' + - 'for_each_mem_range' + - '__for_each_mem_range_rev' + - 'for_each_mem_range_rev' + - 'for_each_migratetype_order' + - 'for_each_msi_entry' + - 'for_each_msi_entry_safe' + - 'for_each_net' + - 'for_each_net_continue_reverse' + - 'for_each_netdev' + - 'for_each_netdev_continue' + - 'for_each_netdev_continue_rcu' + - 'for_each_netdev_continue_reverse' + - 'for_each_netdev_feature' + - 'for_each_netdev_in_bond_rcu' + - 'for_each_netdev_rcu' + - 'for_each_netdev_reverse' + - 'for_each_netdev_safe' + - 'for_each_net_rcu' + - 'for_each_new_connector_in_state' + - 'for_each_new_crtc_in_state' + - 'for_each_new_mst_mgr_in_state' + - 'for_each_new_plane_in_state' + - 'for_each_new_private_obj_in_state' + - 'for_each_node' + - 'for_each_node_by_name' + - 'for_each_node_by_type' + - 'for_each_node_mask' + - 'for_each_node_state' + - 'for_each_node_with_cpus' + - 'for_each_node_with_property' + - 'for_each_nonreserved_multicast_dest_pgid' + - 'for_each_of_allnodes' + - 'for_each_of_allnodes_from' + - 'for_each_of_cpu_node' + - 'for_each_of_pci_range' + - 'for_each_old_connector_in_state' + - 'for_each_old_crtc_in_state' + - 'for_each_old_mst_mgr_in_state' + - 'for_each_oldnew_connector_in_state' + - 'for_each_oldnew_crtc_in_state' + - 'for_each_oldnew_mst_mgr_in_state' + - 'for_each_oldnew_plane_in_state' + - 'for_each_oldnew_plane_in_state_reverse' + - 'for_each_oldnew_private_obj_in_state' + - 'for_each_old_plane_in_state' + - 'for_each_old_private_obj_in_state' + - 'for_each_online_cpu' + - 'for_each_online_node' + - 'for_each_online_pgdat' + - 'for_each_pci_bridge' + - 'for_each_pci_dev' + - 'for_each_pci_msi_entry' + - 'for_each_pcm_streams' + - 'for_each_physmem_range' + - 'for_each_populated_zone' + - 'for_each_possible_cpu' + - 'for_each_present_cpu' + - 'for_each_prime_number' + - 'for_each_prime_number_from' + - 'for_each_process' + - 'for_each_process_thread' + - 'for_each_property_of_node' + - 'for_each_registered_fb' + - 'for_each_requested_gpio' + - 'for_each_requested_gpio_in_range' + - 'for_each_reserved_mem_range' + - 'for_each_reserved_mem_region' + - 'for_each_rtd_codec_dais' + - 'for_each_rtd_codec_dais_rollback' + - 'for_each_rtd_components' + - 'for_each_rtd_cpu_dais' + - 'for_each_rtd_cpu_dais_rollback' + - 'for_each_rtd_dais' + - 'for_each_set_bit' + - 'for_each_set_bit_from' + - 'for_each_set_clump8' + - 'for_each_sg' + - 'for_each_sg_dma_page' + - 'for_each_sg_page' + - 'for_each_sgtable_dma_page' + - 'for_each_sgtable_dma_sg' + - 'for_each_sgtable_page' + - 'for_each_sgtable_sg' + - 'for_each_sibling_event' + - 'for_each_subelement' + - 'for_each_subelement_extid' + - 'for_each_subelement_id' + - '__for_each_thread' + - 'for_each_thread' + - 'for_each_unicast_dest_pgid' + - 'for_each_wakeup_source' + - 'for_each_zone' + - 'for_each_zone_zonelist' + - 'for_each_zone_zonelist_nodemask' + - 'fwnode_for_each_available_child_node' + - 'fwnode_for_each_child_node' + - 'fwnode_graph_for_each_endpoint' + - 'gadget_for_each_ep' + - 'genradix_for_each' + - 'genradix_for_each_from' + - 'hash_for_each' + - 'hash_for_each_possible' + - 'hash_for_each_possible_rcu' + - 'hash_for_each_possible_rcu_notrace' + - 'hash_for_each_possible_safe' + - 'hash_for_each_rcu' + - 'hash_for_each_safe' + - 'hctx_for_each_ctx' + - 'hlist_bl_for_each_entry' + - 'hlist_bl_for_each_entry_rcu' + - 'hlist_bl_for_each_entry_safe' + - 'hlist_for_each' + - 'hlist_for_each_entry' + - 'hlist_for_each_entry_continue' + - 'hlist_for_each_entry_continue_rcu' + - 'hlist_for_each_entry_continue_rcu_bh' + - 'hlist_for_each_entry_from' + - 'hlist_for_each_entry_from_rcu' + - 'hlist_for_each_entry_rcu' + - 'hlist_for_each_entry_rcu_bh' + - 'hlist_for_each_entry_rcu_notrace' + - 'hlist_for_each_entry_safe' + - '__hlist_for_each_rcu' + - 'hlist_for_each_safe' + - 'hlist_nulls_for_each_entry' + - 'hlist_nulls_for_each_entry_from' + - 'hlist_nulls_for_each_entry_rcu' + - 'hlist_nulls_for_each_entry_safe' + - 'i3c_bus_for_each_i2cdev' + - 'i3c_bus_for_each_i3cdev' + - 'ide_host_for_each_port' + - 'ide_port_for_each_dev' + - 'ide_port_for_each_present_dev' + - 'idr_for_each_entry' + - 'idr_for_each_entry_continue' + - 'idr_for_each_entry_continue_ul' + - 'idr_for_each_entry_ul' + - 'in_dev_for_each_ifa_rcu' + - 'in_dev_for_each_ifa_rtnl' + - 'inet_bind_bucket_for_each' + - 'inet_lhash2_for_each_icsk_rcu' + - 'key_for_each' + - 'key_for_each_safe' + - 'klp_for_each_func' + - 'klp_for_each_func_safe' + - 'klp_for_each_func_static' + - 'klp_for_each_object' + - 'klp_for_each_object_safe' + - 'klp_for_each_object_static' + - 'kunit_suite_for_each_test_case' + - 'kvm_for_each_memslot' + - 'kvm_for_each_vcpu' + - 'list_for_each' + - 'list_for_each_codec' + - 'list_for_each_codec_safe' + - 'list_for_each_continue' + - 'list_for_each_entry' + - 'list_for_each_entry_continue' + - 'list_for_each_entry_continue_rcu' + - 'list_for_each_entry_continue_reverse' + - 'list_for_each_entry_from' + - 'list_for_each_entry_from_rcu' + - 'list_for_each_entry_from_reverse' + - 'list_for_each_entry_lockless' + - 'list_for_each_entry_rcu' + - 'list_for_each_entry_reverse' + - 'list_for_each_entry_safe' + - 'list_for_each_entry_safe_continue' + - 'list_for_each_entry_safe_from' + - 'list_for_each_entry_safe_reverse' + - 'list_for_each_prev' + - 'list_for_each_prev_safe' + - 'list_for_each_safe' + - 'llist_for_each' + - 'llist_for_each_entry' + - 'llist_for_each_entry_safe' + - 'llist_for_each_safe' + - 'mci_for_each_dimm' + - 'media_device_for_each_entity' + - 'media_device_for_each_intf' + - 'media_device_for_each_link' + - 'media_device_for_each_pad' + - 'nanddev_io_for_each_page' + - 'netdev_for_each_lower_dev' + - 'netdev_for_each_lower_private' + - 'netdev_for_each_lower_private_rcu' + - 'netdev_for_each_mc_addr' + - 'netdev_for_each_uc_addr' + - 'netdev_for_each_upper_dev_rcu' + - 'netdev_hw_addr_list_for_each' + - 'nft_rule_for_each_expr' + - 'nla_for_each_attr' + - 'nla_for_each_nested' + - 'nlmsg_for_each_attr' + - 'nlmsg_for_each_msg' + - 'nr_neigh_for_each' + - 'nr_neigh_for_each_safe' + - 'nr_node_for_each' + - 'nr_node_for_each_safe' + - 'of_for_each_phandle' + - 'of_property_for_each_string' + - 'of_property_for_each_u32' + - 'pci_bus_for_each_resource' + - 'pcm_for_each_format' + - 'ping_portaddr_for_each_entry' + - 'plist_for_each' + - 'plist_for_each_continue' + - 'plist_for_each_entry' + - 'plist_for_each_entry_continue' + - 'plist_for_each_entry_safe' + - 'plist_for_each_safe' + - 'pnp_for_each_card' + - 'pnp_for_each_dev' + - 'protocol_for_each_card' + - 'protocol_for_each_dev' + - 'queue_for_each_hw_ctx' + - 'radix_tree_for_each_slot' + - 'radix_tree_for_each_tagged' + - 'rbtree_postorder_for_each_entry_safe' + - 'rdma_for_each_block' + - 'rdma_for_each_port' + - 'rdma_umem_for_each_dma_block' + - 'resource_list_for_each_entry' + - 'resource_list_for_each_entry_safe' + - 'rhl_for_each_entry_rcu' + - 'rhl_for_each_rcu' + - 'rht_for_each' + - 'rht_for_each_entry' + - 'rht_for_each_entry_from' + - 'rht_for_each_entry_rcu' + - 'rht_for_each_entry_rcu_from' + - 'rht_for_each_entry_safe' + - 'rht_for_each_from' + - 'rht_for_each_rcu' + - 'rht_for_each_rcu_from' + - '__rq_for_each_bio' + - 'rq_for_each_bvec' + - 'rq_for_each_segment' + - 'scsi_for_each_prot_sg' + - 'scsi_for_each_sg' + - 'sctp_for_each_hentry' + - 'sctp_skb_for_each' + - 'shdma_for_each_chan' + - '__shost_for_each_device' + - 'shost_for_each_device' + - 'sk_for_each' + - 'sk_for_each_bound' + - 'sk_for_each_entry_offset_rcu' + - 'sk_for_each_from' + - 'sk_for_each_rcu' + - 'sk_for_each_safe' + - 'sk_nulls_for_each' + - 'sk_nulls_for_each_from' + - 'sk_nulls_for_each_rcu' + - 'snd_array_for_each' + - 'snd_pcm_group_for_each_entry' + - 'snd_soc_dapm_widget_for_each_path' + - 'snd_soc_dapm_widget_for_each_path_safe' + - 'snd_soc_dapm_widget_for_each_sink_path' + - 'snd_soc_dapm_widget_for_each_source_path' + - 'tb_property_for_each' + - 'tcf_exts_for_each_action' + - 'udp_portaddr_for_each_entry' + - 'udp_portaddr_for_each_entry_rcu' + - 'usb_hub_for_each_child' + - 'v4l2_device_for_each_subdev' + - 'v4l2_m2m_for_each_dst_buf' + - 'v4l2_m2m_for_each_dst_buf_safe' + - 'v4l2_m2m_for_each_src_buf' + - 'v4l2_m2m_for_each_src_buf_safe' + - 'virtio_device_for_each_vq' + - 'while_for_each_ftrace_op' + - 'xa_for_each' + - 'xa_for_each_marked' + - 'xa_for_each_range' + - 'xa_for_each_start' + - 'xas_for_each' + - 'xas_for_each_conflict' + - 'xas_for_each_marked' + - 'xbc_array_for_each_value' + - 'xbc_for_each_key_value' + - 'xbc_node_for_each_array_value' + - 'xbc_node_for_each_child' + - 'xbc_node_for_each_key_value' + - 'zorro_for_each_dev' + +#IncludeBlocks: Preserve # Unknown to clang-format-5.0 +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +#IndentPPDirectives: None # Unknown to clang-format-5.0 +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +#SortUsingDeclarations: false # Unknown to clang-format-4.0 +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 +#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 +SpaceBeforeParens: ControlStatements +#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... diff --git a/kernel/.clangd b/kernel/.clangd new file mode 100644 index 00000000..5efbb7ef --- /dev/null +++ b/kernel/.clangd @@ -0,0 +1,4 @@ +Diagnostics: + UnusedIncludes: Strict + ClangTidy: + Remove: bugprone-sizeof-expression diff --git a/kernel/Kconfig b/kernel/Kconfig new file mode 100644 index 00000000..aa218726 --- /dev/null +++ b/kernel/Kconfig @@ -0,0 +1,182 @@ +menu "KernelSU" + +config KSU + tristate "KernelSU function support" + depends on OVERLAY_FS + default y + help + Enable kernel-level root privileges on Android System. + To compile as a module, choose M here: the + module will be called kernelsu. + +config KSU_DEBUG + bool "KernelSU debug mode" + depends on KSU + default n + help + Enable KernelSU debug mode. + +config KSU_ALLOWLIST_WORKAROUND + bool "KernelSU Session Keyring Init workaround" + depends on KSU + default n + help + Enable session keyring init workaround for problematic devices. + Useful for situations where the SU allowlist is not kept after a reboot + +config KSU_MANUAL_HOOK + bool "Manual hooking GKI kernels without kprobes" + depends on KSU && KSU != m + depends on KPROBES + default n + help + Keep KPROBES enabled but do not use KPROBES to implement + the hooks required by KernelSU, but instead hook them manually. + This function only available on GKI kernels, non-GKI are not + affected. + +menu "KernelSU - SUSFS" +config KSU_SUSFS + bool "KernelSU addon - SUSFS" + depends on KSU + default y + help + Patch and Enable SUSFS to kernel with KernelSU. + +config KSU_SUSFS_HAS_MAGIC_MOUNT + bool "Say yes if the current KernelSU repo has magic mount implemented (default n)" + depends on KSU + default y + help + - Enable to indicate that the current SUSFS kernel supports the auto hide features for 5ec1cff's Magic Mount KernelSU + - Every mounts from /debug_ramdisk/workdir will be treated as magic mount and processed differently by susfs + +config KSU_SUSFS_SUS_PATH + bool "Enable to hide suspicious path (NOT recommended)" + depends on KSU_SUSFS + default y + help + - Allow hiding the user-defined path and all its sub-paths from various system calls. + - tmpfs filesystem is not allowed to be added. + - Effective only on zygote spawned user app process. + - Use with cautious as it may cause performance loss and will be vulnerable to side channel attacks, + just disable this feature if it doesn't work for you or you don't need it at all. + +config KSU_SUSFS_SUS_MOUNT + bool "Enable to hide suspicious mounts" + depends on KSU_SUSFS + default y + help + - Allow hiding the user-defined mount paths from /proc/self/[mounts|mountinfo|mountstat]. + - Effective on all processes for hiding mount entries. + - Mounts mounted by process with ksu domain will be forced to be assigned the dev name "KSU". + - mnt_id and mnt_group_id of the sus mount will be assigned to a much bigger number to solve the issue of id not being contiguous. + +config KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT + bool "Enable to hide KSU's default mounts automatically (experimental)" + depends on KSU_SUSFS_SUS_MOUNT + default y + help + - Automatically add KSU's default mounts to sus_mount. + - No susfs command is needed in userspace. + - Only mount operation from process with ksu domain will be checked. + +config KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT + bool "Enable to hide suspicious bind mounts automatically (experimental)" + depends on KSU_SUSFS_SUS_MOUNT + default y + help + - Automatically add binded mounts to sus_mount. + - No susfs command is needed in userspace. + - Only mount operation from process with ksu domain will be checked. + +config KSU_SUSFS_SUS_KSTAT + bool "Enable to spoof suspicious kstat" + depends on KSU_SUSFS + default y + help + - Allow spoofing the kstat of user-defined file/directory. + - Effective only on zygote spawned user app process. + +config KSU_SUSFS_SUS_OVERLAYFS + bool "Enable to automatically spoof kstat and kstatfs for overlayed files/directories" + depends on KSU_SUSFS + default n + help + - Automatically spoof the kstat and kstatfs for overlayed files/directories. + - Enable it if you are using legacy KernelSU and dont have auto hide features enabled. + - No susfs command is needed in userspace. + - Effective on all processes. + +config KSU_SUSFS_TRY_UMOUNT + bool "Enable to use ksu's ksu_try_umount" + depends on KSU_SUSFS + default y + help + - Allow using ksu_try_umount to umount other user-defined mount paths prior to ksu's default umount paths. + - Effective on all NO-root-access-granted processes. + +config KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT + bool "Enable to add bind mounts to ksu's ksu_try_umount automatically (experimental)" + depends on KSU_SUSFS_TRY_UMOUNT + default y + help + - Automatically add binded mounts to ksu's ksu_try_umount. + - No susfs command is needed in userspace. + - Only mount operation from process with ksu domain will be checked. + +config KSU_SUSFS_SPOOF_UNAME + bool "Enable to spoof uname" + depends on KSU_SUSFS + default y + help + - Allow spoofing the string returned by uname syscall to user-defined string. + - Effective on all processes. + +config KSU_SUSFS_ENABLE_LOG + bool "Enable logging susfs log to kernel" + depends on KSU_SUSFS + default y + help + - Allow logging susfs log to kernel, uncheck it to completely disable all susfs log. + +config KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS + bool "Enable to automatically hide ksu and susfs symbols from /proc/kallsyms" + depends on KSU_SUSFS + default y + help + - Automatically hide ksu and susfs symbols from '/proc/kallsyms'. + - Effective on all processes. + +config KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG + bool "Enable to spoof /proc/bootconfig (gki) or /proc/cmdline (non-gki)" + depends on KSU_SUSFS + default y + help + - Spoof the output of /proc/bootconfig (gki) or /proc/cmdline (non-gki) with a user-defined file. + - Effective on all processes. + +config KSU_SUSFS_OPEN_REDIRECT + bool "Enable to redirect a path to be opened with another path (experimental)" + depends on KSU_SUSFS + default y + help + - Allow redirecting a target path to be opened with another user-defined path. + - Effective only on processes with uid < 2000. + - Please be reminded that process with open access to the target and redirected path can be detected. + +config KSU_SUSFS_SUS_SU + bool "Enable SUS-SU in runtime temporarily" + depends on KSU_SUSFS && KPROBES && HAVE_KPROBES && KPROBE_EVENTS + default y + help + - Allow user to enable or disable core ksu kprobes hooks temporarily in runtime. There are 2 working modes for sus_su. + - Mode 0 (default): Disable sus_su, and enable ksu kprobe hooks for su instead. + - Mode 1 (deprecated): + - Mode 2: Enable sus_su, and disable ksu kprobe hooks for su, which means the kernel inline hooks are enabled, + the same as the su implementaion of non-gki kernel without kprobe supported. + - Only apps with root access granted by ksu manager are allowed to get root. + +endmenu + +endmenu diff --git a/kernel/LICENSE b/kernel/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/kernel/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/kernel/Makefile b/kernel/Makefile new file mode 100644 index 00000000..d85d7c90 --- /dev/null +++ b/kernel/Makefile @@ -0,0 +1,145 @@ +kernelsu-objs := ksu.o +kernelsu-objs += allowlist.o +kernelsu-objs += apk_sign.o +kernelsu-objs += sucompat.o +kernelsu-objs += throne_tracker.o +kernelsu-objs += core_hook.o +kernelsu-objs += ksud.o +kernelsu-objs += embed_ksud.o +kernelsu-objs += kernel_compat.o + +kernelsu-objs += selinux/selinux.o +kernelsu-objs += selinux/sepolicy.o +kernelsu-objs += selinux/rules.o +ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include +ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h + +# Do checks before compile +ifneq ($(shell grep -q "int path_umount" $(srctree)/fs/namespace.c; echo $$?),0) +$(error -- Backporting path_umount is mandatory !! Read: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#how-to-backport-path-umount) +endif + +# https://github.com/tiann/KernelSU/pull/2102/files#diff-3a325663233178293ee38b8161f3be511a466af7e0156b9d03d5aed0497564bfR19 +IS_GKI := $(strip $(shell \ + if [ "$(VERSION)" -ge "5" -a "$(PATCHLEVEL)" -ge "10" ]; then \ + echo TRUE; \ + else \ + echo FALSE; \ + fi \ + )) + +ifeq ($(IS_GKI),TRUE) +$(info -- KernelSU: Kernel version is GKI.) +# GKI manual hook checks +# https://github.com/Pzqqt/android_kernel_xiaomi_marble/commit/5b8596b5604bcd0e6e12697a01136a0bb9eb0257 +ifeq ($(strip $(CONFIG_KSU_MANUAL_HOOK)),y) +$(info -- KernelSU: Hooks with Manual hook!) +ifeq ($(strip $(CONFIG_KSU)),m) +$(error CONFIG_KSU_MANUAL_HOOK cannot be enabled when compiling KernelSU as LKM!) +endif +else +ccflags-y += -DKSU_HOOK_WITH_KPROBES +endif +endif + +obj-$(CONFIG_KSU) += kernelsu.o + + +KSU_MANUAL_VERSION := 12500 + +ifeq ($(strip $(KSU_MANUAL_VERSION)),) + ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0) + $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow) + KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD) + # ksu_version: major * 10000 + git version + 600 for historical reasons + $(eval KSU_VERSION=$(shell expr 12000 + $(KSU_GIT_VERSION) + 500)) + $(info -- KernelSU version (Git-based): $(KSU_VERSION)) + ccflags-y += -DKSU_VERSION=$(KSU_VERSION) + else + # .git is a text file while the module is imported by 'git submodule add'. + $(warning "KSU_GIT_VERSION not defined! Using default version.") + KSU_VERSION := 12500 + $(info -- KernelSU version (Default): $(KSU_VERSION)) + ccflags-y += -DKSU_VERSION=$(KSU_VERSION) + endif +else + KSU_VERSION := $(KSU_MANUAL_VERSION) + $(info -- KernelSU version (Manual): $(KSU_VERSION)) + ccflags-y += -DKSU_VERSION=$(KSU_VERSION) +endif + +# SELinux drivers check +ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID +endif + +ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE +endif + +# This feature was introduced in linux 5.0-rc1 +ifeq ($(shell grep -q "get_cred_rcu" $(srctree)/include/linux/cred.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_GET_CRED_RCU +else +# https://github.com/gregkh/linux/commit/123e44b9a49a42ff5313ec65256f1540d7c52fa0 +ifeq ($(shell grep -q "atomic_long_t\s\+\usage" $(srctree)/include/linux/cred.h; echo $$?),0) +$(info -- KernelSU compat: atomic_long_t detected.) +ccflags-y += -DKSU_COMPAT_ATOMIC_LONG +else +$(info -- KernelSU compat: atomic_t detected.) +endif +endif + +# Handle optional backports +ifeq ($(shell grep -q "strncpy_from_user_nofault" $(srctree)/include/linux/uaccess.h; echo $$?),0) +ccflags-y += -DKSU_STRNCPY_FROM_USER_NOFAULT +endif + +ifeq ($(shell grep -q "ssize_t kernel_read" $(srctree)/fs/read_write.c; echo $$?),0) +ccflags-y += -DKSU_KERNEL_READ +endif + +ifeq ($(shell grep "ssize_t kernel_write" $(srctree)/fs/read_write.c | grep -q "const void" ; echo $$?),0) +ccflags-y += -DKSU_KERNEL_WRITE +endif +# Checks Samsung UH drivers +ifeq ($(shell grep -q "CONFIG_KDP_CRED" $(srctree)/kernel/cred.c; echo $$?),0) +ccflags-y += -DSAMSUNG_UH_DRIVER_EXIST +endif + +ifndef KSU_EXPECTED_SIZE +KSU_EXPECTED_SIZE := 0x35c +endif + +ifndef KSU_EXPECTED_HASH +KSU_EXPECTED_HASH := 947ae944f3de4ed4c21a7e4f7953ecf351bfa2b36239da37a34111ad29993eef +endif + +ifdef KSU_MANAGER_PACKAGE +ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\" +$(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE)) +endif + +$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE)) +$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH)) +$(info -- Supported Unofficial Manager: 5ec1cff (GKI) rsuntk (Non-GKI) ShirkNeko (GKI and non-GKI)) +KERNEL_VERSION := $(VERSION).$(PATCHLEVEL) +$(info -- KERNEL_VERSION: $(KERNEL_VERSION)) + +ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE) +ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\" + +ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat +ccflags-y += -Wno-declaration-after-statement -Wno-unused-function + +## For susfs stuff ## +ifeq ($(shell test -e $(srctree)/fs/susfs.c; echo $$?),0) +$(eval SUSFS_VERSION=$(shell cat $(srctree)/include/linux/susfs.h | grep -E '^#define SUSFS_VERSION' | cut -d' ' -f3 | sed 's/"//g')) +$(info ) +$(info -- SUSFS_VERSION: $(SUSFS_VERSION)) +else +$(info -- You have not integrate susfs in your kernel.) +$(info -- Read: https://gitlab.com/simonpunk/susfs4ksu) +endif +# Keep a new line here!! Because someone may append config + diff --git a/kernel/allowlist.c b/kernel/allowlist.c new file mode 100644 index 00000000..8010305a --- /dev/null +++ b/kernel/allowlist.c @@ -0,0 +1,533 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) +#include +#endif + +#include "ksu.h" +#include "klog.h" // IWYU pragma: keep +#include "selinux/selinux.h" +#include "kernel_compat.h" +#include "allowlist.h" +#include "manager.h" + +#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32 +#define FILE_FORMAT_VERSION 3 // u32 + +#define KSU_APP_PROFILE_PRESERVE_UID 9999 // NOBODY_UID +#define KSU_DEFAULT_SELINUX_DOMAIN "u:r:su:s0" + +static DEFINE_MUTEX(allowlist_mutex); + +// default profiles, these may be used frequently, so we cache it +static struct root_profile default_root_profile; +static struct non_root_profile default_non_root_profile; + +static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE); +static int allow_list_pointer __read_mostly = 0; + +static void remove_uid_from_arr(uid_t uid) +{ + int *temp_arr; + int i, j; + + if (allow_list_pointer == 0) + return; + + temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL); + if (temp_arr == NULL) { + pr_err("%s: unable to allocate memory\n", __func__); + return; + } + + for (i = j = 0; i < allow_list_pointer; i++) { + if (allow_list_arr[i] == uid) + continue; + temp_arr[j++] = allow_list_arr[i]; + } + + allow_list_pointer = j; + + for (; j < ARRAY_SIZE(allow_list_arr); j++) + temp_arr[j] = -1; + + memcpy(&allow_list_arr, temp_arr, PAGE_SIZE); + kfree(temp_arr); +} + +static void init_default_profiles() +{ + kernel_cap_t full_cap = CAP_FULL_SET; + + default_root_profile.uid = 0; + default_root_profile.gid = 0; + default_root_profile.groups_count = 1; + default_root_profile.groups[0] = 0; + memcpy(&default_root_profile.capabilities.effective, &full_cap, + sizeof(default_root_profile.capabilities.effective)); + default_root_profile.namespaces = 0; + strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN); + + // This means that we will umount modules by default! + default_non_root_profile.umount_modules = true; +} + +struct perm_data { + struct list_head list; + struct app_profile profile; +}; + +static struct list_head allow_list; + +static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE); +#define BITMAP_UID_MAX ((sizeof(allow_list_bitmap) * BITS_PER_BYTE) - 1) + +#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist" + +static struct work_struct ksu_save_work; +static struct work_struct ksu_load_work; + +static bool persistent_allow_list(void); + +void ksu_show_allow_list(void) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + pr_info("ksu_show_allow_list\n"); + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + pr_info("uid :%d, allow: %d\n", p->profile.current_uid, + p->profile.allow_su); + } +} + +#ifdef CONFIG_KSU_DEBUG +static void ksu_grant_root_to_shell() +{ + struct app_profile profile = { + .version = KSU_APP_PROFILE_VER, + .allow_su = true, + .current_uid = 2000, + }; + strcpy(profile.key, "com.android.shell"); + strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN); + ksu_set_app_profile(&profile, false); +} +#endif + +bool ksu_get_app_profile(struct app_profile *profile) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + bool found = false; + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + bool uid_match = profile->current_uid == p->profile.current_uid; + if (uid_match) { + // found it, override it with ours + memcpy(profile, &p->profile, sizeof(*profile)); + found = true; + goto exit; + } + } + +exit: + return found; +} + +static inline bool forbid_system_uid(uid_t uid) { + #define SHELL_UID 2000 + #define SYSTEM_UID 1000 + return uid < SHELL_UID && uid != SYSTEM_UID; +} + +static bool profile_valid(struct app_profile *profile) +{ + if (!profile) { + return false; + } + + if (forbid_system_uid(profile->current_uid)) { + pr_err("uid lower than 2000 is unsupported: %d\n", profile->current_uid); + return false; + } + + if (profile->version < KSU_APP_PROFILE_VER) { + pr_info("Unsupported profile version: %d\n", profile->version); + return false; + } + + if (profile->allow_su) { + if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) { + return false; + } + + if (strlen(profile->rp_config.profile.selinux_domain) == 0) { + return false; + } + } + + return true; +} + +bool ksu_set_app_profile(struct app_profile *profile, bool persist) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + bool result = false; + + if (!profile_valid(profile)) { + pr_err("Failed to set app profile: invalid profile!\n"); + return false; + } + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + // both uid and package must match, otherwise it will break multiple package with different user id + if (profile->current_uid == p->profile.current_uid && + !strcmp(profile->key, p->profile.key)) { + // found it, just override it all! + memcpy(&p->profile, profile, sizeof(*profile)); + result = true; + goto out; + } + } + + // not found, alloc a new node! + p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL); + if (!p) { + pr_err("ksu_set_app_profile alloc failed\n"); + return false; + } + + memcpy(&p->profile, profile, sizeof(*profile)); + if (profile->allow_su) { + pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n", + profile->key, profile->current_uid, + profile->rp_config.profile.gid, + profile->rp_config.profile.selinux_domain); + } else { + pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n", + profile->key, profile->current_uid, + profile->nrp_config.profile.umount_modules); + } + list_add_tail(&p->list, &allow_list); + +out: + if (profile->current_uid <= BITMAP_UID_MAX) { + if (profile->allow_su) + allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE); + else + allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE)); + } else { + if (profile->allow_su) { + /* + * 1024 apps with uid higher than BITMAP_UID_MAX + * registered to request superuser? + */ + if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) { + pr_err("too many apps registered\n"); + WARN_ON(1); + return false; + } + allow_list_arr[allow_list_pointer++] = profile->current_uid; + } else { + remove_uid_from_arr(profile->current_uid); + } + } + result = true; + + // check if the default profiles is changed, cache it to a single struct to accelerate access. + if (unlikely(!strcmp(profile->key, "$"))) { + // set default non root profile + memcpy(&default_non_root_profile, &profile->nrp_config.profile, + sizeof(default_non_root_profile)); + } + + if (unlikely(!strcmp(profile->key, "#"))) { + // set default root profile + memcpy(&default_root_profile, &profile->rp_config.profile, + sizeof(default_root_profile)); + } + + if (persist) + persistent_allow_list(); + + return result; +} + +bool __ksu_is_allow_uid(uid_t uid) +{ + int i; + + if (unlikely(uid == 0)) { + // already root, but only allow our domain. + return ksu_is_ksu_domain(); + } + + if (forbid_system_uid(uid)) { + // do not bother going through the list if it's system + return false; + } + + if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) { + // manager is always allowed! + return true; + } + + if (likely(uid <= BITMAP_UID_MAX)) { + return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE))); + } else { + for (i = 0; i < allow_list_pointer; i++) { + if (allow_list_arr[i] == uid) + return true; + } + } + + return false; +} + +bool ksu_uid_should_umount(uid_t uid) +{ + struct app_profile profile = { .current_uid = uid }; + if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) { + // we should not umount on manager! + return false; + } + bool found = ksu_get_app_profile(&profile); + if (!found) { + // no app profile found, it must be non root app + return default_non_root_profile.umount_modules; + } + if (profile.allow_su) { + // if found and it is granted to su, we shouldn't umount for it + return false; + } else { + // found an app profile + if (profile.nrp_config.use_default) { + return default_non_root_profile.umount_modules; + } else { + return profile.nrp_config.profile.umount_modules; + } + } +} + +struct root_profile *ksu_get_root_profile(uid_t uid) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + if (uid == p->profile.current_uid && p->profile.allow_su) { + if (!p->profile.rp_config.use_default) { + return &p->profile.rp_config.profile; + } + } + } + + // use default profile + return &default_root_profile; +} + +bool ksu_get_allow_list(int *array, int *length, bool allow) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + int i = 0; + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + // pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow); + if (p->profile.allow_su == allow) { + array[i++] = p->profile.current_uid; + } + } + *length = i; + + return true; +} + +static void do_save_allow_list(struct work_struct *work) +{ + u32 magic = FILE_MAGIC; + u32 version = FILE_FORMAT_VERSION; + struct perm_data *p = NULL; + struct list_head *pos = NULL; + loff_t off = 0; + + struct file *fp = + ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (IS_ERR(fp)) { + pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp)); + return; + } + + // store magic and version + if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != + sizeof(magic)) { + pr_err("save_allow_list write magic failed.\n"); + goto exit; + } + + if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != + sizeof(version)) { + pr_err("save_allow_list write version failed.\n"); + goto exit; + } + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + pr_info("save allow list, name: %s uid :%d, allow: %d\n", + p->profile.key, p->profile.current_uid, + p->profile.allow_su); + + ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile), + &off); + } + +exit: + filp_close(fp, 0); +} + +static void do_load_allow_list(struct work_struct *work) +{ + loff_t off = 0; + ssize_t ret = 0; + struct file *fp = NULL; + u32 magic; + u32 version; + +#ifdef CONFIG_KSU_DEBUG + // always allow adb shell by default + ksu_grant_root_to_shell(); +#endif + + // load allowlist now! + fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp)); + return; + } + + // verify magic + if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != + sizeof(magic) || + magic != FILE_MAGIC) { + pr_err("allowlist file invalid: %d!\n", magic); + goto exit; + } + + if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != + sizeof(version)) { + pr_err("allowlist read version: %d failed\n", version); + goto exit; + } + + pr_info("allowlist version: %d\n", version); + + while (true) { + struct app_profile profile; + + ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile), + &off); + + if (ret <= 0) { + pr_info("load_allow_list read err: %zd\n", ret); + break; + } + + pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", + profile.key, profile.current_uid, profile.allow_su); + ksu_set_app_profile(&profile, false); + } + +exit: + ksu_show_allow_list(); + filp_close(fp, 0); +} + +void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data) +{ + struct perm_data *np = NULL; + struct perm_data *n = NULL; + + bool modified = false; + // TODO: use RCU! + mutex_lock(&allowlist_mutex); + list_for_each_entry_safe (np, n, &allow_list, list) { + uid_t uid = np->profile.current_uid; + char *package = np->profile.key; + // we use this uid for special cases, don't prune it! + bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID; + if (!is_preserved_uid && !is_uid_valid(uid, package, data)) { + modified = true; + pr_info("prune uid: %d, package: %s\n", uid, package); + list_del(&np->list); + if (likely(uid <= BITMAP_UID_MAX)) { + allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE)); + } + remove_uid_from_arr(uid); + smp_mb(); + kfree(np); + } + } + mutex_unlock(&allowlist_mutex); + + if (modified) { + persistent_allow_list(); + } +} + +// make sure allow list works cross boot +static bool persistent_allow_list(void) +{ + return ksu_queue_work(&ksu_save_work); +} + +bool ksu_load_allow_list(void) +{ + return ksu_queue_work(&ksu_load_work); +} + +void ksu_allowlist_init(void) +{ + int i; + + BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE); + BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE); + + for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++) + allow_list_arr[i] = -1; + + INIT_LIST_HEAD(&allow_list); + + INIT_WORK(&ksu_save_work, do_save_allow_list); + INIT_WORK(&ksu_load_work, do_load_allow_list); + + init_default_profiles(); +} + +void ksu_allowlist_exit(void) +{ + struct perm_data *np = NULL; + struct perm_data *n = NULL; + + do_save_allow_list(NULL); + + // free allowlist + mutex_lock(&allowlist_mutex); + list_for_each_entry_safe (np, n, &allow_list, list) { + list_del(&np->list); + kfree(np); + } + mutex_unlock(&allowlist_mutex); +} diff --git a/kernel/allowlist.h b/kernel/allowlist.h new file mode 100644 index 00000000..e89bf71f --- /dev/null +++ b/kernel/allowlist.h @@ -0,0 +1,27 @@ +#ifndef __KSU_H_ALLOWLIST +#define __KSU_H_ALLOWLIST + +#include +#include "ksu.h" + +void ksu_allowlist_init(void); + +void ksu_allowlist_exit(void); + +bool ksu_load_allow_list(void); + +void ksu_show_allow_list(void); + +bool __ksu_is_allow_uid(uid_t uid); +#define ksu_is_allow_uid(uid) unlikely(__ksu_is_allow_uid(uid)) + +bool ksu_get_allow_list(int *array, int *length, bool allow); + +void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, char *, void *), void *data); + +bool ksu_get_app_profile(struct app_profile *); +bool ksu_set_app_profile(struct app_profile *, bool persist); + +bool ksu_uid_should_umount(uid_t uid); +struct root_profile *ksu_get_root_profile(uid_t uid); +#endif diff --git a/kernel/apk_sign.c b/kernel/apk_sign.c new file mode 100644 index 00000000..ab80f7fe --- /dev/null +++ b/kernel/apk_sign.c @@ -0,0 +1,331 @@ +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_KSU_DEBUG +#include +#endif +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) +#include +#else +#include +#endif + +#include "apk_sign.h" +#include "klog.h" // IWYU pragma: keep +#include "kernel_compat.h" + + +struct sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static struct sdesc *init_sdesc(struct crypto_shash *alg) +{ + struct sdesc *sdesc; + int size; + + size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); + sdesc = kmalloc(size, GFP_KERNEL); + if (!sdesc) + return ERR_PTR(-ENOMEM); + sdesc->shash.tfm = alg; + return sdesc; +} + +static int calc_hash(struct crypto_shash *alg, const unsigned char *data, + unsigned int datalen, unsigned char *digest) +{ + struct sdesc *sdesc; + int ret; + + sdesc = init_sdesc(alg); + if (IS_ERR(sdesc)) { + pr_info("can't alloc sdesc\n"); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); + kfree(sdesc); + return ret; +} + +static int ksu_sha256(const unsigned char *data, unsigned int datalen, + unsigned char *digest) +{ + struct crypto_shash *alg; + char *hash_alg_name = "sha256"; + int ret; + + alg = crypto_alloc_shash(hash_alg_name, 0, 0); + if (IS_ERR(alg)) { + pr_info("can't alloc alg %s\n", hash_alg_name); + return PTR_ERR(alg); + } + ret = calc_hash(alg, data, datalen, digest); + crypto_free_shash(alg); + return ret; +} + +static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, + unsigned expected_size, const char *expected_sha256) +{ + ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length + ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length + ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length + + *offset += 0x4 * 3; + + ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length + + *pos += *size4; + *offset += 0x4 + *size4; + + ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length + ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length + *offset += 0x4 * 2; + + if (*size4 == expected_size) { + *offset += *size4; + +#define CERT_MAX_LENGTH 1024 + char cert[CERT_MAX_LENGTH]; + if (*size4 > CERT_MAX_LENGTH) { + pr_info("cert length overlimit\n"); + return false; + } + ksu_kernel_read_compat(fp, cert, *size4, pos); + unsigned char digest[SHA256_DIGEST_SIZE]; + if (IS_ERR(ksu_sha256(cert, *size4, digest))) { + pr_info("sha256 error\n"); + return false; + } + + char hash_str[SHA256_DIGEST_SIZE * 2 + 1]; + hash_str[SHA256_DIGEST_SIZE * 2] = '\0'; + + bin2hex(hash_str, digest, SHA256_DIGEST_SIZE); + pr_info("sha256: %s, expected: %s\n", hash_str, + expected_sha256); + if (strcmp(expected_sha256, hash_str) == 0) { + return true; + } + } + return false; +} + +struct zip_entry_header { + uint32_t signature; + uint16_t version; + uint16_t flags; + uint16_t compression; + uint16_t mod_time; + uint16_t mod_date; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t file_name_length; + uint16_t extra_field_length; +} __attribute__((packed)); + +// This is a necessary but not sufficient condition, but it is enough for us +static bool has_v1_signature_file(struct file *fp) +{ + struct zip_entry_header header; + const char MANIFEST[] = "META-INF/MANIFEST.MF"; + + loff_t pos = 0; + + while (ksu_kernel_read_compat(fp, &header, + sizeof(struct zip_entry_header), &pos) == + sizeof(struct zip_entry_header)) { + if (header.signature != 0x04034b50) { + // ZIP magic: 'PK' + return false; + } + // Read the entry file name + if (header.file_name_length == sizeof(MANIFEST) - 1) { + char fileName[sizeof(MANIFEST)]; + ksu_kernel_read_compat(fp, fileName, + header.file_name_length, &pos); + fileName[header.file_name_length] = '\0'; + + // Check if the entry matches META-INF/MANIFEST.MF + if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == + 0) { + return true; + } + } else { + // Skip the entry file name + pos += header.file_name_length; + } + + // Skip to the next entry + pos += header.extra_field_length + header.compressed_size; + } + + return false; +} + +static __always_inline bool check_v2_signature(char *path, + unsigned expected_size, + const char *expected_sha256) +{ + unsigned char buffer[0x11] = { 0 }; + u32 size4; + u64 size8, size_of_block; + + loff_t pos; + + bool v2_signing_valid = false; + int v2_signing_blocks = 0; + bool v3_signing_exist = false; + bool v3_1_signing_exist = false; + + int i; + struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_err("open %s error.\n", path); + return false; + } + + // disable inotify for this file + fp->f_mode |= FMODE_NONOTIFY; + + // https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD) + for (i = 0;; ++i) { + unsigned short n; + pos = generic_file_llseek(fp, -i - 2, SEEK_END); + ksu_kernel_read_compat(fp, &n, 2, &pos); + if (n == i) { + pos -= 22; + ksu_kernel_read_compat(fp, &size4, 4, &pos); + if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) { + break; + } + } + if (i == 0xffff) { + pr_info("error: cannot find eocd\n"); + goto clean; + } + } + + pos += 12; + // offset + ksu_kernel_read_compat(fp, &size4, 0x4, &pos); + pos = size4 - 0x18; + + ksu_kernel_read_compat(fp, &size8, 0x8, &pos); + ksu_kernel_read_compat(fp, buffer, 0x10, &pos); + if (strcmp((char *)buffer, "APK Sig Block 42")) { + goto clean; + } + + pos = size4 - (size8 + 0x8); + ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos); + if (size_of_block != size8) { + goto clean; + } + + int loop_count = 0; + while (loop_count++ < 10) { + uint32_t id; + uint32_t offset; + ksu_kernel_read_compat(fp, &size8, 0x8, + &pos); // sequence length + if (size8 == size_of_block) { + break; + } + ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id + offset = 4; + if (id == 0x7109871au) { + v2_signing_blocks++; + v2_signing_valid = + check_block(fp, &size4, &pos, &offset, + expected_size, expected_sha256); + } else if (id == 0xf05368c0u) { + // http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73 + v3_signing_exist = true; + } else if (id == 0x1b93ad61u) { + // http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#74 + v3_1_signing_exist = true; + } else { +#ifdef CONFIG_KSU_DEBUG + pr_info("Unknown id: 0x%08x\n", id); +#endif + } + pos += (size8 - offset); + } + + if (v2_signing_blocks != 1) { +#ifdef CONFIG_KSU_DEBUG + pr_err("Unexpected v2 signature count: %d\n", + v2_signing_blocks); +#endif + v2_signing_valid = false; + } + + if (v2_signing_valid) { + int has_v1_signing = has_v1_signature_file(fp); + if (has_v1_signing) { + pr_err("Unexpected v1 signature scheme found!\n"); + filp_close(fp, 0); + return false; + } + } +clean: + filp_close(fp, 0); + + if (v3_signing_exist || v3_1_signing_exist) { +#ifdef CONFIG_KSU_DEBUG + pr_err("Unexpected v3 signature scheme found!\n"); +#endif + return false; + } + + return v2_signing_valid; +} + +#ifdef CONFIG_KSU_DEBUG + +int ksu_debug_manager_uid = -1; + +#include "manager.h" + +static int set_expected_size(const char *val, const struct kernel_param *kp) +{ + int rv = param_set_uint(val, kp); + ksu_set_manager_uid(ksu_debug_manager_uid); + pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid); + return rv; +} + +static struct kernel_param_ops expected_size_ops = { + .set = set_expected_size, + .get = param_get_uint, +}; + +module_param_cb(ksu_debug_manager_uid, &expected_size_ops, + &ksu_debug_manager_uid, S_IRUSR | S_IWUSR); + +#endif + +// include custom manager header +#include "manager_sign.h" + +bool ksu_is_manager_apk(char *path) +{ + return (check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH) || +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) + check_v2_signature(path, EXPECTED_SIZE_5EC1CFF, EXPECTED_HASH_5EC1CFF) || + check_v2_signature(path, EXPECTED_SIZE_WEISHU, EXPECTED_HASH_WEISHU) || + check_v2_signature(path, EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO) || +#endif + check_v2_signature(path, EXPECTED_SIZE_RSUNTK, EXPECTED_HASH_RSUNTK) || + check_v2_signature(path, EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO) || + check_v2_signature(path, EXPECTED_SIZE_NEKO, EXPECTED_HASH_NEKO)); +} \ No newline at end of file diff --git a/kernel/apk_sign.h b/kernel/apk_sign.h new file mode 100644 index 00000000..e02aa514 --- /dev/null +++ b/kernel/apk_sign.h @@ -0,0 +1,8 @@ +#ifndef __KSU_H_APK_V2_SIGN +#define __KSU_H_APK_V2_SIGN + +#include + +bool ksu_is_manager_apk(char *path); + +#endif diff --git a/kernel/arch.h b/kernel/arch.h new file mode 100644 index 00000000..4004ee67 --- /dev/null +++ b/kernel/arch.h @@ -0,0 +1,102 @@ +#ifndef __KSU_H_ARCH +#define __KSU_H_ARCH + +#include + +#if defined(__aarch64__) + +#define __PT_PARM1_REG regs[0] +#define __PT_PARM2_REG regs[1] +#define __PT_PARM3_REG regs[2] +#define __PT_SYSCALL_PARM4_REG regs[3] +#define __PT_CCALL_PARM4_REG regs[3] +#define __PT_PARM5_REG regs[4] +#define __PT_PARM6_REG regs[5] +#define __PT_RET_REG regs[30] +#define __PT_FP_REG regs[29] /* Works only with CONFIG_FRAME_POINTER */ +#define __PT_RC_REG regs[0] +#define __PT_SP_REG sp +#define __PT_IP_REG pc + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#define PRCTL_SYMBOL "__arm64_sys_prctl" +#define SYS_READ_SYMBOL "__arm64_sys_read" +#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat" +#define SYS_FSTATAT64_SYMBOL "__arm64_sys_fstatat64" +#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat" +#define SYS_EXECVE_SYMBOL "__arm64_sys_execve" +#define SYS_EXECVE_COMPAT_SYMBOL "__arm64_compat_sys_execve" +#else +#define PRCTL_SYMBOL "sys_prctl" +#define SYS_READ_SYMBOL "sys_read" +#define SYS_NEWFSTATAT_SYMBOL "sys_newfstatat" +#define SYS_FSTATAT64_SYMBOL "sys_fstatat64" +#define SYS_FACCESSAT_SYMBOL "sys_faccessat" +#define SYS_EXECVE_SYMBOL "sys_execve" +#define SYS_EXECVE_COMPAT_SYMBOL "compat_sys_execve" +#endif + +#elif defined(__x86_64__) + +#define __PT_PARM1_REG di +#define __PT_PARM2_REG si +#define __PT_PARM3_REG dx +/* syscall uses r10 for PARM4 */ +#define __PT_SYSCALL_PARM4_REG r10 +#define __PT_CCALL_PARM4_REG cx +#define __PT_PARM5_REG r8 +#define __PT_PARM6_REG r9 +#define __PT_RET_REG sp +#define __PT_FP_REG bp +#define __PT_RC_REG ax +#define __PT_SP_REG sp +#define __PT_IP_REG ip +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#define PRCTL_SYMBOL "__x64_sys_prctl" +#define SYS_READ_SYMBOL "__x64_sys_read" +#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat" +#define SYS_FSTATAT64_SYMBOL "__x64_sys_fstatat64" +#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat" +#define SYS_EXECVE_SYMBOL "__x64_sys_execve" +#define SYS_EXECVE_COMPAT_SYMBOL "__x64_compat_sys_execve" +#else +#define PRCTL_SYMBOL "sys_prctl" +#define SYS_READ_SYMBOL "sys_read" +#define SYS_NEWFSTATAT_SYMBOL "sys_newfstatat" +#define SYS_FSTATAT64_SYMBOL "sys_fstatat64" +#define SYS_FACCESSAT_SYMBOL "sys_faccessat" +#define SYS_EXECVE_SYMBOL "sys_execve" +#define SYS_EXECVE_COMPAT_SYMBOL "compat_sys_execve" +#endif + +#else +#ifdef KSU_HOOK_WITH_KPROBES +#error "Unsupported arch" +#endif +#endif + +/* allow some architecutres to override `struct pt_regs` */ +#ifndef __PT_REGS_CAST +#define __PT_REGS_CAST(x) (x) +#endif + +#define PT_REGS_PARM1(x) (__PT_REGS_CAST(x)->__PT_PARM1_REG) +#define PT_REGS_PARM2(x) (__PT_REGS_CAST(x)->__PT_PARM2_REG) +#define PT_REGS_PARM3(x) (__PT_REGS_CAST(x)->__PT_PARM3_REG) +#define PT_REGS_SYSCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_SYSCALL_PARM4_REG) +#define PT_REGS_CCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_CCALL_PARM4_REG) +#define PT_REGS_PARM5(x) (__PT_REGS_CAST(x)->__PT_PARM5_REG) +#define PT_REGS_PARM6(x) (__PT_REGS_CAST(x)->__PT_PARM6_REG) +#define PT_REGS_RET(x) (__PT_REGS_CAST(x)->__PT_RET_REG) +#define PT_REGS_FP(x) (__PT_REGS_CAST(x)->__PT_FP_REG) +#define PT_REGS_RC(x) (__PT_REGS_CAST(x)->__PT_RC_REG) +#define PT_REGS_SP(x) (__PT_REGS_CAST(x)->__PT_SP_REG) +#define PT_REGS_IP(x) (__PT_REGS_CAST(x)->__PT_IP_REG) + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#define PT_REAL_REGS(regs) ((struct pt_regs *)PT_REGS_PARM1(regs)) +#else +#define PT_REAL_REGS(regs) ((regs)) +#endif + +#endif diff --git a/kernel/core_hook.c b/kernel/core_hook.c new file mode 100644 index 00000000..70b80450 --- /dev/null +++ b/kernel/core_hook.c @@ -0,0 +1,1432 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef MODULE +#include +#include +#include +#include +#include +#endif + +#ifdef CONFIG_KSU_SUSFS +#include +#endif // #ifdef CONFIG_KSU_SUSFS + +#include "allowlist.h" +#include "arch.h" +#include "core_hook.h" +#include "klog.h" // IWYU pragma: keep +#include "ksu.h" +#include "ksud.h" +#include "manager.h" +#include "selinux/selinux.h" +#include "throne_tracker.h" +#include "throne_tracker.h" +#include "kernel_compat.h" + +#ifdef CONFIG_KSU_SUSFS +bool susfs_is_allow_su(void) +{ +if (ksu_is_manager()) { + // we are manager, allow! + return true; + } + return ksu_is_allow_uid(current_uid().val); +} + +extern u32 susfs_zygote_sid; +extern bool susfs_is_mnt_devname_ksu(struct path *path); +#ifdef CONFIG_KSU_SUSFS_ENABLE_LOG +extern bool susfs_is_log_enabled __read_mostly; +#endif +#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT +extern void susfs_run_try_umount_for_current_mnt_ns(void); +#endif // #ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT +static bool susfs_is_umount_for_zygote_system_process_enabled = false; +#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT +extern bool susfs_is_auto_add_sus_bind_mount_enabled; +#endif // #ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT +extern bool susfs_is_auto_add_sus_ksu_default_mount_enabled; +#endif // #ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT +extern bool susfs_is_auto_add_try_umount_for_bind_mount_enabled; +#endif // #ifdef CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT + +static inline void susfs_on_post_fs_data(void) { + struct path path; +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT +if (!kern_path(DATA_ADB_UMOUNT_FOR_ZYGOTE_SYSTEM_PROCESS, 0, &path)) { + susfs_is_umount_for_zygote_system_process_enabled = true; + path_put(&path); +} +pr_info("susfs_is_umount_for_zygote_system_process_enabled: %d\n", susfs_is_umount_for_zygote_system_process_enabled); +#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT + if (!kern_path(DATA_ADB_NO_AUTO_ADD_SUS_BIND_MOUNT, 0, &path)) { + susfs_is_auto_add_sus_bind_mount_enabled = false; + path_put(&path); +} +pr_info("susfs_is_auto_add_sus_bind_mount_enabled: %d\n", susfs_is_auto_add_sus_bind_mount_enabled); +#endif // #ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT + if (!kern_path(DATA_ADB_NO_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT, 0, &path)) { + susfs_is_auto_add_sus_ksu_default_mount_enabled = false; + path_put(&path); +} +pr_info("susfs_is_auto_add_sus_ksu_default_mount_enabled: %d\n", susfs_is_auto_add_sus_ksu_default_mount_enabled); +#endif // #ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT + if (!kern_path(DATA_ADB_NO_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT, 0, &path)) { + susfs_is_auto_add_try_umount_for_bind_mount_enabled = false; + path_put(&path); +} +pr_info("susfs_is_auto_add_try_umount_for_bind_mount_enabled: %d\n", susfs_is_auto_add_try_umount_for_bind_mount_enabled); +#endif // #ifdef CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT +} +#endif // #ifdef CONFIG_KSU_SUSFS + +#ifdef CONFIG_KSU_SUSFS_SUS_SU +extern bool susfs_is_sus_su_ready; +#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU + +static bool ksu_module_mounted = false; + +extern int ksu_handle_sepolicy(unsigned long arg3, void __user *arg4); + +static bool ksu_su_compat_enabled = true; +extern void ksu_sucompat_init(); +extern void ksu_sucompat_exit(); + +static inline bool is_allow_su() +{ + if (ksu_is_manager()) { + // we are manager, allow! + return true; + } + return ksu_is_allow_uid(current_uid().val); +} + +static inline bool is_unsupported_uid(uid_t uid) +{ +#define LAST_APPLICATION_UID 19999 + uid_t appid = uid % 100000; + return appid > LAST_APPLICATION_UID; +} + +static struct group_info root_groups = { .usage = ATOMIC_INIT(2) }; + +static void setup_groups(struct root_profile *profile, struct cred *cred) +{ + if (profile->groups_count > KSU_MAX_GROUPS) { + pr_warn("Failed to setgroups, too large group: %d!\n", + profile->uid); + return; + } + + if (profile->groups_count == 1 && profile->groups[0] == 0) { + // setgroup to root and return early. + if (cred->group_info) + put_group_info(cred->group_info); + cred->group_info = get_group_info(&root_groups); + return; + } + + u32 ngroups = profile->groups_count; + struct group_info *group_info = groups_alloc(ngroups); + if (!group_info) { + pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid); + return; + } + + int i; + for (i = 0; i < ngroups; i++) { + gid_t gid = profile->groups[i]; + kgid_t kgid = make_kgid(current_user_ns(), gid); + if (!gid_valid(kgid)) { + pr_warn("Failed to setgroups, invalid gid: %d\n", gid); + put_group_info(group_info); + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) + group_info->gid[i] = kgid; +#else + GROUP_AT(group_info, i) = kgid; +#endif + } + + groups_sort(group_info); + set_groups(cred, group_info); +} + +static void disable_seccomp() +{ + assert_spin_locked(¤t->sighand->siglock); + // disable seccomp +#if defined(CONFIG_GENERIC_ENTRY) && \ + LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) + current_thread_info()->syscall_work &= ~SYSCALL_WORK_SECCOMP; +#else + current_thread_info()->flags &= ~(TIF_SECCOMP | _TIF_SECCOMP); +#endif + +#ifdef CONFIG_SECCOMP + current->seccomp.mode = 0; + current->seccomp.filter = NULL; +#else +#endif +} + +void ksu_escape_to_root(void) +{ + struct cred *cred; + +#ifdef KSU_GET_CRED_RCU + rcu_read_lock(); + + do { + cred = (struct cred *)__task_cred((current)); + BUG_ON(!cred); + } while (!get_cred_rcu(cred)); + + if (cred->euid.val == 0) { + pr_warn("Already root, don't escape!\n"); + rcu_read_unlock(); + return; + } +#else + cred = (struct cred *)__task_cred(current); + + if (cred->euid.val == 0) { + pr_warn("Already root, don't escape!\n"); + return; + } +#endif + + struct root_profile *profile = ksu_get_root_profile(cred->uid.val); + + cred->uid.val = profile->uid; + cred->suid.val = profile->uid; + cred->euid.val = profile->uid; + cred->fsuid.val = profile->uid; + + cred->gid.val = profile->gid; + cred->fsgid.val = profile->gid; + cred->sgid.val = profile->gid; + cred->egid.val = profile->gid; + cred->securebits = 0; + + BUILD_BUG_ON(sizeof(profile->capabilities.effective) != + sizeof(kernel_cap_t)); + + // setup capabilities + // we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process + // we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec! + u64 cap_for_ksud = + profile->capabilities.effective | CAP_DAC_READ_SEARCH; + memcpy(&cred->cap_effective, &cap_for_ksud, + sizeof(cred->cap_effective)); + memcpy(&cred->cap_permitted, &profile->capabilities.effective, + sizeof(cred->cap_permitted)); + memcpy(&cred->cap_bset, &profile->capabilities.effective, + sizeof(cred->cap_bset)); + // set ambient caps to all-zero + // fixes "operation not permitted" on dbus cap dropping + memset(&cred->cap_ambient, 0, + sizeof(cred->cap_ambient)); + + setup_groups(profile, cred); + +#ifdef KSU_GET_CRED_RCU + rcu_read_unlock(); +#endif + + // Refer to kernel/seccomp.c: seccomp_set_mode_strict + // When disabling Seccomp, ensure that current->sighand->siglock is held during the operation. + spin_lock_irq(¤t->sighand->siglock); + disable_seccomp(); + spin_unlock_irq(¤t->sighand->siglock); + + ksu_setup_selinux(profile->selinux_domain); +} + +int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry) +{ + if (!current->mm) { + // skip kernel threads + return 0; + } + + if (current_uid().val != 1000) { + // skip non system uid + return 0; + } + + if (!old_dentry || !new_dentry) { + return 0; + } + + // /data/system/packages.list.tmp -> /data/system/packages.list + if (strcmp(new_dentry->d_iname, "packages.list")) { + return 0; + } + + char path[128]; + char *buf = dentry_path_raw(new_dentry, path, sizeof(path)); + if (IS_ERR(buf)) { + pr_err("dentry_path_raw failed.\n"); + return 0; + } + + if (!strstr(buf, "/system/packages.list")) { + return 0; + } + pr_info("renameat: %s -> %s, new path: %s\n", old_dentry->d_iname, + new_dentry->d_iname, buf); + + ksu_track_throne(); + + return 0; +} + +static void nuke_ext4_sysfs() { + struct path path; + int err = kern_path("/data/adb/modules", 0, &path); + if (err) { + pr_err("nuke path err: %d\n", err); + return; + } + + struct super_block* sb = path.dentry->d_inode->i_sb; + const char* name = sb->s_type->name; + if (strcmp(name, "ext4") != 0) { + pr_info("nuke but module aren't mounted\n"); + path_put(&path); + return; + } + + ext4_unregister_sysfs(sb); + path_put(&path); +} + +int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + // if success, we modify the arg5 as result! + u32 *result = (u32 *)arg5; + u32 reply_ok = KERNEL_SU_OPTION; + + if (KERNEL_SU_OPTION != option) { + return 0; + } + + // TODO: find it in throne tracker! + uid_t current_uid_val = current_uid().val; + uid_t manager_uid = ksu_get_manager_uid(); + if (current_uid_val != manager_uid && + current_uid_val % 100000 == manager_uid) { + ksu_set_manager_uid(current_uid_val); + } + + bool from_root = 0 == current_uid().val; + bool from_manager = ksu_is_manager(); + + if (!from_root && !from_manager) { + // only root or manager can access this interface + return 0; + } + +#ifdef CONFIG_KSU_DEBUG + pr_info("option: 0x%x, cmd: %ld\n", option, arg2); +#endif + + if (arg2 == CMD_BECOME_MANAGER) { + if (from_manager) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("become_manager: prctl reply error\n"); + } + return 0; + } + return 0; + } + + if (arg2 == CMD_GRANT_ROOT) { + if (is_allow_su()) { + pr_info("allow root for: %d\n", current_uid().val); + ksu_escape_to_root(); + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("grant_root: prctl reply error\n"); + } + } + return 0; + } + + // Both root manager and root processes should be allowed to get version + if (arg2 == CMD_GET_VERSION) { + u32 version = KERNEL_SU_VERSION; + if (copy_to_user(arg3, &version, sizeof(version))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + u32 version_flags = 0; +#ifdef MODULE + version_flags |= 0x1; +#endif + if (arg4 && + copy_to_user(arg4, &version_flags, sizeof(version_flags))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + return 0; + } + + if (arg2 == CMD_REPORT_EVENT) { + if (!from_root) { + return 0; + } + switch (arg3) { + case EVENT_POST_FS_DATA: { + static bool post_fs_data_lock = false; +#ifdef CONFIG_KSU_SUSFS + susfs_on_post_fs_data(); +#endif + if (!post_fs_data_lock) { + post_fs_data_lock = true; + pr_info("post-fs-data triggered\n"); + ksu_on_post_fs_data(); + } + break; + } + case EVENT_BOOT_COMPLETED: { + static bool boot_complete_lock = false; + if (!boot_complete_lock) { + boot_complete_lock = true; + pr_info("boot_complete triggered\n"); + } + break; + } + case EVENT_MODULE_MOUNTED: { + ksu_module_mounted = true; + pr_info("module mounted!\n"); + nuke_ext4_sysfs(); + break; + } + default: + break; + } + return 0; + } + + if (arg2 == CMD_SET_SEPOLICY) { + if (!from_root) { + return 0; + } + if (!ksu_handle_sepolicy(arg3, arg4)) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("sepolicy: prctl reply error\n"); + } + } + + return 0; + } + + if (arg2 == CMD_CHECK_SAFEMODE) { + if (ksu_is_safe_mode()) { + pr_warn("safemode enabled!\n"); + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("safemode: prctl reply error\n"); + } + } + return 0; + } + + if (arg2 == CMD_GET_ALLOW_LIST || arg2 == CMD_GET_DENY_LIST) { + u32 array[128]; + u32 array_length; + bool success = ksu_get_allow_list(array, &array_length, + arg2 == CMD_GET_ALLOW_LIST); + if (success) { + if (!copy_to_user(arg4, &array_length, + sizeof(array_length)) && + !copy_to_user(arg3, array, + sizeof(u32) * array_length)) { + if (copy_to_user(result, &reply_ok, + sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", + arg2); + } + } else { + pr_err("prctl copy allowlist error\n"); + } + } + return 0; + } + + if (arg2 == CMD_UID_GRANTED_ROOT || arg2 == CMD_UID_SHOULD_UMOUNT) { + uid_t target_uid = (uid_t)arg3; + bool allow = false; + if (arg2 == CMD_UID_GRANTED_ROOT) { + allow = ksu_is_allow_uid(target_uid); + } else if (arg2 == CMD_UID_SHOULD_UMOUNT) { + allow = ksu_uid_should_umount(target_uid); + } else { + pr_err("unknown cmd: %lu\n", arg2); + } + if (!copy_to_user(arg4, &allow, sizeof(allow))) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + } else { + pr_err("prctl copy err, cmd: %lu\n", arg2); + } + return 0; + } + +#ifdef CONFIG_KSU_SUSFS + if (current_uid_val == 0) { +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + if (arg2 == CMD_SUSFS_ADD_SUS_PATH) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_sus_path))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_PATH -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_PATH -> arg5 is not accessible\n"); + return 0; + } + error = susfs_add_sus_path((struct st_susfs_sus_path __user*)arg3); + pr_info("susfs: CMD_SUSFS_ADD_SUS_PATH -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_SUS_PATH +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT + if (arg2 == CMD_SUSFS_ADD_SUS_MOUNT) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_sus_mount))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_MOUNT -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_MOUNT -> arg5 is not accessible\n"); + return 0; + } + error = susfs_add_sus_mount((struct st_susfs_sus_mount __user*)arg3); + pr_info("susfs: CMD_SUSFS_ADD_SUS_MOUNT -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT +#ifdef CONFIG_KSU_SUSFS_SUS_KSTAT + if (arg2 == CMD_SUSFS_ADD_SUS_KSTAT) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_sus_kstat))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_KSTAT -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_KSTAT -> arg5 is not accessible\n"); + return 0; + } + error = susfs_add_sus_kstat((struct st_susfs_sus_kstat __user*)arg3); + pr_info("susfs: CMD_SUSFS_ADD_SUS_KSTAT -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } + if (arg2 == CMD_SUSFS_UPDATE_SUS_KSTAT) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_sus_kstat))) { + pr_err("susfs: CMD_SUSFS_UPDATE_SUS_KSTAT -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_UPDATE_SUS_KSTAT -> arg5 is not accessible\n"); + return 0; + } + error = susfs_update_sus_kstat((struct st_susfs_sus_kstat __user*)arg3); + pr_info("susfs: CMD_SUSFS_UPDATE_SUS_KSTAT -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } + if (arg2 == CMD_SUSFS_ADD_SUS_KSTAT_STATICALLY) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_sus_kstat))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_KSTAT_STATICALLY -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_ADD_SUS_KSTAT_STATICALLY -> arg5 is not accessible\n"); + return 0; + } + error = susfs_add_sus_kstat((struct st_susfs_sus_kstat __user*)arg3); + pr_info("susfs: CMD_SUSFS_ADD_SUS_KSTAT_STATICALLY -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_SUS_KSTAT +#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT + if (arg2 == CMD_SUSFS_ADD_TRY_UMOUNT) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_try_umount))) { + pr_err("susfs: CMD_SUSFS_ADD_TRY_UMOUNT -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_ADD_TRY_UMOUNT -> arg5 is not accessible\n"); + return 0; + } + error = susfs_add_try_umount((struct st_susfs_try_umount __user*)arg3); + pr_info("susfs: CMD_SUSFS_ADD_TRY_UMOUNT -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } + if (arg2 == CMD_SUSFS_RUN_UMOUNT_FOR_CURRENT_MNT_NS) { + int error = 0; + susfs_run_try_umount_for_current_mnt_ns(); + pr_info("susfs: CMD_SUSFS_RUN_UMOUNT_FOR_CURRENT_MNT_NS -> ret: %d\n", error); + } +#endif //#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT +#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME + if (arg2 == CMD_SUSFS_SET_UNAME) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_uname))) { + pr_err("susfs: CMD_SUSFS_SET_UNAME -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_SET_UNAME -> arg5 is not accessible\n"); + return 0; + } + error = susfs_set_uname((struct st_susfs_uname __user*)arg3); + pr_info("susfs: CMD_SUSFS_SET_UNAME -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME +#ifdef CONFIG_KSU_SUSFS_ENABLE_LOG + if (arg2 == CMD_SUSFS_ENABLE_LOG) { + int error = 0; + if (arg3 != 0 && arg3 != 1) { + pr_err("susfs: CMD_SUSFS_ENABLE_LOG -> arg3 can only be 0 or 1\n"); + return 0; + } + susfs_set_log(arg3); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_ENABLE_LOG +#ifdef CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG + if (arg2 == CMD_SUSFS_SET_CMDLINE_OR_BOOTCONFIG) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, SUSFS_FAKE_CMDLINE_OR_BOOTCONFIG_SIZE)) { + pr_err("susfs: CMD_SUSFS_SET_CMDLINE_OR_BOOTCONFIG -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_SET_CMDLINE_OR_BOOTCONFIG -> arg5 is not accessible\n"); + return 0; + } + error = susfs_set_cmdline_or_bootconfig((char __user*)arg3); + pr_info("susfs: CMD_SUSFS_SET_CMDLINE_OR_BOOTCONFIG -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG +#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT + if (arg2 == CMD_SUSFS_ADD_OPEN_REDIRECT) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_susfs_open_redirect))) { + pr_err("susfs: CMD_SUSFS_ADD_OPEN_REDIRECT -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_ADD_OPEN_REDIRECT -> arg5 is not accessible\n"); + return 0; + } + error = susfs_add_open_redirect((struct st_susfs_open_redirect __user*)arg3); + pr_info("susfs: CMD_SUSFS_ADD_OPEN_REDIRECT -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT +#ifdef CONFIG_KSU_SUSFS_SUS_SU + if (arg2 == CMD_SUSFS_SUS_SU) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(struct st_sus_su))) { + pr_err("susfs: CMD_SUSFS_SUS_SU -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_SUS_SU -> arg5 is not accessible\n"); + return 0; + } + error = susfs_sus_su((struct st_sus_su __user*)arg3); + pr_info("susfs: CMD_SUSFS_SUS_SU -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif //#ifdef CONFIG_KSU_SUSFS_SUS_SU + if (arg2 == CMD_SUSFS_SHOW_VERSION) { + int error = 0; + int len_of_susfs_version = strlen(SUSFS_VERSION); + char *susfs_version = SUSFS_VERSION; + if (!ksu_access_ok((void __user*)arg3, len_of_susfs_version+1)) { + pr_err("susfs: CMD_SUSFS_SHOW_VERSION -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_SHOW_VERSION -> arg5 is not accessible\n"); + return 0; + } + error = copy_to_user((void __user*)arg3, (void*)susfs_version, len_of_susfs_version+1); + pr_info("susfs: CMD_SUSFS_SHOW_VERSION -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } + if (arg2 == CMD_SUSFS_SHOW_ENABLED_FEATURES) { + int error = 0; + u64 enabled_features = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(u64))) { + pr_err("susfs: CMD_SUSFS_SHOW_ENABLED_FEATURES -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_SHOW_ENABLED_FEATURES -> arg5 is not accessible\n"); + return 0; + } +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + enabled_features |= (1 << 0); +#endif +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT + enabled_features |= (1 << 1); +#endif +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT + enabled_features |= (1 << 2); +#endif +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT + enabled_features |= (1 << 3); +#endif +#ifdef CONFIG_KSU_SUSFS_SUS_KSTAT + enabled_features |= (1 << 4); +#endif +#ifdef CONFIG_KSU_SUSFS_SUS_OVERLAYFS + enabled_features |= (1 << 5); +#endif +#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT + enabled_features |= (1 << 6); +#endif +#ifdef CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT + enabled_features |= (1 << 7); +#endif +#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME + enabled_features |= (1 << 8); +#endif +#ifdef CONFIG_KSU_SUSFS_ENABLE_LOG + enabled_features |= (1 << 9); +#endif +#ifdef CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS + enabled_features |= (1 << 10); +#endif +#ifdef CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG + enabled_features |= (1 << 11); +#endif +#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT + enabled_features |= (1 << 12); +#endif +#ifdef CONFIG_KSU_SUSFS_SUS_SU + enabled_features |= (1 << 13); +#endif +#ifdef CONFIG_KSU_SUSFS_HAS_MAGIC_MOUNT + enabled_features |= (1 << 14); +#endif + error = copy_to_user((void __user*)arg3, (void*)&enabled_features, sizeof(enabled_features)); + pr_info("susfs: CMD_SUSFS_SHOW_ENABLED_FEATURES -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } + if (arg2 == CMD_SUSFS_SHOW_VARIANT) { + int error = 0; + int len_of_variant = strlen(SUSFS_VARIANT); + char *susfs_variant = SUSFS_VARIANT; + if (!ksu_access_ok((void __user*)arg3, len_of_variant+1)) { + pr_err("susfs: CMD_SUSFS_SHOW_VARIANT -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_SHOW_VARIANT -> arg5 is not accessible\n"); + return 0; + } + error = copy_to_user((void __user*)arg3, (void*)susfs_variant, len_of_variant+1); + pr_info("susfs: CMD_SUSFS_SHOW_VARIANT -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#ifdef CONFIG_KSU_SUSFS_SUS_SU + if (arg2 == CMD_SUSFS_IS_SUS_SU_READY) { + int error = 0; + if (!ksu_access_ok((void __user*)arg3, sizeof(susfs_is_sus_su_ready))) { + pr_err("susfs: CMD_SUSFS_IS_SUS_SU_READY -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_IS_SUS_SU_READY -> arg5 is not accessible\n"); + return 0; + } + error = copy_to_user((void __user*)arg3, (void*)&susfs_is_sus_su_ready, sizeof(susfs_is_sus_su_ready)); + pr_info("susfs: CMD_SUSFS_IS_SUS_SU_READY -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } + if (arg2 == CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE) { + int error = 0; + int working_mode = susfs_get_sus_su_working_mode(); + if (!ksu_access_ok((void __user*)arg3, sizeof(working_mode))) { + pr_err("susfs: CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE -> arg3 is not accessible\n"); + return 0; + } + if (!ksu_access_ok((void __user*)arg5, sizeof(error))) { + pr_err("susfs: CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE -> arg5 is not accessible\n"); + return 0; + } + error = copy_to_user((void __user*)arg3, (void*)&working_mode, sizeof(working_mode)); + pr_info("susfs: CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE -> ret: %d\n", error); + if (copy_to_user((void __user*)arg5, &error, sizeof(error))) + pr_info("susfs: copy_to_user() failed\n"); + return 0; + } +#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU + } +#endif //#ifdef CONFIG_KSU_SUSFS + + // all other cmds are for 'root manager' + if (!from_manager) { + return 0; + } + + // we are already manager + if (arg2 == CMD_GET_APP_PROFILE) { + struct app_profile profile; + if (copy_from_user(&profile, arg3, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + + bool success = ksu_get_app_profile(&profile); + if (success) { + if (copy_to_user(arg3, &profile, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + } + return 0; + } + + if (arg2 == CMD_SET_APP_PROFILE) { + struct app_profile profile; + if (copy_from_user(&profile, arg3, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + + // todo: validate the params + if (ksu_set_app_profile(&profile, true)) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + } + return 0; + } + + if (arg2 == CMD_IS_SU_ENABLED) { + if (copy_to_user(arg3, &ksu_su_compat_enabled, + sizeof(ksu_su_compat_enabled))) { + pr_err("copy su compat failed\n"); + return 0; + } + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + return 0; + } + + if (arg2 == CMD_ENABLE_SU) { + bool enabled = (arg3 != 0); + if (enabled == ksu_su_compat_enabled) { + pr_info("cmd enable su but no need to change.\n"); + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {// return the reply_ok directly + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + return 0; + } + + if (enabled) { + ksu_sucompat_init(); + } else { + ksu_sucompat_exit(); + } + ksu_su_compat_enabled = enabled; + + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %lu\n", arg2); + } + + return 0; + } + + return 0; +} + +static bool is_appuid(kuid_t uid) +{ +#define PER_USER_RANGE 100000 +#define FIRST_APPLICATION_UID 10000 +#define LAST_APPLICATION_UID 19999 + + uid_t appid = uid.val % PER_USER_RANGE; + return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID; +} + +static bool should_umount(struct path *path) +{ + if (!path) { + return false; + } + + if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) { + pr_info("ignore global mnt namespace process: %d\n", + current_uid().val); + return false; + } + +#ifdef CONFIG_KSU_SUSFS + return susfs_is_mnt_devname_ksu(path); +#else + if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) { + const char *fstype = path->mnt->mnt_sb->s_type->name; + return strcmp(fstype, "overlay") == 0; + } + return false; +#endif +} + +static int ksu_umount_mnt(struct path *path, int flags) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || defined(KSU_UMOUNT) + return path_umount(path, flags); +#else + // TODO: umount for non GKI kernel + return -ENOSYS; +#endif +} + +#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT +void ksu_try_umount(const char *mnt, bool check_mnt, int flags, uid_t uid) +#else +static void ksu_try_umount(const char *mnt, bool check_mnt, int flags) +#endif +{ + struct path path; + int err = kern_path(mnt, 0, &path); + if (err) { + return; + } + + if (path.dentry != path.mnt->mnt_root) { + // it is not root mountpoint, maybe umounted by others already. + return; + } + + // we are only interest in some specific mounts + if (check_mnt && !should_umount(&path)) { + return; + } + +#if defined(CONFIG_KSU_SUSFS_TRY_UMOUNT) && defined(CONFIG_KSU_SUSFS_ENABLE_LOG) + if (susfs_is_log_enabled) { + pr_info("susfs: umounting '%s' for uid: %d\n", mnt, uid); + } +#endif + + err = ksu_umount_mnt(&path, flags); + if (err) { + pr_warn("umount %s failed: %d\n", mnt, err); + } +} + +#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT +void susfs_try_umount_all(uid_t uid) { + susfs_try_umount(uid); + /* For Legacy KSU only */ + ksu_try_umount("/system", true, 0, uid); + ksu_try_umount("/system_ext", true, 0, uid); + ksu_try_umount("/vendor", true, 0, uid); + ksu_try_umount("/product", true, 0, uid); + ksu_try_umount("/odm", true, 0, uid); + // - For '/data/adb/modules' we pass 'false' here because it is a loop device that we can't determine whether + // its dev_name is KSU or not, and it is safe to just umount it if it is really a mountpoint + ksu_try_umount("/data/adb/modules", false, MNT_DETACH, uid); + /* For both Legacy KSU and Magic Mount KSU */ + ksu_try_umount("/debug_ramdisk", true, MNT_DETACH, uid); + ksu_try_umount("/sbin", false, MNT_DETACH, uid); + + // try umount hosts file + ksu_try_umount("/system/etc/hosts", false, MNT_DETACH, uid); + + // try umount lsposed dex2oat bins + ksu_try_umount("/apex/com.android.art/bin/dex2oat64", false, MNT_DETACH, uid); + ksu_try_umount("/apex/com.android.art/bin/dex2oat32", false, MNT_DETACH, uid); +} +#endif + +int ksu_handle_setuid(struct cred *new, const struct cred *old) +{ + // this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it! + if (!ksu_module_mounted) { + return 0; + } + + if (!new || !old) { + return 0; + } + + kuid_t new_uid = new->uid; + kuid_t old_uid = old->uid; + + if (0 != old_uid.val) { + // old process is not root, ignore it. + return 0; + } + +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT + // check if current process is zygote + bool is_zygote_child = susfs_is_sid_equal(old->security, susfs_zygote_sid); + if (likely(is_zygote_child)) { + // if spawned process is non user app process + if (unlikely(new_uid.val < 10000 && new_uid.val >= 1000)) { + // umount for the system process if path DATA_ADB_UMOUNT_FOR_ZYGOTE_SYSTEM_PROCESS exists + if (susfs_is_umount_for_zygote_system_process_enabled) { + goto out_ksu_try_umount; + } + } + } +#endif + + if (!is_appuid(new_uid) || is_unsupported_uid(new_uid.val)) { + // pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid.val); + return 0; + } + + if (ksu_is_allow_uid(new_uid.val)) { + // pr_info("handle setuid ignore allowed application: %d\n", new_uid.val); + return 0; + } +#ifdef CONFIG_KSU_SUSFS + else { + task_lock(current); + current->susfs_task_state |= TASK_STRUCT_NON_ROOT_USER_APP_PROC; + task_unlock(current); + } +#endif + +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT +out_ksu_try_umount: +#endif + if (!ksu_uid_should_umount(new_uid.val)) { + return 0; + } else { +#ifdef CONFIG_KSU_DEBUG + pr_info("uid: %d should not umount!\n", current_uid().val); +#endif + } +#ifndef CONFIG_KSU_SUSFS_SUS_MOUNT + // check old process's selinux context, if it is not zygote, ignore it! + // because some su apps may setuid to untrusted_app but they are in global mount namespace + // when we umount for such process, that is a disaster! + bool is_zygote_child = ksu_is_zygote(old->security); +#endif + if (!is_zygote_child) { + pr_info("handle umount ignore non zygote child: %d\n", + current->pid); + return 0; + } +#ifdef CONFIG_KSU_DEBUG + // umount the target mnt + pr_info("handle umount for uid: %d, pid: %d\n", new_uid.val, + current->pid); +#endif + +#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT + // susfs come first, and lastly umount by ksu, make sure umount in reversed order + susfs_try_umount_all(new_uid.val); +#else + + // fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and + // filter the mountpoint whose target is `/data/adb` + ksu_try_umount("/system", true, 0); + ksu_try_umount("/system_ext", true, 0); + ksu_try_umount("/vendor", true, 0); + ksu_try_umount("/product", true, 0); + ksu_try_umount("/data/adb/modules", false, MNT_DETACH); + + // try umount ksu temp path + ksu_try_umount("/debug_ramdisk", false, MNT_DETACH); + ksu_try_umount("/sbin", false, MNT_DETACH); + + // try umount hosts file + ksu_try_umount("/system/etc/hosts", false, MNT_DETACH); + + // try umount lsposed dex2oat bins + ksu_try_umount("/apex/com.android.art/bin/dex2oat64", false, MNT_DETACH); + ksu_try_umount("/apex/com.android.art/bin/dex2oat32", false, MNT_DETACH); +#endif + return 0; +} + +// Init functons + +static int handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + int option = (int)PT_REGS_PARM1(real_regs); + unsigned long arg2 = (unsigned long)PT_REGS_PARM2(real_regs); + unsigned long arg3 = (unsigned long)PT_REGS_PARM3(real_regs); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) + // PRCTL_SYMBOL is the arch-specificed one, which receive raw pt_regs from syscall + unsigned long arg4 = (unsigned long)PT_REGS_SYSCALL_PARM4(real_regs); +#else + // PRCTL_SYMBOL is the common one, called by C convention in do_syscall_64 + // https://elixir.bootlin.com/linux/v4.15.18/source/arch/x86/entry/common.c#L287 + unsigned long arg4 = (unsigned long)PT_REGS_CCALL_PARM4(real_regs); +#endif + unsigned long arg5 = (unsigned long)PT_REGS_PARM5(real_regs); + + return ksu_handle_prctl(option, arg2, arg3, arg4, arg5); +} + +static struct kprobe prctl_kp = { + .symbol_name = PRCTL_SYMBOL, + .pre_handler = handler_pre, +}; + +static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + // https://elixir.bootlin.com/linux/v5.12-rc1/source/include/linux/fs.h + struct renamedata *rd = PT_REGS_PARM1(regs); + struct dentry *old_entry = rd->old_dentry; + struct dentry *new_entry = rd->new_dentry; +#else + struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs); + struct dentry *new_entry = (struct dentry *)PT_REGS_CCALL_PARM4(regs); +#endif + + return ksu_handle_rename(old_entry, new_entry); +} + +static struct kprobe renameat_kp = { + .symbol_name = "vfs_rename", + .pre_handler = renameat_handler_pre, +}; + +__maybe_unused int ksu_kprobe_init(void) +{ + int rc = 0; + rc = register_kprobe(&prctl_kp); + + if (rc) { + pr_info("prctl kprobe failed: %d.\n", rc); + return rc; + } + + rc = register_kprobe(&renameat_kp); + pr_info("renameat kp: %d\n", rc); + + return rc; +} + +__maybe_unused int ksu_kprobe_exit(void) +{ + unregister_kprobe(&prctl_kp); + unregister_kprobe(&renameat_kp); + return 0; +} + +static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + ksu_handle_prctl(option, arg2, arg3, arg4, arg5); + return -ENOSYS; +} +// kernel 4.4 and 4.9 +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND) +static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred, + unsigned perm) +{ + if (init_session_keyring != NULL) { + return 0; + } + if (strcmp(current->comm, "init")) { + // we are only interested in `init` process + return 0; + } + init_session_keyring = cred->session_keyring; + pr_info("kernel_compat: got init_session_keyring\n"); + return 0; +} +#endif +static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return ksu_handle_rename(old_dentry, new_dentry); +} + +static int ksu_task_fix_setuid(struct cred *new, const struct cred *old, + int flags) +{ + return ksu_handle_setuid(new, old); +} + +#ifndef MODULE +static struct security_hook_list ksu_hooks[] = { + LSM_HOOK_INIT(task_prctl, ksu_task_prctl), + LSM_HOOK_INIT(inode_rename, ksu_inode_rename), + LSM_HOOK_INIT(task_fix_setuid, ksu_task_fix_setuid), +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND) + LSM_HOOK_INIT(key_permission, ksu_key_permission) +#endif +}; + +void __init ksu_lsm_hook_init(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks), "ksu"); +#else + // https://elixir.bootlin.com/linux/v4.10.17/source/include/linux/lsm_hooks.h#L1892 + security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks)); +#endif +} + +#else +static int override_security_head(void *head, const void *new_head, size_t len) +{ + unsigned long base = (unsigned long)head & PAGE_MASK; + unsigned long offset = offset_in_page(head); + + // this is impossible for our case because the page alignment + // but be careful for other cases! + BUG_ON(offset + len > PAGE_SIZE); + struct page *page = phys_to_page(__pa(base)); + if (!page) { + return -EFAULT; + } + + void *addr = vmap(&page, 1, VM_MAP, PAGE_KERNEL); + if (!addr) { + return -ENOMEM; + } + local_irq_disable(); + memcpy(addr + offset, new_head, len); + local_irq_enable(); + vunmap(addr); + return 0; +} + +static void free_security_hook_list(struct hlist_head *head) +{ + struct hlist_node *temp; + struct security_hook_list *entry; + + if (!head) + return; + + hlist_for_each_entry_safe (entry, temp, head, list) { + hlist_del(&entry->list); + kfree(entry); + } + + kfree(head); +} + +struct hlist_head *copy_security_hlist(struct hlist_head *orig) +{ + struct hlist_head *new_head = kmalloc(sizeof(*new_head), GFP_KERNEL); + if (!new_head) + return NULL; + + INIT_HLIST_HEAD(new_head); + + struct security_hook_list *entry; + struct security_hook_list *new_entry; + + hlist_for_each_entry (entry, orig, list) { + new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) { + free_security_hook_list(new_head); + return NULL; + } + + *new_entry = *entry; + + hlist_add_tail_rcu(&new_entry->list, new_head); + } + + return new_head; +} + +#define LSM_SEARCH_MAX 180 // This should be enough to iterate +static void *find_head_addr(void *security_ptr, int *index) +{ + if (!security_ptr) { + return NULL; + } + struct hlist_head *head_start = + (struct hlist_head *)&security_hook_heads; + + for (int i = 0; i < LSM_SEARCH_MAX; i++) { + struct hlist_head *head = head_start + i; + struct security_hook_list *pos; + hlist_for_each_entry (pos, head, list) { + if (pos->hook.capget == security_ptr) { + if (index) { + *index = i; + } + return head; + } + } + } + + return NULL; +} + +#define GET_SYMBOL_ADDR(sym) \ + ({ \ + void *addr = kallsyms_lookup_name(#sym ".cfi_jt"); \ + if (!addr) { \ + addr = kallsyms_lookup_name(#sym); \ + } \ + addr; \ + }) + +#define KSU_LSM_HOOK_HACK_INIT(head_ptr, name, func) \ + do { \ + static struct security_hook_list hook = { \ + .hook = { .name = func } \ + }; \ + hook.head = head_ptr; \ + hook.lsm = "ksu"; \ + struct hlist_head *new_head = copy_security_hlist(hook.head); \ + if (!new_head) { \ + pr_err("Failed to copy security list: %s\n", #name); \ + break; \ + } \ + hlist_add_tail_rcu(&hook.list, new_head); \ + if (override_security_head(hook.head, new_head, \ + sizeof(*new_head))) { \ + free_security_hook_list(new_head); \ + pr_err("Failed to hack lsm for: %s\n", #name); \ + } \ + } while (0) + +void __init ksu_lsm_hook_init(void) +{ + void *cap_prctl = GET_SYMBOL_ADDR(cap_task_prctl); + void *prctl_head = find_head_addr(cap_prctl, NULL); + if (prctl_head) { + if (prctl_head != &security_hook_heads.task_prctl) { + pr_warn("prctl's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(prctl_head, task_prctl, ksu_task_prctl); + } else { + pr_warn("Failed to find task_prctl!\n"); + } + + int inode_killpriv_index = -1; + void *cap_killpriv = GET_SYMBOL_ADDR(cap_inode_killpriv); + find_head_addr(cap_killpriv, &inode_killpriv_index); + if (inode_killpriv_index < 0) { + pr_warn("Failed to find inode_rename, use kprobe instead!\n"); + register_kprobe(&renameat_kp); + } else { + int inode_rename_index = inode_killpriv_index + + &security_hook_heads.inode_rename - + &security_hook_heads.inode_killpriv; + struct hlist_head *head_start = + (struct hlist_head *)&security_hook_heads; + void *inode_rename_head = head_start + inode_rename_index; + if (inode_rename_head != &security_hook_heads.inode_rename) { + pr_warn("inode_rename's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(inode_rename_head, inode_rename, + ksu_inode_rename); + } + void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid); + void *setuid_head = find_head_addr(cap_setuid, NULL); + if (setuid_head) { + if (setuid_head != &security_hook_heads.task_fix_setuid) { + pr_warn("setuid's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(setuid_head, task_fix_setuid, + ksu_task_fix_setuid); + } else { + pr_warn("Failed to find task_fix_setuid!\n"); + } + smp_mb(); +} +#endif + +void __init ksu_core_init(void) +{ + ksu_lsm_hook_init(); +} + +void ksu_core_exit(void) +{ +#ifdef KSU_HOOK_WITH_KPROBES + pr_info("ksu_core_kprobe_exit\n"); + // we dont use this now + // ksu_kprobe_exit(); +#endif +} diff --git a/kernel/core_hook.h b/kernel/core_hook.h new file mode 100644 index 00000000..616951e8 --- /dev/null +++ b/kernel/core_hook.h @@ -0,0 +1,9 @@ +#ifndef __KSU_H_KSU_CORE +#define __KSU_H_KSU_CORE + +#include + +void __init ksu_core_init(void); +void ksu_core_exit(void); + +#endif diff --git a/kernel/embed_ksud.c b/kernel/embed_ksud.c new file mode 100644 index 00000000..24c40121 --- /dev/null +++ b/kernel/embed_ksud.c @@ -0,0 +1,5 @@ +// WARNING: THIS IS A STUB FILE +// This file will be regenerated by CI + +unsigned int ksud_size = 0; +const char ksud[0] = {}; diff --git a/kernel/export_symbol.txt b/kernel/export_symbol.txt new file mode 100644 index 00000000..1abd805e --- /dev/null +++ b/kernel/export_symbol.txt @@ -0,0 +1,2 @@ +register_kprobe +unregister_kprobe diff --git a/kernel/include/ksu_hook.h b/kernel/include/ksu_hook.h new file mode 100644 index 00000000..ea0b04d3 --- /dev/null +++ b/kernel/include/ksu_hook.h @@ -0,0 +1,28 @@ +#ifndef __KSU_H_KSHOOK +#define __KSU_H_KSHOOK + +#include +#include + +// For sucompat + +int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *flags); + +int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); + +// For ksud + +int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, + size_t *count_ptr, loff_t **pos); + +// For ksud and sucompat + +int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags); + +// For volume button +int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, + int *value); + +#endif diff --git a/kernel/kernel_compat.c b/kernel/kernel_compat.c new file mode 100644 index 00000000..47d8f5e3 --- /dev/null +++ b/kernel/kernel_compat.c @@ -0,0 +1,185 @@ +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#include +#else +#include +#endif +#include +#include "klog.h" // IWYU pragma: keep +#include "kernel_compat.h" // Add check Huawei Device +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND) +#include +#include +#include +struct key *init_session_keyring = NULL; + +static inline int install_session_keyring(struct key *keyring) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + ret = install_session_keyring_to_cred(new, keyring); + if (ret < 0) { + abort_creds(new); + return ret; + } + + return commit_creds(new); +} +#endif + +extern struct task_struct init_task; + +// mnt_ns context switch for environment that android_init->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns, such as WSA +struct ksu_ns_fs_saved { + struct nsproxy *ns; + struct fs_struct *fs; +}; + +static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved) +{ + ns_fs_saved->ns = current->nsproxy; + ns_fs_saved->fs = current->fs; +} + +static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved) +{ + current->nsproxy = ns_fs_saved->ns; + current->fs = ns_fs_saved->fs; +} + +static bool android_context_saved_checked = false; +static bool android_context_saved_enabled = false; +static struct ksu_ns_fs_saved android_context_saved; + +void ksu_android_ns_fs_check() +{ + if (android_context_saved_checked) + return; + android_context_saved_checked = true; + task_lock(current); + if (current->nsproxy && current->fs && + current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) { + android_context_saved_enabled = true; + pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n", + current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns); + ksu_save_ns_fs(&android_context_saved); + } else { + pr_info("android context saved disabled\n"); + } + task_unlock(current); +} + +int ksu_access_ok(const void *addr, unsigned long size) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0) + /* For kernels before 5.0.0, pass the type argument to access_ok. */ + return access_ok(VERIFY_READ, addr, size); +#else + /* For kernels 5.0.0 and later, ignore the type argument. */ + return access_ok(addr, size); +#endif +} + +struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND) + if (init_session_keyring != NULL && !current_cred()->session_keyring && + (current->flags & PF_WQ_WORKER)) { + pr_info("installing init session keyring for older kernel\n"); + install_session_keyring(init_session_keyring); + } +#endif + // switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns + struct ksu_ns_fs_saved saved; + if (android_context_saved_enabled) { + pr_info("start switch current nsproxy and fs to android context\n"); + task_lock(current); + ksu_save_ns_fs(&saved); + ksu_load_ns_fs(&android_context_saved); + task_unlock(current); + } + struct file *fp = filp_open(filename, flags, mode); + if (android_context_saved_enabled) { + task_lock(current); + ksu_load_ns_fs(&saved); + task_unlock(current); + pr_info("switch current nsproxy and fs back to saved successfully\n"); + } + return fp; +} + +ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, + loff_t *pos) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) || defined(KSU_KERNEL_READ) + return kernel_read(p, buf, count, pos); +#else + loff_t offset = pos ? *pos : 0; + ssize_t result = kernel_read(p, offset, (char *)buf, count); + if (pos && result > 0) { + *pos = offset + result; + } + return result; +#endif +} + +ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, + loff_t *pos) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) || defined(KSU_KERNEL_WRITE) + return kernel_write(p, buf, count, pos); +#else + loff_t offset = pos ? *pos : 0; + ssize_t result = kernel_write(p, buf, count, offset); + if (pos && result > 0) { + *pos = offset + result; + } + return result; +#endif +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) || defined(KSU_STRNCPY_FROM_USER_NOFAULT) +long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, + long count) +{ + return strncpy_from_user_nofault(dst, unsafe_addr, count); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0) +long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, + long count) +{ + return strncpy_from_unsafe_user(dst, unsafe_addr, count); +} +#else +// Copied from: https://elixir.bootlin.com/linux/v4.9.337/source/mm/maccess.c#L201 +long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, + long count) +{ + mm_segment_t old_fs = get_fs(); + long ret; + + if (unlikely(count <= 0)) + return 0; + + set_fs(USER_DS); + pagefault_disable(); + ret = strncpy_from_user(dst, unsafe_addr, count); + pagefault_enable(); + set_fs(old_fs); + + if (ret >= count) { + ret = count; + dst[ret - 1] = '\0'; + } else if (ret > 0) { + ret++; + } + + return ret; +} +#endif diff --git a/kernel/kernel_compat.h b/kernel/kernel_compat.h new file mode 100644 index 00000000..d4170355 --- /dev/null +++ b/kernel/kernel_compat.h @@ -0,0 +1,67 @@ +#ifndef __KSU_H_KERNEL_COMPAT +#define __KSU_H_KERNEL_COMPAT + +#include +#include +#include +#include "ss/policydb.h" +#include "linux/key.h" + +// for kernel without get_cred_rcu + #ifndef KSU_COMPAT_HAS_GET_CRED_RCU + static inline const struct cred *get_cred_rcu(const struct cred *cred) + { + struct cred *nonconst_cred = (struct cred *) cred; + if (!cred) + return NULL; + #ifdef KSU_COMPAT_ATOMIC_LONG + if (!atomic_long_inc_not_zero(&nonconst_cred->usage)) + #else + if (!atomic_inc_not_zero(&nonconst_cred->usage)) + #endif + return NULL; + validate_creds(cred); + nonconst_cred->non_rcu = 0; + return cred; + } + #endif + +/* + * Adapt to Huawei HISI kernel without affecting other kernels , + * Huawei Hisi Kernel EBITMAP Enable or Disable Flag , + * From ss/ebitmap.h + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) && \ + (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) || \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) && \ + (LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)) +#ifdef HISI_SELINUX_EBITMAP_RO +#define CONFIG_IS_HW_HISI +#endif +#endif + +// Checks for UH, KDP and RKP +#ifdef SAMSUNG_UH_DRIVER_EXIST +#if defined(CONFIG_UH) || defined(CONFIG_KDP) || defined(CONFIG_RKP) +#error "CONFIG_UH, CONFIG_KDP and CONFIG_RKP is enabled! Please disable or remove it before compile a kernel with KernelSU!" +#endif +#endif + +extern long ksu_strncpy_from_user_nofault(char *dst, + const void __user *unsafe_addr, + long count); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND) +extern struct key *init_session_keyring; +#endif + +extern void ksu_android_ns_fs_check(); +extern int ksu_access_ok(const void *addr, unsigned long size); +extern struct file *ksu_filp_open_compat(const char *filename, int flags, + umode_t mode); +extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, + loff_t *pos); +extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, + size_t count, loff_t *pos); + +#endif diff --git a/kernel/klog.h b/kernel/klog.h new file mode 100644 index 00000000..a934027f --- /dev/null +++ b/kernel/klog.h @@ -0,0 +1,11 @@ +#ifndef __KSU_H_KLOG +#define __KSU_H_KLOG + +#include + +#ifdef pr_fmt +#undef pr_fmt +#define pr_fmt(fmt) "KernelSU: " fmt +#endif + +#endif diff --git a/kernel/ksu.c b/kernel/ksu.c new file mode 100644 index 00000000..92e215df --- /dev/null +++ b/kernel/ksu.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "arch.h" +#include "core_hook.h" +#include "klog.h" // IWYU pragma: keep +#include "ksu.h" +#include "throne_tracker.h" + +#ifdef CONFIG_KSU_SUSFS +#include +#endif + +static struct workqueue_struct *ksu_workqueue; + +bool ksu_queue_work(struct work_struct *work) +{ + return queue_work(ksu_workqueue, work); +} + +extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); + +extern int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); + +int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags) +{ + ksu_handle_execveat_ksud(fd, filename_ptr, argv, envp, flags); + return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, + flags); +} + +extern void ksu_sucompat_init(); +extern void ksu_sucompat_exit(); +extern void ksu_ksud_init(); +extern void ksu_ksud_exit(); + +int __init ksu_kernelsu_init(void) +{ +#ifdef CONFIG_KSU_DEBUG + pr_alert("*************************************************************"); + pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); + pr_alert("** **"); + pr_alert("** You are running KernelSU in DEBUG mode **"); + pr_alert("** **"); + pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); + pr_alert("*************************************************************"); +#endif + +#ifdef CONFIG_KSU_SUSFS + susfs_init(); +#endif + + ksu_core_init(); + + ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0); + + ksu_allowlist_init(); + + ksu_throne_tracker_init(); + +#ifdef KSU_HOOK_WITH_KPROBES + ksu_sucompat_init(); + ksu_ksud_init(); +#else + pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html"); +#endif + +#ifdef MODULE +#ifndef CONFIG_KSU_DEBUG + kobject_del(&THIS_MODULE->mkobj.kobj); +#endif +#endif + return 0; +} + +void ksu_kernelsu_exit(void) +{ + ksu_allowlist_exit(); + + ksu_throne_tracker_exit(); + + destroy_workqueue(ksu_workqueue); + +#ifdef KSU_HOOK_WITH_KPROBES + ksu_ksud_exit(); + ksu_sucompat_exit(); +#endif + + ksu_core_exit(); +} + +module_init(ksu_kernelsu_init); +module_exit(ksu_kernelsu_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("weishu"); +MODULE_DESCRIPTION("Android KernelSU"); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) +MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); +#endif diff --git a/kernel/ksu.h b/kernel/ksu.h new file mode 100644 index 00000000..e5050024 --- /dev/null +++ b/kernel/ksu.h @@ -0,0 +1,101 @@ +#ifndef __KSU_H_KSU +#define __KSU_H_KSU + +#include +#include + +#define KERNEL_SU_VERSION KSU_VERSION +#define KERNEL_SU_OPTION 0xDEADBEEF + +#define CMD_GRANT_ROOT 0 +#define CMD_BECOME_MANAGER 1 +#define CMD_GET_VERSION 2 +#define CMD_ALLOW_SU 3 +#define CMD_DENY_SU 4 +#define CMD_GET_ALLOW_LIST 5 +#define CMD_GET_DENY_LIST 6 +#define CMD_REPORT_EVENT 7 +#define CMD_SET_SEPOLICY 8 +#define CMD_CHECK_SAFEMODE 9 +#define CMD_GET_APP_PROFILE 10 +#define CMD_SET_APP_PROFILE 11 +#define CMD_UID_GRANTED_ROOT 12 +#define CMD_UID_SHOULD_UMOUNT 13 +#define CMD_IS_SU_ENABLED 14 +#define CMD_ENABLE_SU 15 + +#define EVENT_POST_FS_DATA 1 +#define EVENT_BOOT_COMPLETED 2 +#define EVENT_MODULE_MOUNTED 3 + +#define KSU_APP_PROFILE_VER 2 +#define KSU_MAX_PACKAGE_NAME 256 +// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. +#define KSU_MAX_GROUPS 32 +#define KSU_SELINUX_DOMAIN 64 + +struct root_profile { + int32_t uid; + int32_t gid; + + int32_t groups_count; + int32_t groups[KSU_MAX_GROUPS]; + + // kernel_cap_t is u32[2] for capabilities v3 + struct { + u64 effective; + u64 permitted; + u64 inheritable; + } capabilities; + + char selinux_domain[KSU_SELINUX_DOMAIN]; + + int32_t namespaces; +}; + +struct non_root_profile { + bool umount_modules; +}; + +struct app_profile { + // It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this. + u32 version; + + // this is usually the package of the app, but can be other value for special apps + char key[KSU_MAX_PACKAGE_NAME]; + int32_t current_uid; + bool allow_su; + + union { + struct { + bool use_default; + char template_name[KSU_MAX_PACKAGE_NAME]; + + struct root_profile profile; + } rp_config; + + struct { + bool use_default; + + struct non_root_profile profile; + } nrp_config; + }; +}; + +bool ksu_queue_work(struct work_struct *work); + +static inline int startswith(char *s, char *prefix) +{ + return strncmp(s, prefix, strlen(prefix)); +} + +static inline int endswith(const char *s, const char *t) +{ + size_t slen = strlen(s); + size_t tlen = strlen(t); + if (tlen > slen) + return 1; + return strcmp(s + slen - tlen, t); +} + +#endif diff --git a/kernel/ksud.c b/kernel/ksud.c new file mode 100644 index 00000000..50f40bfd --- /dev/null +++ b/kernel/ksud.c @@ -0,0 +1,706 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) +#include +#else +#include +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) +#include +#endif +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "arch.h" +#include "klog.h" // IWYU pragma: keep +#include "ksud.h" +#include "kernel_compat.h" +#include "selinux/selinux.h" + +#define KERNEL_VERSION_5_10 KERNEL_VERSION(5, 10, 0) + +static const char KERNEL_SU_RC[] = + "\n" + "on post-fs-data\n" + " start logd\n" + " exec u:r:su:s0 root -- " KSUD_PATH " post-fs-data\n" + "\n" + "on nonencrypted\n" + " exec u:r:su:s0 root -- " KSUD_PATH " services\n" + "\n" + "on property:vold.decrypt=trigger_restart_framework\n" + " exec u:r:su:s0 root -- " KSUD_PATH " services\n" + "\n" + "on property:sys.boot_completed=1\n" + " exec u:r:su:s0 root -- " KSUD_PATH " boot-completed\n" + "\n"; + +static void stop_vfs_read_hook(); +static void stop_execve_hook(); +static void stop_input_hook(); + +#ifdef KSU_HOOK_WITH_KPROBES +static struct work_struct stop_vfs_read_work; +static struct work_struct stop_execve_hook_work; +static struct work_struct stop_input_hook_work; +#else +bool ksu_vfs_read_hook __read_mostly = true; +bool ksu_execveat_hook __read_mostly = true; +bool ksu_input_hook __read_mostly = true; +#endif + +#ifdef CONFIG_KSU_SUSFS_SUS_SU +bool ksu_devpts_hook = false; +bool susfs_is_sus_su_ready = false; +#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU + +u32 ksu_devpts_sid; + +// Detect whether it is on or not +static bool is_boot_phase = true; + +void ksu_on_post_fs_data(void) +{ + static bool done = false; + if (done) { + pr_info("ksu_on_post_fs_data already done\n"); + return; + } + done = true; + pr_info("ksu_on_post_fs_data!\n"); + ksu_load_allow_list(); + stop_input_hook(); + + ksu_devpts_sid = ksu_get_devpts_sid(); + pr_info("devpts sid: %d\n", ksu_devpts_sid); + + // End of boot state + is_boot_phase = false; +} + +#define MAX_ARG_STRINGS 0x7FFFFFFF +struct user_arg_ptr { +#ifdef CONFIG_COMPAT + bool is_compat; +#endif + union { + const char __user *const __user *native; +#ifdef CONFIG_COMPAT + const compat_uptr_t __user *compat; +#endif + } ptr; +}; + +static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr) +{ + const char __user *native; + +#ifdef CONFIG_COMPAT + if (unlikely(argv.is_compat)) { + compat_uptr_t compat; + + if (get_user(compat, argv.ptr.compat + nr)) + return ERR_PTR(-EFAULT); + + return compat_ptr(compat); + } +#endif + + if (get_user(native, argv.ptr.native + nr)) + return ERR_PTR(-EFAULT); + + return native; +} + +/* + * count() counts the number of strings in array ARGV. + */ + +/* + * Make sure old GCC compiler can use __maybe_unused, + * Test passed in 4.4.x ~ 4.9.x when use GCC. + */ + +static int __maybe_unused count(struct user_arg_ptr argv, int max) +{ + int i = 0; + + if (argv.ptr.native != NULL) { + for (;;) { + const char __user *p = get_user_arg_ptr(argv, i); + + if (!p) + break; + + if (IS_ERR(p)) + return -EFAULT; + + if (i >= max) + return -E2BIG; + ++i; + + if (fatal_signal_pending(current)) + return -ERESTARTNOHAND; + cond_resched(); + } + } + return i; +} + +// IMPORTANT NOTE: the call from execve_handler_pre WON'T provided correct value for envp and flags in GKI version +int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, + struct user_arg_ptr *argv, + struct user_arg_ptr *envp, int *flags) +{ +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_execveat_hook) { + return 0; + } +#endif + struct filename *filename; + + static const char app_process[] = "/system/bin/app_process"; + static bool first_app_process = true; + + /* This applies to versions Android 10+ */ + static const char system_bin_init[] = "/system/bin/init"; + /* This applies to versions between Android 6 ~ 9 */ + static const char old_system_init[] = "/init"; + static bool init_second_stage_executed = false; + + if (!filename_ptr) + return 0; + + filename = *filename_ptr; + if (IS_ERR(filename)) { + return 0; + } + + if (unlikely(!memcmp(filename->name, system_bin_init, + sizeof(system_bin_init) - 1) && + argv)) { + // /system/bin/init executed + int argc = count(*argv, MAX_ARG_STRINGS); + pr_info("/system/bin/init argc: %d\n", argc); + if (argc > 1 && !init_second_stage_executed) { + const char __user *p = get_user_arg_ptr(*argv, 1); + if (p && !IS_ERR(p)) { + char first_arg[16]; + ksu_strncpy_from_user_nofault( + first_arg, p, sizeof(first_arg)); + pr_info("/system/bin/init first arg: %s\n", + first_arg); + if (!strcmp(first_arg, "second_stage")) { + pr_info("/system/bin/init second_stage executed\n"); + ksu_apply_kernelsu_rules(); + init_second_stage_executed = true; + ksu_android_ns_fs_check(); + } + } else { + pr_err("/system/bin/init parse args err!\n"); + } + } + } else if (unlikely(!memcmp(filename->name, old_system_init, + sizeof(old_system_init) - 1) && + argv)) { + // /init executed + int argc = count(*argv, MAX_ARG_STRINGS); + pr_info("/init argc: %d\n", argc); + if (argc > 1 && !init_second_stage_executed) { + /* This applies to versions between Android 6 ~ 7 */ + const char __user *p = get_user_arg_ptr(*argv, 1); + if (p && !IS_ERR(p)) { + char first_arg[16]; + ksu_strncpy_from_user_nofault( + first_arg, p, sizeof(first_arg)); + pr_info("/init first arg: %s\n", first_arg); + if (!strcmp(first_arg, "--second-stage")) { + pr_info("/init second_stage executed\n"); + ksu_apply_kernelsu_rules(); + init_second_stage_executed = true; + ksu_android_ns_fs_check(); + } + } else { + pr_err("/init parse args err!\n"); + } + } else if (argc == 1 && !init_second_stage_executed && envp) { + /* This applies to versions between Android 8 ~ 9 */ + int envc = count(*envp, MAX_ARG_STRINGS); + if (envc > 0) { + int n; + for (n = 1; n <= envc; n++) { + const char __user *p = + get_user_arg_ptr(*envp, n); + if (!p || IS_ERR(p)) { + continue; + } + char env[256]; + // Reading environment variable strings from user space + if (ksu_strncpy_from_user_nofault( + env, p, sizeof(env)) < 0) + continue; + // Parsing environment variable names and values + char *env_name = env; + char *env_value = strchr(env, '='); + if (env_value == NULL) + continue; + // Replace equal sign with string terminator + *env_value = '\0'; + env_value++; + // Check if the environment variable name and value are matching + if (!strcmp(env_name, + "INIT_SECOND_STAGE") && + (!strcmp(env_value, "1") || + !strcmp(env_value, "true"))) { + pr_info("/init second_stage executed\n"); + ksu_apply_kernelsu_rules(); + init_second_stage_executed = + true; + ksu_android_ns_fs_check(); + } + } + } + } + } + + if (unlikely(first_app_process && !memcmp(filename->name, app_process, + sizeof(app_process) - 1))) { + first_app_process = false; + pr_info("exec app_process, /data prepared, second_stage: %d\n", + init_second_stage_executed); + ksu_on_post_fs_data(); // we keep this for old ksud + stop_execve_hook(); + } + + return 0; +} + +static ssize_t (*orig_read)(struct file *, char __user *, size_t, loff_t *); +static ssize_t (*orig_read_iter)(struct kiocb *, struct iov_iter *); +static struct file_operations fops_proxy; +static ssize_t read_count_append = 0; + +static ssize_t read_proxy(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + bool first_read = file->f_pos == 0; + ssize_t ret = orig_read(file, buf, count, pos); + if (first_read) { + pr_info("read_proxy append %ld + %ld\n", ret, + read_count_append); + ret += read_count_append; + } + return ret; +} + +static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to) +{ + bool first_read = iocb->ki_pos == 0; + ssize_t ret = orig_read_iter(iocb, to); + if (first_read) { + pr_info("read_iter_proxy append %ld + %ld\n", ret, + read_count_append); + ret += read_count_append; + } + return ret; +} + +int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, + size_t *count_ptr, loff_t **pos) +{ +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_vfs_read_hook) { + return 0; + } +#endif + struct file *file; + char __user *buf; + size_t count; + + if (strcmp(current->comm, "init")) { + // we are only interest in `init` process + return 0; + } + + file = *file_ptr; + if (IS_ERR(file)) { + return 0; + } + + if (!d_is_reg(file->f_path.dentry)) { + return 0; + } + + const char *short_name = file->f_path.dentry->d_name.name; + if (strcmp(short_name, "atrace.rc")) { + // we are only interest `atrace.rc` file name file + return 0; + } + char path[256]; + char *dpath = d_path(&file->f_path, path, sizeof(path)); + + if (IS_ERR(dpath)) { + return 0; + } + + if (strcmp(dpath, "/system/etc/init/atrace.rc")) { + return 0; + } + + // we only process the first read + static bool rc_inserted = false; + if (rc_inserted) { + // we don't need this kprobe, unregister it! + stop_vfs_read_hook(); + return 0; + } + rc_inserted = true; + + // now we can sure that the init process is reading + // `/system/etc/init/atrace.rc` + buf = *buf_ptr; + count = *count_ptr; + + size_t rc_count = strlen(KERNEL_SU_RC); + + pr_info("vfs_read: %s, comm: %s, count: %zu, rc_count: %zu\n", dpath, + current->comm, count, rc_count); + + if (count < rc_count) { + pr_err("count: %zu < rc_count: %zu\n", count, rc_count); + return 0; + } + + size_t ret = copy_to_user(buf, KERNEL_SU_RC, rc_count); + if (ret) { + pr_err("copy ksud.rc failed: %zu\n", ret); + return 0; + } + + // we've succeed to insert ksud.rc, now we need to proxy the read and modify the result! + // But, we can not modify the file_operations directly, because it's in read-only memory. + // We just replace the whole file_operations with a proxy one. + memcpy(&fops_proxy, file->f_op, sizeof(struct file_operations)); + orig_read = file->f_op->read; + if (orig_read) { + fops_proxy.read = read_proxy; + } + orig_read_iter = file->f_op->read_iter; + if (orig_read_iter) { + fops_proxy.read_iter = read_iter_proxy; + } + // replace the file_operations + file->f_op = &fops_proxy; + read_count_append = rc_count; + + *buf_ptr = buf + rc_count; + *count_ptr = count - rc_count; + + return 0; +} + +int ksu_handle_sys_read(unsigned int fd, char __user **buf_ptr, + size_t *count_ptr) +{ + struct file *file = fget(fd); + if (!file) { + return 0; + } + int result = ksu_handle_vfs_read(&file, buf_ptr, count_ptr, NULL); + fput(file); + return result; +} + +static unsigned int volumedown_pressed_count = 0; + +static bool is_volumedown_enough(unsigned int count) +{ + return count >= 3; +} + +int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, + int *value) +{ +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_input_hook) { + return 0; + } +#endif + if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) { + int val = *value; + pr_info("KEY_VOLUMEDOWN val: %d\n", val); + if (val && is_boot_phase) { // Accumulates only during the power-up phase + volumedown_pressed_count += 1; + if (is_volumedown_enough(volumedown_pressed_count)) { + stop_input_hook(); + } + } + } + + return 0; +} + +bool ksu_is_safe_mode() +{ + static bool safe_mode = false; + if (safe_mode) { + // don't need to check again, userspace may call multiple times + return true; + } + + // stop hook first! + stop_input_hook(); + + pr_info("volumedown_pressed_count: %d\n", volumedown_pressed_count); + if (is_volumedown_enough(volumedown_pressed_count)) { + // pressed over 3 times + pr_info("KEY_VOLUMEDOWN pressed max times, safe mode detected!\n"); + safe_mode = true; + return true; + } + + return false; +} + +#ifdef KSU_HOOK_WITH_KPROBES + +static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + int *fd = (int *)&PT_REGS_PARM1(regs); + struct filename **filename_ptr = + (struct filename **)&PT_REGS_PARM2(regs); + struct user_arg_ptr argv; +#ifdef CONFIG_COMPAT + argv.is_compat = PT_REGS_PARM3(regs); + if (unlikely(argv.is_compat)) { + argv.ptr.compat = PT_REGS_CCALL_PARM4(regs); + } else { + argv.ptr.native = PT_REGS_CCALL_PARM4(regs); + } +#else + argv.ptr.native = PT_REGS_PARM3(regs); +#endif + + return ksu_handle_execveat_ksud(fd, filename_ptr, &argv, NULL, NULL); +} + +static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM1(real_regs); + const char __user *const __user *__argv = + (const char __user *const __user *)PT_REGS_PARM2(real_regs); + struct user_arg_ptr argv = { .ptr.native = __argv }; + struct filename filename_in, *filename_p; + char path[32]; + + if (!filename_user) + return 0; + + memset(path, 0, sizeof(path)); + ksu_strncpy_from_user_nofault(path, *filename_user, 32); + filename_in.name = path; + + filename_p = &filename_in; + return ksu_handle_execveat_ksud(AT_FDCWD, &filename_p, &argv, NULL, + NULL); +} + +__maybe_unused static int vfs_read_handler_pre(struct kprobe *p, + struct pt_regs *regs) +{ + struct file **file_ptr = (struct file **)&PT_REGS_PARM1(regs); + char __user **buf_ptr = (char **)&PT_REGS_PARM2(regs); + size_t *count_ptr = (size_t *)&PT_REGS_PARM3(regs); + loff_t **pos_ptr = (loff_t **)&PT_REGS_CCALL_PARM4(regs); + + return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr); +} + +static int sys_read_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + unsigned int fd = PT_REGS_PARM1(real_regs); + char __user **buf_ptr = (char __user **)&PT_REGS_PARM2(real_regs); + size_t count_ptr = (size_t *)&PT_REGS_PARM3(real_regs); + + return ksu_handle_sys_read(fd, buf_ptr, count_ptr); +} + +static int input_handle_event_handler_pre(struct kprobe *p, + struct pt_regs *regs) +{ + unsigned int *type = (unsigned int *)&PT_REGS_PARM2(regs); + unsigned int *code = (unsigned int *)&PT_REGS_PARM3(regs); + int *value = (int *)&PT_REGS_CCALL_PARM4(regs); + return ksu_handle_input_handle_event(type, code, value); +} + +#if 1 +static struct kprobe execve_kp = { + .symbol_name = SYS_EXECVE_SYMBOL, + .pre_handler = sys_execve_handler_pre, +}; +#else +static struct kprobe execve_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + .symbol_name = "do_execveat_common", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + .symbol_name = "__do_execve_file", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) + .symbol_name = "do_execveat_common", +#endif + .pre_handler = execve_handler_pre, +}; +#endif + +#if 1 +static struct kprobe vfs_read_kp = { + .symbol_name = SYS_READ_SYMBOL, + .pre_handler = sys_read_handler_pre, +}; +#else +static struct kprobe vfs_read_kp = { + .symbol_name = "vfs_read", + .pre_handler = vfs_read_handler_pre, +}; +#endif + +static struct kprobe input_event_kp = { + .symbol_name = "input_event", + .pre_handler = input_handle_event_handler_pre, +}; + +static void do_stop_vfs_read_hook(struct work_struct *work) +{ + unregister_kprobe(&vfs_read_kp); +} + +static void do_stop_execve_hook(struct work_struct *work) +{ + unregister_kprobe(&execve_kp); +} + +static void do_stop_input_hook(struct work_struct *work) +{ + unregister_kprobe(&input_event_kp); +} +#else + /* + * ksu_handle_execve_ksud, execve_ksud handler for non kprobe + * adapted from sys_execve_handler_pre + * https://github.com/tiann/KernelSU/commit/2027ac3 + */ + __maybe_unused int ksu_handle_execve_ksud(const char __user *filename_user, + const char __user *const __user *__argv) + { + struct user_arg_ptr argv = { .ptr.native = __argv }; + struct filename filename_in, *filename_p; + char path[32]; + + // return early if disabled. + if (!ksu_execveat_hook) { + return 0; + } + + if (!filename_user) + return 0; + + memset(path, 0, sizeof(path)); + ksu_strncpy_from_user_nofault(path, filename_user, 32); + + // this is because ksu_handle_execveat_ksud calls it filename->name + filename_in.name = path; + filename_p = &filename_in; + + return ksu_handle_execveat_ksud(AT_FDCWD, &filename_p, &argv, NULL, NULL); + } +#endif + +static void stop_vfs_read_hook() +{ +#ifdef KSU_HOOK_WITH_KPROBES + bool ret = schedule_work(&stop_vfs_read_work); + pr_info("unregister vfs_read kprobe: %d!\n", ret); +#else + ksu_vfs_read_hook = false; + pr_info("stop vfs_read_hook\n"); +#endif +} + +static void stop_execve_hook() +{ +#ifdef KSU_HOOK_WITH_KPROBES + bool ret = schedule_work(&stop_execve_hook_work); + pr_info("unregister execve kprobe: %d!\n", ret); +#else + ksu_execveat_hook = false; + pr_info("stop execve_hook\n"); +#endif +#ifdef CONFIG_KSU_SUSFS_SUS_SU + susfs_is_sus_su_ready = true; + pr_info("susfs: sus_su is ready\n"); +#endif +} + +static void stop_input_hook() +{ +#ifdef KSU_HOOK_WITH_KPROBES + static bool input_hook_stopped = false; + if (input_hook_stopped) { + return; + } + input_hook_stopped = true; + + bool ret = schedule_work(&stop_input_hook_work); + pr_info("unregister input kprobe: %d!\n", ret); +#else + if (!ksu_input_hook) { return; } + ksu_input_hook = false; + pr_info("stop input_hook\n"); +#endif +} + +// ksud: module support +void ksu_ksud_init() +{ +#ifdef KSU_HOOK_WITH_KPROBES + int ret; + + ret = register_kprobe(&execve_kp); + pr_info("ksud: execve_kp: %d\n", ret); + + ret = register_kprobe(&vfs_read_kp); + pr_info("ksud: vfs_read_kp: %d\n", ret); + + ret = register_kprobe(&input_event_kp); + pr_info("ksud: input_event_kp: %d\n", ret); + + INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook); + INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook); + INIT_WORK(&stop_input_hook_work, do_stop_input_hook); +#endif +} + +void ksu_ksud_exit() +{ +#ifdef KSU_HOOK_WITH_KPROBES + unregister_kprobe(&execve_kp); + // this should be done before unregister vfs_read_kp + // unregister_kprobe(&vfs_read_kp); + unregister_kprobe(&input_event_kp); +#endif + is_boot_phase = false; + volumedown_pressed_count = 0; +} \ No newline at end of file diff --git a/kernel/ksud.h b/kernel/ksud.h new file mode 100644 index 00000000..26974c9c --- /dev/null +++ b/kernel/ksud.h @@ -0,0 +1,14 @@ +#ifndef __KSU_H_KSUD +#define __KSU_H_KSUD + +#include + +#define KSUD_PATH "/data/adb/ksud" + +void ksu_on_post_fs_data(void); + +bool ksu_is_safe_mode(void); + +extern u32 ksu_devpts_sid; + +#endif diff --git a/kernel/manager.h b/kernel/manager.h new file mode 100644 index 00000000..93fa2678 --- /dev/null +++ b/kernel/manager.h @@ -0,0 +1,36 @@ +#ifndef __KSU_H_KSU_MANAGER +#define __KSU_H_KSU_MANAGER + +#include +#include + +#define KSU_INVALID_UID -1 + +extern uid_t ksu_manager_uid; // DO NOT DIRECT USE + +static inline bool ksu_is_manager_uid_valid() +{ + return ksu_manager_uid != KSU_INVALID_UID; +} + +static inline bool ksu_is_manager() +{ + return unlikely(ksu_manager_uid == current_uid().val); +} + +static inline uid_t ksu_get_manager_uid() +{ + return ksu_manager_uid; +} + +static inline void ksu_set_manager_uid(uid_t uid) +{ + ksu_manager_uid = uid; +} + +static inline void ksu_invalidate_manager_uid() +{ + ksu_manager_uid = KSU_INVALID_UID; +} + +#endif diff --git a/kernel/manager_sign.h b/kernel/manager_sign.h new file mode 100644 index 00000000..cab4fd01 --- /dev/null +++ b/kernel/manager_sign.h @@ -0,0 +1,24 @@ +#ifndef MANAGER_SIGN_H +#define MANAGER_SIGN_H + +// rsuntk/KernelSU +#define EXPECTED_SIZE_RSUNTK 0x396 +#define EXPECTED_HASH_RSUNTK "f415f4ed9435427e1fdf7f1fccd4dbc07b3d6b8751e4dbcec6f19671f427870b" + +// 5ec1cff/KernelSU +#define EXPECTED_SIZE_5EC1CFF 384 +#define EXPECTED_HASH_5EC1CFF "7e0c6d7278a3bb8e364e0fcba95afaf3666cf5ff3c245a3b63c8833bd0445cc4" + +// ShirkNeko/KernelSU +#define EXPECTED_SIZE_SHIRKNEKO 0x35c +#define EXPECTED_HASH_SHIRKNEKO "947ae944f3de4ed4c21a7e4f7953ecf351bfa2b36239da37a34111ad29993eef" + +// weishu/KernelSU +#define EXPECTED_SIZE_WEISHU 0x033b +#define EXPECTED_HASH_WEISHU "c371061b19d8c7d7d6133c6a9bafe198fa944e50c1b31c9d8daa8d7f1fc2d2d6" + +// Neko/KernelSU +#define EXPECTED_SIZE_NEKO 0x29c +#define EXPECTED_HASH_NEKO "946b0557e450a6430a0ba6b6bccee5bc12953ec8735d55e26139b0ec12303b21" + +#endif /* MANAGER_SIGN_H */ diff --git a/kernel/selinux/Makefile b/kernel/selinux/Makefile new file mode 100644 index 00000000..870750be --- /dev/null +++ b/kernel/selinux/Makefile @@ -0,0 +1,16 @@ +obj-y += selinux.o +obj-y += sepolicy.o +obj-y += rules.o + +ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID +endif + +ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE +endif + +ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion +ccflags-y += -Wno-declaration-after-statement -Wno-unused-function +ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include +ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h diff --git a/kernel/selinux/rules.c b/kernel/selinux/rules.c new file mode 100644 index 00000000..0532996b --- /dev/null +++ b/kernel/selinux/rules.c @@ -0,0 +1,483 @@ +#include +#include +#include + +#include "../klog.h" // IWYU pragma: keep +#include "selinux.h" +#include "sepolicy.h" +#include "ss/services.h" +#include "linux/lsm_audit.h" +#include "xfrm.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +#define SELINUX_POLICY_INSTEAD_SELINUX_SS +#endif + +#define KERNEL_SU_DOMAIN "su" +#define KERNEL_SU_FILE "ksu_file" +#define KERNEL_EXEC_TYPE "ksu_exec" +#define ALL NULL + +static struct policydb *get_policydb(void) +{ + struct policydb *db; +// selinux_state does not exists before 4.19 +#ifdef KSU_COMPAT_USE_SELINUX_STATE +#ifdef SELINUX_POLICY_INSTEAD_SELINUX_SS + struct selinux_policy *policy = rcu_dereference(selinux_state.policy); + db = &policy->policydb; +#else + struct selinux_ss *ss = rcu_dereference(selinux_state.ss); + db = &ss->policydb; +#endif +#else + db = &policydb; +#endif + return db; +} + +void ksu_apply_kernelsu_rules() +{ + if (!ksu_getenforce()) { + pr_info("SELinux permissive or disabled, apply rules!\n"); + } + + rcu_read_lock(); + struct policydb *db = get_policydb(); + + ksu_permissive(db, KERNEL_SU_DOMAIN); + ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject"); + ksu_typeattribute(db, KERNEL_SU_DOMAIN, "netdomain"); + ksu_typeattribute(db, KERNEL_SU_DOMAIN, "bluetoothdomain"); + + // Create unconstrained file type + ksu_type(db, KERNEL_SU_FILE, "file_type"); + ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject"); + ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL); + + // allow all! + ksu_allow(db, KERNEL_SU_DOMAIN, ALL, ALL, ALL); + + // allow us do any ioctl + if (db->policyvers >= POLICYDB_VERSION_XPERMS_IOCTL) { + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "blk_file", ALL); + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "fifo_file", ALL); + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL); + ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "file", ALL); + } + + // we need to save allowlist in /data/adb/ksu + ksu_allow(db, "kernel", "adb_data_file", "dir", ALL); + ksu_allow(db, "kernel", "adb_data_file", "file", ALL); + // we need to search /data/app + ksu_allow(db, "kernel", "apk_data_file", "file", "open"); + ksu_allow(db, "kernel", "apk_data_file", "dir", "open"); + ksu_allow(db, "kernel", "apk_data_file", "dir", "read"); + ksu_allow(db, "kernel", "apk_data_file", "dir", "search"); + // we may need to do mount on shell + ksu_allow(db, "kernel", "shell_data_file", "file", ALL); + // we need to read /data/system/packages.list + ksu_allow(db, "kernel", "kernel", "capability", "dac_override"); + // Android 10+: + // http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512 + ksu_allow(db, "kernel", "packages_list_file", "file", ALL); + // Kernel 4.4 + ksu_allow(db, "kernel", "packages_list_file", "dir", ALL); + // Android 9-: + // http://aospxref.com/android-9.0.0_r61/xref/system/sepolicy/private/file_contexts#360 + ksu_allow(db, "kernel", "system_data_file", "file", ALL); + ksu_allow(db, "kernel", "system_data_file", "dir", ALL); + // our ksud triggered by init + ksu_allow(db, "init", "adb_data_file", "file", ALL); + ksu_allow(db, "init", "adb_data_file", "dir", ALL); // #1289 + ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL); + // we need to umount modules in zygote + ksu_allow(db, "zygote", "adb_data_file", "dir", "search"); + + // copied from Magisk rules + // suRights + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "search"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "read"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "open"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "read"); + ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "process", "getattr"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "process", "sigchld"); + + // allowLog + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "dir", "search"); + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "read"); + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "open"); + ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "getattr"); + + // dumpsys + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fd", "use"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "write"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "read"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "open"); + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "getattr"); + + // bootctl + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "dir", "search"); + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "read"); + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "open"); + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process", + "getattr"); + + // For mounting loop devices, mirrors, tmpfs + ksu_allow(db, "kernel", ALL, "file", "read"); + ksu_allow(db, "kernel", ALL, "file", "write"); + + // Allow all binder transactions + ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL); + + // Allow system server kill su process + ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid"); + ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill"); + +#ifdef CONFIG_KSU_SUSFS + // Allow umount in zygote process without installing zygisk + ksu_allow(db, "zygote", "labeledfs", "filesystem", "unmount"); + susfs_set_init_sid(); + susfs_set_ksu_sid(); + susfs_set_zygote_sid(); +#endif + + rcu_read_unlock(); +} + +#define MAX_SEPOL_LEN 128 + +#define CMD_NORMAL_PERM 1 +#define CMD_XPERM 2 +#define CMD_TYPE_STATE 3 +#define CMD_TYPE 4 +#define CMD_TYPE_ATTR 5 +#define CMD_ATTR 6 +#define CMD_TYPE_TRANSITION 7 +#define CMD_TYPE_CHANGE 8 +#define CMD_GENFSCON 9 + +struct sepol_data { + u32 cmd; + u32 subcmd; + char __user *sepol1; + char __user *sepol2; + char __user *sepol3; + char __user *sepol4; + char __user *sepol5; + char __user *sepol6; + char __user *sepol7; +}; + +static int get_object(char *buf, char __user *user_object, size_t buf_sz, + char **object) +{ + if (!user_object) { + *object = ALL; + return 0; + } + + if (strncpy_from_user(buf, user_object, buf_sz) < 0) { + return -1; + } + + *object = buf; + + return 0; +} + +// reset avc cache table, otherwise the new rules will not take effect if already denied +static void reset_avc_cache() +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) || !defined(KSU_COMPAT_USE_SELINUX_STATE) + avc_ss_reset(0); + selnl_notify_policyload(0); + selinux_status_update_policyload(0); +#else + struct selinux_avc *avc = selinux_state.avc; + avc_ss_reset(avc, 0); + selnl_notify_policyload(0); + selinux_status_update_policyload(&selinux_state, 0); +#endif + selinux_xfrm_notify_policyload(); +} + +int ksu_handle_sepolicy(unsigned long arg3, void __user *arg4) +{ + if (!arg4) { + return -1; + } + + if (!ksu_getenforce()) { + pr_info("SELinux permissive or disabled when handle policy!\n"); + } + + struct sepol_data data; + if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) { + pr_err("sepol: copy sepol_data failed.\n"); + return -1; + } + + u32 cmd = data.cmd; + u32 subcmd = data.subcmd; + + rcu_read_lock(); + + struct policydb *db = get_policydb(); + + int ret = -1; + if (cmd == CMD_NORMAL_PERM) { + char src_buf[MAX_SEPOL_LEN]; + char tgt_buf[MAX_SEPOL_LEN]; + char cls_buf[MAX_SEPOL_LEN]; + char perm_buf[MAX_SEPOL_LEN]; + + char *s, *t, *c, *p; + if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + + if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + + if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + + if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) < + 0) { + pr_err("sepol: copy perm failed.\n"); + goto exit; + } + + bool success = false; + if (subcmd == 1) { + success = ksu_allow(db, s, t, c, p); + } else if (subcmd == 2) { + success = ksu_deny(db, s, t, c, p); + } else if (subcmd == 3) { + success = ksu_auditallow(db, s, t, c, p); + } else if (subcmd == 4) { + success = ksu_dontaudit(db, s, t, c, p); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + ret = success ? 0 : -1; + + } else if (cmd == CMD_XPERM) { + char src_buf[MAX_SEPOL_LEN]; + char tgt_buf[MAX_SEPOL_LEN]; + char cls_buf[MAX_SEPOL_LEN]; + + char __maybe_unused + operation[MAX_SEPOL_LEN]; // it is always ioctl now! + char perm_set[MAX_SEPOL_LEN]; + + char *s, *t, *c; + if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + if (strncpy_from_user(operation, data.sepol4, + sizeof(operation)) < 0) { + pr_err("sepol: copy operation failed.\n"); + goto exit; + } + if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) < + 0) { + pr_err("sepol: copy perm_set failed.\n"); + goto exit; + } + + bool success = false; + if (subcmd == 1) { + success = ksu_allowxperm(db, s, t, c, perm_set); + } else if (subcmd == 2) { + success = ksu_auditallowxperm(db, s, t, c, perm_set); + } else if (subcmd == 3) { + success = ksu_dontauditxperm(db, s, t, c, perm_set); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + ret = success ? 0 : -1; + } else if (cmd == CMD_TYPE_STATE) { + char src[MAX_SEPOL_LEN]; + + if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + + bool success = false; + if (subcmd == 1) { + success = ksu_permissive(db, src); + } else if (subcmd == 2) { + success = ksu_enforce(db, src); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + if (success) + ret = 0; + + } else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) { + char type[MAX_SEPOL_LEN]; + char attr[MAX_SEPOL_LEN]; + + if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) { + pr_err("sepol: copy type failed.\n"); + goto exit; + } + if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) { + pr_err("sepol: copy attr failed.\n"); + goto exit; + } + + bool success = false; + if (cmd == CMD_TYPE) { + success = ksu_type(db, type, attr); + } else { + success = ksu_typeattribute(db, type, attr); + } + if (!success) { + pr_err("sepol: %d failed.\n", cmd); + goto exit; + } + ret = 0; + + } else if (cmd == CMD_ATTR) { + char attr[MAX_SEPOL_LEN]; + + if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) { + pr_err("sepol: copy attr failed.\n"); + goto exit; + } + if (!ksu_attribute(db, attr)) { + pr_err("sepol: %d failed.\n", cmd); + goto exit; + } + ret = 0; + + } else if (cmd == CMD_TYPE_TRANSITION) { + char src[MAX_SEPOL_LEN]; + char tgt[MAX_SEPOL_LEN]; + char cls[MAX_SEPOL_LEN]; + char default_type[MAX_SEPOL_LEN]; + char object[MAX_SEPOL_LEN]; + + if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + if (strncpy_from_user(default_type, data.sepol4, + sizeof(default_type)) < 0) { + pr_err("sepol: copy default_type failed.\n"); + goto exit; + } + char *real_object; + if (data.sepol5 == NULL) { + real_object = NULL; + } else { + if (strncpy_from_user(object, data.sepol5, + sizeof(object)) < 0) { + pr_err("sepol: copy object failed.\n"); + goto exit; + } + real_object = object; + } + + bool success = ksu_type_transition(db, src, tgt, cls, + default_type, real_object); + if (success) + ret = 0; + + } else if (cmd == CMD_TYPE_CHANGE) { + char src[MAX_SEPOL_LEN]; + char tgt[MAX_SEPOL_LEN]; + char cls[MAX_SEPOL_LEN]; + char default_type[MAX_SEPOL_LEN]; + + if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { + pr_err("sepol: copy src failed.\n"); + goto exit; + } + if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) { + pr_err("sepol: copy tgt failed.\n"); + goto exit; + } + if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) { + pr_err("sepol: copy cls failed.\n"); + goto exit; + } + if (strncpy_from_user(default_type, data.sepol4, + sizeof(default_type)) < 0) { + pr_err("sepol: copy default_type failed.\n"); + goto exit; + } + bool success = false; + if (subcmd == 1) { + success = ksu_type_change(db, src, tgt, cls, + default_type); + } else if (subcmd == 2) { + success = ksu_type_member(db, src, tgt, cls, + default_type); + } else { + pr_err("sepol: unknown subcmd: %d\n", subcmd); + } + if (success) + ret = 0; + } else if (cmd == CMD_GENFSCON) { + char name[MAX_SEPOL_LEN]; + char path[MAX_SEPOL_LEN]; + char context[MAX_SEPOL_LEN]; + if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) { + pr_err("sepol: copy name failed.\n"); + goto exit; + } + if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) { + pr_err("sepol: copy path failed.\n"); + goto exit; + } + if (strncpy_from_user(context, data.sepol3, sizeof(context)) < + 0) { + pr_err("sepol: copy context failed.\n"); + goto exit; + } + + if (!ksu_genfscon(db, name, path, context)) { + pr_err("sepol: %d failed.\n", cmd); + goto exit; + } + ret = 0; + } else { + pr_err("sepol: unknown cmd: %d\n", cmd); + } + +exit: + rcu_read_unlock(); + + // only allow and xallow needs to reset avc cache, but we cannot do that because + // we are in atomic context. so we just reset it every time. + reset_avc_cache(); + + return ret; +} diff --git a/kernel/selinux/selinux.c b/kernel/selinux/selinux.c new file mode 100644 index 00000000..e171e010 --- /dev/null +++ b/kernel/selinux/selinux.c @@ -0,0 +1,230 @@ +#include "selinux.h" +#include "objsec.h" +#include "linux/version.h" +#include "../klog.h" // IWYU pragma: keep +#ifndef KSU_COMPAT_USE_SELINUX_STATE +#include "avc.h" +#endif + +#define KERNEL_SU_DOMAIN "u:r:su:s0" + +#ifdef CONFIG_KSU_SUSFS +#define KERNEL_INIT_DOMAIN "u:r:init:s0" +#define KERNEL_ZYGOTE_DOMAIN "u:r:zygote:s0" +u32 susfs_ksu_sid = 0; +u32 susfs_init_sid = 0; +u32 susfs_zygote_sid = 0; +#endif + +static int transive_to_domain(const char *domain) +{ + struct cred *cred; + struct task_security_struct *tsec; + u32 sid; + int error; + + cred = (struct cred *)__task_cred(current); + + tsec = cred->security; + if (!tsec) { + pr_err("tsec == NULL!\n"); + return -1; + } + + error = security_secctx_to_secid(domain, strlen(domain), &sid); + if (error) { + pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n", + domain, sid, error); + } + if (!error) { + tsec->sid = sid; + tsec->create_sid = 0; + tsec->keycreate_sid = 0; + tsec->sockcreate_sid = 0; + } + return error; +} + +void ksu_setup_selinux(const char *domain) +{ + if (transive_to_domain(domain)) { + pr_err("transive domain failed.\n"); + return; + } + + /* we didn't need this now, we have change selinux rules when boot! +if (!is_domain_permissive) { + if (set_domain_permissive() == 0) { + is_domain_permissive = true; + } +}*/ +} + +void ksu_setenforce(bool enforce) +{ +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +#ifdef KSU_COMPAT_USE_SELINUX_STATE + selinux_state.enforcing = enforce; +#else + selinux_enforcing = enforce; +#endif +#endif +} + +bool ksu_getenforce() +{ +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +#ifdef KSU_COMPAT_USE_SELINUX_STATE + if (selinux_state.disabled) { +#else + if (selinux_disabled) { +#endif + return false; + } +#endif + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +#ifdef KSU_COMPAT_USE_SELINUX_STATE + return selinux_state.enforcing; +#else + return selinux_enforcing; +#endif +#else + return true; +#endif +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \ + !defined(KSU_COMPAT_HAS_CURRENT_SID) +/* + * get the subjective security ID of the current task + */ +static inline u32 current_sid(void) +{ + const struct task_security_struct *tsec = current_security(); + + return tsec->sid; +} +#endif + +bool ksu_is_ksu_domain() +{ + char *domain; + u32 seclen; + bool result; + int err = security_secid_to_secctx(current_sid(), &domain, &seclen); + if (err) { + return false; + } + result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0; + security_release_secctx(domain, seclen); + return result; +} + +bool ksu_is_zygote(void *sec) +{ + struct task_security_struct *tsec = (struct task_security_struct *)sec; + if (!tsec) { + return false; + } + char *domain; + u32 seclen; + bool result; + int err = security_secid_to_secctx(tsec->sid, &domain, &seclen); + if (err) { + return false; + } + result = strncmp("u:r:zygote:s0", domain, seclen) == 0; + security_release_secctx(domain, seclen); + return result; +} + +#ifdef CONFIG_KSU_SUSFS +static inline void susfs_set_sid(const char *secctx_name, u32 *out_sid) +{ + int err; + + if (!secctx_name || !out_sid) { + pr_err("secctx_name || out_sid is NULL\n"); + return; + } + + err = security_secctx_to_secid(secctx_name, strlen(secctx_name), + out_sid); + if (err) { + pr_err("failed setting sid for '%s', err: %d\n", secctx_name, err); + return; + } + pr_info("sid '%u' is set for secctx_name '%s'\n", *out_sid, secctx_name); +} + +bool susfs_is_sid_equal(void *sec, u32 sid2) { + struct task_security_struct *tsec = (struct task_security_struct *)sec; + if (!tsec) { + return false; + } + return tsec->sid == sid2; +} + +u32 susfs_get_sid_from_name(const char *secctx_name) +{ + u32 out_sid = 0; + int err; + + if (!secctx_name) { + pr_err("secctx_name is NULL\n"); + return 0; + } + err = security_secctx_to_secid(secctx_name, strlen(secctx_name), + &out_sid); + if (err) { + pr_err("failed getting sid from secctx_name: %s, err: %d\n", secctx_name, err); + return 0; + } + return out_sid; +} + +u32 susfs_get_current_sid(void) { + return current_sid(); +} + +void susfs_set_zygote_sid(void) +{ + susfs_set_sid(KERNEL_ZYGOTE_DOMAIN, &susfs_zygote_sid); +} + +bool susfs_is_current_zygote_domain(void) { + return unlikely(current_sid() == susfs_zygote_sid); +} + +void susfs_set_ksu_sid(void) +{ + susfs_set_sid(KERNEL_SU_DOMAIN, &susfs_ksu_sid); +} + +bool susfs_is_current_ksu_domain(void) { + return unlikely(current_sid() == susfs_ksu_sid); +} + +void susfs_set_init_sid(void) +{ + susfs_set_sid(KERNEL_INIT_DOMAIN, &susfs_init_sid); +} + +bool susfs_is_current_init_domain(void) { + return unlikely(current_sid() == susfs_init_sid); +} +#endif + +#define DEVPTS_DOMAIN "u:object_r:ksu_file:s0" + +u32 ksu_get_devpts_sid() +{ + u32 devpts_sid = 0; + int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN), + &devpts_sid); + if (err) { + pr_info("get devpts sid err %d\n", err); + } + return devpts_sid; +} diff --git a/kernel/selinux/selinux.h b/kernel/selinux/selinux.h new file mode 100644 index 00000000..d0dfdf9c --- /dev/null +++ b/kernel/selinux/selinux.h @@ -0,0 +1,37 @@ +#ifndef __KSU_H_SELINUX +#define __KSU_H_SELINUX + +#include "linux/types.h" +#include "linux/version.h" + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) || defined(KSU_COMPAT_HAS_SELINUX_STATE) +#define KSU_COMPAT_USE_SELINUX_STATE +#endif + +void ksu_setup_selinux(const char *); + +void ksu_setenforce(bool); + +bool ksu_getenforce(); + +bool ksu_is_ksu_domain(); + +bool ksu_is_zygote(void *cred); + +void ksu_apply_kernelsu_rules(); + +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT +bool susfs_is_sid_equal(void *sec, u32 sid2); +u32 susfs_get_sid_from_name(const char *secctx_name); +u32 susfs_get_current_sid(void); +void susfs_set_zygote_sid(void); +bool susfs_is_current_zygote_domain(void); +void susfs_set_ksu_sid(void); +bool susfs_is_current_ksu_domain(void); +void susfs_set_init_sid(void); +bool susfs_is_current_init_domain(void); +#endif + +u32 ksu_get_devpts_sid(); + +#endif diff --git a/kernel/selinux/sepolicy.c b/kernel/selinux/sepolicy.c new file mode 100644 index 00000000..acdc45ad --- /dev/null +++ b/kernel/selinux/sepolicy.c @@ -0,0 +1,1070 @@ +#include +#include +#include +#include + +#include "sepolicy.h" +#include "../klog.h" // IWYU pragma: keep +#include "ss/symtab.h" +#include "../kernel_compat.h" // Add check Huawei Device + +#define KSU_SUPPORT_ADD_TYPE + +////////////////////////////////////////////////////// +// Declaration +////////////////////////////////////////////////////// + +static struct avtab_node *get_avtab_node(struct policydb *db, + struct avtab_key *key, + struct avtab_extended_perms *xperms); + +static bool add_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *p, int effect, bool invert); + +static void add_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + struct perm_datum *perm, int effect, bool invert); + +static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + uint16_t low, uint16_t high, int effect, + bool invert); +static bool add_xperm_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *range, int effect, + bool invert); + +static bool add_type_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *d, int effect); + +static bool add_filename_trans(struct policydb *db, const char *s, + const char *t, const char *c, const char *d, + const char *o); + +static bool add_genfscon(struct policydb *db, const char *fs_name, + const char *path, const char *context); + +static bool add_type(struct policydb *db, const char *type_name, bool attr); + +static bool set_type_state(struct policydb *db, const char *type_name, + bool permissive); + +static void add_typeattribute_raw(struct policydb *db, struct type_datum *type, + struct type_datum *attr); + +static bool add_typeattribute(struct policydb *db, const char *type, + const char *attr); + +////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////// + +// Invert is adding rules for auditdeny; in other cases, invert is removing +// rules +#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert) + +#define ksu_hash_for_each(node_ptr, n_slot, cur) \ + int i; \ + for (i = 0; i < n_slot; ++i) \ + for (cur = node_ptr[i]; cur; cur = cur->next) + +// htable is a struct instead of pointer above 5.8.0: +// https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) +#define ksu_hashtab_for_each(htab, cur) \ + ksu_hash_for_each(htab.htable, htab.size, cur) +#else +#define ksu_hashtab_for_each(htab, cur) \ + ksu_hash_for_each(htab->htable, htab->size, cur) +#endif + +// symtab_search is introduced on 5.9.0: +// https://elixir.bootlin.com/linux/v5.9-rc1/source/security/selinux/ss/symtab.h +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) +#define symtab_search(s, name) hashtab_search((s)->table, name) +#define symtab_insert(s, name, datum) hashtab_insert((s)->table, name, datum) +#endif + +#define avtab_for_each(avtab, cur) \ + ksu_hash_for_each(avtab.htable, avtab.nslot, cur); + +static struct avtab_node *get_avtab_node(struct policydb *db, + struct avtab_key *key, + struct avtab_extended_perms *xperms) +{ + struct avtab_node *node; + + /* AVTAB_XPERMS entries are not necessarily unique */ + if (key->specified & AVTAB_XPERMS) { + bool match = false; + node = avtab_search_node(&db->te_avtab, key); + while (node) { + if ((node->datum.u.xperms->specified == + xperms->specified) && + (node->datum.u.xperms->driver == xperms->driver)) { + match = true; + break; + } + node = avtab_search_node_next(node, key->specified); + } + if (!match) + node = NULL; + } else { + node = avtab_search_node(&db->te_avtab, key); + } + + if (!node) { + struct avtab_datum avdatum = {}; + /* + * AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for + * others. Initialize the data accordingly. + */ + if (key->specified & AVTAB_XPERMS) { + avdatum.u.xperms = xperms; + } else { + avdatum.u.data = + key->specified == AVTAB_AUDITDENY ? ~0U : 0U; + } + /* this is used to get the node - insertion is actually unique */ + node = avtab_insert_nonunique(&db->te_avtab, key, &avdatum); + + int grow_size = sizeof(struct avtab_key); + grow_size += sizeof(struct avtab_datum); + if (key->specified & AVTAB_XPERMS) { + grow_size += sizeof(u8); + grow_size += sizeof(u8); + grow_size += sizeof(u32) * + ARRAY_SIZE(avdatum.u.xperms->perms.p); + } + db->len += grow_size; + } + + return node; +} + +static bool add_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *p, int effect, bool invert) +{ + struct type_datum *src = NULL, *tgt = NULL; + struct class_datum *cls = NULL; + struct perm_datum *perm = NULL; + + if (s) { + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_info("source type %s does not exist\n", s); + return false; + } + } + + if (t) { + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_info("target type %s does not exist\n", t); + return false; + } + } + + if (c) { + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_info("class %s does not exist\n", c); + return false; + } + } + + if (p) { + if (c == NULL) { + pr_info("No class is specified, cannot add perm [%s] \n", + p); + return false; + } + + perm = symtab_search(&cls->permissions, p); + if (perm == NULL && cls->comdatum != NULL) { + perm = symtab_search(&cls->comdatum->permissions, p); + } + if (perm == NULL) { + pr_info("perm %s does not exist in class %s\n", p, c); + return false; + } + } + add_rule_raw(db, src, tgt, cls, perm, effect, invert); + return true; +} + +static void add_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + struct perm_datum *perm, int effect, bool invert) +{ + if (src == NULL) { + struct hashtab_node *node; + if (strip_av(effect, invert)) { + ksu_hashtab_for_each(db->p_types.table, node) + { + add_rule_raw(db, + (struct type_datum *)node->datum, + tgt, cls, perm, effect, invert); + }; + } else { + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_rule_raw(db, type, tgt, cls, perm, + effect, invert); + } + }; + } + } else if (tgt == NULL) { + struct hashtab_node *node; + if (strip_av(effect, invert)) { + ksu_hashtab_for_each(db->p_types.table, node) + { + add_rule_raw(db, src, + (struct type_datum *)node->datum, + cls, perm, effect, invert); + }; + } else { + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_rule_raw(db, src, type, cls, perm, + effect, invert); + } + }; + } + } else if (cls == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_classes.table, node) + { + add_rule_raw(db, src, tgt, + (struct class_datum *)node->datum, perm, + effect, invert); + } + } else { + struct avtab_key key; + key.source_type = src->value; + key.target_type = tgt->value; + key.target_class = cls->value; + key.specified = effect; + + struct avtab_node *node = get_avtab_node(db, &key, NULL); + if (invert) { + if (perm) + node->datum.u.data &= + ~(1U << (perm->value - 1)); + else + node->datum.u.data = 0U; + } else { + if (perm) + node->datum.u.data |= 1U << (perm->value - 1); + else + node->datum.u.data = ~0U; + } + } +} + +#define ioctl_driver(x) (x >> 8 & 0xFF) +#define ioctl_func(x) (x & 0xFF) + +#define xperm_test(x, p) (1 & (p[x >> 5] >> (x & 0x1f))) +#define xperm_set(x, p) (p[x >> 5] |= (1 << (x & 0x1f))) +#define xperm_clear(x, p) (p[x >> 5] &= ~(1 << (x & 0x1f))) + +static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src, + struct type_datum *tgt, struct class_datum *cls, + uint16_t low, uint16_t high, int effect, + bool invert) +{ + if (src == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_xperm_rule_raw(db, type, tgt, cls, low, + high, effect, invert); + } + }; + } else if (tgt == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_types.table, node) + { + struct type_datum *type = + (struct type_datum *)(node->datum); + if (type->attribute) { + add_xperm_rule_raw(db, src, type, cls, low, + high, effect, invert); + } + }; + } else if (cls == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_classes.table, node) + { + add_xperm_rule_raw(db, src, tgt, + (struct class_datum *)(node->datum), + low, high, effect, invert); + }; + } else { + struct avtab_key key; + key.source_type = src->value; + key.target_type = tgt->value; + key.target_class = cls->value; + key.specified = effect; + + struct avtab_datum *datum; + struct avtab_node *node; + struct avtab_extended_perms xperms; + + memset(&xperms, 0, sizeof(xperms)); + if (ioctl_driver(low) != ioctl_driver(high)) { + xperms.specified = AVTAB_XPERMS_IOCTLDRIVER; + xperms.driver = 0; + } else { + xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION; + xperms.driver = ioctl_driver(low); + } + int i; + if (xperms.specified == AVTAB_XPERMS_IOCTLDRIVER) { + for (i = ioctl_driver(low); i <= ioctl_driver(high); + ++i) { + if (invert) + xperm_clear(i, xperms.perms.p); + else + xperm_set(i, xperms.perms.p); + } + } else { + for (i = ioctl_func(low); i <= ioctl_func(high); ++i) { + if (invert) + xperm_clear(i, xperms.perms.p); + else + xperm_set(i, xperms.perms.p); + } + } + + node = get_avtab_node(db, &key, &xperms); + if (!node) { + pr_warn("add_xperm_rule_raw cannot found node!\n"); + return; + } + datum = &node->datum; + + if (datum->u.xperms == NULL) { + datum->u.xperms = + (struct avtab_extended_perms *)(kmalloc( + sizeof(xperms), GFP_KERNEL)); + if (!datum->u.xperms) { + pr_err("alloc xperms failed\n"); + return; + } + memcpy(datum->u.xperms, &xperms, sizeof(xperms)); + } + } +} + +static bool add_xperm_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *range, int effect, + bool invert) +{ + struct type_datum *src = NULL, *tgt = NULL; + struct class_datum *cls = NULL; + + if (s) { + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_info("source type %s does not exist\n", s); + return false; + } + } + + if (t) { + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_info("target type %s does not exist\n", t); + return false; + } + } + + if (c) { + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_info("class %s does not exist\n", c); + return false; + } + } + + u16 low, high; + + if (range) { + if (strchr(range, '-')) { + sscanf(range, "%hx-%hx", &low, &high); + } else { + sscanf(range, "%hx", &low); + high = low; + } + } else { + low = 0; + high = 0xFFFF; + } + + add_xperm_rule_raw(db, src, tgt, cls, low, high, effect, invert); + return true; +} + +static bool add_type_rule(struct policydb *db, const char *s, const char *t, + const char *c, const char *d, int effect) +{ + struct type_datum *src, *tgt, *def; + struct class_datum *cls; + + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_info("source type %s does not exist\n", s); + return false; + } + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_info("target type %s does not exist\n", t); + return false; + } + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_info("class %s does not exist\n", c); + return false; + } + def = symtab_search(&db->p_types, d); + if (def == NULL) { + pr_info("default type %s does not exist\n", d); + return false; + } + + struct avtab_key key; + key.source_type = src->value; + key.target_type = tgt->value; + key.target_class = cls->value; + key.specified = effect; + + struct avtab_node *node = get_avtab_node(db, &key, NULL); + node->datum.u.data = def->value; + + return true; +} + +// 5.9.0 : static inline int hashtab_insert(struct hashtab *h, void *key, void +// *datum, struct hashtab_key_params key_params) 5.8.0: int +// hashtab_insert(struct hashtab *h, void *k, void *d); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) +static u32 filenametr_hash(const void *k) +{ + const struct filename_trans_key *ft = k; + unsigned long hash; + unsigned int byte_num; + unsigned char focus; + + hash = ft->ttype ^ ft->tclass; + + byte_num = 0; + while ((focus = ft->name[byte_num++])) + hash = partial_name_hash(focus, hash); + return hash; +} + +static int filenametr_cmp(const void *k1, const void *k2) +{ + const struct filename_trans_key *ft1 = k1; + const struct filename_trans_key *ft2 = k2; + int v; + + v = ft1->ttype - ft2->ttype; + if (v) + return v; + + v = ft1->tclass - ft2->tclass; + if (v) + return v; + + return strcmp(ft1->name, ft2->name); +} + +static const struct hashtab_key_params filenametr_key_params = { + .hash = filenametr_hash, + .cmp = filenametr_cmp, +}; +#endif + +static bool add_filename_trans(struct policydb *db, const char *s, + const char *t, const char *c, const char *d, + const char *o) +{ + struct type_datum *src, *tgt, *def; + struct class_datum *cls; + + src = symtab_search(&db->p_types, s); + if (src == NULL) { + pr_warn("source type %s does not exist\n", s); + return false; + } + tgt = symtab_search(&db->p_types, t); + if (tgt == NULL) { + pr_warn("target type %s does not exist\n", t); + return false; + } + cls = symtab_search(&db->p_classes, c); + if (cls == NULL) { + pr_warn("class %s does not exist\n", c); + return false; + } + def = symtab_search(&db->p_types, d); + if (def == NULL) { + pr_warn("default type %s does not exist\n", d); + return false; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0) + struct filename_trans_key key; + key.ttype = tgt->value; + key.tclass = cls->value; + key.name = (char *)o; + + struct filename_trans_datum *last = NULL; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + struct filename_trans_datum *trans = + policydb_filenametr_search(db, &key); +#else + struct filename_trans_datum *trans = + hashtab_search(&db->filename_trans, &key); +#endif + while (trans) { + if (ebitmap_get_bit(&trans->stypes, src->value - 1)) { + // Duplicate, overwrite existing data and return + trans->otype = def->value; + return true; + } + if (trans->otype == def->value) + break; + last = trans; + trans = trans->next; + } + + if (trans == NULL) { + trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans), + 1, GFP_ATOMIC); + struct filename_trans_key *new_key = + (struct filename_trans_key *)kmalloc(sizeof(*new_key), + GFP_ATOMIC); + *new_key = key; + new_key->name = kstrdup(key.name, GFP_ATOMIC); + trans->next = last; + trans->otype = def->value; + hashtab_insert(&db->filename_trans, new_key, trans, + filenametr_key_params); + } + + db->compat_filename_trans_count++; + return ebitmap_set_bit(&trans->stypes, src->value - 1, 1) == 0; +#else // < 5.7.0, has no filename_trans_key, but struct filename_trans + + struct filename_trans key; + key.ttype = tgt->value; + key.tclass = cls->value; + key.name = (char *)o; + + struct filename_trans_datum *trans = + hashtab_search(db->filename_trans, &key); + + if (trans == NULL) { + trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans), + 1, GFP_ATOMIC); + if (!trans) { + pr_err("add_filename_trans: Failed to alloc datum\n"); + return false; + } + struct filename_trans *new_key = + (struct filename_trans *)kmalloc(sizeof(*new_key), + GFP_ATOMIC); + if (!new_key) { + pr_err("add_filename_trans: Failed to alloc new_key\n"); + return false; + } + *new_key = key; + new_key->name = kstrdup(key.name, GFP_ATOMIC); + trans->otype = def->value; + hashtab_insert(db->filename_trans, new_key, trans); + } + + return ebitmap_set_bit(&db->filename_trans_ttypes, src->value - 1, 1) == + 0; +#endif +} + +static bool add_genfscon(struct policydb *db, const char *fs_name, + const char *path, const char *context) +{ + return false; +} + +static void *ksu_realloc(void *old, size_t new_size, size_t old_size) +{ + // we can't use krealloc, because it may be read-only + void *new = kzalloc(new_size, GFP_ATOMIC); + if (!new) { + return NULL; + } + if (old_size) { + memcpy(new, old, old_size); + } + // we can't use kfree, because it may be read-only + // there maybe some leaks, maybe we can check ptr_write, but it's not a big deal + // kfree(old); + return new; +} + +static bool add_type(struct policydb *db, const char *type_name, bool attr) +{ +#ifdef KSU_SUPPORT_ADD_TYPE + struct type_datum *type = symtab_search(&db->p_types, type_name); + if (type) { + pr_warn("Type %s already exists\n", type_name); + return true; + } + + u32 value = ++db->p_types.nprim; + type = (struct type_datum *)kzalloc(sizeof(struct type_datum), + GFP_ATOMIC); + if (!type) { + pr_err("add_type: alloc type_datum failed.\n"); + return false; + } + + type->primary = 1; + type->value = value; + type->attribute = attr; + + char *key = kstrdup(type_name, GFP_ATOMIC); + if (!key) { + pr_err("add_type: alloc key failed.\n"); + return false; + } + + if (symtab_insert(&db->p_types, key, type)) { + pr_err("add_type: insert symtab failed.\n"); + return false; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) + struct ebitmap *new_type_attr_map_array = + ksu_realloc(db->type_attr_map_array, + value * sizeof(struct ebitmap), + (value - 1) * sizeof(struct ebitmap)); + + if (!new_type_attr_map_array) { + pr_err("add_type: alloc type_attr_map_array failed\n"); + return false; + } + + struct type_datum **new_type_val_to_struct = + ksu_realloc(db->type_val_to_struct, + sizeof(*db->type_val_to_struct) * value, + sizeof(*db->type_val_to_struct) * (value - 1)); + + if (!new_type_val_to_struct) { + pr_err("add_type: alloc type_val_to_struct failed\n"); + return false; + } + + char **new_val_to_name_types = + ksu_realloc(db->sym_val_to_name[SYM_TYPES], + sizeof(char *) * value, + sizeof(char *) * (value - 1)); + if (!new_val_to_name_types) { + pr_err("add_type: alloc val_to_name failed\n"); + return false; + } + + db->type_attr_map_array = new_type_attr_map_array; + ebitmap_init(&db->type_attr_map_array[value - 1]); + ebitmap_set_bit(&db->type_attr_map_array[value - 1], value - 1, 1); + + db->type_val_to_struct = new_type_val_to_struct; + db->type_val_to_struct[value - 1] = type; + + db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types; + db->sym_val_to_name[SYM_TYPES][value - 1] = key; + + int i; + for (i = 0; i < db->p_roles.nprim; ++i) { + ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, + 1); + } + + return true; +#elif defined(CONFIG_IS_HW_HISI) + /* + * Huawei use type_attr_map and type_val_to_struct. + * And use ebitmap not flex_array. + */ + size_t new_size = sizeof(struct ebitmap) * db->p_types.nprim; + struct ebitmap *new_type_attr_map = + (krealloc(db->type_attr_map, new_size, GFP_ATOMIC)); + + struct type_datum **new_type_val_to_struct = + krealloc(db->type_val_to_struct, + sizeof(*db->type_val_to_struct) * db->p_types.nprim, + GFP_ATOMIC); + + if (!new_type_attr_map) { + pr_err("add_type: alloc type_attr_map failed\n"); + return false; + } + + if (!new_type_val_to_struct) { + pr_err("add_type: alloc type_val_to_struct failed\n"); + return false; + } + + char **new_val_to_name_types = + krealloc(db->sym_val_to_name[SYM_TYPES], + sizeof(char *) * db->symtab[SYM_TYPES].nprim, + GFP_KERNEL); + if (!new_val_to_name_types) { + pr_err("add_type: alloc val_to_name failed\n"); + return false; + } + + db->type_attr_map = new_type_attr_map; + ebitmap_init(&db->type_attr_map[value - 1], HISI_SELINUX_EBITMAP_RO); + ebitmap_set_bit(&db->type_attr_map[value - 1], value - 1, 1); + + db->type_val_to_struct = new_type_val_to_struct; + db->type_val_to_struct[value - 1] = type; + + db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types; + db->sym_val_to_name[SYM_TYPES][value - 1] = key; + + int i; + for (i = 0; i < db->p_roles.nprim; ++i) { + ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, + 1); + } + + return true; +#else + // flex_array is not extensible, we need to create a new bigger one instead + struct flex_array *new_type_attr_map_array = + flex_array_alloc(sizeof(struct ebitmap), db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO); + + struct flex_array *new_type_val_to_struct = + flex_array_alloc(sizeof(struct type_datum *), db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO); + + struct flex_array *new_val_to_name_types = + flex_array_alloc(sizeof(char *), db->symtab[SYM_TYPES].nprim, + GFP_ATOMIC | __GFP_ZERO); + + if (!new_type_attr_map_array) { + pr_err("add_type: alloc type_attr_map_array failed\n"); + return false; + } + + if (!new_type_val_to_struct) { + pr_err("add_type: alloc type_val_to_struct failed\n"); + return false; + } + + if (!new_val_to_name_types) { + pr_err("add_type: alloc val_to_name failed\n"); + return false; + } + + // preallocate so we don't have to worry about the put ever failing + if (flex_array_prealloc(new_type_attr_map_array, 0, db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO)) { + pr_err("add_type: prealloc type_attr_map_array failed\n"); + return false; + } + + if (flex_array_prealloc(new_type_val_to_struct, 0, db->p_types.nprim, + GFP_ATOMIC | __GFP_ZERO)) { + pr_err("add_type: prealloc type_val_to_struct_array failed\n"); + return false; + } + + if (flex_array_prealloc(new_val_to_name_types, 0, + db->symtab[SYM_TYPES].nprim, + GFP_ATOMIC | __GFP_ZERO)) { + pr_err("add_type: prealloc val_to_name_types failed\n"); + return false; + } + + int j; + void *old_elem; + // copy the old data or pointers to new flex arrays + for (j = 0; j < db->type_attr_map_array->total_nr_elements; j++) { + old_elem = flex_array_get(db->type_attr_map_array, j); + if (old_elem) + flex_array_put(new_type_attr_map_array, j, old_elem, + GFP_ATOMIC | __GFP_ZERO); + } + + for (j = 0; j < db->type_val_to_struct_array->total_nr_elements; j++) { + old_elem = flex_array_get_ptr(db->type_val_to_struct_array, j); + if (old_elem) + flex_array_put_ptr(new_type_val_to_struct, j, old_elem, + GFP_ATOMIC | __GFP_ZERO); + } + + for (j = 0; j < db->symtab[SYM_TYPES].nprim; j++) { + old_elem = + flex_array_get_ptr(db->sym_val_to_name[SYM_TYPES], j); + if (old_elem) + flex_array_put_ptr(new_val_to_name_types, j, old_elem, + GFP_ATOMIC | __GFP_ZERO); + } + + // store the pointer of old flex arrays first, when assigning new ones we + // should free it + struct flex_array *old_fa; + + old_fa = db->type_attr_map_array; + db->type_attr_map_array = new_type_attr_map_array; + if (old_fa) { + flex_array_free(old_fa); + } + + ebitmap_init(flex_array_get(db->type_attr_map_array, value - 1)); + ebitmap_set_bit(flex_array_get(db->type_attr_map_array, value - 1), + value - 1, 1); + + old_fa = db->type_val_to_struct_array; + db->type_val_to_struct_array = new_type_val_to_struct; + if (old_fa) { + flex_array_free(old_fa); + } + flex_array_put_ptr(db->type_val_to_struct_array, value - 1, type, + GFP_ATOMIC | __GFP_ZERO); + + old_fa = db->sym_val_to_name[SYM_TYPES]; + db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types; + if (old_fa) { + flex_array_free(old_fa); + } + flex_array_put_ptr(db->sym_val_to_name[SYM_TYPES], value - 1, key, + GFP_ATOMIC | __GFP_ZERO); + + int i; + for (i = 0; i < db->p_roles.nprim; ++i) { + ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, + 1); + } + return true; +#endif + +#else + return false; +#endif +} + +static bool set_type_state(struct policydb *db, const char *type_name, + bool permissive) +{ + struct type_datum *type; + if (type_name == NULL) { + struct hashtab_node *node; + ksu_hashtab_for_each(db->p_types.table, node) + { + type = (struct type_datum *)(node->datum); + if (ebitmap_set_bit(&db->permissive_map, type->value, + permissive)) + pr_info("Could not set bit in permissive map\n"); + }; + } else { + type = (struct type_datum *)symtab_search(&db->p_types, + type_name); + if (type == NULL) { + pr_info("type %s does not exist\n", type_name); + return false; + } + if (ebitmap_set_bit(&db->permissive_map, type->value, + permissive)) { + pr_info("Could not set bit in permissive map\n"); + return false; + } + } + return true; +} + +static void add_typeattribute_raw(struct policydb *db, struct type_datum *type, + struct type_datum *attr) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) + struct ebitmap *sattr = &db->type_attr_map_array[type->value - 1]; +#elif defined(CONFIG_IS_HW_HISI) + /* + * HISI_SELINUX_EBITMAP_RO is Huawei's unique features. + */ + struct ebitmap *sattr = &db->type_attr_map[type->value - 1], + HISI_SELINUX_EBITMAP_RO; +#else + struct ebitmap *sattr = + flex_array_get(db->type_attr_map_array, type->value - 1); +#endif + ebitmap_set_bit(sattr, attr->value - 1, 1); + + struct hashtab_node *node; + struct constraint_node *n; + struct constraint_expr *e; + ksu_hashtab_for_each(db->p_classes.table, node) + { + struct class_datum *cls = (struct class_datum *)(node->datum); + for (n = cls->constraints; n; n = n->next) { + for (e = n->expr; e; e = e->next) { + if (e->expr_type == CEXPR_NAMES && + ebitmap_get_bit(&e->type_names->types, + attr->value - 1)) { + ebitmap_set_bit(&e->names, + type->value - 1, 1); + } + } + } + }; +} + +static bool add_typeattribute(struct policydb *db, const char *type, + const char *attr) +{ + struct type_datum *type_d = symtab_search(&db->p_types, type); + if (type_d == NULL) { + pr_info("type %s does not exist\n", type); + return false; + } else if (type_d->attribute) { + pr_info("type %s is an attribute\n", attr); + return false; + } + + struct type_datum *attr_d = symtab_search(&db->p_types, attr); + if (attr_d == NULL) { + pr_info("attribute %s does not exist\n", type); + return false; + } else if (!attr_d->attribute) { + pr_info("type %s is not an attribute \n", attr); + return false; + } + + add_typeattribute_raw(db, type_d, attr_d); + return true; +} + +////////////////////////////////////////////////////////////////////////// + +// Operation on types +bool ksu_type(struct policydb *db, const char *name, const char *attr) +{ + return add_type(db, name, false) && add_typeattribute(db, name, attr); +} + +bool ksu_attribute(struct policydb *db, const char *name) +{ + return add_type(db, name, true); +} + +bool ksu_permissive(struct policydb *db, const char *type) +{ + return set_type_state(db, type, true); +} + +bool ksu_enforce(struct policydb *db, const char *type) +{ + return set_type_state(db, type, false); +} + +bool ksu_typeattribute(struct policydb *db, const char *type, const char *attr) +{ + return add_typeattribute(db, type, attr); +} + +bool ksu_exists(struct policydb *db, const char *type) +{ + return symtab_search(&db->p_types, type) != NULL; +} + +// Access vector rules +bool ksu_allow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_ALLOWED, false); +} + +bool ksu_deny(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_ALLOWED, true); +} + +bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_AUDITALLOW, false); +} +bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm) +{ + return add_rule(db, src, tgt, cls, perm, AVTAB_AUDITDENY, true); +} + +// Extended permissions access vector rules +bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range) +{ + return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_ALLOWED, + false); +} + +bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range) +{ + return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_AUDITALLOW, + false); +} + +bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range) +{ + return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_DONTAUDIT, + false); +} + +// Type rules +bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def, const char *obj) +{ + if (obj) { + return add_filename_trans(db, src, tgt, cls, def, obj); + } else { + return add_type_rule(db, src, tgt, cls, def, AVTAB_TRANSITION); + } +} + +bool ksu_type_change(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def) +{ + return add_type_rule(db, src, tgt, cls, def, AVTAB_CHANGE); +} + +bool ksu_type_member(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def) +{ + return add_type_rule(db, src, tgt, cls, def, AVTAB_MEMBER); +} + +// File system labeling +bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path, + const char *ctx) +{ + return add_genfscon(db, fs_name, path, ctx); +} diff --git a/kernel/selinux/sepolicy.h b/kernel/selinux/sepolicy.h new file mode 100644 index 00000000..675d1499 --- /dev/null +++ b/kernel/selinux/sepolicy.h @@ -0,0 +1,46 @@ +#ifndef __KSU_H_SEPOLICY +#define __KSU_H_SEPOLICY + +#include + +#include "ss/policydb.h" + +// Operation on types +bool ksu_type(struct policydb *db, const char *name, const char *attr); +bool ksu_attribute(struct policydb *db, const char *name); +bool ksu_permissive(struct policydb *db, const char *type); +bool ksu_enforce(struct policydb *db, const char *type); +bool ksu_typeattribute(struct policydb *db, const char *type, const char *attr); +bool ksu_exists(struct policydb *db, const char *type); + +// Access vector rules +bool ksu_allow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_deny(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); + +// Extended permissions access vector rules +bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); +bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); +bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); + +// Type rules +bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def, const char *obj); +bool ksu_type_change(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def); +bool ksu_type_member(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def); + +// File system labeling +bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path, + const char *ctx); + +#endif diff --git a/kernel/setup.sh b/kernel/setup.sh new file mode 100644 index 00000000..c4a3f597 --- /dev/null +++ b/kernel/setup.sh @@ -0,0 +1,75 @@ +#!/bin/sh +set -eu + +GKI_ROOT=$(pwd) + +display_usage() { + echo "Usage: $0 [--cleanup | ]" + echo " --cleanup: Cleans up previous modifications made by the script." + echo " : Sets up or updates the KernelSU to specified tag or commit." + echo " -h, --help: Displays this usage information." + echo " (no args): Sets up or updates the KernelSU environment to the latest tagged version." +} + +initialize_variables() { + if test -d "$GKI_ROOT/common/drivers"; then + DRIVER_DIR="$GKI_ROOT/common/drivers" + elif test -d "$GKI_ROOT/drivers"; then + DRIVER_DIR="$GKI_ROOT/drivers" + else + echo '[ERROR] "drivers/" directory not found.' + exit 127 + fi + + DRIVER_MAKEFILE=$DRIVER_DIR/Makefile + DRIVER_KCONFIG=$DRIVER_DIR/Kconfig +} + +# Reverts modifications made by this script +perform_cleanup() { + echo "[+] Cleaning up..." + [ -L "$DRIVER_DIR/kernelsu" ] && rm "$DRIVER_DIR/kernelsu" && echo "[-] Symlink removed." + grep -q "kernelsu" "$DRIVER_MAKEFILE" && sed -i '/kernelsu/d' "$DRIVER_MAKEFILE" && echo "[-] Makefile reverted." + grep -q "drivers/kernelsu/Kconfig" "$DRIVER_KCONFIG" && sed -i '/drivers\/kernelsu\/Kconfig/d' "$DRIVER_KCONFIG" && echo "[-] Kconfig reverted." + if [ -d "$GKI_ROOT/KernelSU" ]; then + rm -rf "$GKI_ROOT/KernelSU" && echo "[-] KernelSU directory deleted." + fi +} + +# Sets up or update KernelSU environment +setup_kernelsu() { + echo "[+] Setting up KernelSU..." + test -d "$GKI_ROOT/KernelSU" || git clone https://github.com/ShirkNeko/KernelSU && echo "[+] Repository cloned." + cd "$GKI_ROOT/KernelSU" + git stash && echo "[-] Stashed current changes." + if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then + git checkout main && echo "[-] Switched to main branch." + fi + git pull && echo "[+] Repository updated." + if [ -z "${1-}" ]; then + git checkout "$(git describe --abbrev=0 --tags)" && echo "[-] Checked out latest tag." + else + git checkout "$1" && echo "[-] Checked out $1." || echo "[-] Checkout default branch" + fi + cd "$DRIVER_DIR" + ln -sf "$(realpath --relative-to="$DRIVER_DIR" "$GKI_ROOT/KernelSU/kernel")" "kernelsu" && echo "[+] Symlink created." + + # Add entries in Makefile and Kconfig if not already existing + grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" && echo "[+] Modified Makefile." + grep -q "source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" && echo "[+] Modified Kconfig." + echo '[+] Done.' +} + +# Process command-line arguments +if [ "$#" -eq 0 ]; then + initialize_variables + setup_kernelsu +elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + display_usage +elif [ "$1" = "--cleanup" ]; then + initialize_variables + perform_cleanup +else + initialize_variables + setup_kernelsu "$@" +fi diff --git a/kernel/sucompat.c b/kernel/sucompat.c new file mode 100644 index 00000000..bdd59f86 --- /dev/null +++ b/kernel/sucompat.c @@ -0,0 +1,441 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include +#else +#include +#endif + +#include "objsec.h" +#include "allowlist.h" +#include "arch.h" +#include "klog.h" // IWYU pragma: keep +#include "ksud.h" +#include "kernel_compat.h" + +#define SU_PATH "/system/bin/su" +#define SH_PATH "/system/bin/sh" + +bool ksu_faccessat_hook __read_mostly = true; +bool ksu_stat_hook __read_mostly = true; +bool ksu_execve_sucompat_hook __read_mostly = true; +bool ksu_execveat_sucompat_hook __read_mostly = true; +#ifndef CONFIG_KSU_SUSFS_SUS_SU +bool ksu_devpts_hook __read_mostly = true; +#endif + +extern void ksu_escape_to_root(); + +static void __user *userspace_stack_buffer(const void *d, size_t len) +{ + /* To avoid having to mmap a page in userspace, just write below the stack + * pointer. */ + char __user *p = (void __user *)current_user_stack_pointer() - len; + + return copy_to_user(p, d, len) ? NULL : p; +} + +static char __user *sh_user_path(void) +{ + static const char sh_path[] = SH_PATH; + return userspace_stack_buffer(sh_path, sizeof(sh_path)); +} + +static char __user *ksud_user_path(void) +{ + static const char ksud_path[] = KSUD_PATH; + + return userspace_stack_buffer(ksud_path, sizeof(ksud_path)); +} + +int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, int *__unused_flags) +{ + const char su[] = SU_PATH; + +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_faccessat_hook) { + return 0; + } +#endif + + if (!ksu_is_allow_uid(current_uid().val)) { + return 0; + } + + char path[sizeof(su) + 1]; + memset(path, 0, sizeof(path)); + ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); + + if (unlikely(!memcmp(path, su, sizeof(su)))) { + pr_info("faccessat su->sh!\n"); + *filename_user = sh_user_path(); + } + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) && defined(CONFIG_KSU_SUSFS_SUS_SU) +struct filename* susfs_ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) { + const char su[] = SU_PATH; + struct filename *name = getname_flags(*filename_user, getname_statx_lookup_flags(*flags), NULL); + + if (unlikely(IS_ERR(name) || name->name == NULL)) { + return name; + } + + if (!ksu_is_allow_uid(current_uid().val)) { + return name; + } + + if (likely(memcmp(name->name, su, sizeof(su)))) { + return name; + } + + const char sh[] = SH_PATH; + pr_info("vfs_fstatat su->sh!\n"); + memcpy((void *)name->name, sh, sizeof(sh)); + return name; +} +#endif + +int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) +{ + // const char sh[] = SH_PATH; + const char su[] = SU_PATH; + +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_stat_hook){ + return 0; + } +#endif + + if (!ksu_is_allow_uid(current_uid().val)) { + return 0; + } + + if (unlikely(!filename_user)) { + return 0; + } + + char path[sizeof(su) + 1]; + memset(path, 0, sizeof(path)); +// Remove this later!! we use syscall hook, so this will never happen!!!!! +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && 0 + // it becomes a `struct filename *` after 5.18 + // https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216 + const char sh[] = SH_PATH; + struct filename *filename = *((struct filename **)filename_user); + if (IS_ERR(filename)) { + return 0; + } + if (likely(memcmp(filename->name, su, sizeof(su)))) + return 0; + pr_info("vfs_statx su->sh!\n"); + memcpy((void *)filename->name, sh, sizeof(sh)); +#else + ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); + + if (unlikely(!memcmp(path, su, sizeof(su)))) { + pr_info("newfstatat su->sh!\n"); + *filename_user = sh_user_path(); + } +#endif + + return 0; +} + +int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, void *__never_use_argv, void *__never_use_envp, int *__never_use_flags) +{ + struct filename *filename; + const char su[] = SU_PATH; + const char ksud[] = KSUD_PATH; +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_execveat_sucompat_hook) { + return 0; + } +#endif + + if (unlikely(!filename_ptr)) { + return 0; + } + + filename = *filename_ptr; + + if (IS_ERR(filename)) { + return 0; + } + + if (likely(memcmp(filename->name, su, sizeof(su)))) + return 0; + + if (!ksu_is_allow_uid(current_uid().val)) + return 0; + + pr_info("do_execveat_common su found\n"); + memcpy((void *)filename->name, ksud, sizeof(ksud)); + + ksu_escape_to_root(); + + return 0; +} + +int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, void *__never_use_argv, void *__never_use_envp, int *__never_use_flags) +{ + const char su[] = SU_PATH; + char path[sizeof(su) + 1]; + +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_execve_sucompat_hook) { + return 0; + } +#endif + + if (unlikely(!filename_user)) + return 0; + + memset(path, 0, sizeof(path)); + ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); + + if (likely(memcmp(path, su, sizeof(su)))) + return 0; + + if (!ksu_is_allow_uid(current_uid().val)) + return 0; + + pr_info("sys_execve su found\n"); + *filename_user = ksud_user_path(); + + ksu_escape_to_root(); + + return 0; +} + +int ksu_handle_devpts(struct inode *inode) +{ +#ifndef KSU_HOOK_WITH_KPROBES + if (!ksu_devpts_hook) { + return 0; + } +#endif + + if (!current->mm) { + return 0; + } + + uid_t uid = current_uid().val; + if (uid % 100000 < 10000) { + return 0; + } + + if (!ksu_is_allow_uid(uid)) + return 0; + + if (ksu_devpts_sid) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) + struct inode_security_struct *sec = selinux_inode(inode); +#else + struct inode_security_struct *sec = (struct inode_security_struct *)inode->i_security; +#endif + if (sec) { + sec->sid = ksu_devpts_sid; + } + } + + return 0; +} + +#ifdef KSU_HOOK_WITH_KPROBES +static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + int *dfd = (int *)&PT_REGS_PARM1(real_regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM2(real_regs); + int *mode = (int *)&PT_REGS_PARM3(real_regs); + + return ksu_handle_faccessat(dfd, filename_user, mode, NULL); +} + +static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + int *dfd = (int *)&PT_REGS_PARM1(real_regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM2(real_regs); + int *flags = (int *)&PT_REGS_SYSCALL_PARM4(real_regs); + + return ksu_handle_stat(dfd, filename_user, flags); +} + +static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + const char __user **filename_user = + (const char **)&PT_REGS_PARM1(real_regs); + + return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL, + NULL); +} + +#if 1 +static struct kprobe faccessat_kp = { + .symbol_name = SYS_FACCESSAT_SYMBOL, + .pre_handler = faccessat_handler_pre, +}; +#else +static struct kprobe faccessat_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) + .symbol_name = "do_faccessat", +#else + .symbol_name = "sys_faccessat", +#endif + .pre_handler = faccessat_handler_pre, +}; +#endif + +#if 1 +static struct kprobe newfstatat_kp = { + .symbol_name = SYS_NEWFSTATAT_SYMBOL, + .pre_handler = newfstatat_handler_pre, +}; +#else +static struct kprobe newfstatat_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + .symbol_name = "vfs_statx", +#else + .symbol_name = "vfs_fstatat", +#endif + .pre_handler = newfstatat_handler_pre, +}; +#endif + +#if 1 +static struct kprobe execve_kp = { + .symbol_name = SYS_EXECVE_SYMBOL, + .pre_handler = execve_handler_pre, +}; +#else +static struct kprobe execve_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + .symbol_name = "do_execveat_common", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + .symbol_name = "__do_execve_file", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) + .symbol_name = "do_execveat_common", +#endif + .pre_handler = execve_handler_pre, +}; +#endif + +static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct inode *inode; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) + struct file *file = (struct file *)PT_REGS_PARM2(regs); + inode = file->f_path.dentry->d_inode; +#else + inode = (struct inode *)PT_REGS_PARM2(regs); +#endif + + return ksu_handle_devpts(inode); +} + +static struct kprobe pts_unix98_lookup_kp = { .symbol_name = + "pts_unix98_lookup", +.pre_handler = + pts_unix98_lookup_pre }; + +static struct kprobe *init_kprobe(const char *name, + kprobe_pre_handler_t handler) +{ + struct kprobe *kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL); + if (!kp) + return NULL; + kp->symbol_name = name; + kp->pre_handler = handler; + + int ret = register_kprobe(kp); + pr_info("sucompat: register_%s kprobe: %d\n", name, ret); + if (ret) { + kfree(kp); + return NULL; + } + + return kp; +} + +static void destroy_kprobe(struct kprobe **kp_ptr) +{ + struct kprobe *kp = *kp_ptr; + if (!kp) + return; + unregister_kprobe(kp); + synchronize_rcu(); + kfree(kp); + *kp_ptr = NULL; +} + +static struct kprobe *su_kps[4]; +#endif + +// sucompat: permited process can execute 'su' to gain root access. +void ksu_sucompat_init() +{ +#ifdef KSU_HOOK_WITH_KPROBES + su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre); + su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre); + su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre); + su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre); +#else + ksu_faccessat_hook = true; + ksu_stat_hook = true; + ksu_execve_sucompat_hook = true; + ksu_execveat_sucompat_hook = true; + ksu_devpts_hook = true; + pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat, devpts\n"); +#endif +} + +void ksu_sucompat_exit() +{ +#ifdef KSU_HOOK_WITH_KPROBES + for (int i = 0; i < ARRAY_SIZE(su_kps); i++) { + destroy_kprobe(&su_kps[i]); + } +#else + ksu_faccessat_hook = false; + ksu_stat_hook = false; + ksu_execve_sucompat_hook = false; + ksu_execveat_sucompat_hook = false; + ksu_devpts_hook = false; + pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat, devpts\n"); +#endif +} + +#ifdef CONFIG_KSU_SUSFS_SUS_SU +extern bool ksu_devpts_hook; + +void ksu_susfs_disable_sus_su(void) { + enable_kprobe(&execve_kp); + enable_kprobe(&newfstatat_kp); + enable_kprobe(&faccessat_kp); + enable_kprobe(&pts_unix98_lookup_kp); + ksu_devpts_hook = false; +} + +void ksu_susfs_enable_sus_su(void) { + disable_kprobe(&execve_kp); + disable_kprobe(&newfstatat_kp); + disable_kprobe(&faccessat_kp); + disable_kprobe(&pts_unix98_lookup_kp); + ksu_devpts_hook = true; +} +#endif // CONFIG_KSU_SUSFS_SUS_SU diff --git a/kernel/throne_tracker.c b/kernel/throne_tracker.c new file mode 100644 index 00000000..7b4c776d --- /dev/null +++ b/kernel/throne_tracker.c @@ -0,0 +1,388 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "klog.h" // IWYU pragma: keep +#include "ksu.h" +#include "manager.h" +#include "throne_tracker.h" +#include "kernel_compat.h" + +uid_t ksu_manager_uid = KSU_INVALID_UID; + +#define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list.tmp" + +struct uid_data { + struct list_head list; + u32 uid; + char package[KSU_MAX_PACKAGE_NAME]; +}; + +static int get_pkg_from_apk_path(char *pkg, const char *path) +{ + int len = strlen(path); + if (len >= KSU_MAX_PACKAGE_NAME || len < 1) + return -1; + + const char *last_slash = NULL; + const char *second_last_slash = NULL; + + int i; + for (i = len - 1; i >= 0; i--) { + if (path[i] == '/') { + if (!last_slash) { + last_slash = &path[i]; + } else { + second_last_slash = &path[i]; + break; + } + } + } + + if (!last_slash || !second_last_slash) + return -1; + + const char *last_hyphen = strchr(second_last_slash, '-'); + if (!last_hyphen || last_hyphen > last_slash) + return -1; + + int pkg_len = last_hyphen - second_last_slash - 1; + if (pkg_len >= KSU_MAX_PACKAGE_NAME || pkg_len <= 0) + return -1; + + // Copying the package name + strncpy(pkg, second_last_slash + 1, pkg_len); + pkg[pkg_len] = '\0'; + + return 0; +} + +static void crown_manager(const char *apk, struct list_head *uid_data) +{ + char pkg[KSU_MAX_PACKAGE_NAME]; + if (get_pkg_from_apk_path(pkg, apk) < 0) { + pr_err("Failed to get package name from apk path: %s\n", apk); + return; + } + + pr_info("manager pkg: %s\n", pkg); + +#ifdef KSU_MANAGER_PACKAGE + // pkg is `/` + if (strncmp(pkg, KSU_MANAGER_PACKAGE, sizeof(KSU_MANAGER_PACKAGE))) { + pr_info("manager package is inconsistent with kernel build: %s\n", + KSU_MANAGER_PACKAGE); + return; + } +#endif + struct list_head *list = (struct list_head *)uid_data; + struct uid_data *np; + + list_for_each_entry (np, list, list) { + if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) { + pr_info("Crowning manager: %s(uid=%d)\n", pkg, np->uid); + ksu_set_manager_uid(np->uid); + break; + } + } +} + +#define DATA_PATH_LEN 384 // 384 is enough for /data/app//base.apk + +struct data_path { + char dirpath[DATA_PATH_LEN]; + int depth; + struct list_head list; +}; + +struct apk_path_hash { + unsigned int hash; + bool exists; + struct list_head list; +}; + +static struct list_head apk_path_hash_list = LIST_HEAD_INIT(apk_path_hash_list); + +struct my_dir_context { + struct dir_context ctx; + struct list_head *data_path_list; + char *parent_dir; + void *private_data; + int depth; + int *stop; +}; +// https://docs.kernel.org/filesystems/porting.html +// filldir_t (readdir callbacks) calling conventions have changed. Instead of returning 0 or -E... it returns bool now. false means "no more" (as -E... used to) and true - "keep going" (as 0 in old calling conventions). Rationale: callers never looked at specific -E... values anyway. -> iterate_shared() instances require no changes at all, all filldir_t ones in the tree converted. +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +#define FILLDIR_RETURN_TYPE bool +#define FILLDIR_ACTOR_CONTINUE true +#define FILLDIR_ACTOR_STOP false +#else +#define FILLDIR_RETURN_TYPE int +#define FILLDIR_ACTOR_CONTINUE 0 +#define FILLDIR_ACTOR_STOP -EINVAL +#endif + +FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name, + int namelen, loff_t off, u64 ino, + unsigned int d_type) +{ + struct my_dir_context *my_ctx = + container_of(ctx, struct my_dir_context, ctx); + char dirpath[DATA_PATH_LEN]; + + if (!my_ctx) { + pr_err("Invalid context\n"); + return FILLDIR_ACTOR_STOP; + } + if (my_ctx->stop && *my_ctx->stop) { + pr_info("Stop searching\n"); + return FILLDIR_ACTOR_STOP; + } + + if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen)) + return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".." + + if (d_type == DT_DIR && namelen >= 8 && !strncmp(name, "vmdl", 4) && + !strncmp(name + namelen - 4, ".tmp", 4)) { + pr_info("Skipping directory: %.*s\n", namelen, name); + return FILLDIR_ACTOR_CONTINUE; // Skip staging package + } + + if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir, + namelen, name) >= DATA_PATH_LEN) { + pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen, + name); + return FILLDIR_ACTOR_CONTINUE; + } + + if (d_type == DT_DIR && my_ctx->depth > 0 && + (my_ctx->stop && !*my_ctx->stop)) { + struct data_path *data = kmalloc(sizeof(struct data_path), GFP_ATOMIC); + + if (!data) { + pr_err("Failed to allocate memory for %s\n", dirpath); + return FILLDIR_ACTOR_CONTINUE; + } + + strscpy(data->dirpath, dirpath, DATA_PATH_LEN); + data->depth = my_ctx->depth - 1; + list_add_tail(&data->list, my_ctx->data_path_list); + } else { + if ((namelen == 8) && (strncmp(name, "base.apk", namelen) == 0)) { + struct apk_path_hash *pos, *n; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) + unsigned int hash = full_name_hash(dirpath, strlen(dirpath)); +#else + unsigned int hash = full_name_hash(NULL, dirpath, strlen(dirpath)); +#endif + list_for_each_entry(pos, &apk_path_hash_list, list) { + if (hash == pos->hash) { + pos->exists = true; + return FILLDIR_ACTOR_CONTINUE; + } + } + + bool is_manager = ksu_is_manager_apk(dirpath); + pr_info("Found new base.apk at path: %s, is_manager: %d\n", + dirpath, is_manager); + if (is_manager) { + crown_manager(dirpath, my_ctx->private_data); + *my_ctx->stop = 1; + + // Manager found, clear APK cache list + list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) { + list_del(&pos->list); + kfree(pos); + } + } else { + struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC); + apk_data->hash = hash; + apk_data->exists = true; + list_add_tail(&apk_data->list, &apk_path_hash_list); + } + } + } + + return FILLDIR_ACTOR_CONTINUE; +} + +void search_manager(const char *path, int depth, struct list_head *uid_data) +{ + int i, stop = 0; + struct list_head data_path_list; + INIT_LIST_HEAD(&data_path_list); + + // Initialize APK cache list + struct apk_path_hash *pos, *n; + list_for_each_entry(pos, &apk_path_hash_list, list) { + pos->exists = false; + } + + // First depth + struct data_path data; + strscpy(data.dirpath, path, DATA_PATH_LEN); + data.depth = depth; + list_add_tail(&data.list, &data_path_list); + + for (i = depth; i > 0; i--) { + struct data_path *pos, *n; + + list_for_each_entry_safe(pos, n, &data_path_list, list) { + struct my_dir_context ctx = { .ctx.actor = my_actor, + .data_path_list = &data_path_list, + .parent_dir = pos->dirpath, + .private_data = uid_data, + .depth = pos->depth, + .stop = &stop }; + struct file *file; + + if (!stop) { + file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0); + if (IS_ERR(file)) { + pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file)); + goto skip_iterate; + } + + iterate_dir(file, &ctx.ctx); + filp_close(file, NULL); + } +skip_iterate: + list_del(&pos->list); + if (pos != &data) + kfree(pos); + } + } + + // Remove stale cached APK entries + list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) { + if (!pos->exists) { + list_del(&pos->list); + kfree(pos); + } + } +} + +static bool is_uid_exist(uid_t uid, char *package, void *data) +{ + struct list_head *list = (struct list_head *)data; + struct uid_data *np; + + bool exist = false; + list_for_each_entry (np, list, list) { + if (np->uid == uid % 100000 && + strncmp(np->package, package, KSU_MAX_PACKAGE_NAME) == 0) { + exist = true; + break; + } + } + return exist; +} + +void ksu_track_throne() +{ + struct file *fp = + ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n", + __func__, PTR_ERR(fp)); + return; + } + + struct list_head uid_list; + INIT_LIST_HEAD(&uid_list); + + char chr = 0; + loff_t pos = 0; + loff_t line_start = 0; + char buf[KSU_MAX_PACKAGE_NAME]; + for (;;) { + ssize_t count = + ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos); + if (count != sizeof(chr)) + break; + if (chr != '\n') + continue; + + count = ksu_kernel_read_compat(fp, buf, sizeof(buf), + &line_start); + + struct uid_data *data = + kzalloc(sizeof(struct uid_data), GFP_ATOMIC); + if (!data) { + filp_close(fp, 0); + goto out; + } + + char *tmp = buf; + const char *delim = " "; + char *package = strsep(&tmp, delim); + char *uid = strsep(&tmp, delim); + if (!uid || !package) { + pr_err("update_uid: package or uid is NULL!\n"); + break; + } + + u32 res; + if (kstrtou32(uid, 10, &res)) { + pr_err("update_uid: uid parse err\n"); + break; + } + data->uid = res; + strncpy(data->package, package, KSU_MAX_PACKAGE_NAME); + list_add_tail(&data->list, &uid_list); + // reset line start + line_start = pos; + } + filp_close(fp, 0); + + // now update uid list + struct uid_data *np; + struct uid_data *n; + + // first, check if manager_uid exist! + bool manager_exist = false; + list_for_each_entry (np, &uid_list, list) { + // if manager is installed in work profile, the uid in packages.list is still equals main profile + // don't delete it in this case! + int manager_uid = ksu_get_manager_uid() % 100000; + if (np->uid == manager_uid) { + manager_exist = true; + break; + } + } + + if (!manager_exist) { + if (ksu_is_manager_uid_valid()) { + pr_info("manager is uninstalled, invalidate it!\n"); + ksu_invalidate_manager_uid(); + } + pr_info("Searching manager...\n"); + search_manager("/data/app", 2, &uid_list); + pr_info("Search manager finished\n"); + } + + // then prune the allowlist + ksu_prune_allowlist(is_uid_exist, &uid_list); +out: + // free uid_list + list_for_each_entry_safe (np, n, &uid_list, list) { + list_del(&np->list); + kfree(np); + } +} + +void ksu_throne_tracker_init() +{ + // nothing to do +} + +void ksu_throne_tracker_exit() +{ + // nothing to do +} diff --git a/kernel/throne_tracker.h b/kernel/throne_tracker.h new file mode 100644 index 00000000..428c737d --- /dev/null +++ b/kernel/throne_tracker.h @@ -0,0 +1,10 @@ +#ifndef __KSU_H_UID_OBSERVER +#define __KSU_H_UID_OBSERVER + +void ksu_throne_tracker_init(); + +void ksu_throne_tracker_exit(); + +void ksu_track_throne(); + +#endif