Updated to 4.0.0
This commit is contained in:
parent
b7554a5383
commit
938198bf11
234 changed files with 21069 additions and 12710 deletions
674
LICENSE
674
LICENSE
|
|
@ -1,674 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# sukisu
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# Reporting Security Issues
|
||||
|
||||
The KernelSU team and community take security bugs in KernelSU seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||
|
||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/tiann/KernelSU/security/advisories/new) tab, or you can mailto [weishu](mailto:twsxtd@gmail.com) directly.
|
||||
|
||||
The KernelSU team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_API_TOKEN
|
||||
preserve_hierarchy: 1
|
||||
files:
|
||||
- source: /manager/app/src/main/res/values/strings.xml
|
||||
translation: /manager/app/src/main/res/values-%two_letters_code%/strings.xml
|
||||
|
|
@ -44,7 +44,7 @@ Generally need to modify the `do_execve` and `compat_do_execve` methods in `fs/e
|
|||
.ptr.compat = __envp,
|
||||
};
|
||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
+ trace_ksu_trace_execveat_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */
|
||||
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0); // 32-bit su and 32-on-64 support
|
||||
+#endif
|
||||
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
|
||||
}
|
||||
|
|
@ -237,34 +237,3 @@ Need to modify the `input_event` method in `drivers/input/input.c`, not `input_h
|
|||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
```
|
||||
|
||||
### devpts Hook (`pty.c`)
|
||||
|
||||
Need to modify the `pts_unix98_lookup` method in `drivers/tty/pty.c`
|
||||
|
||||
```patch
|
||||
--- a/drivers/tty/pty.c
|
||||
+++ b/drivers/tty/pty.c
|
||||
@@ -31,6 +31,10 @@
|
||||
#include <linux/compat.h>
|
||||
#include "tty.h"
|
||||
|
||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
+#include <../../drivers/kernelsu/ksu_trace.h>
|
||||
+#endif
|
||||
+
|
||||
#undef TTY_DEBUG_HANGUP
|
||||
#ifdef TTY_DEBUG_HANGUP
|
||||
# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args)
|
||||
@@ -707,6 +711,10 @@ static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
|
||||
{
|
||||
struct tty_struct *tty;
|
||||
|
||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
+ trace_ksu_trace_devpts_hook((struct inode *)file->f_path.dentry->d_inode);
|
||||
+#endif
|
||||
+
|
||||
mutex_lock(&devpts_mutex);
|
||||
tty = devpts_get_priv(file->f_path.dentry);
|
||||
mutex_unlock(&devpts_mutex);
|
||||
```
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
.ptr.compat = __envp,
|
||||
};
|
||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
+ trace_ksu_trace_execveat_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */
|
||||
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0)); // 32-bit su and 32-on-64 support
|
||||
+#endif
|
||||
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
|
||||
}
|
||||
|
|
@ -236,35 +236,4 @@
|
|||
if (is_event_supported(type, dev->evbit, EV_MAX)) {
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
```
|
||||
|
||||
### devpts 钩子 (`pty.c`)
|
||||
|
||||
需要修改 `drivers/tty/pty.c` 的 `pts_unix98_lookup` 方法
|
||||
|
||||
```patch
|
||||
--- a/drivers/tty/pty.c
|
||||
+++ b/drivers/tty/pty.c
|
||||
@@ -31,6 +31,10 @@
|
||||
#include <linux/compat.h>
|
||||
#include "tty.h"
|
||||
|
||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
+#include <../../drivers/kernelsu/ksu_trace.h>
|
||||
+#endif
|
||||
+
|
||||
#undef TTY_DEBUG_HANGUP
|
||||
#ifdef TTY_DEBUG_HANGUP
|
||||
# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args)
|
||||
@@ -707,6 +711,10 @@ static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
|
||||
{
|
||||
struct tty_struct *tty;
|
||||
|
||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
+ trace_ksu_trace_devpts_hook((struct inode *)file->f_path.dentry->d_inode);
|
||||
+#endif
|
||||
+
|
||||
mutex_lock(&devpts_mutex);
|
||||
tty = devpts_get_priv(file->f_path.dentry);
|
||||
mutex_unlock(&devpts_mutex);
|
||||
```
|
||||
```
|
||||
BIN
icon/logo.png
BIN
icon/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 342 B |
14
justfile
14
justfile
|
|
@ -1,14 +0,0 @@
|
|||
alias bk := build_ksud
|
||||
alias bm := build_manager
|
||||
|
||||
build_ksud:
|
||||
cross build --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
|
||||
build_manager: build_ksud
|
||||
cp userspace/ksud/target/aarch64-linux-android/release/ksud manager/app/src/main/jniLibs/arm64-v8a/libksud.so
|
||||
cd manager && ./gradlew aDebug
|
||||
|
||||
clippy:
|
||||
cargo fmt --manifest-path ./userspace/ksud/Cargo.toml
|
||||
cross clippy --target x86_64-pc-windows-gnu --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
cross clippy --target aarch64-linux-android --release --manifest-path ./userspace/ksud/Cargo.toml
|
||||
|
|
@ -56,8 +56,8 @@ ColumnLimit: 80
|
|||
CommentPragmas: '^ IWYU pragma:'
|
||||
#CompactNamespaces: false # Unknown to clang-format-4.0
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
|
|
@ -501,7 +501,7 @@ IncludeCategories:
|
|||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
#IndentPPDirectives: None # Unknown to clang-format-5.0
|
||||
IndentWidth: 8
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
|
|
@ -511,7 +511,7 @@ MacroBlockEnd: ''
|
|||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
|
||||
ObjCBlockIndentWidth: 8
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
|
|
@ -543,6 +543,6 @@ SpacesInCStyleCastParentheses: false
|
|||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp03
|
||||
TabWidth: 8
|
||||
UseTab: Always
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
...
|
||||
|
|
|
|||
22
kernel/.gitignore
vendored
Normal file
22
kernel/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.cache/
|
||||
.thinlto-cache/
|
||||
compile_commands.json
|
||||
*.ko
|
||||
*.o
|
||||
*.mod
|
||||
*.lds
|
||||
*.mod.o
|
||||
.*.o*
|
||||
.*.mod*
|
||||
*.ko*
|
||||
*.mod.c
|
||||
*.symvers*
|
||||
*.order
|
||||
.*.ko.cmd
|
||||
.tmp_versions/
|
||||
libs/
|
||||
obj/
|
||||
|
||||
CLAUDE.md
|
||||
.ddk-version
|
||||
.vscode/settings.json
|
||||
11
kernel/.vscode/c_cpp_properties.json
vendored
Normal file
11
kernel/.vscode/c_cpp_properties.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"cStandard": "c11",
|
||||
"intelliSenseMode": "gcc-arm64",
|
||||
"compileCommands": "${workspaceFolder}/compile_commands.json"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
92
kernel/.vscode/generate_compdb.py
vendored
Normal file
92
kernel/.vscode/generate_compdb.py
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import print_function, division
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import functools
|
||||
import json
|
||||
import math
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
CMD_VAR_RE = re.compile(r'^\s*(?:saved)?cmd_(\S+)\s*:=\s*(.+)\s*$', re.MULTILINE)
|
||||
SOURCE_VAR_RE = re.compile(r'^\s*source_(\S+)\s*:=\s*(.+)\s*$', re.MULTILINE)
|
||||
|
||||
|
||||
def print_progress_bar(progress):
|
||||
progress_bar = '[' + '|' * int(50 * progress) + '-' * int(50 * (1.0 - progress)) + ']'
|
||||
print('\r', progress_bar, "{0:.1%}".format(progress), end='\r', file=sys.stderr)
|
||||
|
||||
|
||||
def parse_cmd_file(out_dir, cmdfile_path):
|
||||
with open(cmdfile_path, 'r') as cmdfile:
|
||||
cmdfile_content = cmdfile.read()
|
||||
|
||||
commands = { match.group(1): match.group(2) for match in CMD_VAR_RE.finditer(cmdfile_content) }
|
||||
sources = { match.group(1): match.group(2) for match in SOURCE_VAR_RE.finditer(cmdfile_content) }
|
||||
|
||||
return [{
|
||||
'directory': out_dir,
|
||||
'command': commands[o_file_name],
|
||||
'file': source,
|
||||
'output': o_file_name
|
||||
} for o_file_name, source in sources.items()]
|
||||
|
||||
|
||||
def gen_compile_commands(cmd_file_search_path, out_dir):
|
||||
print("Building *.o.cmd file list...", file=sys.stderr)
|
||||
|
||||
out_dir = os.path.abspath(out_dir)
|
||||
|
||||
if not cmd_file_search_path:
|
||||
cmd_file_search_path = [out_dir]
|
||||
|
||||
cmd_files = []
|
||||
for search_path in cmd_file_search_path:
|
||||
if (os.path.isdir(search_path)):
|
||||
for cur_dir, subdir, files in os.walk(search_path):
|
||||
cmd_files.extend(os.path.join(cur_dir, cmdfile_name) for cmdfile_name in fnmatch.filter(files, '*.o.cmd'))
|
||||
else:
|
||||
cmd_files.extend(search_path)
|
||||
|
||||
if not cmd_files:
|
||||
print("No *.o.cmd files found in", ", ".join(cmd_file_search_path), file=sys.stderr)
|
||||
return
|
||||
|
||||
print("Parsing *.o.cmd files...", file=sys.stderr)
|
||||
|
||||
n_processed = 0
|
||||
print_progress_bar(0)
|
||||
|
||||
compdb = []
|
||||
pool = multiprocessing.Pool()
|
||||
try:
|
||||
for compdb_chunk in pool.imap_unordered(functools.partial(parse_cmd_file, out_dir), cmd_files, chunksize=int(math.sqrt(len(cmd_files)))):
|
||||
compdb.extend(compdb_chunk)
|
||||
n_processed += 1
|
||||
print_progress_bar(n_processed / len(cmd_files))
|
||||
|
||||
finally:
|
||||
pool.terminate()
|
||||
pool.join()
|
||||
|
||||
print(file=sys.stderr)
|
||||
print("Writing compile_commands.json...", file=sys.stderr)
|
||||
|
||||
with open('compile_commands.json', 'w') as compdb_file:
|
||||
json.dump(compdb, compdb_file, indent=1)
|
||||
|
||||
|
||||
def main():
|
||||
cmd_parser = argparse.ArgumentParser()
|
||||
cmd_parser.add_argument('-O', '--out-dir', type=str, default=os.getcwd(), help="Build output directory")
|
||||
cmd_parser.add_argument('cmd_file_search_path', nargs='*', help="*.cmd file search path")
|
||||
gen_compile_commands(**vars(cmd_parser.parse_args()))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
16
kernel/.vscode/tasks.json
vendored
Normal file
16
kernel/.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Generate compile_commands.json",
|
||||
"type": "process",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"${workspaceRoot}/.vscode/generate_compdb.py"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,57 +1,42 @@
|
|||
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.
|
||||
tristate "KernelSU function support"
|
||||
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.
|
||||
bool "KernelSU debug mode"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
Enable KernelSU debug mode.
|
||||
|
||||
config KSU_MANUAL_SU
|
||||
bool "Use manual su"
|
||||
depends on KSU
|
||||
default y
|
||||
help
|
||||
Use manual su and authorize the corresponding command line and application via prctl
|
||||
|
||||
config KPM
|
||||
bool "Enable SukiSU KPM"
|
||||
depends on KSU && 64BIT
|
||||
default n
|
||||
help
|
||||
Enabling this option will activate the KPM feature of SukiSU.
|
||||
This option is suitable for scenarios where you need to force KPM to be enabled.
|
||||
but it may affect system stability.
|
||||
select KALLSYMS
|
||||
select KALLSYMS_ALL
|
||||
|
||||
choice
|
||||
prompt "KernelSU hook type"
|
||||
depends on KSU
|
||||
default KSU_KPROBES_HOOK
|
||||
bool "Enable SukiSU KPM"
|
||||
depends on KSU && 64BIT
|
||||
default n
|
||||
help
|
||||
Hook type for KernelSU
|
||||
|
||||
config KSU_KPROBES_HOOK
|
||||
bool "Hook KernelSU with Kprobes"
|
||||
depends on KPROBES
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with Kernel-probe.
|
||||
|
||||
config KSU_TRACEPOINT_HOOK
|
||||
bool "Hook KernelSU with Tracepoint"
|
||||
depends on TRACEPOINTS
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with Tracepoint.
|
||||
Enabling this option will activate the KPM feature of SukiSU.
|
||||
This option is suitable for scenarios where you need to force KPM to be enabled.
|
||||
but it may affect system stability.
|
||||
select KALLSYMS
|
||||
select KALLSYMS_ALL
|
||||
|
||||
config KSU_MANUAL_HOOK
|
||||
bool "Hook KernelSU manually"
|
||||
depends on KSU != m
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
||||
|
||||
endchoice
|
||||
depends on KSU != m
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
||||
|
||||
endmenu
|
||||
|
|
|
|||
|
|
@ -1,17 +1,28 @@
|
|||
kernelsu-objs := ksu.o
|
||||
kernelsu-objs += allowlist.o
|
||||
kernelsu-objs += app_profile.o
|
||||
kernelsu-objs += dynamic_manager.o
|
||||
kernelsu-objs += apk_sign.o
|
||||
kernelsu-objs += sucompat.o
|
||||
kernelsu-objs += syscall_hook_manager.o
|
||||
kernelsu-objs += throne_tracker.o
|
||||
kernelsu-objs += core_hook.o
|
||||
kernelsu-objs += pkg_observer.o
|
||||
kernelsu-objs += throne_tracker.o
|
||||
kernelsu-objs += umount_manager.o
|
||||
kernelsu-objs += setuid_hook.o
|
||||
kernelsu-objs += kernel_umount.o
|
||||
kernelsu-objs += supercalls.o
|
||||
kernelsu-objs += feature.o
|
||||
kernelsu-objs += ksud.o
|
||||
kernelsu-objs += embed_ksud.o
|
||||
kernelsu-objs += kernel_compat.o
|
||||
kernelsu-objs += seccomp_cache.o
|
||||
kernelsu-objs += file_wrapper.o
|
||||
kernelsu-objs += throne_comm.o
|
||||
kernelsu-objs += sulog.o
|
||||
|
||||
ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
|
||||
kernelsu-objs += ksu_trace.o
|
||||
ifeq ($(CONFIG_KSU_MANUAL_SU), y)
|
||||
ccflags-y += -DCONFIG_KSU_MANUAL_SU
|
||||
kernelsu-objs += manual_su.o
|
||||
endif
|
||||
|
||||
kernelsu-objs += selinux/selinux.o
|
||||
|
|
@ -21,46 +32,62 @@ 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
|
||||
|
||||
obj-$(CONFIG_KSU) += kernelsu.o
|
||||
obj-$(CONFIG_KSU_TRACEPOINT_HOOK) += ksu_trace_export.o
|
||||
|
||||
obj-$(CONFIG_KPM) += kpm/
|
||||
|
||||
|
||||
REPO_OWNER := SukiSU-Ultra
|
||||
REPO_NAME := SukiSU-Ultra
|
||||
REPO_BRANCH := main
|
||||
KSU_VERSION_API := 3.2.0
|
||||
KSU_VERSION_API := 4.0.0
|
||||
|
||||
GIT_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git
|
||||
CURL_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin curl
|
||||
|
||||
KDIR := $(KDIR)
|
||||
MDIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
|
||||
|
||||
ifneq ($(KDIR),)
|
||||
$(info -- KDIR: $(KDIR))
|
||||
$(info -- MDIR: $(MDIR))
|
||||
endif
|
||||
|
||||
KSU_GITHUB_VERSION := $(shell $(CURL_BIN) -s "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
|
||||
KSU_GITHUB_VERSION_COMMIT := $(shell $(CURL_BIN) -sI "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/commits?sha=$(REPO_BRANCH)&per_page=1" | grep -i "link:" | sed -n 's/.*page=\([0-9]*\)>; rel="last".*/\1/p')
|
||||
|
||||
LOCAL_GIT_EXISTS := $(shell test -e $(srctree)/$(src)/../.git && echo 1 || echo 0)
|
||||
ifeq ($(findstring $(srctree),$(src)),$(srctree))
|
||||
KSU_SRC := $(src)
|
||||
else
|
||||
KSU_SRC := $(srctree)/$(src)
|
||||
endif
|
||||
|
||||
ifneq ($(shell test -e $(KSU_SRC)/../.git && echo "in-tree"),in-tree)
|
||||
KSU_SRC := $(MDIR)
|
||||
endif
|
||||
|
||||
LOCAL_GIT_EXISTS := $(shell test -e $(KSU_SRC)/../.git && echo 1 || echo 0)
|
||||
|
||||
define get_ksu_version_full
|
||||
v$1-$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
|
||||
v$1-$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
|
||||
endef
|
||||
|
||||
ifeq ($(KSU_GITHUB_VERSION_COMMIT),)
|
||||
ifeq ($(LOCAL_GIT_EXISTS),1)
|
||||
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
KSU_LOCAL_VERSION := $(shell cd $(srctree)/$(src); $(GIT_BIN) rev-list --count $(REPO_BRANCH))
|
||||
KSU_VERSION := $(shell expr 10000 + $(KSU_LOCAL_VERSION) + 700)
|
||||
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
KSU_LOCAL_VERSION := $(shell cd $(KSU_SRC); $(GIT_BIN) rev-list --count $(REPO_BRANCH))
|
||||
KSU_VERSION := $(shell expr 40000 + $(KSU_LOCAL_VERSION) - 2815)
|
||||
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION))
|
||||
else
|
||||
KSU_VERSION := 13000
|
||||
$(warning -- Could not fetch version online or via local .git! Using fallback version: $(KSU_VERSION))
|
||||
endif
|
||||
else
|
||||
KSU_VERSION := $(shell expr 10000 + $(KSU_GITHUB_VERSION_COMMIT) + 700)
|
||||
KSU_VERSION := $(shell expr 40000 + $(KSU_GITHUB_VERSION_COMMIT) - 2815)
|
||||
$(info -- $(REPO_NAME) version (GitHub): $(KSU_VERSION))
|
||||
endif
|
||||
|
||||
ifeq ($(KSU_GITHUB_VERSION),)
|
||||
ifeq ($(LOCAL_GIT_EXISTS),1)
|
||||
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_VERSION_API))
|
||||
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION_FULL))
|
||||
$(info -- $(REPO_NAME) Formatted version (local .git): $(KSU_VERSION))
|
||||
|
|
@ -69,7 +96,7 @@ ifeq ($(KSU_GITHUB_VERSION),)
|
|||
$(warning -- $(REPO_NAME) version: $(KSU_VERSION_FULL))
|
||||
endif
|
||||
else
|
||||
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_GITHUB_VERSION))
|
||||
$(info -- $(REPO_NAME) version (Github): $(KSU_VERSION_FULL))
|
||||
endif
|
||||
|
|
@ -93,14 +120,13 @@ ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
|||
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
||||
|
||||
ifeq ($(CONFIG_KSU_KPROBES_HOOK), y)
|
||||
$(info -- SukiSU: CONFIG_KSU_KPROBES_HOOK)
|
||||
else ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
|
||||
$(info -- SukiSU: CONFIG_KSU_TRACEPOINT_HOOK)
|
||||
else ifeq ($(CONFIG_KSU_MANUAL_HOOK), y)
|
||||
$(info -- SukiSU: CONFIG_KSU_MANUAL_HOOK)
|
||||
ifeq ($(CONFIG_KSU_MANUAL_HOOK), y)
|
||||
ccflags-y += -DKSU_MANUAL_HOOK
|
||||
$(info -- SukiSU: KSU_MANUAL_HOOK Temporarily discontinued))
|
||||
else
|
||||
ccflags-y += -DKSU_KPROBES_HOOK
|
||||
ccflags-y += -DKSU_TP_HOOK
|
||||
$(info -- SukiSU: KSU_TRACEPOINT_HOOK)
|
||||
endif
|
||||
|
||||
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
|
||||
|
|
@ -117,14 +143,30 @@ endif
|
|||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
|
||||
|
||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
ifeq ($(CONFIG_KPM), y)
|
||||
$(info -- KPM is enabled)
|
||||
else
|
||||
$(info -- KPM is disabled)
|
||||
endif
|
||||
|
||||
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||
# Check new vfs_getattr()
|
||||
ifeq ($(shell grep -A1 "^int vfs_getattr" $(srctree)/fs/stat.c | grep -q "query_flags" ; echo $$?),0)
|
||||
ccflags-y += -DKSU_HAS_NEW_VFS_GETATTR
|
||||
endif
|
||||
|
||||
# Function proc_ops check
|
||||
ifeq ($(shell grep -q "struct proc_ops " $(srctree)/include/linux/proc_fs.h; echo $$?),0)
|
||||
ccflags-y += -DKSU_COMPAT_HAS_PROC_OPS
|
||||
endif
|
||||
|
||||
ccflags-y += -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat -Wno-missing-prototypes
|
||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function -Wno-unused-variable
|
||||
|
||||
all:
|
||||
make -C $(KDIR) M=$(MDIR) modules
|
||||
compdb:
|
||||
python3 $(MDIR)/.vscode/generate_compdb.py -O $(KDIR) $(MDIR)
|
||||
clean:
|
||||
make -C $(KDIR) M=$(MDIR) clean
|
||||
|
||||
# Keep a new line here!! Because someone may append config
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#include <linux/mutex.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/fs.h>
|
||||
|
|
@ -8,14 +10,16 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/version.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
||||
#include <linux/compiler_types.h>
|
||||
#endif
|
||||
|
||||
#include "ksu.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "kernel_compat.h"
|
||||
#include "allowlist.h"
|
||||
#include "manager.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
|
||||
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
|
||||
#define FILE_FORMAT_VERSION 3 // u32
|
||||
|
|
@ -29,58 +33,61 @@ static DEFINE_MUTEX(allowlist_mutex);
|
|||
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);
|
||||
void persistent_allow_list(void);
|
||||
|
||||
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;
|
||||
int *temp_arr;
|
||||
int i, j;
|
||||
|
||||
if (allow_list_pointer == 0)
|
||||
return;
|
||||
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;
|
||||
}
|
||||
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];
|
||||
}
|
||||
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;
|
||||
allow_list_pointer = j;
|
||||
|
||||
for (; j < ARRAY_SIZE(allow_list_arr); j++)
|
||||
temp_arr[j] = -1;
|
||||
for (; j < ARRAY_SIZE(allow_list_arr); j++)
|
||||
temp_arr[j] = -1;
|
||||
|
||||
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
|
||||
kfree(temp_arr);
|
||||
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
|
||||
kfree(temp_arr);
|
||||
}
|
||||
|
||||
static void init_default_profiles()
|
||||
static void init_default_profiles(void)
|
||||
{
|
||||
kernel_cap_t full_cap = CAP_FULL_SET;
|
||||
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);
|
||||
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;
|
||||
// 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;
|
||||
struct list_head list;
|
||||
struct app_profile profile;
|
||||
};
|
||||
|
||||
static struct list_head allow_list;
|
||||
|
|
@ -90,437 +97,535 @@ static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE);
|
|||
|
||||
#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"
|
||||
|
||||
static struct work_struct ksu_save_work;
|
||||
static struct work_struct ksu_load_work;
|
||||
|
||||
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);
|
||||
}
|
||||
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()
|
||||
static void ksu_grant_root_to_shell(void)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
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 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 (!profile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile->version < KSU_APP_PROFILE_VER) {
|
||||
pr_info("Unsupported profile version: %d\n", profile->version);
|
||||
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 (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;
|
||||
}
|
||||
}
|
||||
if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
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;
|
||||
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));
|
||||
}
|
||||
// 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 (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();
|
||||
if (persist) {
|
||||
persistent_allow_list();
|
||||
// FIXME: use a new flag
|
||||
ksu_mark_running_process();
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool __ksu_is_allow_uid(uid_t uid)
|
||||
{
|
||||
int i;
|
||||
int i;
|
||||
|
||||
if (unlikely(uid == 0)) {
|
||||
// already root, but only allow our domain.
|
||||
return is_ksu_domain();
|
||||
}
|
||||
if (forbid_system_uid(uid)) {
|
||||
// do not bother going through the list if it's system
|
||||
return false;
|
||||
}
|
||||
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
bool __ksu_is_allow_uid_for_current(uid_t uid)
|
||||
{
|
||||
if (unlikely(uid == 0)) {
|
||||
// already root, but only allow our domain.
|
||||
return is_ksu_domain();
|
||||
}
|
||||
return __ksu_is_allow_uid(uid);
|
||||
}
|
||||
|
||||
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 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
// 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;
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
void do_save_allow_list(struct work_struct *work)
|
||||
static void do_persistent_allow_list(struct callback_head *_cb)
|
||||
{
|
||||
u32 magic = FILE_MAGIC;
|
||||
u32 version = FILE_FORMAT_VERSION;
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
loff_t off = 0;
|
||||
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;
|
||||
}
|
||||
mutex_lock(&allowlist_mutex);
|
||||
struct file *fp =
|
||||
filp_open(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));
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
// store magic and version
|
||||
if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||
pr_err("save_allow_list write magic failed.\n");
|
||||
goto close_file;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("save_allow_list write version failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (kernel_write(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
pr_err("save_allow_list write version failed.\n");
|
||||
goto close_file;
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
kernel_write(fp, &p->profile, sizeof(p->profile), &off);
|
||||
}
|
||||
|
||||
exit:
|
||||
filp_close(fp, 0);
|
||||
close_file:
|
||||
filp_close(fp, 0);
|
||||
unlock:
|
||||
mutex_unlock(&allowlist_mutex);
|
||||
kfree(_cb);
|
||||
}
|
||||
|
||||
void do_load_allow_list(struct work_struct *work)
|
||||
void persistent_allow_list()
|
||||
{
|
||||
loff_t off = 0;
|
||||
ssize_t ret = 0;
|
||||
struct file *fp = NULL;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
struct task_struct *tsk;
|
||||
|
||||
tsk = get_pid_task(find_vpid(1), PIDTYPE_PID);
|
||||
if (!tsk) {
|
||||
pr_err("save_allow_list find init task err\n");
|
||||
return;
|
||||
}
|
||||
|
||||
struct callback_head *cb =
|
||||
kzalloc(sizeof(struct callback_head), GFP_KERNEL);
|
||||
if (!cb) {
|
||||
pr_err("save_allow_list alloc cb err\b");
|
||||
goto put_task;
|
||||
}
|
||||
cb->func = do_persistent_allow_list;
|
||||
task_work_add(tsk, cb, TWA_RESUME);
|
||||
|
||||
put_task:
|
||||
put_task_struct(tsk);
|
||||
}
|
||||
|
||||
void ksu_load_allow_list()
|
||||
{
|
||||
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();
|
||||
// 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;
|
||||
}
|
||||
// load allowlist now!
|
||||
fp = filp_open(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;
|
||||
}
|
||||
// verify magic
|
||||
if (kernel_read(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;
|
||||
}
|
||||
if (kernel_read(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);
|
||||
pr_info("allowlist version: %d\n", version);
|
||||
|
||||
while (true) {
|
||||
struct app_profile profile;
|
||||
while (true) {
|
||||
struct app_profile profile;
|
||||
|
||||
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
|
||||
&off);
|
||||
ret = kernel_read(fp, &profile, sizeof(profile), &off);
|
||||
|
||||
if (ret <= 0) {
|
||||
pr_info("load_allow_list read err: %zd\n", ret);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
ksu_show_allow_list();
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data)
|
||||
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *),
|
||||
void *data)
|
||||
{
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
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 (!ksu_boot_completed) {
|
||||
pr_info("boot not completed, skip prune\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
persistent_allow_list();
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
// make sure allow list works cross boot
|
||||
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);
|
||||
if (modified) {
|
||||
persistent_allow_list();
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_allowlist_init(void)
|
||||
{
|
||||
int i;
|
||||
int i;
|
||||
|
||||
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
|
||||
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
|
||||
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;
|
||||
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
|
||||
allow_list_arr[i] = -1;
|
||||
|
||||
INIT_LIST_HEAD(&allow_list);
|
||||
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();
|
||||
init_default_profiles();
|
||||
}
|
||||
|
||||
void ksu_allowlist_exit(void)
|
||||
{
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
bool ksu_temp_grant_root_once(uid_t uid)
|
||||
{
|
||||
struct app_profile profile = {
|
||||
.version = KSU_APP_PROFILE_VER,
|
||||
.allow_su = true,
|
||||
.current_uid = uid,
|
||||
};
|
||||
|
||||
const char *default_key = "com.temp.once";
|
||||
|
||||
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);
|
||||
if (p->profile.current_uid == uid) {
|
||||
strcpy(profile.key, p->profile.key);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
strcpy(profile.key, default_key);
|
||||
}
|
||||
|
||||
profile.rp_config.profile.uid = default_root_profile.uid;
|
||||
profile.rp_config.profile.gid = default_root_profile.gid;
|
||||
profile.rp_config.profile.groups_count = default_root_profile.groups_count;
|
||||
memcpy(profile.rp_config.profile.groups, default_root_profile.groups, sizeof(default_root_profile.groups));
|
||||
memcpy(&profile.rp_config.profile.capabilities, &default_root_profile.capabilities, sizeof(default_root_profile.capabilities));
|
||||
profile.rp_config.profile.namespaces = default_root_profile.namespaces;
|
||||
strcpy(profile.rp_config.profile.selinux_domain, default_root_profile.selinux_domain);
|
||||
|
||||
bool ok = ksu_set_app_profile(&profile, false);
|
||||
if (ok)
|
||||
pr_info("pending_root: UID=%d granted and persisted\n", uid);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void ksu_temp_revoke_root_once(uid_t uid)
|
||||
{
|
||||
struct app_profile profile = {
|
||||
.version = KSU_APP_PROFILE_VER,
|
||||
.allow_su = false,
|
||||
.current_uid = uid,
|
||||
};
|
||||
|
||||
const char *default_key = "com.temp.once";
|
||||
|
||||
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);
|
||||
if (p->profile.current_uid == uid) {
|
||||
strcpy(profile.key, p->profile.key);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
strcpy(profile.key, default_key);
|
||||
}
|
||||
|
||||
profile.nrp_config.profile.umount_modules = default_non_root_profile.umount_modules;
|
||||
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
|
||||
ksu_set_app_profile(&profile, false);
|
||||
persistent_allow_list();
|
||||
pr_info("pending_root: UID=%d removed and persist updated\n", uid);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -2,19 +2,29 @@
|
|||
#define __KSU_H_ALLOWLIST
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "ksu.h"
|
||||
#include <linux/uidgid.h>
|
||||
#include "app_profile.h"
|
||||
|
||||
#define PER_USER_RANGE 100000
|
||||
#define FIRST_APPLICATION_UID 10000
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
|
||||
void ksu_allowlist_init(void);
|
||||
|
||||
void ksu_allowlist_exit(void);
|
||||
|
||||
bool ksu_load_allow_list(void);
|
||||
void ksu_load_allow_list(void);
|
||||
|
||||
void ksu_show_allow_list(void);
|
||||
|
||||
// Check if the uid is in allow list
|
||||
bool __ksu_is_allow_uid(uid_t uid);
|
||||
#define ksu_is_allow_uid(uid) unlikely(__ksu_is_allow_uid(uid))
|
||||
|
||||
// Check if the uid is in allow list, or current is ksu domain root
|
||||
bool __ksu_is_allow_uid_for_current(uid_t uid);
|
||||
#define ksu_is_allow_uid_for_current(uid) unlikely(__ksu_is_allow_uid_for_current(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);
|
||||
|
|
@ -24,4 +34,16 @@ 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);
|
||||
|
||||
static inline bool is_appuid(uid_t uid)
|
||||
{
|
||||
uid_t appid = uid % PER_USER_RANGE;
|
||||
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
bool ksu_temp_grant_root_once(uid_t uid);
|
||||
void ksu_temp_revoke_root_once(uid_t uid);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -17,69 +17,65 @@
|
|||
#include "apk_sign.h"
|
||||
#include "dynamic_manager.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "manager_sign.h"
|
||||
|
||||
struct sdesc {
|
||||
struct shash_desc shash;
|
||||
char ctx[];
|
||||
struct shash_desc shash;
|
||||
char ctx[];
|
||||
};
|
||||
|
||||
static struct apk_sign_key {
|
||||
unsigned size;
|
||||
const char *sha256;
|
||||
} apk_sign_keys[] = {
|
||||
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
|
||||
static apk_sign_key_t apk_sign_keys[] = {
|
||||
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
|
||||
#ifdef EXPECTED_SIZE
|
||||
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
|
||||
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct sdesc *init_sdesc(struct crypto_shash *alg)
|
||||
{
|
||||
struct sdesc *sdesc;
|
||||
int size;
|
||||
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;
|
||||
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)
|
||||
unsigned int datalen, unsigned char *digest)
|
||||
{
|
||||
struct sdesc *sdesc;
|
||||
int ret;
|
||||
struct sdesc *sdesc;
|
||||
int ret;
|
||||
|
||||
sdesc = init_sdesc(alg);
|
||||
if (IS_ERR(sdesc)) {
|
||||
pr_info("can't alloc sdesc\n");
|
||||
return PTR_ERR(sdesc);
|
||||
}
|
||||
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;
|
||||
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)
|
||||
unsigned char *digest)
|
||||
{
|
||||
struct crypto_shash *alg;
|
||||
char *hash_alg_name = "sha256";
|
||||
int ret;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -87,304 +83,307 @@ static struct dynamic_sign_key dynamic_sign = DYNAMIC_SIGN_DEFAULT_CONFIG;
|
|||
|
||||
static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *matched_index)
|
||||
{
|
||||
struct dynamic_sign_key current_dynamic_key = dynamic_sign;
|
||||
|
||||
if (ksu_get_dynamic_manager_config(¤t_dynamic_key.size, ¤t_dynamic_key.hash)) {
|
||||
pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n",
|
||||
current_dynamic_key.size, current_dynamic_key.hash);
|
||||
}
|
||||
|
||||
if (size4 != current_dynamic_key.size) {
|
||||
return false;
|
||||
}
|
||||
struct dynamic_sign_key current_dynamic_key = dynamic_sign;
|
||||
|
||||
if (ksu_get_dynamic_manager_config(¤t_dynamic_key.size, ¤t_dynamic_key.hash)) {
|
||||
pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n",
|
||||
current_dynamic_key.size, current_dynamic_key.hash);
|
||||
}
|
||||
|
||||
if (size4 != current_dynamic_key.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#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 (ksu_sha256(cert, size4, digest) < 0) {
|
||||
pr_info("sha256 error\n");
|
||||
return false;
|
||||
}
|
||||
char cert[CERT_MAX_LENGTH];
|
||||
if (size4 > CERT_MAX_LENGTH) {
|
||||
pr_info("cert length overlimit\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
kernel_read(fp, cert, size4, pos);
|
||||
|
||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||
if (ksu_sha256(cert, size4, digest) < 0) {
|
||||
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, index: dynamic\n", hash_str, current_dynamic_key.hash);
|
||||
|
||||
if (strcmp(current_dynamic_key.hash, hash_str) == 0) {
|
||||
if (matched_index) {
|
||||
*matched_index = DYNAMIC_SIGN_INDEX;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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, index: dynamic\n", hash_str, current_dynamic_key.hash);
|
||||
|
||||
if (strcmp(current_dynamic_key.hash, hash_str) == 0) {
|
||||
if (matched_index) {
|
||||
*matched_index = DYNAMIC_SIGN_INDEX;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, int *matched_index)
|
||||
{
|
||||
int i;
|
||||
struct apk_sign_key sign_key;
|
||||
bool signature_valid = false;
|
||||
int i;
|
||||
apk_sign_key_t sign_key;
|
||||
bool signature_valid = false;
|
||||
|
||||
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
|
||||
kernel_read(fp, size4, 0x4, pos); // signer-sequence length
|
||||
kernel_read(fp, size4, 0x4, pos); // signer length
|
||||
kernel_read(fp, size4, 0x4, pos); // signed data length
|
||||
|
||||
*offset += 0x4 * 3;
|
||||
*offset += 0x4 * 3;
|
||||
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length
|
||||
kernel_read(fp, size4, 0x4, pos); // digests-sequence length
|
||||
|
||||
*pos += *size4;
|
||||
*offset += 0x4 + *size4;
|
||||
*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;
|
||||
kernel_read(fp, size4, 0x4, pos); // certificates length
|
||||
kernel_read(fp, size4, 0x4, pos); // certificate length
|
||||
*offset += 0x4 * 2;
|
||||
|
||||
if (ksu_is_dynamic_manager_enabled()) {
|
||||
loff_t temp_pos = *pos;
|
||||
if (check_dynamic_sign(fp, *size4, &temp_pos, matched_index)) {
|
||||
*pos = temp_pos;
|
||||
*offset += *size4;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (ksu_is_dynamic_manager_enabled()) {
|
||||
loff_t temp_pos = *pos;
|
||||
if (check_dynamic_sign(fp, *size4, &temp_pos, matched_index)) {
|
||||
*pos = temp_pos;
|
||||
*offset += *size4;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
|
||||
sign_key = apk_sign_keys[i];
|
||||
for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
|
||||
sign_key = apk_sign_keys[i];
|
||||
|
||||
if (*size4 != sign_key.size)
|
||||
continue;
|
||||
*offset += *size4;
|
||||
if (*size4 != sign_key.size)
|
||||
continue;
|
||||
*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 cert[CERT_MAX_LENGTH];
|
||||
if (*size4 > CERT_MAX_LENGTH) {
|
||||
pr_info("cert length overlimit\n");
|
||||
return false;
|
||||
}
|
||||
kernel_read(fp, cert, *size4, pos);
|
||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||
if (ksu_sha256(cert, *size4, digest) < 0 ) {
|
||||
pr_info("sha256 error\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
|
||||
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
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, index: %d\n", hash_str, sign_key.sha256, i);
|
||||
|
||||
if (strcmp(sign_key.sha256, hash_str) == 0) {
|
||||
signature_valid = true;
|
||||
if (matched_index) {
|
||||
*matched_index = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return signature_valid;
|
||||
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
|
||||
pr_info("sha256: %s, expected: %s, index: %d\n", hash_str, sign_key.sha256, i);
|
||||
|
||||
if (strcmp(sign_key.sha256, hash_str) == 0) {
|
||||
signature_valid = true;
|
||||
if (matched_index) {
|
||||
*matched_index = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return signature_valid;
|
||||
}
|
||||
|
||||
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;
|
||||
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";
|
||||
struct zip_entry_header header;
|
||||
const char MANIFEST[] = "META-INF/MANIFEST.MF";
|
||||
|
||||
loff_t pos = 0;
|
||||
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';
|
||||
while (kernel_read(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)];
|
||||
kernel_read(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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// Skip to the next entry
|
||||
pos += header.extra_field_length + header.compressed_size;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
static __always_inline bool check_v2_signature(char *path, bool check_multi_manager, int *signature_index)
|
||||
{
|
||||
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 matched_index = -1;
|
||||
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;
|
||||
}
|
||||
unsigned char buffer[0x11] = { 0 };
|
||||
u32 size4;
|
||||
u64 size8, size_of_block;
|
||||
|
||||
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip
|
||||
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
|
||||
filp_close(fp, 0);
|
||||
return 0;
|
||||
}
|
||||
loff_t pos;
|
||||
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
bool v2_signing_valid = false;
|
||||
int v2_signing_blocks = 0;
|
||||
bool v3_signing_exist = false;
|
||||
bool v3_1_signing_exist = false;
|
||||
int matched_index = -1;
|
||||
int i;
|
||||
struct file *fp = filp_open(path, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("open %s error.\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip
|
||||
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
|
||||
filp_close(fp, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pos += 12;
|
||||
// offset
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
kernel_read(fp, &n, 2, &pos);
|
||||
if (n == i) {
|
||||
pos -= 22;
|
||||
kernel_read(fp, &size4, 4, &pos);
|
||||
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 0xffff) {
|
||||
pr_info("error: cannot find eocd\n");
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
|
||||
pos = size4 - (size8 + 0x8);
|
||||
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
|
||||
if (size_of_block != size8) {
|
||||
goto clean;
|
||||
}
|
||||
pos += 12;
|
||||
// offset
|
||||
kernel_read(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
|
||||
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++;
|
||||
bool result = check_block(fp, &size4, &pos, &offset, &matched_index);
|
||||
if (result) {
|
||||
v2_signing_valid = true;
|
||||
}
|
||||
} 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 {
|
||||
kernel_read(fp, &size8, 0x8, &pos);
|
||||
kernel_read(fp, buffer, 0x10, &pos);
|
||||
if (strcmp((char *)buffer, "APK Sig Block 42")) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
pos = size4 - (size8 + 0x8);
|
||||
kernel_read(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;
|
||||
kernel_read(fp, &size8, 0x8,
|
||||
&pos); // sequence length
|
||||
if (size8 == size_of_block) {
|
||||
break;
|
||||
}
|
||||
kernel_read(fp, &id, 0x4, &pos); // id
|
||||
offset = 4;
|
||||
if (id == 0x7109871au) {
|
||||
v2_signing_blocks++;
|
||||
bool result = check_block(fp, &size4, &pos, &offset, &matched_index);
|
||||
if (result) {
|
||||
v2_signing_valid = true;
|
||||
}
|
||||
} 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);
|
||||
pr_info("Unknown id: 0x%08x\n", id);
|
||||
#endif
|
||||
}
|
||||
pos += (size8 - offset);
|
||||
}
|
||||
}
|
||||
pos += (size8 - offset);
|
||||
}
|
||||
|
||||
if (v2_signing_blocks != 1) {
|
||||
if (v2_signing_blocks != 1) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_err("Unexpected v2 signature count: %d\n",
|
||||
v2_signing_blocks);
|
||||
pr_err("Unexpected v2 signature count: %d\n",
|
||||
v2_signing_blocks);
|
||||
#endif
|
||||
v2_signing_valid = false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
filp_close(fp, 0);
|
||||
|
||||
if (v3_signing_exist || v3_1_signing_exist) {
|
||||
if (v3_signing_exist || v3_1_signing_exist) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_err("Unexpected v3 signature scheme found!\n");
|
||||
pr_err("Unexpected v3 signature scheme found!\n");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (v2_signing_valid) {
|
||||
if (signature_index) {
|
||||
*signature_index = matched_index;
|
||||
}
|
||||
|
||||
if (check_multi_manager) {
|
||||
// 0: ShirkNeko/SukiSU, DYNAMIC_SIGN_INDEX : Dynamic Sign
|
||||
if (matched_index == 0 || matched_index == DYNAMIC_SIGN_INDEX) {
|
||||
pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// Common manager check: any valid signature will do
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (v2_signing_valid) {
|
||||
if (signature_index) {
|
||||
*signature_index = matched_index;
|
||||
}
|
||||
|
||||
if (check_multi_manager) {
|
||||
// 0: ShirkNeko/SukiSU, DYNAMIC_SIGN_INDEX : Dynamic Sign
|
||||
if (matched_index == 0 || matched_index == DYNAMIC_SIGN_INDEX) {
|
||||
pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// Common manager check: any valid signature will do
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
|
|
@ -395,19 +394,19 @@ int ksu_debug_manager_uid = -1;
|
|||
|
||||
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;
|
||||
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,
|
||||
.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);
|
||||
&ksu_debug_manager_uid, S_IRUSR | S_IWUSR);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
302
kernel/app_profile.c
Normal file
302
kernel/app_profile.c
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/uidgid.h>
|
||||
#include <linux/version.h>
|
||||
#include "objsec.h"
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "app_profile.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "selinux/selinux.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "sucompat.h"
|
||||
#include "sulog.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0)
|
||||
static struct group_info root_groups = { .usage = REFCOUNT_INIT(2), };
|
||||
#else
|
||||
static struct group_info root_groups = { .usage = ATOMIC_INIT(2) };
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
group_info->gid[i] = kgid;
|
||||
}
|
||||
|
||||
groups_sort(group_info);
|
||||
set_groups(cred, group_info);
|
||||
put_group_info(group_info);
|
||||
}
|
||||
|
||||
void disable_seccomp(void)
|
||||
{
|
||||
assert_spin_locked(¤t->sighand->siglock);
|
||||
// disable seccomp
|
||||
#if defined(CONFIG_GENERIC_ENTRY) && \
|
||||
LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
clear_syscall_work(SECCOMP);
|
||||
#else
|
||||
clear_thread_flag(TIF_SECCOMP);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SECCOMP
|
||||
current->seccomp.mode = 0;
|
||||
current->seccomp.filter = NULL;
|
||||
atomic_set(¤t->seccomp.filter_count, 0);
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
void escape_with_root_profile(void)
|
||||
{
|
||||
struct cred *cred;
|
||||
struct task_struct *p = current;
|
||||
struct task_struct *t;
|
||||
|
||||
cred = prepare_creds();
|
||||
if (!cred) {
|
||||
pr_warn("prepare_creds failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root_failed");
|
||||
#endif
|
||||
abort_creds(cred);
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
setup_groups(profile, cred);
|
||||
|
||||
commit_creds(cred);
|
||||
|
||||
// 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);
|
||||
|
||||
setup_selinux(profile->selinux_domain);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root");
|
||||
#endif
|
||||
|
||||
for_each_thread (p, t) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
|
||||
#include "ksud.h"
|
||||
|
||||
#ifndef DEVPTS_SUPER_MAGIC
|
||||
#define DEVPTS_SUPER_MAGIC 0x1cd1
|
||||
#endif
|
||||
|
||||
static int __manual_su_handle_devpts(struct inode *inode)
|
||||
{
|
||||
if (!current->mm) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uid_t uid = current_uid().val;
|
||||
if (uid % 100000 < 10000) {
|
||||
// not untrusted_app, ignore it
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (likely(!ksu_is_allow_uid_for_current(uid)))
|
||||
return 0;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) || defined(KSU_OPTIONAL_SELINUX_INODE)
|
||||
struct inode_security_struct *sec = selinux_inode(inode);
|
||||
#else
|
||||
struct inode_security_struct *sec =
|
||||
(struct inode_security_struct *)inode->i_security;
|
||||
#endif
|
||||
if (ksu_file_sid && sec)
|
||||
sec->sid = ksu_file_sid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void disable_seccomp_for_task(struct task_struct *tsk)
|
||||
{
|
||||
assert_spin_locked(&tsk->sighand->siglock);
|
||||
#ifdef CONFIG_SECCOMP
|
||||
if (tsk->seccomp.mode == SECCOMP_MODE_DISABLED && !tsk->seccomp.filter)
|
||||
return;
|
||||
#endif
|
||||
clear_tsk_thread_flag(tsk, TIF_SECCOMP);
|
||||
#ifdef CONFIG_SECCOMP
|
||||
tsk->seccomp.mode = SECCOMP_MODE_DISABLED;
|
||||
if (tsk->seccomp.filter) {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
||||
seccomp_filter_release(tsk);
|
||||
#else
|
||||
put_seccomp_filter(tsk);
|
||||
tsk->seccomp.filter = NULL;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid)
|
||||
{
|
||||
struct cred *newcreds;
|
||||
struct task_struct *target_task;
|
||||
unsigned long flags;
|
||||
struct task_struct *p = current;
|
||||
struct task_struct *t;
|
||||
|
||||
pr_info("cmd_su: escape_to_root_for_cmd_su called for UID: %d, PID: %d\n", target_uid, target_pid);
|
||||
|
||||
// Find target task by PID
|
||||
rcu_read_lock();
|
||||
target_task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
||||
if (!target_task) {
|
||||
rcu_read_unlock();
|
||||
pr_err("cmd_su: target task not found for PID: %d\n", target_pid);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
get_task_struct(target_task);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (task_uid(target_task).val == 0) {
|
||||
pr_warn("cmd_su: target task is already root, PID: %d\n", target_pid);
|
||||
put_task_struct(target_task);
|
||||
return;
|
||||
}
|
||||
|
||||
newcreds = prepare_kernel_cred(target_task);
|
||||
if (newcreds == NULL) {
|
||||
pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed");
|
||||
#endif
|
||||
put_task_struct(target_task);
|
||||
return;
|
||||
}
|
||||
|
||||
struct root_profile *profile = ksu_get_root_profile(target_uid);
|
||||
|
||||
newcreds->uid.val = profile->uid;
|
||||
newcreds->suid.val = profile->uid;
|
||||
newcreds->euid.val = profile->uid;
|
||||
newcreds->fsuid.val = profile->uid;
|
||||
|
||||
newcreds->gid.val = profile->gid;
|
||||
newcreds->fsgid.val = profile->gid;
|
||||
newcreds->sgid.val = profile->gid;
|
||||
newcreds->egid.val = profile->gid;
|
||||
newcreds->securebits = 0;
|
||||
|
||||
u64 cap_for_cmd_su = profile->capabilities.effective | CAP_DAC_READ_SEARCH | CAP_SETUID | CAP_SETGID;
|
||||
memcpy(&newcreds->cap_effective, &cap_for_cmd_su, sizeof(newcreds->cap_effective));
|
||||
memcpy(&newcreds->cap_permitted, &profile->capabilities.effective, sizeof(newcreds->cap_permitted));
|
||||
memcpy(&newcreds->cap_bset, &profile->capabilities.effective, sizeof(newcreds->cap_bset));
|
||||
|
||||
setup_groups(profile, newcreds);
|
||||
task_lock(target_task);
|
||||
|
||||
const struct cred *old_creds = get_task_cred(target_task);
|
||||
|
||||
rcu_assign_pointer(target_task->real_cred, newcreds);
|
||||
rcu_assign_pointer(target_task->cred, get_cred(newcreds));
|
||||
task_unlock(target_task);
|
||||
|
||||
if (target_task->sighand) {
|
||||
spin_lock_irqsave(&target_task->sighand->siglock, flags);
|
||||
disable_seccomp_for_task(target_task);
|
||||
spin_unlock_irqrestore(&target_task->sighand->siglock, flags);
|
||||
}
|
||||
|
||||
setup_selinux(profile->selinux_domain);
|
||||
put_cred(old_creds);
|
||||
wake_up_process(target_task);
|
||||
|
||||
if (target_task->signal->tty) {
|
||||
struct inode *inode = target_task->signal->tty->driver_data;
|
||||
if (inode && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) {
|
||||
__manual_su_handle_devpts(inode);
|
||||
}
|
||||
}
|
||||
|
||||
put_task_struct(target_task);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation");
|
||||
#endif
|
||||
for_each_thread (p, t) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
}
|
||||
pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid);
|
||||
}
|
||||
#endif
|
||||
70
kernel/app_profile.h
Normal file
70
kernel/app_profile.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#ifndef __KSU_H_APP_PROFILE
|
||||
#define __KSU_H_APP_PROFILE
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
// Forward declarations
|
||||
struct cred;
|
||||
|
||||
#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;
|
||||
};
|
||||
};
|
||||
|
||||
// Escalate current process to root with the appropriate profile
|
||||
void escape_with_root_profile(void);
|
||||
|
||||
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid);
|
||||
|
||||
void disable_seccomp(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -18,10 +18,8 @@
|
|||
#define __PT_SP_REG sp
|
||||
#define __PT_IP_REG pc
|
||||
|
||||
#define PRCTL_SYMBOL "__arm64_sys_prctl"
|
||||
#define REBOOT_SYMBOL "__arm64_sys_reboot"
|
||||
#define SYS_READ_SYMBOL "__arm64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat"
|
||||
#define SYS_EXECVE_SYMBOL "__arm64_sys_execve"
|
||||
|
||||
#elif defined(__x86_64__)
|
||||
|
|
@ -39,10 +37,8 @@
|
|||
#define __PT_RC_REG ax
|
||||
#define __PT_SP_REG sp
|
||||
#define __PT_IP_REG ip
|
||||
#define PRCTL_SYMBOL "__x64_sys_prctl"
|
||||
#define REBOOT_SYMBOL "__x64_sys_reboot"
|
||||
#define SYS_READ_SYMBOL "__x64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat"
|
||||
#define SYS_EXECVE_SYMBOL "__x64_sys_execve"
|
||||
|
||||
#else
|
||||
|
|
|
|||
1092
kernel/core_hook.c
1092
kernel/core_hook.c
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +0,0 @@
|
|||
#ifndef __KSU_H_KSU_CORE
|
||||
#define __KSU_H_KSU_CORE
|
||||
|
||||
#include <linux/init.h>
|
||||
#include "apk_sign.h"
|
||||
|
||||
void __init ksu_core_init(void);
|
||||
void ksu_core_exit(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#include "dynamic_manager.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "manager.h"
|
||||
|
||||
#define MAX_MANAGERS 2
|
||||
|
|
@ -233,23 +232,23 @@ static void do_save_dynamic_manager(struct work_struct *work)
|
|||
return;
|
||||
}
|
||||
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||
if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||
pr_err("save_dynamic_manager write magic failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
if (kernel_write(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
pr_err("save_dynamic_manager write version failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
|
||||
if (kernel_write(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
|
||||
pr_err("save_dynamic_manager write config failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
|
@ -271,7 +270,7 @@ static void do_load_dynamic_manager(struct work_struct *work)
|
|||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
|
||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
if (PTR_ERR(fp) == -ENOENT) {
|
||||
pr_info("No saved dynamic manager config found\n");
|
||||
|
|
@ -281,20 +280,20 @@ static void do_load_dynamic_manager(struct work_struct *work)
|
|||
return;
|
||||
}
|
||||
|
||||
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
||||
if (kernel_read(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
||||
magic != DYNAMIC_MANAGER_FILE_MAGIC) {
|
||||
pr_err("dynamic manager file invalid magic: %x!\n", magic);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
if (kernel_read(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
pr_err("dynamic manager read version failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pr_info("dynamic manager file version: %d\n", version);
|
||||
|
||||
ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off);
|
||||
ret = kernel_read(fp, &loaded_config, sizeof(loaded_config), &off);
|
||||
if (ret <= 0) {
|
||||
pr_info("load_dynamic_manager read err: %zd\n", ret);
|
||||
goto exit;
|
||||
|
|
@ -348,14 +347,14 @@ static void do_clear_dynamic_manager(struct work_struct *work)
|
|||
|
||||
memset(zero_buffer, 0, sizeof(zero_buffer));
|
||||
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("clear_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
// Write null bytes to overwrite the file content
|
||||
if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
|
||||
if (kernel_write(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
|
||||
pr_err("clear_dynamic_manager write null bytes failed.\n");
|
||||
} else {
|
||||
pr_info("Dynamic sign config file cleared successfully\n");
|
||||
|
|
|
|||
173
kernel/feature.c
Normal file
173
kernel/feature.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
|
||||
#include <linux/mutex.h>
|
||||
|
||||
static const struct ksu_feature_handler *feature_handlers[KSU_FEATURE_MAX];
|
||||
|
||||
static DEFINE_MUTEX(feature_mutex);
|
||||
|
||||
int ksu_register_feature_handler(const struct ksu_feature_handler *handler)
|
||||
{
|
||||
if (!handler) {
|
||||
pr_err("feature: register handler is NULL\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (handler->feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", handler->feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!handler->get_handler && !handler->set_handler) {
|
||||
pr_err("feature: no handler provided for feature %u\n", handler->feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
if (feature_handlers[handler->feature_id]) {
|
||||
pr_warn("feature: handler for %u already registered, overwriting\n",
|
||||
handler->feature_id);
|
||||
}
|
||||
|
||||
feature_handlers[handler->feature_id] = handler;
|
||||
|
||||
pr_info("feature: registered handler for %s (id=%u)\n",
|
||||
handler->name ? handler->name : "unknown", handler->feature_id);
|
||||
|
||||
mutex_unlock(&feature_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_unregister_feature_handler(u32 feature_id)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
if (!feature_handlers[feature_id]) {
|
||||
pr_warn("feature: no handler registered for %u\n", feature_id);
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
feature_handlers[feature_id] = NULL;
|
||||
|
||||
pr_info("feature: unregistered handler for id=%u\n", feature_id);
|
||||
|
||||
out:
|
||||
mutex_unlock(&feature_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ksu_get_feature(u32 feature_id, u64 *value, bool *supported)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct ksu_feature_handler *handler;
|
||||
|
||||
if (feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!value || !supported) {
|
||||
pr_err("feature: invalid parameters\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
handler = feature_handlers[feature_id];
|
||||
|
||||
if (!handler) {
|
||||
*supported = false;
|
||||
*value = 0;
|
||||
pr_debug("feature: feature %u not supported\n", feature_id);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*supported = true;
|
||||
|
||||
if (!handler->get_handler) {
|
||||
pr_warn("feature: no get_handler for feature %u\n", feature_id);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = handler->get_handler(value);
|
||||
if (ret) {
|
||||
pr_err("feature: get_handler for %u failed: %d\n", feature_id, ret);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&feature_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ksu_set_feature(u32 feature_id, u64 value)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct ksu_feature_handler *handler;
|
||||
|
||||
if (feature_id >= KSU_FEATURE_MAX) {
|
||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
handler = feature_handlers[feature_id];
|
||||
|
||||
if (!handler) {
|
||||
pr_err("feature: feature %u not registered\n", feature_id);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!handler->set_handler) {
|
||||
pr_warn("feature: no set_handler for feature %u\n", feature_id);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = handler->set_handler(value);
|
||||
if (ret) {
|
||||
pr_err("feature: set_handler for %u failed: %d\n", feature_id, ret);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&feature_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ksu_feature_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < KSU_FEATURE_MAX; i++) {
|
||||
feature_handlers[i] = NULL;
|
||||
}
|
||||
|
||||
pr_info("feature: feature management initialized\n");
|
||||
}
|
||||
|
||||
void ksu_feature_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
mutex_lock(&feature_mutex);
|
||||
|
||||
for (i = 0; i < KSU_FEATURE_MAX; i++) {
|
||||
feature_handlers[i] = NULL;
|
||||
}
|
||||
|
||||
mutex_unlock(&feature_mutex);
|
||||
|
||||
pr_info("feature: feature management cleaned up\n");
|
||||
}
|
||||
36
kernel/feature.h
Normal file
36
kernel/feature.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef __KSU_H_FEATURE
|
||||
#define __KSU_H_FEATURE
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
enum ksu_feature_id {
|
||||
KSU_FEATURE_SU_COMPAT = 0,
|
||||
KSU_FEATURE_KERNEL_UMOUNT = 1,
|
||||
KSU_FEATURE_ENHANCED_SECURITY = 2,
|
||||
|
||||
KSU_FEATURE_MAX
|
||||
};
|
||||
|
||||
typedef int (*ksu_feature_get_t)(u64 *value);
|
||||
typedef int (*ksu_feature_set_t)(u64 value);
|
||||
|
||||
struct ksu_feature_handler {
|
||||
u32 feature_id;
|
||||
const char *name;
|
||||
ksu_feature_get_t get_handler;
|
||||
ksu_feature_set_t set_handler;
|
||||
};
|
||||
|
||||
int ksu_register_feature_handler(const struct ksu_feature_handler *handler);
|
||||
|
||||
int ksu_unregister_feature_handler(u32 feature_id);
|
||||
|
||||
int ksu_get_feature(u32 feature_id, u64 *value, bool *supported);
|
||||
|
||||
int ksu_set_feature(u32 feature_id, u64 value);
|
||||
|
||||
void ksu_feature_init(void);
|
||||
|
||||
void ksu_feature_exit(void);
|
||||
|
||||
#endif // __KSU_H_FEATURE
|
||||
341
kernel/file_wrapper.c
Normal file
341
kernel/file_wrapper.c
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
#include <linux/export.h>
|
||||
#include <linux/anon_inodes.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
#include "file_wrapper.h"
|
||||
|
||||
static loff_t ksu_wrapper_llseek(struct file *fp, loff_t off, int flags) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->llseek(data->orig, off, flags);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_read(struct file *fp, char __user *ptr, size_t sz, loff_t *off) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->read(orig, ptr, sz, off);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_write(struct file *fp, const char __user *ptr, size_t sz, loff_t *off) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->write(orig, ptr, sz, off);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_read_iter(struct kiocb *iocb, struct iov_iter *iovi) {
|
||||
struct ksu_file_wrapper* data = iocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
iocb->ki_filp = orig;
|
||||
return orig->f_op->read_iter(iocb, iovi);
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_write_iter(struct kiocb *iocb, struct iov_iter *iovi) {
|
||||
struct ksu_file_wrapper* data = iocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
iocb->ki_filp = orig;
|
||||
return orig->f_op->write_iter(iocb, iovi);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
|
||||
static int ksu_wrapper_iopoll(struct kiocb *kiocb, struct io_comp_batch* icb, unsigned int v) {
|
||||
struct ksu_file_wrapper* data = kiocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
kiocb->ki_filp = orig;
|
||||
return orig->f_op->iopoll(kiocb, icb, v);
|
||||
}
|
||||
#else
|
||||
static int ksu_wrapper_iopoll(struct kiocb *kiocb, bool spin) {
|
||||
struct ksu_file_wrapper* data = kiocb->ki_filp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
kiocb->ki_filp = orig;
|
||||
return orig->f_op->iopoll(kiocb, spin);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
static int ksu_wrapper_iterate (struct file *fp, struct dir_context *dc) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->iterate(orig, dc);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ksu_wrapper_iterate_shared(struct file *fp, struct dir_context *dc) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->iterate_shared(orig, dc);
|
||||
}
|
||||
|
||||
static __poll_t ksu_wrapper_poll(struct file *fp, struct poll_table_struct *pts) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->poll(orig, pts);
|
||||
}
|
||||
|
||||
static long ksu_wrapper_unlocked_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->unlocked_ioctl(orig, cmd, arg);
|
||||
}
|
||||
|
||||
static long ksu_wrapper_compat_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->compat_ioctl(orig, cmd, arg);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_mmap(struct file *fp, struct vm_area_struct * vma) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->mmap(orig, vma);
|
||||
}
|
||||
|
||||
// static unsigned long mmap_supported_flags {}
|
||||
|
||||
static int ksu_wrapper_open(struct inode *ino, struct file *fp) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
struct inode *orig_ino = file_inode(orig);
|
||||
return orig->f_op->open(orig_ino, orig);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_flush(struct file *fp, fl_owner_t id) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->flush(orig, id);
|
||||
}
|
||||
|
||||
|
||||
static int ksu_wrapper_fsync(struct file *fp, loff_t off1, loff_t off2, int datasync) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->fsync(orig, off1, off2, datasync);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_fasync(int arg, struct file *fp, int arg2) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->fasync(arg, orig, arg2);
|
||||
}
|
||||
|
||||
static int ksu_wrapper_lock(struct file *fp, int arg1, struct file_lock *fl) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
return orig->f_op->lock(orig, arg1, fl);
|
||||
}
|
||||
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
static ssize_t ksu_wrapper_sendpage(struct file *fp, struct page *pg, int arg1, size_t sz, loff_t *off, int arg2) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->sendpage) {
|
||||
return orig->f_op->sendpage(orig, pg, arg1, sz, off, arg2);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static unsigned long ksu_wrapper_get_unmapped_area(struct file *fp, unsigned long arg1, unsigned long arg2, unsigned long arg3, unsigned long arg4) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->get_unmapped_area) {
|
||||
return orig->f_op->get_unmapped_area(orig, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// static int ksu_wrapper_check_flags(int arg) {}
|
||||
|
||||
static int ksu_wrapper_flock(struct file *fp, int arg1, struct file_lock *fl) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->flock) {
|
||||
return orig->f_op->flock(orig, arg1, fl);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_splice_write(struct pipe_inode_info * pii, struct file *fp, loff_t *off, size_t sz, unsigned int arg1) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->splice_write) {
|
||||
return orig->f_op->splice_write(pii, orig, off, sz, arg1);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_splice_read(struct file *fp, loff_t *off, struct pipe_inode_info *pii, size_t sz, unsigned int arg1) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->splice_read) {
|
||||
return orig->f_op->splice_read(orig, off, pii, sz, arg1);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
||||
void ksu_wrapper_splice_eof(struct file *fp) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->splice_eof) {
|
||||
return orig->f_op->splice_eof(orig);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
static int ksu_wrapper_setlease(struct file *fp, int arg1, struct file_lease **fl, void **p) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->setlease) {
|
||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
||||
static int ksu_wrapper_setlease(struct file *fp, int arg1, struct file_lock **fl, void **p) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->setlease) {
|
||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#else
|
||||
static int ksu_wrapper_setlease(struct file *fp, long arg1, struct file_lock **fl, void **p) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->setlease) {
|
||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static long ksu_wrapper_fallocate(struct file *fp, int mode, loff_t offset, loff_t len) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->fallocate) {
|
||||
return orig->f_op->fallocate(orig, mode, offset, len);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void ksu_wrapper_show_fdinfo(struct seq_file *m, struct file *f) {
|
||||
struct ksu_file_wrapper* data = f->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->show_fdinfo) {
|
||||
orig->f_op->show_fdinfo(m, orig);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t ksu_wrapper_copy_file_range(struct file *f1, loff_t off1, struct file *f2,
|
||||
loff_t off2, size_t sz, unsigned int flags) {
|
||||
// TODO: determine which file to use
|
||||
struct ksu_file_wrapper* data = f1->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->copy_file_range) {
|
||||
return orig->f_op->copy_file_range(orig, off1, f2, off2, sz, flags);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static loff_t ksu_wrapper_remap_file_range(struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out,
|
||||
loff_t len, unsigned int remap_flags) {
|
||||
// TODO: determine which file to use
|
||||
struct ksu_file_wrapper* data = file_in->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->remap_file_range) {
|
||||
return orig->f_op->remap_file_range(orig, pos_in, file_out, pos_out, len, remap_flags);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ksu_wrapper_fadvise(struct file *fp, loff_t off1, loff_t off2, int flags) {
|
||||
struct ksu_file_wrapper* data = fp->private_data;
|
||||
struct file* orig = data->orig;
|
||||
if (orig->f_op->fadvise) {
|
||||
return orig->f_op->fadvise(orig, off1, off2, flags);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ksu_wrapper_release(struct inode *inode, struct file *filp) {
|
||||
ksu_delete_file_wrapper(filp->private_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ksu_file_wrapper* ksu_create_file_wrapper(struct file* fp) {
|
||||
struct ksu_file_wrapper* p = kcalloc(sizeof(struct ksu_file_wrapper), 1, GFP_KERNEL);
|
||||
if (!p) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
get_file(fp);
|
||||
|
||||
p->orig = fp;
|
||||
p->ops.owner = THIS_MODULE;
|
||||
p->ops.llseek = fp->f_op->llseek ? ksu_wrapper_llseek : NULL;
|
||||
p->ops.read = fp->f_op->read ? ksu_wrapper_read : NULL;
|
||||
p->ops.write = fp->f_op->write ? ksu_wrapper_write : NULL;
|
||||
p->ops.read_iter = fp->f_op->read_iter ? ksu_wrapper_read_iter : NULL;
|
||||
p->ops.write_iter = fp->f_op->write_iter ? ksu_wrapper_write_iter : NULL;
|
||||
p->ops.iopoll = fp->f_op->iopoll ? ksu_wrapper_iopoll : NULL;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
p->ops.iterate = fp->f_op->iterate ? ksu_wrapper_iterate : NULL;
|
||||
#endif
|
||||
p->ops.iterate_shared = fp->f_op->iterate_shared ? ksu_wrapper_iterate_shared : NULL;
|
||||
p->ops.poll = fp->f_op->poll ? ksu_wrapper_poll : NULL;
|
||||
p->ops.unlocked_ioctl = fp->f_op->unlocked_ioctl ? ksu_wrapper_unlocked_ioctl : NULL;
|
||||
p->ops.compat_ioctl = fp->f_op->compat_ioctl ? ksu_wrapper_compat_ioctl : NULL;
|
||||
p->ops.mmap = fp->f_op->mmap ? ksu_wrapper_mmap : NULL;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
p->ops.fop_flags = fp->f_op->fop_flags;
|
||||
#else
|
||||
p->ops.mmap_supported_flags = fp->f_op->mmap_supported_flags;
|
||||
#endif
|
||||
p->ops.open = fp->f_op->open ? ksu_wrapper_open : NULL;
|
||||
p->ops.flush = fp->f_op->flush ? ksu_wrapper_flush : NULL;
|
||||
p->ops.release = ksu_wrapper_release;
|
||||
p->ops.fsync = fp->f_op->fsync ? ksu_wrapper_fsync : NULL;
|
||||
p->ops.fasync = fp->f_op->fasync ? ksu_wrapper_fasync : NULL;
|
||||
p->ops.lock = fp->f_op->lock ? ksu_wrapper_lock : NULL;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
p->ops.sendpage = fp->f_op->sendpage ? ksu_wrapper_sendpage : NULL;
|
||||
#endif
|
||||
p->ops.get_unmapped_area = fp->f_op->get_unmapped_area ? ksu_wrapper_get_unmapped_area : NULL;
|
||||
p->ops.check_flags = fp->f_op->check_flags;
|
||||
p->ops.flock = fp->f_op->flock ? ksu_wrapper_flock : NULL;
|
||||
p->ops.splice_write = fp->f_op->splice_write ? ksu_wrapper_splice_write : NULL;
|
||||
p->ops.splice_read = fp->f_op->splice_read ? ksu_wrapper_splice_read : NULL;
|
||||
p->ops.setlease = fp->f_op->setlease ? ksu_wrapper_setlease : NULL;
|
||||
p->ops.fallocate = fp->f_op->fallocate ? ksu_wrapper_fallocate : NULL;
|
||||
p->ops.show_fdinfo = fp->f_op->show_fdinfo ? ksu_wrapper_show_fdinfo : NULL;
|
||||
p->ops.copy_file_range = fp->f_op->copy_file_range ? ksu_wrapper_copy_file_range : NULL;
|
||||
p->ops.remap_file_range = fp->f_op->remap_file_range ? ksu_wrapper_remap_file_range : NULL;
|
||||
p->ops.fadvise = fp->f_op->fadvise ? ksu_wrapper_fadvise : NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
||||
p->ops.splice_eof = fp->f_op->splice_eof ? ksu_wrapper_splice_eof : NULL;
|
||||
#endif
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void ksu_delete_file_wrapper(struct ksu_file_wrapper* data) {
|
||||
fput((struct file*) data->orig);
|
||||
kfree(data);
|
||||
}
|
||||
14
kernel/file_wrapper.h
Normal file
14
kernel/file_wrapper.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef KSU_FILE_WRAPPER_H
|
||||
#define KSU_FILE_WRAPPER_H
|
||||
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
struct ksu_file_wrapper {
|
||||
struct file* orig;
|
||||
struct file_operations ops;
|
||||
};
|
||||
|
||||
struct ksu_file_wrapper* ksu_create_file_wrapper(struct file* fp);
|
||||
void ksu_delete_file_wrapper(struct ksu_file_wrapper* data);
|
||||
#endif // KSU_FILE_WRAPPER_H
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#ifndef __KSU_H_KSHOOK
|
||||
#define __KSU_H_KSHOOK
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
// 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
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
#include <linux/version.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
|
||||
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;
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
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);
|
||||
#endif
|
||||
ksu_save_ns_fs(&android_context_saved);
|
||||
} else {
|
||||
pr_info("android context saved disabled\n");
|
||||
}
|
||||
task_unlock(current);
|
||||
}
|
||||
|
||||
struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode)
|
||||
{
|
||||
// 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) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("start switch current nsproxy and fs to android context\n");
|
||||
#endif
|
||||
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);
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("switch current nsproxy and fs back to saved successfully\n");
|
||||
#endif
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
return kernel_read(p, buf, count, pos);
|
||||
}
|
||||
|
||||
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
return kernel_write(p, buf, count, pos);
|
||||
}
|
||||
|
||||
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||
long count)
|
||||
{
|
||||
return strncpy_from_user_nofault(dst, unsafe_addr, count);
|
||||
}
|
||||
|
|
@ -3,63 +3,7 @@
|
|||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/version.h>
|
||||
#include "ss/policydb.h"
|
||||
#include "linux/key.h"
|
||||
|
||||
/**
|
||||
* list_count_nodes - count the number of nodes in a list
|
||||
* @head: the head of the list
|
||||
*
|
||||
* This function iterates over the list starting from @head and counts
|
||||
* the number of nodes in the list. It does not modify the list.
|
||||
*
|
||||
* Context: Any context. The function is safe to call in any context,
|
||||
* including interrupt context, as it does not sleep or allocate
|
||||
* memory.
|
||||
*
|
||||
* Return: the number of nodes in the list (excluding the head)
|
||||
*/
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
static inline __maybe_unused size_t list_count_nodes(const struct list_head *head)
|
||||
{
|
||||
const struct list_head *pos;
|
||||
size_t count = 0;
|
||||
|
||||
if (!head)
|
||||
return 0;
|
||||
|
||||
list_for_each(pos, head)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
#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
|
||||
|
||||
extern long ksu_strncpy_from_user_nofault(char *dst,
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
|
||||
extern void ksu_android_ns_fs_check();
|
||||
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);
|
||||
/*
|
||||
* ksu_copy_from_user_retry
|
||||
* try nofault copy first, if it fails, try with plain
|
||||
|
|
@ -67,14 +11,14 @@ extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf,
|
|||
* 0 = success
|
||||
*/
|
||||
static long ksu_copy_from_user_retry(void *to,
|
||||
const void __user *from, unsigned long count)
|
||||
const void __user *from, unsigned long count)
|
||||
{
|
||||
long ret = copy_from_user_nofault(to, from, count);
|
||||
if (likely(!ret))
|
||||
return ret;
|
||||
long ret = copy_from_user_nofault(to, from, count);
|
||||
if (likely(!ret))
|
||||
return ret;
|
||||
|
||||
// we faulted! fallback to slow path
|
||||
return copy_from_user(to, from, count);
|
||||
// we faulted! fallback to slow path
|
||||
return copy_from_user(to, from, count);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
193
kernel/kernel_umount.c
Normal file
193
kernel/kernel_umount.c
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "kernel_umount.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "allowlist.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "feature.h"
|
||||
#include "ksud.h"
|
||||
|
||||
#include "umount_manager.h"
|
||||
#include "sulog.h"
|
||||
|
||||
static bool ksu_kernel_umount_enabled = true;
|
||||
|
||||
static int kernel_umount_feature_get(u64 *value)
|
||||
{
|
||||
*value = ksu_kernel_umount_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kernel_umount_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
ksu_kernel_umount_enabled = enable;
|
||||
pr_info("kernel_umount: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler kernel_umount_handler = {
|
||||
.feature_id = KSU_FEATURE_KERNEL_UMOUNT,
|
||||
.name = "kernel_umount",
|
||||
.get_handler = kernel_umount_feature_get,
|
||||
.set_handler = kernel_umount_feature_set,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
extern int path_umount(struct path *path, int flags);
|
||||
|
||||
static void ksu_umount_mnt(struct path *path, int flags)
|
||||
{
|
||||
int err = path_umount(path, flags);
|
||||
if (err) {
|
||||
pr_info("umount %s failed: %d\n", path->dentry->d_iname, err);
|
||||
}
|
||||
}
|
||||
|
||||
void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
{
|
||||
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.
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
ksu_umount_mnt(&path, flags);
|
||||
}
|
||||
|
||||
struct umount_tw {
|
||||
struct callback_head cb;
|
||||
const struct cred *old_cred;
|
||||
};
|
||||
|
||||
static void umount_tw_func(struct callback_head *cb)
|
||||
{
|
||||
struct umount_tw *tw = container_of(cb, struct umount_tw, cb);
|
||||
const struct cred *saved = NULL;
|
||||
if (tw->old_cred) {
|
||||
saved = override_creds(tw->old_cred);
|
||||
}
|
||||
|
||||
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
||||
// filter the mountpoint whose target is `/data/adb`
|
||||
ksu_umount_manager_execute_all(tw->old_cred);
|
||||
|
||||
if (saved)
|
||||
revert_creds(saved);
|
||||
|
||||
if (tw->old_cred)
|
||||
put_cred(tw->old_cred);
|
||||
|
||||
kfree(tw);
|
||||
}
|
||||
|
||||
int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
||||
{
|
||||
struct umount_tw *tw;
|
||||
|
||||
// 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 (!ksu_kernel_umount_enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FIXME: isolated process which directly forks from zygote is not handled
|
||||
if (!is_appuid(new_uid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_uid_should_umount(new_uid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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 = is_zygote(get_current_cred());
|
||||
if (!is_zygote_child) {
|
||||
pr_info("handle umount ignore non zygote child: %d\n", current->pid);
|
||||
return 0;
|
||||
}
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_syscall(new_uid, NULL, "setuid", NULL);
|
||||
#endif
|
||||
// umount the target mnt
|
||||
pr_info("handle umount for uid: %d, pid: %d\n", new_uid, current->pid);
|
||||
|
||||
tw = kmalloc(sizeof(*tw), GFP_ATOMIC);
|
||||
if (!tw)
|
||||
return 0;
|
||||
|
||||
tw->old_cred = get_current_cred();
|
||||
tw->cb.func = umount_tw_func;
|
||||
|
||||
int err = task_work_add(current, &tw->cb, TWA_RESUME);
|
||||
if (err) {
|
||||
if (tw->old_cred) {
|
||||
put_cred(tw->old_cred);
|
||||
}
|
||||
kfree(tw);
|
||||
pr_warn("unmount add task_work failed\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_kernel_umount_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
rc = ksu_umount_manager_init();
|
||||
if (rc) {
|
||||
pr_err("Failed to initialize umount manager: %d\n", rc);
|
||||
}
|
||||
if (ksu_register_feature_handler(&kernel_umount_handler)) {
|
||||
pr_err("Failed to register kernel_umount feature handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_kernel_umount_exit(void)
|
||||
{
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT);
|
||||
}
|
||||
14
kernel/kernel_umount.h
Normal file
14
kernel/kernel_umount.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef __KSU_H_KERNEL_UMOUNT
|
||||
#define __KSU_H_KERNEL_UMOUNT
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
void ksu_kernel_umount_init(void);
|
||||
void ksu_kernel_umount_exit(void);
|
||||
|
||||
void try_umount(const char *mnt, bool check_mnt, int flags);
|
||||
|
||||
// Handler function to be called from setresuid hook
|
||||
int ksu_handle_umount(uid_t old_uid, uid_t new_uid);
|
||||
|
||||
#endif
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
static int sukisu_is_su_allow_uid(uid_t uid)
|
||||
{
|
||||
return ksu_is_allow_uid(uid) ? 1 : 0;
|
||||
return ksu_is_allow_uid_for_current(uid) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int sukisu_get_ap_mod_exclude(uid_t uid)
|
||||
|
|
|
|||
273
kernel/kpm/kpm.c
273
kernel/kpm/kpm.c
|
|
@ -9,13 +9,10 @@
|
|||
* 并参照KernelPatch的标准KPM格式实现加载和控制
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/kernfs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/elf.h>
|
||||
|
|
@ -25,26 +22,25 @@
|
|||
#include <linux/spinlock.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <asm/elf.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/string.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/set_memory.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/insn.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/stacktrace.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0) && defined(CONFIG_MODULES)
|
||||
#include <linux/moduleloader.h>
|
||||
#endif
|
||||
#include "kpm.h"
|
||||
#include "compact.h"
|
||||
|
||||
#define KPM_NAME_LEN 32
|
||||
#define KPM_ARGS_LEN 1024
|
||||
|
||||
#ifndef NO_OPTIMIZE
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#define NO_OPTIMIZE __attribute__((optimize("O0")))
|
||||
|
|
@ -56,156 +52,231 @@
|
|||
#endif
|
||||
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_load_module_path(const char *path,
|
||||
const char *args, void *ptr, void __user *result)
|
||||
const char *args, void *ptr, int *result)
|
||||
{
|
||||
int res = -1;
|
||||
|
||||
printk("KPM: Stub function called (sukisu_kpm_load_module_path). "
|
||||
pr_info("kpm: Stub function called (sukisu_kpm_load_module_path). "
|
||||
"path=%s args=%s ptr=%p\n", path, args, ptr);
|
||||
|
||||
__asm__ volatile("nop");
|
||||
|
||||
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||
printk("KPM: Copy to user failed.");
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_kpm_load_module_path);
|
||||
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_unload_module(const char *name,
|
||||
void *ptr, void __user *result)
|
||||
void *ptr, int *result)
|
||||
{
|
||||
int res = -1;
|
||||
|
||||
printk("KPM: Stub function called (sukisu_kpm_unload_module). "
|
||||
pr_info("kpm: Stub function called (sukisu_kpm_unload_module). "
|
||||
"name=%s ptr=%p\n", name, ptr);
|
||||
|
||||
__asm__ volatile("nop");
|
||||
|
||||
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||
printk("KPM: Copy to user failed.");
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_kpm_unload_module);
|
||||
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_num(void __user *result)
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_num(int *result)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
printk("KPM: Stub function called (sukisu_kpm_num).\n");
|
||||
pr_info("kpm: Stub function called (sukisu_kpm_num).\n");
|
||||
|
||||
__asm__ volatile("nop");
|
||||
|
||||
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||
printk("KPM: Copy to user failed.");
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_kpm_num);
|
||||
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_info(const char *name, void __user *out,
|
||||
void __user *result)
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_info(const char *name, char *buf, int bufferSize,
|
||||
int *size)
|
||||
{
|
||||
int res = -1;
|
||||
|
||||
printk("KPM: Stub function called (sukisu_kpm_info). "
|
||||
"name=%s buffer=%p\n", name, out);
|
||||
pr_info("kpm: Stub function called (sukisu_kpm_info). "
|
||||
"name=%s buffer=%p\n", name, buf);
|
||||
|
||||
__asm__ volatile("nop");
|
||||
|
||||
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||
printk("KPM: Copy to user failed.");
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_kpm_info);
|
||||
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_list(void __user *out, unsigned int bufferSize,
|
||||
void __user *result)
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_list(void *out, int bufferSize,
|
||||
int *result)
|
||||
{
|
||||
int res = -1;
|
||||
|
||||
printk("KPM: Stub function called (sukisu_kpm_list). "
|
||||
pr_info("kpm: Stub function called (sukisu_kpm_list). "
|
||||
"buffer=%p size=%d\n", out, bufferSize);
|
||||
|
||||
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||
printk("KPM: Copy to user failed.");
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_kpm_list);
|
||||
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_control(void __user *name, void __user *args,
|
||||
void __user *result)
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_control(const char *name, const char *args, long arg_len,
|
||||
int *result)
|
||||
{
|
||||
int res = -1;
|
||||
|
||||
printk("KPM: Stub function called (sukisu_kpm_control). "
|
||||
"name=%p args=%p\n", name, args);
|
||||
pr_info("kpm: Stub function called (sukisu_kpm_control). "
|
||||
"name=%p args=%p arg_len=%ld\n", name, args, arg_len);
|
||||
|
||||
__asm__ volatile("nop");
|
||||
|
||||
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||
printk("KPM: Copy to user failed.");
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_kpm_control);
|
||||
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_version(void __user *out, unsigned int bufferSize,
|
||||
void __user *result)
|
||||
noinline NO_OPTIMIZE void sukisu_kpm_version(char *buf, int bufferSize)
|
||||
{
|
||||
int res = -1;
|
||||
|
||||
printk("KPM: Stub function called (sukisu_kpm_version). "
|
||||
"buffer=%p size=%d\n", out, bufferSize);
|
||||
|
||||
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||
printk("KPM: Copy to user failed.");
|
||||
pr_info("kpm: Stub function called (sukisu_kpm_version). "
|
||||
"buffer=%p\n", buf);
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_kpm_version);
|
||||
|
||||
noinline int sukisu_handle_kpm(unsigned long arg2, unsigned long arg3, unsigned long arg4,
|
||||
unsigned long arg5)
|
||||
noinline int sukisu_handle_kpm(unsigned long control_code, unsigned long arg1, unsigned long arg2,
|
||||
unsigned long result_code)
|
||||
{
|
||||
if (arg2 == SUKISU_KPM_LOAD) {
|
||||
char kernel_load_path[256] = { 0 };
|
||||
char kernel_args_buffer[256] = { 0 };
|
||||
int res = -1;
|
||||
if (control_code == SUKISU_KPM_LOAD) {
|
||||
char kernel_load_path[256];
|
||||
char kernel_args_buffer[256];
|
||||
|
||||
if (arg3 == 0)
|
||||
return -1;
|
||||
if (arg1 == 0) {
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!access_ok(arg1, 255)) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
strncpy_from_user((char *)&kernel_load_path, (const char __user *)arg3, 255);
|
||||
strncpy_from_user((char *)&kernel_load_path, (const char *)arg1, 255);
|
||||
|
||||
if (arg4 != 0)
|
||||
strncpy_from_user((char *)&kernel_args_buffer, (const char __user *)arg4, 255);
|
||||
if (arg2 != 0) {
|
||||
if (!access_ok(arg2, 255)) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
strncpy_from_user((char *)&kernel_args_buffer, (const char *)arg2, 255);
|
||||
}
|
||||
|
||||
sukisu_kpm_load_module_path((const char *)&kernel_load_path,
|
||||
(const char *)&kernel_args_buffer, NULL, (void __user *)arg5);
|
||||
} else if (arg2 == SUKISU_KPM_UNLOAD) {
|
||||
char kernel_name_buffer[256] = { 0 };
|
||||
(const char *)&kernel_args_buffer, NULL, &res);
|
||||
} else if (control_code == SUKISU_KPM_UNLOAD) {
|
||||
char kernel_name_buffer[256];
|
||||
|
||||
if (arg3 == 0)
|
||||
return -1;
|
||||
|
||||
strncpy_from_user((char *)&kernel_name_buffer, (const char __user *)arg3, 255);
|
||||
|
||||
sukisu_kpm_unload_module((const char *)&kernel_name_buffer, NULL,
|
||||
(void __user *)arg5);
|
||||
} else if (arg2 == SUKISU_KPM_NUM) {
|
||||
sukisu_kpm_num((void __user *)arg5);
|
||||
} else if (arg2 == SUKISU_KPM_INFO) {
|
||||
char kernel_name_buffer[256] = { 0 };
|
||||
if (arg1 == 0) {
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (arg3 == 0 || arg4 == 0)
|
||||
return -1;
|
||||
if (!access_ok(arg1, sizeof(kernel_name_buffer))) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
strncpy_from_user((char *)&kernel_name_buffer, (const char __user *)arg3, 255);
|
||||
strncpy_from_user((char *)&kernel_name_buffer, (const char *)arg1, sizeof(kernel_name_buffer));
|
||||
|
||||
sukisu_kpm_info((const char *)&kernel_name_buffer, (char __user *)arg4,
|
||||
(void __user *)arg5);
|
||||
} else if (arg2 == SUKISU_KPM_LIST) {
|
||||
sukisu_kpm_list((char __user *)arg3, (unsigned int)arg4, (void __user *)arg5);
|
||||
} else if (arg2 == SUKISU_KPM_CONTROL) {
|
||||
sukisu_kpm_control((char __user *)arg3, (char __user *)arg4, (void __user *)arg5);
|
||||
} else if (arg2 == SUKISU_KPM_VERSION) {
|
||||
sukisu_kpm_version((char __user *)arg3, (unsigned int)arg4, (void __user *)arg5);
|
||||
sukisu_kpm_unload_module((const char *)&kernel_name_buffer, NULL, &res);
|
||||
} else if (control_code == SUKISU_KPM_NUM) {
|
||||
sukisu_kpm_num(&res);
|
||||
} else if (control_code == SUKISU_KPM_INFO) {
|
||||
char kernel_name_buffer[256];
|
||||
char buf[256];
|
||||
int size;
|
||||
|
||||
if (arg1 == 0 || arg2 == 0) {
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!access_ok(arg1, sizeof(kernel_name_buffer))) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
strncpy_from_user((char *)&kernel_name_buffer, (const char __user *)arg1, sizeof(kernel_name_buffer));
|
||||
|
||||
sukisu_kpm_info((const char *)&kernel_name_buffer, (char *)&buf, sizeof(buf), &size);
|
||||
|
||||
if (!access_ok(arg2, size)) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
res = copy_to_user(arg2, &buf, size);
|
||||
|
||||
} else if (control_code == SUKISU_KPM_LIST) {
|
||||
char buf[1024];
|
||||
int len = (int) arg2;
|
||||
|
||||
if (len <= 0) {
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!access_ok(arg2, len)) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
sukisu_kpm_list((char *)&buf, sizeof(buf), &res);
|
||||
|
||||
if (res > len) {
|
||||
res = -ENOBUFS;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (copy_to_user(arg1, &buf, len) != 0)
|
||||
pr_info("kpm: Copy to user failed.");
|
||||
|
||||
} else if (control_code == SUKISU_KPM_CONTROL) {
|
||||
char kpm_name[KPM_NAME_LEN] = { 0 };
|
||||
char kpm_args[KPM_ARGS_LEN] = { 0 };
|
||||
|
||||
if (!access_ok(arg1, sizeof(kpm_name))) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
if (!access_ok(arg2, sizeof(kpm_args))) {
|
||||
goto invalid_arg;
|
||||
}
|
||||
|
||||
long name_len = strncpy_from_user((char *)&kpm_name, (const char __user *)arg1, sizeof(kpm_name));
|
||||
if (name_len <= 0) {
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
long arg_len = strncpy_from_user((char *)&kpm_args, (const char __user *)arg2, sizeof(kpm_args));
|
||||
|
||||
sukisu_kpm_control((const char *)&kpm_name, (const char *)&kpm_args, arg_len, &res);
|
||||
|
||||
} else if (control_code == SUKISU_KPM_VERSION) {
|
||||
char buffer[256] = {0};
|
||||
|
||||
sukisu_kpm_version((char*) &buffer, sizeof(buffer));
|
||||
|
||||
unsigned int outlen = (unsigned int) arg2;
|
||||
int len = strlen(buffer);
|
||||
if (len >= outlen) len = outlen - 1;
|
||||
|
||||
res = copy_to_user(arg1, &buffer, len + 1);
|
||||
}
|
||||
|
||||
exit:
|
||||
if (copy_to_user(result_code, &res, sizeof(res)) != 0)
|
||||
pr_info("kpm: Copy to user failed.");
|
||||
|
||||
return 0;
|
||||
invalid_arg:
|
||||
pr_err("kpm: invalid pointer detected! arg1: %px arg2: %px\n", (void *)arg1, (void *)arg2);
|
||||
res = -EFAULT;
|
||||
goto exit;
|
||||
}
|
||||
EXPORT_SYMBOL(sukisu_handle_kpm);
|
||||
|
||||
int sukisu_is_kpm_control_code(unsigned long arg2) {
|
||||
return (arg2 >= CMD_KPM_CONTROL &&
|
||||
arg2 <= CMD_KPM_CONTROL_MAX) ? 1 : 0;
|
||||
int sukisu_is_kpm_control_code(unsigned long control_code) {
|
||||
return (control_code >= CMD_KPM_CONTROL &&
|
||||
control_code <= CMD_KPM_CONTROL_MAX) ? 1 : 0;
|
||||
}
|
||||
|
||||
int do_kpm(void __user *arg)
|
||||
{
|
||||
struct ksu_kpm_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("kpm: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!access_ok(cmd.control_code, sizeof(int))) {
|
||||
pr_err("kpm: invalid control_code pointer %px\n", (void *)cmd.control_code);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!access_ok(cmd.result_code, sizeof(int))) {
|
||||
pr_err("kpm: invalid result_code pointer %px\n", (void *)cmd.result_code);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return sukisu_handle_kpm(cmd.control_code, cmd.arg1, cmd.arg2, cmd.result_code);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +1,70 @@
|
|||
#ifndef __SUKISU_KPM_H
|
||||
#define __SUKISU_KPM_H
|
||||
|
||||
extern int sukisu_handle_kpm(unsigned long arg2, unsigned long arg3, unsigned long arg4,
|
||||
unsigned long arg5);
|
||||
extern int sukisu_is_kpm_control_code(unsigned long arg2);
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
struct ksu_kpm_cmd {
|
||||
__aligned_u64 __user control_code;
|
||||
__aligned_u64 __user arg1;
|
||||
__aligned_u64 __user arg2;
|
||||
__aligned_u64 __user result_code;
|
||||
};
|
||||
|
||||
int sukisu_handle_kpm(unsigned long control_code, unsigned long arg3, unsigned long arg4, unsigned long result_code);
|
||||
int sukisu_is_kpm_control_code(unsigned long control_code);
|
||||
int do_kpm(void __user *arg);
|
||||
|
||||
#define KSU_IOCTL_KPM _IOC(_IOC_READ|_IOC_WRITE, 'K', 200, 0)
|
||||
|
||||
/* KPM Control Code */
|
||||
#define CMD_KPM_CONTROL 28
|
||||
#define CMD_KPM_CONTROL_MAX 35
|
||||
#define CMD_KPM_CONTROL 1
|
||||
#define CMD_KPM_CONTROL_MAX 10
|
||||
|
||||
/* Control Code */
|
||||
/*
|
||||
* prctl(xxx, 28, "PATH", "ARGS")
|
||||
* prctl(xxx, 1, "PATH", "ARGS")
|
||||
* success return 0, error return -N
|
||||
*/
|
||||
#define SUKISU_KPM_LOAD 28
|
||||
#define SUKISU_KPM_LOAD 1
|
||||
|
||||
/*
|
||||
* prctl(xxx, 29, "NAME")
|
||||
* prctl(xxx, 2, "NAME")
|
||||
* success return 0, error return -N
|
||||
*/
|
||||
#define SUKISU_KPM_UNLOAD 29
|
||||
#define SUKISU_KPM_UNLOAD 2
|
||||
|
||||
/*
|
||||
* num = prctl(xxx, 30)
|
||||
* num = prctl(xxx, 3)
|
||||
* error return -N
|
||||
* success return +num or 0
|
||||
*/
|
||||
#define SUKISU_KPM_NUM 30
|
||||
#define SUKISU_KPM_NUM 3
|
||||
|
||||
/*
|
||||
* prctl(xxx, 31, Buffer, BufferSize)
|
||||
* prctl(xxx, 4, Buffer, BufferSize)
|
||||
* success return +out, error return -N
|
||||
*/
|
||||
#define SUKISU_KPM_LIST 31
|
||||
#define SUKISU_KPM_LIST 4
|
||||
|
||||
/*
|
||||
* prctl(xxx, 32, "NAME", Buffer[256])
|
||||
* prctl(xxx, 5, "NAME", Buffer[256])
|
||||
* success return +out, error return -N
|
||||
*/
|
||||
#define SUKISU_KPM_INFO 32
|
||||
#define SUKISU_KPM_INFO 5
|
||||
|
||||
/*
|
||||
* prctl(xxx, 33, "NAME", "ARGS")
|
||||
* prctl(xxx, 6, "NAME", "ARGS")
|
||||
* success return KPM's result value
|
||||
* error return -N
|
||||
*/
|
||||
#define SUKISU_KPM_CONTROL 33
|
||||
#define SUKISU_KPM_CONTROL 6
|
||||
|
||||
/*
|
||||
* prctl(xxx, 34, buffer, bufferSize)
|
||||
* prctl(xxx, 7, buffer, bufferSize)
|
||||
* success return KPM's result value
|
||||
* error return -N
|
||||
*/
|
||||
#define SUKISU_KPM_VERSION 34
|
||||
#define SUKISU_KPM_VERSION 7
|
||||
|
||||
#endif
|
||||
|
|
|
|||
107
kernel/ksu.c
107
kernel/ksu.c
|
|
@ -3,89 +3,103 @@
|
|||
#include <linux/kobject.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "core_hook.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "throne_tracker.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "ksud.h"
|
||||
#include "supercalls.h"
|
||||
|
||||
#include "sulog.h"
|
||||
#include "throne_comm.h"
|
||||
#include "dynamic_manager.h"
|
||||
|
||||
static struct workqueue_struct *ksu_workqueue;
|
||||
|
||||
bool ksu_queue_work(struct work_struct *work)
|
||||
{
|
||||
return queue_work(ksu_workqueue, 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);
|
||||
void sukisu_custom_config_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
extern void ksu_sucompat_init();
|
||||
extern void ksu_sucompat_exit();
|
||||
extern void ksu_ksud_init();
|
||||
extern void ksu_ksud_exit();
|
||||
#ifdef CONFIG_KSU_TRACEPOINT_HOOK
|
||||
extern void ksu_trace_register();
|
||||
extern void ksu_trace_unregister();
|
||||
void sukisu_custom_config_exit(void)
|
||||
{
|
||||
ksu_uid_exit();
|
||||
ksu_throne_comm_exit();
|
||||
ksu_dynamic_manager_exit();
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_exit();
|
||||
#endif
|
||||
}
|
||||
|
||||
int __init 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("*************************************************************");
|
||||
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
|
||||
|
||||
ksu_core_init();
|
||||
ksu_feature_init();
|
||||
|
||||
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
||||
ksu_supercalls_init();
|
||||
|
||||
ksu_allowlist_init();
|
||||
sukisu_custom_config_init();
|
||||
|
||||
ksu_throne_tracker_init();
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
ksu_sucompat_init();
|
||||
ksu_ksud_init();
|
||||
ksu_syscall_hook_manager_init();
|
||||
|
||||
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
||||
|
||||
ksu_allowlist_init();
|
||||
|
||||
ksu_throne_tracker_init();
|
||||
|
||||
#ifdef KSU_KPROBES_HOOK
|
||||
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 CONFIG_KSU_TRACEPOINT_HOOK
|
||||
ksu_trace_register();
|
||||
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);
|
||||
kobject_del(&THIS_MODULE->mkobj.kobj);
|
||||
#endif
|
||||
#endif
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern void ksu_observer_exit(void);
|
||||
void kernelsu_exit(void)
|
||||
{
|
||||
ksu_allowlist_exit();
|
||||
ksu_allowlist_exit();
|
||||
|
||||
ksu_throne_tracker_exit();
|
||||
ksu_observer_exit();
|
||||
|
||||
destroy_workqueue(ksu_workqueue);
|
||||
ksu_throne_tracker_exit();
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
ksu_ksud_exit();
|
||||
ksu_sucompat_exit();
|
||||
destroy_workqueue(ksu_workqueue);
|
||||
|
||||
#ifdef KSU_KPROBES_HOOK
|
||||
ksu_ksud_exit();
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_TRACEPOINT_HOOK
|
||||
ksu_trace_unregister();
|
||||
#endif
|
||||
ksu_syscall_hook_manager_exit();
|
||||
|
||||
ksu_core_exit();
|
||||
sukisu_custom_config_exit();
|
||||
|
||||
ksu_supercalls_exit();
|
||||
|
||||
ksu_feature_exit();
|
||||
}
|
||||
|
||||
module_init(kernelsu_init);
|
||||
|
|
@ -94,4 +108,9 @@ module_exit(kernelsu_exit);
|
|||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("weishu");
|
||||
MODULE_DESCRIPTION("Android KernelSU");
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0)
|
||||
MODULE_IMPORT_NS("VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver");
|
||||
#else
|
||||
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
|
||||
#endif
|
||||
|
|
|
|||
96
kernel/ksu.h
96
kernel/ksu.h
|
|
@ -7,40 +7,12 @@
|
|||
#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 CMD_GET_FULL_VERSION 0xC0FFEE1A
|
||||
|
||||
#define CMD_ENABLE_KPM 100
|
||||
#define CMD_HOOK_TYPE 101
|
||||
#define CMD_DYNAMIC_MANAGER 103
|
||||
#define CMD_GET_MANAGERS 104
|
||||
extern bool ksu_uid_scanner_enabled;
|
||||
|
||||
#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
|
||||
|
||||
// SukiSU Ultra kernel su version full strings
|
||||
#ifndef KSU_VERSION_FULL
|
||||
#define KSU_VERSION_FULL "v3.x-00000000@unknown"
|
||||
|
|
@ -51,6 +23,10 @@
|
|||
#define DYNAMIC_MANAGER_OP_GET 1
|
||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||
|
||||
#define UID_SCANNER_OP_GET_STATUS 0
|
||||
#define UID_SCANNER_OP_TOGGLE 1
|
||||
#define UID_SCANNER_OP_CLEAR_ENV 2
|
||||
|
||||
struct dynamic_manager_user_config {
|
||||
unsigned int operation;
|
||||
unsigned int size;
|
||||
|
|
@ -65,68 +41,22 @@ struct manager_list_info {
|
|||
} managers[2];
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
#if 0
|
||||
static inline int startswith(char *s, char *prefix)
|
||||
{
|
||||
return strncmp(s, prefix, strlen(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);
|
||||
size_t slen = strlen(s);
|
||||
size_t tlen = strlen(t);
|
||||
if (tlen > slen)
|
||||
return 1;
|
||||
return strcmp(s + slen - tlen, t);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
#include "ksu_trace.h"
|
||||
|
||||
|
||||
// extern kernelsu functions
|
||||
extern bool ksu_vfs_read_hook __read_mostly;
|
||||
extern bool ksu_input_hook __read_mostly;
|
||||
extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags);
|
||||
extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, int *flags);
|
||||
extern int ksu_handle_sys_read(unsigned int fd, char __user **buf_ptr, size_t *count_ptr);
|
||||
extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||
extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value);
|
||||
// end kernelsu functions
|
||||
|
||||
|
||||
// tracepoint callback functions
|
||||
void ksu_trace_execveat_sucompat_hook_callback(void *data, int *fd, struct filename **filename_ptr,
|
||||
void *argv, void *envp, int *flags)
|
||||
{
|
||||
ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, flags);
|
||||
}
|
||||
|
||||
void ksu_trace_faccessat_hook_callback(void *data, int *dfd, const char __user **filename_user,
|
||||
int *mode, int *flags)
|
||||
{
|
||||
ksu_handle_faccessat(dfd, filename_user, mode, flags);
|
||||
}
|
||||
|
||||
void ksu_trace_sys_read_hook_callback(void *data, unsigned int fd, char __user **buf_ptr,
|
||||
size_t *count_ptr)
|
||||
{
|
||||
if (unlikely(ksu_vfs_read_hook))
|
||||
ksu_handle_sys_read(fd, buf_ptr, count_ptr);
|
||||
}
|
||||
|
||||
void ksu_trace_stat_hook_callback(void *data, int *dfd, const char __user **filename_user,
|
||||
int *flags)
|
||||
{
|
||||
ksu_handle_stat(dfd, filename_user, flags);
|
||||
}
|
||||
|
||||
void ksu_trace_input_hook_callback(void *data, unsigned int *type, unsigned int *code,
|
||||
int *value)
|
||||
{
|
||||
if (unlikely(ksu_input_hook))
|
||||
ksu_handle_input_handle_event(type, code, value);
|
||||
}
|
||||
|
||||
// end tracepoint callback functions
|
||||
|
||||
|
||||
// register tracepoint callback functions
|
||||
void ksu_trace_register(void)
|
||||
{
|
||||
register_trace_ksu_trace_execveat_sucompat_hook(ksu_trace_execveat_sucompat_hook_callback, NULL);
|
||||
register_trace_ksu_trace_faccessat_hook(ksu_trace_faccessat_hook_callback, NULL);
|
||||
register_trace_ksu_trace_sys_read_hook(ksu_trace_sys_read_hook_callback, NULL);
|
||||
register_trace_ksu_trace_stat_hook(ksu_trace_stat_hook_callback, NULL);
|
||||
register_trace_ksu_trace_input_hook(ksu_trace_input_hook_callback, NULL);
|
||||
}
|
||||
|
||||
// unregister tracepoint callback functions
|
||||
void ksu_trace_unregister(void)
|
||||
{
|
||||
unregister_trace_ksu_trace_execveat_sucompat_hook(ksu_trace_execveat_sucompat_hook_callback, NULL);
|
||||
unregister_trace_ksu_trace_faccessat_hook(ksu_trace_faccessat_hook_callback, NULL);
|
||||
unregister_trace_ksu_trace_sys_read_hook(ksu_trace_sys_read_hook_callback, NULL);
|
||||
unregister_trace_ksu_trace_stat_hook(ksu_trace_stat_hook_callback, NULL);
|
||||
unregister_trace_ksu_trace_input_hook(ksu_trace_input_hook_callback, NULL);
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM ksu_trace
|
||||
|
||||
#if !defined(_KSU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _KSU_TRACE_H
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
DECLARE_TRACE(ksu_trace_execveat_sucompat_hook,
|
||||
TP_PROTO(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags),
|
||||
TP_ARGS(fd, filename_ptr, argv, envp, flags));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_faccessat_hook,
|
||||
TP_PROTO(int *dfd, const char __user **filename_user, int *mode, int *flags),
|
||||
TP_ARGS(dfd, filename_user, mode, flags));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_sys_read_hook,
|
||||
TP_PROTO(unsigned int fd, char __user **buf_ptr, size_t *count_ptr),
|
||||
TP_ARGS(fd, buf_ptr, count_ptr));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_stat_hook,
|
||||
TP_PROTO(int *dfd, const char __user **filename_user, int *flags),
|
||||
TP_ARGS(dfd, filename_user, flags));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_input_hook,
|
||||
TP_PROTO(unsigned int *type, unsigned int *code, int *value),
|
||||
TP_ARGS(type, code, value));
|
||||
|
||||
#endif /* _KSU_TRACE_H */
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_FILE ksu_trace
|
||||
|
||||
#include <trace/define_trace.h>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#define CREATE_TRACE_POINTS
|
||||
#include "ksu_trace.h"
|
||||
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_execveat_sucompat_hook);
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_faccessat_hook);
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_sys_read_hook);
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_stat_hook);
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_input_hook);
|
||||
921
kernel/ksud.c
921
kernel/ksud.c
File diff suppressed because it is too large
Load diff
|
|
@ -3,13 +3,17 @@
|
|||
|
||||
#define KSUD_PATH "/data/adb/ksud"
|
||||
|
||||
void ksu_ksud_init();
|
||||
void ksu_ksud_exit();
|
||||
|
||||
void on_post_fs_data(void);
|
||||
void on_module_mounted(void);
|
||||
void on_boot_completed(void);
|
||||
|
||||
bool ksu_is_safe_mode(void);
|
||||
|
||||
extern u32 ksu_devpts_sid;
|
||||
|
||||
extern bool ksu_execveat_hook __read_mostly;
|
||||
extern int ksu_handle_pre_ksud(const char *filename);
|
||||
extern u32 ksu_file_sid;
|
||||
extern bool ksu_module_mounted;
|
||||
extern bool ksu_boot_completed;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -13,30 +13,31 @@ extern void ksu_add_manager(uid_t uid, int signature_index);
|
|||
extern void ksu_remove_manager(uid_t uid);
|
||||
extern int ksu_get_manager_signature_index(uid_t uid);
|
||||
|
||||
static inline bool ksu_is_manager_uid_valid()
|
||||
static inline bool ksu_is_manager_uid_valid(void)
|
||||
{
|
||||
return ksu_manager_uid != KSU_INVALID_UID;
|
||||
return ksu_manager_uid != KSU_INVALID_UID;
|
||||
}
|
||||
|
||||
static inline bool is_manager()
|
||||
static inline bool is_manager(void)
|
||||
{
|
||||
return unlikely(ksu_is_any_manager(current_uid().val) ||
|
||||
(ksu_manager_uid != KSU_INVALID_UID && ksu_manager_uid == current_uid().val));
|
||||
return unlikely(ksu_is_any_manager(current_uid().val) ||
|
||||
(ksu_manager_uid != KSU_INVALID_UID && ksu_manager_uid == current_uid().val));
|
||||
}
|
||||
|
||||
static inline uid_t ksu_get_manager_uid()
|
||||
static inline uid_t ksu_get_manager_uid(void)
|
||||
{
|
||||
return ksu_manager_uid;
|
||||
return ksu_manager_uid;
|
||||
}
|
||||
|
||||
static inline void ksu_set_manager_uid(uid_t uid)
|
||||
{
|
||||
ksu_manager_uid = uid;
|
||||
ksu_manager_uid = uid;
|
||||
}
|
||||
|
||||
static inline void ksu_invalidate_manager_uid()
|
||||
static inline void ksu_invalidate_manager_uid(void)
|
||||
{
|
||||
ksu_manager_uid = KSU_INVALID_UID;
|
||||
ksu_manager_uid = KSU_INVALID_UID;
|
||||
}
|
||||
|
||||
#endif
|
||||
int ksu_observer_init(void);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -9,5 +9,9 @@
|
|||
#define EXPECTED_SIZE_OTHER 0x300
|
||||
#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
typedef struct {
|
||||
unsigned size;
|
||||
const char *sha256;
|
||||
} apk_sign_key_t;
|
||||
|
||||
#endif /* MANAGER_SIGN_H */
|
||||
#endif /* MANAGER_SIGN_H */
|
||||
|
|
|
|||
356
kernel/manual_su.c
Normal file
356
kernel/manual_su.c
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
#include <linux/string.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include "manual_su.h"
|
||||
#include "ksu.h"
|
||||
#include "allowlist.h"
|
||||
#include "manager.h"
|
||||
#include "app_profile.h"
|
||||
|
||||
static bool current_verified = false;
|
||||
static void ksu_cleanup_expired_tokens(void);
|
||||
static bool is_current_verified(void);
|
||||
static void add_pending_root(uid_t uid);
|
||||
|
||||
static struct pending_uid pending_uids[MAX_PENDING] = {0};
|
||||
static int pending_cnt = 0;
|
||||
static struct ksu_token_entry auth_tokens[MAX_TOKENS] = {0};
|
||||
static int token_count = 0;
|
||||
static DEFINE_SPINLOCK(token_lock);
|
||||
|
||||
static char* get_token_from_envp(void)
|
||||
{
|
||||
struct mm_struct *mm;
|
||||
char *envp_start, *envp_end;
|
||||
char *env_ptr, *token = NULL;
|
||||
unsigned long env_len;
|
||||
char *env_copy = NULL;
|
||||
|
||||
if (!current->mm)
|
||||
return NULL;
|
||||
|
||||
mm = current->mm;
|
||||
|
||||
down_read(&mm->mmap_lock);
|
||||
|
||||
envp_start = (char *)mm->env_start;
|
||||
envp_end = (char *)mm->env_end;
|
||||
env_len = envp_end - envp_start;
|
||||
|
||||
if (env_len <= 0 || env_len > PAGE_SIZE * 32) {
|
||||
up_read(&mm->mmap_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
env_copy = kmalloc(env_len + 1, GFP_KERNEL);
|
||||
if (!env_copy) {
|
||||
up_read(&mm->mmap_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (copy_from_user(env_copy, envp_start, env_len)) {
|
||||
kfree(env_copy);
|
||||
up_read(&mm->mmap_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
up_read(&mm->mmap_lock);
|
||||
|
||||
env_copy[env_len] = '\0';
|
||||
env_ptr = env_copy;
|
||||
|
||||
while (env_ptr < env_copy + env_len) {
|
||||
if (strncmp(env_ptr, KSU_TOKEN_ENV_NAME "=", strlen(KSU_TOKEN_ENV_NAME) + 1) == 0) {
|
||||
char *token_start = env_ptr + strlen(KSU_TOKEN_ENV_NAME) + 1;
|
||||
char *token_end = strchr(token_start, '\0');
|
||||
|
||||
if (token_end && (token_end - token_start) == KSU_TOKEN_LENGTH) {
|
||||
token = kmalloc(KSU_TOKEN_LENGTH + 1, GFP_KERNEL);
|
||||
if (token) {
|
||||
memcpy(token, token_start, KSU_TOKEN_LENGTH);
|
||||
token[KSU_TOKEN_LENGTH] = '\0';
|
||||
pr_info("manual_su: found auth token in environment\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
env_ptr += strlen(env_ptr) + 1;
|
||||
}
|
||||
|
||||
kfree(env_copy);
|
||||
return token;
|
||||
}
|
||||
|
||||
static char* ksu_generate_auth_token(void)
|
||||
{
|
||||
static char token_buffer[KSU_TOKEN_LENGTH + 1];
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
ksu_cleanup_expired_tokens();
|
||||
|
||||
spin_lock_irqsave(&token_lock, flags);
|
||||
|
||||
if (token_count >= MAX_TOKENS) {
|
||||
for (i = 0; i < MAX_TOKENS - 1; i++) {
|
||||
auth_tokens[i] = auth_tokens[i + 1];
|
||||
}
|
||||
token_count = MAX_TOKENS - 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < KSU_TOKEN_LENGTH; i++) {
|
||||
u8 rand_byte;
|
||||
get_random_bytes(&rand_byte, 1);
|
||||
int char_type = rand_byte % 3;
|
||||
if (char_type == 0) {
|
||||
token_buffer[i] = 'A' + (rand_byte % 26);
|
||||
} else if (char_type == 1) {
|
||||
token_buffer[i] = 'a' + (rand_byte % 26);
|
||||
} else {
|
||||
token_buffer[i] = '0' + (rand_byte % 10);
|
||||
}
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
||||
#else
|
||||
strlcpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
||||
#endif
|
||||
auth_tokens[token_count].expire_time = jiffies + KSU_TOKEN_EXPIRE_TIME * HZ;
|
||||
auth_tokens[token_count].used = false;
|
||||
token_count++;
|
||||
|
||||
spin_unlock_irqrestore(&token_lock, flags);
|
||||
|
||||
pr_info("manual_su: generated new auth token (expires in %d seconds)\n", KSU_TOKEN_EXPIRE_TIME);
|
||||
return token_buffer;
|
||||
}
|
||||
|
||||
static bool ksu_verify_auth_token(const char *token)
|
||||
{
|
||||
unsigned long flags;
|
||||
bool valid = false;
|
||||
int i;
|
||||
|
||||
if (!token || strlen(token) != KSU_TOKEN_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&token_lock, flags);
|
||||
|
||||
for (i = 0; i < token_count; i++) {
|
||||
if (!auth_tokens[i].used &&
|
||||
time_before(jiffies, auth_tokens[i].expire_time) &&
|
||||
strcmp(auth_tokens[i].token, token) == 0) {
|
||||
|
||||
auth_tokens[i].used = true;
|
||||
valid = true;
|
||||
pr_info("manual_su: auth token verified successfully\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&token_lock, flags);
|
||||
|
||||
if (!valid) {
|
||||
pr_warn("manual_su: invalid or expired auth token\n");
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static void ksu_cleanup_expired_tokens(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i, j;
|
||||
|
||||
spin_lock_irqsave(&token_lock, flags);
|
||||
|
||||
for (i = 0; i < token_count; ) {
|
||||
if (time_after(jiffies, auth_tokens[i].expire_time) || auth_tokens[i].used) {
|
||||
for (j = i; j < token_count - 1; j++) {
|
||||
auth_tokens[j] = auth_tokens[j + 1];
|
||||
}
|
||||
token_count--;
|
||||
pr_debug("manual_su: cleaned up expired/used token\n");
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&token_lock, flags);
|
||||
}
|
||||
|
||||
static int handle_token_generation(struct manual_su_request *request)
|
||||
{
|
||||
if (current_uid().val > 2000) {
|
||||
pr_warn("manual_su: token generation denied for app UID %d\n", current_uid().val);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
char *new_token = ksu_generate_auth_token();
|
||||
if (!new_token) {
|
||||
pr_err("manual_su: failed to generate token\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
#else
|
||||
strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
#endif
|
||||
|
||||
pr_info("manual_su: auth token generated successfully\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_escalation_request(struct manual_su_request *request)
|
||||
{
|
||||
uid_t target_uid = request->target_uid;
|
||||
pid_t target_pid = request->target_pid;
|
||||
struct task_struct *tsk;
|
||||
|
||||
rcu_read_lock();
|
||||
tsk = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
||||
if (!tsk || ksu_task_is_dead(tsk)) {
|
||||
rcu_read_unlock();
|
||||
pr_err("cmd_su: PID %d is invalid or dead\n", target_pid);
|
||||
return -ESRCH;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
if (current_uid().val == 0 || is_manager() || ksu_is_allow_uid_for_current(current_uid().val))
|
||||
goto allowed;
|
||||
|
||||
char *env_token = get_token_from_envp();
|
||||
if (!env_token) {
|
||||
pr_warn("manual_su: no auth token found in environment\n");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
bool token_valid = ksu_verify_auth_token(env_token);
|
||||
kfree(env_token);
|
||||
|
||||
if (!token_valid) {
|
||||
pr_warn("manual_su: token verification failed\n");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
allowed:
|
||||
current_verified = true;
|
||||
escape_to_root_for_cmd_su(target_uid, target_pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_add_pending_request(struct manual_su_request *request)
|
||||
{
|
||||
uid_t target_uid = request->target_uid;
|
||||
|
||||
if (!is_current_verified()) {
|
||||
pr_warn("manual_su: add_pending denied, not verified\n");
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
add_pending_root(target_uid);
|
||||
current_verified = false;
|
||||
pr_info("manual_su: pending root added for UID %d\n", target_uid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_handle_manual_su_request(int option, struct manual_su_request *request)
|
||||
{
|
||||
if (!request) {
|
||||
pr_err("manual_su: invalid request pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case MANUAL_SU_OP_GENERATE_TOKEN:
|
||||
pr_info("manual_su: handling token generation request\n");
|
||||
return handle_token_generation(request);
|
||||
|
||||
case MANUAL_SU_OP_ESCALATE:
|
||||
pr_info("manual_su: handling escalation request for UID %d, PID %d\n",
|
||||
request->target_uid, request->target_pid);
|
||||
return handle_escalation_request(request);
|
||||
|
||||
case MANUAL_SU_OP_ADD_PENDING:
|
||||
pr_info("manual_su: handling add pending request for UID %d\n", request->target_uid);
|
||||
return handle_add_pending_request(request);
|
||||
|
||||
default:
|
||||
pr_err("manual_su: unknown option %d\n", option);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_current_verified(void)
|
||||
{
|
||||
return current_verified;
|
||||
}
|
||||
|
||||
bool is_pending_root(uid_t uid)
|
||||
{
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].use_count++;
|
||||
pending_uids[i].remove_calls++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void remove_pending_root(uid_t uid)
|
||||
{
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].remove_calls++;
|
||||
|
||||
if (pending_uids[i].remove_calls >= REMOVE_DELAY_CALLS) {
|
||||
pending_uids[i] = pending_uids[--pending_cnt];
|
||||
pr_info("pending_root: removed UID %d after %d calls\n", uid, REMOVE_DELAY_CALLS);
|
||||
ksu_temp_revoke_root_once(uid);
|
||||
} else {
|
||||
pr_info("pending_root: UID %d remove_call=%d (<%d)\n",
|
||||
uid, pending_uids[i].remove_calls, REMOVE_DELAY_CALLS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_pending_root(uid_t uid)
|
||||
{
|
||||
if (pending_cnt >= MAX_PENDING) {
|
||||
pr_warn("pending_root: cache full\n");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].use_count = 0;
|
||||
pending_uids[i].remove_calls = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pending_uids[pending_cnt++] = (struct pending_uid){uid, 0};
|
||||
ksu_temp_grant_root_once(uid);
|
||||
pr_info("pending_root: cached UID %d\n", uid);
|
||||
}
|
||||
|
||||
void ksu_try_escalate_for_uid(uid_t uid)
|
||||
{
|
||||
if (!is_pending_root(uid))
|
||||
return;
|
||||
|
||||
pr_info("pending_root: UID=%d temporarily allowed\n", uid);
|
||||
remove_pending_root(uid);
|
||||
}
|
||||
49
kernel/manual_su.h
Normal file
49
kernel/manual_su.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef __KSU_MANUAL_SU_H
|
||||
#define __KSU_MANUAL_SU_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
|
||||
#define mmap_lock mmap_sem
|
||||
#endif
|
||||
|
||||
#define ksu_task_is_dead(t) ((t)->exit_state != 0)
|
||||
|
||||
#define MAX_PENDING 16
|
||||
#define REMOVE_DELAY_CALLS 150
|
||||
#define MAX_TOKENS 10
|
||||
|
||||
#define KSU_SU_VERIFIED_BIT (1UL << 0)
|
||||
#define KSU_TOKEN_LENGTH 32
|
||||
#define KSU_TOKEN_ENV_NAME "KSU_AUTH_TOKEN"
|
||||
#define KSU_TOKEN_EXPIRE_TIME 150
|
||||
|
||||
#define MANUAL_SU_OP_GENERATE_TOKEN 0
|
||||
#define MANUAL_SU_OP_ESCALATE 1
|
||||
#define MANUAL_SU_OP_ADD_PENDING 2
|
||||
|
||||
struct pending_uid {
|
||||
uid_t uid;
|
||||
int use_count;
|
||||
int remove_calls;
|
||||
};
|
||||
|
||||
struct manual_su_request {
|
||||
uid_t target_uid;
|
||||
pid_t target_pid;
|
||||
char token_buffer[KSU_TOKEN_LENGTH + 1];
|
||||
};
|
||||
|
||||
struct ksu_token_entry {
|
||||
char token[KSU_TOKEN_LENGTH + 1];
|
||||
unsigned long expire_time;
|
||||
bool used;
|
||||
};
|
||||
|
||||
int ksu_handle_manual_su_request(int option, struct manual_su_request *request);
|
||||
bool is_pending_root(uid_t uid);
|
||||
void remove_pending_root(uid_t uid);
|
||||
void ksu_try_escalate_for_uid(uid_t uid);
|
||||
#endif
|
||||
133
kernel/pkg_observer.c
Normal file
133
kernel/pkg_observer.c
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/fsnotify_backend.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/version.h>
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "throne_tracker.h"
|
||||
#include "throne_comm.h"
|
||||
|
||||
#define MASK_SYSTEM (FS_CREATE | FS_MOVE | FS_EVENT_ON_CHILD)
|
||||
|
||||
struct watch_dir {
|
||||
const char *path;
|
||||
u32 mask;
|
||||
struct path kpath;
|
||||
struct inode *inode;
|
||||
struct fsnotify_mark *mark;
|
||||
};
|
||||
|
||||
static struct fsnotify_group *g;
|
||||
|
||||
static int ksu_handle_inode_event(struct fsnotify_mark *mark, u32 mask,
|
||||
struct inode *inode, struct inode *dir,
|
||||
const struct qstr *file_name, u32 cookie)
|
||||
{
|
||||
if (!file_name)
|
||||
return 0;
|
||||
if (mask & FS_ISDIR)
|
||||
return 0;
|
||||
if (file_name->len == 13 &&
|
||||
!memcmp(file_name->name, "packages.list", 13)) {
|
||||
pr_info("packages.list detected: %d\n", mask);
|
||||
if (ksu_uid_scanner_enabled) {
|
||||
ksu_request_userspace_scan();
|
||||
}
|
||||
track_throne(false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct fsnotify_ops ksu_ops = {
|
||||
.handle_inode_event = ksu_handle_inode_event,
|
||||
};
|
||||
|
||||
static int add_mark_on_inode(struct inode *inode, u32 mask,
|
||||
struct fsnotify_mark **out)
|
||||
{
|
||||
struct fsnotify_mark *m;
|
||||
|
||||
m = kzalloc(sizeof(*m), GFP_KERNEL);
|
||||
if (!m)
|
||||
return -ENOMEM;
|
||||
|
||||
fsnotify_init_mark(m, g);
|
||||
m->mask = mask;
|
||||
|
||||
if (fsnotify_add_inode_mark(m, inode, 0)) {
|
||||
fsnotify_put_mark(m);
|
||||
return -EINVAL;
|
||||
}
|
||||
*out = m;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int watch_one_dir(struct watch_dir *wd)
|
||||
{
|
||||
int ret = kern_path(wd->path, LOOKUP_FOLLOW, &wd->kpath);
|
||||
if (ret) {
|
||||
pr_info("path not ready: %s (%d)\n", wd->path, ret);
|
||||
return ret;
|
||||
}
|
||||
wd->inode = d_inode(wd->kpath.dentry);
|
||||
ihold(wd->inode);
|
||||
|
||||
ret = add_mark_on_inode(wd->inode, wd->mask, &wd->mark);
|
||||
if (ret) {
|
||||
pr_err("Add mark failed for %s (%d)\n", wd->path, ret);
|
||||
path_put(&wd->kpath);
|
||||
iput(wd->inode);
|
||||
wd->inode = NULL;
|
||||
return ret;
|
||||
}
|
||||
pr_info("watching %s\n", wd->path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void unwatch_one_dir(struct watch_dir *wd)
|
||||
{
|
||||
if (wd->mark) {
|
||||
fsnotify_destroy_mark(wd->mark, g);
|
||||
fsnotify_put_mark(wd->mark);
|
||||
wd->mark = NULL;
|
||||
}
|
||||
if (wd->inode) {
|
||||
iput(wd->inode);
|
||||
wd->inode = NULL;
|
||||
}
|
||||
if (wd->kpath.dentry) {
|
||||
path_put(&wd->kpath);
|
||||
memset(&wd->kpath, 0, sizeof(wd->kpath));
|
||||
}
|
||||
}
|
||||
|
||||
static struct watch_dir g_watch = { .path = "/data/system",
|
||||
.mask = MASK_SYSTEM };
|
||||
|
||||
int ksu_observer_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
|
||||
g = fsnotify_alloc_group(&ksu_ops, 0);
|
||||
#else
|
||||
g = fsnotify_alloc_group(&ksu_ops);
|
||||
#endif
|
||||
if (IS_ERR(g))
|
||||
return PTR_ERR(g);
|
||||
|
||||
ret = watch_one_dir(&g_watch);
|
||||
pr_info("observer init done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_observer_exit(void)
|
||||
{
|
||||
unwatch_one_dir(&g_watch);
|
||||
fsnotify_put_group(g);
|
||||
pr_info("observer exit done\n");
|
||||
}
|
||||
69
kernel/seccomp_cache.c
Normal file
69
kernel/seccomp_cache.c
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include <linux/version.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "seccomp_cache.h"
|
||||
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
||||
struct action_cache {
|
||||
DECLARE_BITMAP(allow_native, SECCOMP_ARCH_NATIVE_NR);
|
||||
#ifdef SECCOMP_ARCH_COMPAT
|
||||
DECLARE_BITMAP(allow_compat, SECCOMP_ARCH_COMPAT_NR);
|
||||
#endif
|
||||
};
|
||||
|
||||
struct seccomp_filter {
|
||||
refcount_t refs;
|
||||
refcount_t users;
|
||||
bool log;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
|
||||
bool wait_killable_recv;
|
||||
#endif
|
||||
struct action_cache cache;
|
||||
struct seccomp_filter *prev;
|
||||
struct bpf_prog *prog;
|
||||
struct notification *notif;
|
||||
struct mutex notify_lock;
|
||||
wait_queue_head_t wqh;
|
||||
};
|
||||
|
||||
void ksu_seccomp_clear_cache(struct seccomp_filter *filter, int nr)
|
||||
{
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nr >= 0 && nr < SECCOMP_ARCH_NATIVE_NR) {
|
||||
clear_bit(nr, filter->cache.allow_native);
|
||||
}
|
||||
|
||||
#ifdef SECCOMP_ARCH_COMPAT
|
||||
if (nr >= 0 && nr < SECCOMP_ARCH_COMPAT_NR) {
|
||||
clear_bit(nr, filter->cache.allow_compat);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_seccomp_allow_cache(struct seccomp_filter *filter, int nr)
|
||||
{
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nr >= 0 && nr < SECCOMP_ARCH_NATIVE_NR) {
|
||||
set_bit(nr, filter->cache.allow_native);
|
||||
}
|
||||
|
||||
#ifdef SECCOMP_ARCH_COMPAT
|
||||
if (nr >= 0 && nr < SECCOMP_ARCH_COMPAT_NR) {
|
||||
set_bit(nr, filter->cache.allow_compat);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
12
kernel/seccomp_cache.h
Normal file
12
kernel/seccomp_cache.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef __KSU_H_SECCOMP_CACHE
|
||||
#define __KSU_H_SECCOMP_CACHE
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
||||
extern void ksu_seccomp_clear_cache(struct seccomp_filter *filter, int nr);
|
||||
extern void ksu_seccomp_allow_cache(struct seccomp_filter *filter, int nr);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -2,15 +2,7 @@ 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-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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#include "selinux.h"
|
||||
#include "sepolicy.h"
|
||||
#include "ss/services.h"
|
||||
#include "linux/lsm_audit.h"
|
||||
#include "linux/lsm_audit.h" // IWYU pragma: keep
|
||||
#include "xfrm.h"
|
||||
|
||||
#define SELINUX_POLICY_INSTEAD_SELINUX_SS
|
||||
|
|
@ -18,119 +18,119 @@
|
|||
|
||||
static struct policydb *get_policydb(void)
|
||||
{
|
||||
struct policydb *db;
|
||||
struct selinux_policy *policy = selinux_state.policy;
|
||||
db = &policy->policydb;
|
||||
return db;
|
||||
struct policydb *db;
|
||||
struct selinux_policy *policy = selinux_state.policy;
|
||||
db = &policy->policydb;
|
||||
return db;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(ksu_rules);
|
||||
|
||||
void apply_kernelsu_rules()
|
||||
{
|
||||
struct policydb *db;
|
||||
struct policydb *db;
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, apply rules!\n");
|
||||
}
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, apply rules!\n");
|
||||
}
|
||||
|
||||
mutex_lock(&ksu_rules);
|
||||
mutex_lock(&ksu_rules);
|
||||
|
||||
db = get_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");
|
||||
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);
|
||||
// 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 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);
|
||||
}
|
||||
// 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");
|
||||
// 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");
|
||||
// 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");
|
||||
// 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");
|
||||
// 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");
|
||||
// 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");
|
||||
// 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 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");
|
||||
|
||||
// https://android-review.googlesource.com/c/platform/system/logging/+/3725346
|
||||
ksu_dontaudit(db, "untrusted_app", KERNEL_SU_DOMAIN, "dir", "getattr");
|
||||
// https://android-review.googlesource.com/c/platform/system/logging/+/3725346
|
||||
ksu_dontaudit(db, "untrusted_app", KERNEL_SU_DOMAIN, "dir", "getattr");
|
||||
|
||||
mutex_unlock(&ksu_rules);
|
||||
mutex_unlock(&ksu_rules);
|
||||
}
|
||||
|
||||
#define MAX_SEPOL_LEN 128
|
||||
|
|
@ -145,401 +145,333 @@ void apply_kernelsu_rules()
|
|||
#define CMD_TYPE_CHANGE 8
|
||||
#define CMD_GENFSCON 9
|
||||
|
||||
#ifdef CONFIG_64BIT
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u64 field_sepol1;
|
||||
u64 field_sepol2;
|
||||
u64 field_sepol3;
|
||||
u64 field_sepol4;
|
||||
u64 field_sepol5;
|
||||
u64 field_sepol6;
|
||||
u64 field_sepol7;
|
||||
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;
|
||||
};
|
||||
#ifdef CONFIG_COMPAT
|
||||
extern bool ksu_is_compat __read_mostly;
|
||||
struct sepol_compat_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_COMPAT
|
||||
#else
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_64BIT
|
||||
|
||||
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||
char **object)
|
||||
char **object)
|
||||
{
|
||||
if (!user_object) {
|
||||
*object = ALL;
|
||||
return 0;
|
||||
}
|
||||
if (!user_object) {
|
||||
*object = ALL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*object = buf;
|
||||
*object = buf;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))
|
||||
extern int avc_ss_reset(u32 seqno);
|
||||
#else
|
||||
extern int avc_ss_reset(struct selinux_avc *avc, u32 seqno);
|
||||
#endif
|
||||
// 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))
|
||||
avc_ss_reset(0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(0);
|
||||
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);
|
||||
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();
|
||||
selinux_xfrm_notify_policyload();
|
||||
}
|
||||
|
||||
int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
{
|
||||
struct policydb *db;
|
||||
struct policydb *db;
|
||||
|
||||
if (!arg4) {
|
||||
return -1;
|
||||
}
|
||||
if (!arg4) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||
}
|
||||
|
||||
u32 cmd, subcmd;
|
||||
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||
}
|
||||
|
||||
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
|
||||
if (unlikely(ksu_is_compat)) {
|
||||
struct sepol_compat_data compat_data;
|
||||
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = compat_ptr(compat_data.field_sepol1);
|
||||
sepol2 = compat_ptr(compat_data.field_sepol2);
|
||||
sepol3 = compat_ptr(compat_data.field_sepol3);
|
||||
sepol4 = compat_ptr(compat_data.field_sepol4);
|
||||
sepol5 = compat_ptr(compat_data.field_sepol5);
|
||||
sepol6 = compat_ptr(compat_data.field_sepol6);
|
||||
sepol7 = compat_ptr(compat_data.field_sepol7);
|
||||
cmd = compat_data.cmd;
|
||||
subcmd = compat_data.subcmd;
|
||||
} else {
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
}
|
||||
#else
|
||||
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=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;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
#endif
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ksu_rules);
|
||||
u32 cmd = data.cmd;
|
||||
u32 subcmd = data.subcmd;
|
||||
|
||||
db = get_policydb();
|
||||
mutex_lock(&ksu_rules);
|
||||
|
||||
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];
|
||||
db = get_policydb();
|
||||
|
||||
char *s, *t, *c, *p;
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
int ret = -EINVAL;
|
||||
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];
|
||||
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
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(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls 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(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm 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;
|
||||
}
|
||||
|
||||
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;
|
||||
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
} else if (cmd == CMD_XPERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
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 : -EINVAL;
|
||||
|
||||
char __maybe_unused
|
||||
operation[MAX_SEPOL_LEN]; // it is always ioctl now!
|
||||
char perm_set[MAX_SEPOL_LEN];
|
||||
} else if (cmd == CMD_XPERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c;
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(operation, sepol4,
|
||||
sizeof(operation)) < 0) {
|
||||
pr_err("sepol: copy operation failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm_set failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char __maybe_unused
|
||||
operation[MAX_SEPOL_LEN]; // it is always ioctl now!
|
||||
char perm_set[MAX_SEPOL_LEN];
|
||||
|
||||
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];
|
||||
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;
|
||||
}
|
||||
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src 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 : -EINVAL;
|
||||
} else if (cmd == CMD_TYPE_STATE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
|
||||
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;
|
||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
} else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) {
|
||||
char type[MAX_SEPOL_LEN];
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
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;
|
||||
|
||||
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
|
||||
pr_err("sepol: copy type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
} else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) {
|
||||
char type[MAX_SEPOL_LEN];
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
} else if (cmd == CMD_ATTR) {
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
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;
|
||||
|
||||
if (strncpy_from_user(attr, 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_ATTR) {
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
} 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(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;
|
||||
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *real_object;
|
||||
if (sepol5 == NULL) {
|
||||
real_object = NULL;
|
||||
} else {
|
||||
if (strncpy_from_user(object, sepol5,
|
||||
sizeof(object)) < 0) {
|
||||
pr_err("sepol: copy object failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
real_object = object;
|
||||
}
|
||||
} 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];
|
||||
|
||||
bool success = ksu_type_transition(db, src, tgt, cls,
|
||||
default_type, real_object);
|
||||
if (success)
|
||||
ret = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
} 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];
|
||||
bool success = ksu_type_transition(db, src, tgt, cls,
|
||||
default_type, real_object);
|
||||
if (success)
|
||||
ret = 0;
|
||||
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, 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, sepol1, sizeof(name)) < 0) {
|
||||
pr_err("sepol: copy name failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
|
||||
pr_err("sepol: copy path failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||
0) {
|
||||
pr_err("sepol: copy context failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
} 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 (!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);
|
||||
}
|
||||
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:
|
||||
mutex_unlock(&ksu_rules);
|
||||
mutex_unlock(&ksu_rules);
|
||||
|
||||
// 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();
|
||||
// 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;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
#include "selinux.h"
|
||||
#include "linux/cred.h"
|
||||
#include "linux/sched.h"
|
||||
#include "objsec.h"
|
||||
#include "linux/version.h"
|
||||
#include "../klog.h" // IWYU pragma: keep
|
||||
|
|
@ -7,124 +9,146 @@
|
|||
|
||||
static int transive_to_domain(const char *domain)
|
||||
{
|
||||
struct cred *cred;
|
||||
struct task_security_struct *tsec;
|
||||
u32 sid;
|
||||
int error;
|
||||
struct cred *cred;
|
||||
struct task_security_struct *tsec;
|
||||
u32 sid;
|
||||
int error;
|
||||
|
||||
cred = (struct cred *)__task_cred(current);
|
||||
cred = (struct cred *)__task_cred(current);
|
||||
|
||||
tsec = cred->security;
|
||||
if (!tsec) {
|
||||
pr_err("tsec == NULL!\n");
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
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 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;
|
||||
}
|
||||
}*/
|
||||
if (transive_to_domain(domain)) {
|
||||
pr_err("transive domain failed.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void setenforce(bool enforce)
|
||||
{
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
|
||||
selinux_state.enforcing = enforce;
|
||||
selinux_state.enforcing = enforce;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool getenforce()
|
||||
{
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
|
||||
if (selinux_state.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (selinux_state.disabled) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
|
||||
return selinux_state.enforcing;
|
||||
return selinux_state.enforcing;
|
||||
#else
|
||||
return true;
|
||||
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)
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 14, 0)
|
||||
struct lsm_context {
|
||||
char *context;
|
||||
u32 len;
|
||||
};
|
||||
|
||||
static int __security_secid_to_secctx(u32 secid, struct lsm_context *cp)
|
||||
{
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
|
||||
return tsec->sid;
|
||||
return security_secid_to_secctx(secid, &cp->context, &cp->len);
|
||||
}
|
||||
static void __security_release_secctx(struct lsm_context *cp)
|
||||
{
|
||||
return security_release_secctx(cp->context, cp->len);
|
||||
}
|
||||
#else
|
||||
#define __security_secid_to_secctx security_secid_to_secctx
|
||||
#define __security_release_secctx security_release_secctx
|
||||
#endif
|
||||
|
||||
bool is_task_ksu_domain(const struct cred* cred)
|
||||
{
|
||||
struct lsm_context ctx;
|
||||
bool result;
|
||||
if (!cred) {
|
||||
return false;
|
||||
}
|
||||
const struct task_security_struct *tsec = selinux_cred(cred);
|
||||
if (!tsec) {
|
||||
return false;
|
||||
}
|
||||
int err = __security_secid_to_secctx(tsec->sid, &ctx);
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
result = strncmp(KERNEL_SU_DOMAIN, ctx.context, ctx.len) == 0;
|
||||
__security_release_secctx(&ctx);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool 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;
|
||||
current_sid();
|
||||
return is_task_ksu_domain(current_cred());
|
||||
}
|
||||
|
||||
bool is_zygote(void *sec)
|
||||
bool is_context(const struct cred* cred, const char* context)
|
||||
{
|
||||
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;
|
||||
if (!cred) {
|
||||
return false;
|
||||
}
|
||||
const struct task_security_struct * tsec = selinux_cred(cred);
|
||||
if (!tsec) {
|
||||
return false;
|
||||
}
|
||||
struct lsm_context ctx;
|
||||
bool result;
|
||||
int err = __security_secid_to_secctx(tsec->sid, &ctx);
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
result = strncmp(context, ctx.context, ctx.len) == 0;
|
||||
__security_release_secctx(&ctx);
|
||||
return result;
|
||||
}
|
||||
|
||||
#define DEVPTS_DOMAIN "u:object_r:ksu_file:s0"
|
||||
|
||||
u32 ksu_get_devpts_sid()
|
||||
bool is_zygote(const struct cred* cred)
|
||||
{
|
||||
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;
|
||||
return is_context(cred, "u:r:zygote:s0");
|
||||
}
|
||||
|
||||
bool is_init(const struct cred* cred) {
|
||||
return is_context(cred, "u:r:init:s0");
|
||||
}
|
||||
|
||||
#define KSU_FILE_DOMAIN "u:object_r:ksu_file:s0"
|
||||
|
||||
u32 ksu_get_ksu_file_sid()
|
||||
{
|
||||
u32 ksu_file_sid = 0;
|
||||
int err = security_secctx_to_secid(KSU_FILE_DOMAIN, strlen(KSU_FILE_DOMAIN),
|
||||
&ksu_file_sid);
|
||||
if (err) {
|
||||
pr_info("get ksufile sid err %d\n", err);
|
||||
}
|
||||
return ksu_file_sid;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "linux/types.h"
|
||||
#include "linux/version.h"
|
||||
#include "linux/cred.h"
|
||||
|
||||
void setup_selinux(const char *);
|
||||
|
||||
|
|
@ -10,12 +11,18 @@ void setenforce(bool);
|
|||
|
||||
bool getenforce();
|
||||
|
||||
bool is_task_ksu_domain(const struct cred* cred);
|
||||
|
||||
bool is_ksu_domain();
|
||||
|
||||
bool is_zygote(void *cred);
|
||||
bool is_zygote(const struct cred* cred);
|
||||
|
||||
bool is_init(const struct cred* cred);
|
||||
|
||||
void apply_kernelsu_rules();
|
||||
|
||||
u32 ksu_get_devpts_sid();
|
||||
u32 ksu_get_ksu_file_sid();
|
||||
|
||||
int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -15,32 +15,32 @@ 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);
|
||||
const char *cls, const char *perm);
|
||||
bool ksu_deny(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *perm);
|
||||
const char *cls, const char *perm);
|
||||
bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *perm);
|
||||
const char *cls, const char *perm);
|
||||
bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *perm);
|
||||
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);
|
||||
const char *cls, const char *range);
|
||||
bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *range);
|
||||
const char *cls, const char *range);
|
||||
bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt,
|
||||
const char *cls, const char *range);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
const char *ctx);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
171
kernel/setuid_hook.c
Normal file
171
kernel/setuid_hook.c
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#include <linux/compiler.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/init_task.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/uidgid.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/tty.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "setuid_hook.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "manager.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "seccomp_cache.h"
|
||||
#include "supercalls.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "kernel_umount.h"
|
||||
#include "app_profile.h"
|
||||
|
||||
static bool ksu_enhanced_security_enabled = false;
|
||||
|
||||
static int enhanced_security_feature_get(u64 *value)
|
||||
{
|
||||
*value = ksu_enhanced_security_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enhanced_security_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
ksu_enhanced_security_enabled = enable;
|
||||
pr_info("enhanced_security: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler enhanced_security_handler = {
|
||||
.feature_id = KSU_FEATURE_ENHANCED_SECURITY,
|
||||
.name = "enhanced_security",
|
||||
.get_handler = enhanced_security_feature_get,
|
||||
.set_handler = enhanced_security_feature_set,
|
||||
};
|
||||
|
||||
static inline bool is_allow_su()
|
||||
{
|
||||
if (is_manager()) {
|
||||
// we are manager, allow!
|
||||
return true;
|
||||
}
|
||||
return ksu_is_allow_uid_for_current(current_uid().val);
|
||||
}
|
||||
|
||||
int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid)
|
||||
{
|
||||
uid_t new_uid = ruid;
|
||||
uid_t old_uid = current_uid().val;
|
||||
|
||||
pr_info("handle_setresuid from %d to %d\n", old_uid, new_uid);
|
||||
|
||||
// if old process is root, ignore it.
|
||||
if (old_uid != 0 && ksu_enhanced_security_enabled) {
|
||||
// disallow any non-ksu domain escalation from non-root to root!
|
||||
// euid is what we care about here as it controls permission
|
||||
if (unlikely(euid == 0)) {
|
||||
if (!is_ksu_domain()) {
|
||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
||||
current->pid, current->comm, old_uid, new_uid);
|
||||
force_sig(SIGKILL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// disallow appuid decrease to any other uid if it is not allowed to su
|
||||
if (is_appuid(old_uid)) {
|
||||
if (euid < current_euid().val && !ksu_is_allow_uid_for_current(old_uid)) {
|
||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
||||
current->pid, current->comm, old_uid, new_uid);
|
||||
force_sig(SIGKILL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if on private space, see if its possibly the manager
|
||||
if (new_uid > PER_USER_RANGE && new_uid % PER_USER_RANGE == ksu_get_manager_uid()) {
|
||||
ksu_set_manager_uid(new_uid);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
||||
if (ksu_get_manager_uid() == new_uid) {
|
||||
pr_info("install fd for manager: %d\n", new_uid);
|
||||
ksu_install_fd();
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ksu_is_allow_uid_for_current(new_uid)) {
|
||||
if (current->seccomp.mode == SECCOMP_MODE_FILTER &&
|
||||
current->seccomp.filter) {
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
}
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
} else {
|
||||
ksu_clear_task_tracepoint_flag_if_needed(current);
|
||||
}
|
||||
#else
|
||||
if (ksu_is_allow_uid_for_current(new_uid)) {
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
disable_seccomp();
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
if (ksu_get_manager_uid() == new_uid) {
|
||||
pr_info("install fd for ksu manager(uid=%d)\n",
|
||||
new_uid);
|
||||
ksu_install_fd();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handle kernel umount
|
||||
ksu_handle_umount(old_uid, new_uid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_setuid_hook_init(void)
|
||||
{
|
||||
ksu_kernel_umount_init();
|
||||
if (ksu_register_feature_handler(&enhanced_security_handler)) {
|
||||
pr_err("Failed to register enhanced security feature handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_setuid_hook_exit(void)
|
||||
{
|
||||
pr_info("ksu_core_exit\n");
|
||||
ksu_kernel_umount_exit();
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY);
|
||||
}
|
||||
14
kernel/setuid_hook.h
Normal file
14
kernel/setuid_hook.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef __KSU_H_KSU_CORE
|
||||
#define __KSU_H_KSU_CORE
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include "apk_sign.h"
|
||||
#include <linux/thread_info.h>
|
||||
|
||||
void ksu_setuid_hook_init(void);
|
||||
void ksu_setuid_hook_exit(void);
|
||||
|
||||
int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,337 +1,188 @@
|
|||
#include <linux/dcache.h>
|
||||
#include <linux/security.h>
|
||||
#include "linux/compiler.h"
|
||||
#include "linux/printk.h"
|
||||
#include <asm/current.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/sched/task_stack.h>
|
||||
#include <linux/ptrace.h>
|
||||
|
||||
#include "objsec.h"
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "kernel_compat.h"
|
||||
#include "sucompat.h"
|
||||
#include "app_profile.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
|
||||
|
||||
#include "sulog.h"
|
||||
|
||||
#define SU_PATH "/system/bin/su"
|
||||
#define SH_PATH "/system/bin/sh"
|
||||
|
||||
extern void escape_to_root();
|
||||
bool ksu_su_compat_enabled __read_mostly = true;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
static bool ksu_sucompat_hook_state __read_mostly = true;
|
||||
#endif
|
||||
static int su_compat_feature_get(u64 *value)
|
||||
{
|
||||
*value = ksu_su_compat_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int su_compat_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
ksu_su_compat_enabled = enable;
|
||||
pr_info("su_compat: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler su_compat_handler = {
|
||||
.feature_id = KSU_FEATURE_SU_COMPAT,
|
||||
.name = "su_compat",
|
||||
.get_handler = su_compat_feature_get,
|
||||
.set_handler = su_compat_feature_set,
|
||||
};
|
||||
|
||||
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;
|
||||
// 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;
|
||||
return copy_to_user(p, d, len) ? NULL : p;
|
||||
}
|
||||
|
||||
static char __user *sh_user_path(void)
|
||||
{
|
||||
static const char sh_path[] = "/system/bin/sh";
|
||||
static const char sh_path[] = "/system/bin/sh";
|
||||
|
||||
return userspace_stack_buffer(sh_path, sizeof(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;
|
||||
static const char ksud_path[] = KSUD_PATH;
|
||||
|
||||
return userspace_stack_buffer(ksud_path, sizeof(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)
|
||||
int *__unused_flags)
|
||||
{
|
||||
const char su[] = SU_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_is_allow_uid_for_current(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "faccessat", path);
|
||||
#endif
|
||||
pr_info("faccessat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
{
|
||||
// const char sh[] = SH_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
// const char sh[] = SH_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_is_allow_uid_for_current(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (unlikely(!filename_user)) {
|
||||
return 0;
|
||||
}
|
||||
if (unlikely(!filename_user)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
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));
|
||||
// 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));
|
||||
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();
|
||||
}
|
||||
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "newfstatat", path);
|
||||
#endif
|
||||
pr_info("newfstatat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
|
||||
void *envp, int *flags)
|
||||
int ksu_handle_execve_sucompat(const char __user **filename_user,
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags)
|
||||
{
|
||||
return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, flags);
|
||||
}
|
||||
const char su[] = SU_PATH;
|
||||
char path[sizeof(su) + 1];
|
||||
|
||||
// the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code
|
||||
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 sh[] = KSUD_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
if (unlikely(!filename_user))
|
||||
return 0;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (unlikely(!filename_ptr))
|
||||
return 0;
|
||||
memset(path, 0, sizeof(path));
|
||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
filename = *filename_ptr;
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (likely(memcmp(path, su, sizeof(su))))
|
||||
return 0;
|
||||
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
#if __SULOG_GATE
|
||||
bool is_allowed = ksu_is_allow_uid_for_current(current_uid().val);
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", path);
|
||||
|
||||
if (!is_allowed)
|
||||
return 0;
|
||||
|
||||
if (!ksu_is_allow_uid(current_uid().val))
|
||||
return 0;
|
||||
|
||||
pr_info("do_execveat_common su found\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
|
||||
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 CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state){
|
||||
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();
|
||||
|
||||
escape_to_root();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dummified
|
||||
int ksu_handle_devpts(struct inode *inode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __ksu_handle_devpts(struct inode *inode)
|
||||
{
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
if (!current->mm) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uid_t uid = current_uid().val;
|
||||
if (uid % 100000 < 10000) {
|
||||
// not untrusted_app, ignore it
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (likely(!ksu_is_allow_uid(uid)))
|
||||
return 0;
|
||||
|
||||
struct inode_security_struct *sec = selinux_inode(inode);
|
||||
|
||||
if (ksu_devpts_sid && sec)
|
||||
sec->sid = ksu_devpts_sid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
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);
|
||||
}
|
||||
|
||||
static struct kprobe *su_kps[4];
|
||||
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;
|
||||
ksu_sulog_report_su_attempt(current_uid().val, NULL, path, is_allowed);
|
||||
#else
|
||||
inode = (struct inode *)PT_REGS_PARM2(regs);
|
||||
if (!ksu_is_allow_uid_for_current(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ksu_handle_devpts(inode);
|
||||
pr_info("sys_execve su found\n");
|
||||
*filename_user = ksud_user_path();
|
||||
|
||||
escape_with_root_profile();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// sucompat: permited process can execute 'su' to gain root access.
|
||||
// sucompat: permitted process can execute 'su' to gain root access.
|
||||
void ksu_sucompat_init()
|
||||
{
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
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_sucompat_hook_state = true;
|
||||
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
|
||||
#endif
|
||||
if (ksu_register_feature_handler(&su_compat_handler)) {
|
||||
pr_err("Failed to register su_compat feature handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_sucompat_exit()
|
||||
{
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||
destroy_kprobe(&su_kps[i]);
|
||||
}
|
||||
#else
|
||||
ksu_sucompat_hook_state = false;
|
||||
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
|
||||
#endif
|
||||
}
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_SU_COMPAT);
|
||||
}
|
||||
18
kernel/sucompat.h
Normal file
18
kernel/sucompat.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef __KSU_H_SUCOMPAT
|
||||
#define __KSU_H_SUCOMPAT
|
||||
#include <linux/types.h>
|
||||
|
||||
extern bool ksu_su_compat_enabled;
|
||||
|
||||
void ksu_sucompat_init(void);
|
||||
void ksu_sucompat_exit(void);
|
||||
|
||||
// Handler functions exported for hook_manager
|
||||
int ksu_handle_faccessat(int *dfd, const char __user **filename_user,
|
||||
int *mode, int *__unused_flags);
|
||||
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||
int ksu_handle_execve_sucompat(const char __user **filename_user,
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags);
|
||||
|
||||
#endif
|
||||
340
kernel/sulog.c
Normal file
340
kernel/sulog.c
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
#include <linux/fs.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include "klog.h"
|
||||
#include "sulog.h"
|
||||
#include "ksu.h"
|
||||
|
||||
#if __SULOG_GATE
|
||||
|
||||
struct dedup_entry dedup_tbl[SULOG_COMM_LEN];
|
||||
static DEFINE_SPINLOCK(dedup_lock);
|
||||
static LIST_HEAD(sulog_queue);
|
||||
static struct workqueue_struct *sulog_workqueue;
|
||||
static struct work_struct sulog_work;
|
||||
static bool sulog_enabled = true;
|
||||
|
||||
static void get_timestamp(char *buf, size_t len)
|
||||
{
|
||||
struct timespec64 ts;
|
||||
struct tm tm;
|
||||
|
||||
ktime_get_real_ts64(&ts);
|
||||
time64_to_tm(ts.tv_sec - sys_tz.tz_minuteswest * 60, 0, &tm);
|
||||
|
||||
snprintf(buf, len, "%04ld-%02d-%02d %02d:%02d:%02d",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
}
|
||||
|
||||
static void ksu_get_cmdline(char *full_comm, const char *comm, size_t buf_len)
|
||||
{
|
||||
if (!full_comm || buf_len <= 0)
|
||||
return;
|
||||
|
||||
if (comm && strlen(comm) > 0) {
|
||||
KSU_STRSCPY(full_comm, comm, buf_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_atomic() || in_interrupt() || irqs_disabled()) {
|
||||
KSU_STRSCPY(full_comm, current->comm, buf_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current->mm) {
|
||||
KSU_STRSCPY(full_comm, current->comm, buf_len);
|
||||
return;
|
||||
}
|
||||
|
||||
int n = get_cmdline(current, full_comm, buf_len);
|
||||
if (n <= 0) {
|
||||
KSU_STRSCPY(full_comm, current->comm, buf_len);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n && i < buf_len - 1; i++) {
|
||||
if (full_comm[i] == '\0')
|
||||
full_comm[i] = ' ';
|
||||
}
|
||||
full_comm[n < buf_len ? n : buf_len - 1] = '\0';
|
||||
}
|
||||
|
||||
static void sanitize_string(char *str, size_t len)
|
||||
{
|
||||
if (!str || len == 0)
|
||||
return;
|
||||
|
||||
size_t read_pos = 0, write_pos = 0;
|
||||
|
||||
while (read_pos < len && str[read_pos] != '\0') {
|
||||
char c = str[read_pos];
|
||||
|
||||
if (c == '\n' || c == '\r') {
|
||||
read_pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ' ' && write_pos > 0 && str[write_pos - 1] == ' ') {
|
||||
read_pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
str[write_pos++] = c;
|
||||
read_pos++;
|
||||
}
|
||||
|
||||
str[write_pos] = '\0';
|
||||
}
|
||||
|
||||
static bool dedup_should_print(uid_t uid, u8 type, const char *content, size_t len)
|
||||
{
|
||||
struct dedup_key key = {
|
||||
.crc = dedup_calc_hash(content, len),
|
||||
.uid = uid,
|
||||
.type = type,
|
||||
};
|
||||
u64 now = ktime_get_ns();
|
||||
u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC;
|
||||
|
||||
u32 idx = key.crc & (SULOG_COMM_LEN - 1);
|
||||
spin_lock(&dedup_lock);
|
||||
|
||||
struct dedup_entry *e = &dedup_tbl[idx];
|
||||
if (e->key.crc == key.crc &&
|
||||
e->key.uid == key.uid &&
|
||||
e->key.type == key.type &&
|
||||
(now - e->ts_ns) < delta_ns) {
|
||||
spin_unlock(&dedup_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
e->key = key;
|
||||
e->ts_ns = now;
|
||||
spin_unlock(&dedup_lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sulog_work_handler(struct work_struct *work)
|
||||
{
|
||||
struct file *fp;
|
||||
struct sulog_entry *entry, *tmp;
|
||||
LIST_HEAD(local_queue);
|
||||
loff_t pos = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dedup_lock, flags);
|
||||
list_splice_init(&sulog_queue, &local_queue);
|
||||
spin_unlock_irqrestore(&dedup_lock, flags);
|
||||
|
||||
if (list_empty(&local_queue))
|
||||
return;
|
||||
|
||||
fp = filp_open(SULOG_PATH, O_WRONLY | O_CREAT | O_APPEND, 0640);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("sulog: failed to open log file: %ld\n", PTR_ERR(fp));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (fp->f_inode->i_size > SULOG_MAX_SIZE) {
|
||||
if (vfs_truncate(&fp->f_path, 0))
|
||||
pr_err("sulog: failed to truncate log file\n");
|
||||
pos = 0;
|
||||
} else {
|
||||
pos = fp->f_inode->i_size;
|
||||
}
|
||||
|
||||
list_for_each_entry(entry, &local_queue, list)
|
||||
kernel_write(fp, entry->content, strlen(entry->content), &pos);
|
||||
|
||||
vfs_fsync(fp, 0);
|
||||
filp_close(fp, 0);
|
||||
|
||||
cleanup:
|
||||
list_for_each_entry_safe(entry, tmp, &local_queue, list) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
}
|
||||
|
||||
static void sulog_add_entry(char *log_buf, size_t len, uid_t uid, u8 dedup_type)
|
||||
{
|
||||
struct sulog_entry *entry;
|
||||
unsigned long flags;
|
||||
|
||||
if (!sulog_enabled || !log_buf || len == 0)
|
||||
return;
|
||||
|
||||
if (!dedup_should_print(uid, dedup_type, log_buf, len))
|
||||
return;
|
||||
|
||||
entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
KSU_STRSCPY(entry->content, log_buf, SULOG_ENTRY_MAX_LEN);
|
||||
|
||||
spin_lock_irqsave(&dedup_lock, flags);
|
||||
list_add_tail(&entry->list, &sulog_queue);
|
||||
spin_unlock_irqrestore(&dedup_lock, flags);
|
||||
|
||||
if (sulog_workqueue)
|
||||
queue_work(sulog_workqueue, &sulog_work);
|
||||
}
|
||||
|
||||
void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method)
|
||||
{
|
||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
||||
char timestamp[32];
|
||||
char full_comm[SULOG_COMM_LEN];
|
||||
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
get_timestamp(timestamp, sizeof(timestamp));
|
||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
||||
|
||||
sanitize_string(full_comm, sizeof(full_comm));
|
||||
|
||||
snprintf(log_buf, sizeof(log_buf),
|
||||
"[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n",
|
||||
timestamp, uid, full_comm, method ? method : "unknown", current->pid);
|
||||
|
||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_SU_GRANT);
|
||||
}
|
||||
|
||||
void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success)
|
||||
{
|
||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
||||
char timestamp[32];
|
||||
char full_comm[SULOG_COMM_LEN];
|
||||
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
get_timestamp(timestamp, sizeof(timestamp));
|
||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
||||
|
||||
sanitize_string(full_comm, sizeof(full_comm));
|
||||
|
||||
snprintf(log_buf, sizeof(log_buf),
|
||||
"[%s] SU_EXEC: UID=%d COMM=%s TARGET=%s RESULT=%s PID=%d\n",
|
||||
timestamp, uid, full_comm, target_path ? target_path : "unknown",
|
||||
success ? "SUCCESS" : "DENIED", current->pid);
|
||||
|
||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_SU_ATTEMPT);
|
||||
}
|
||||
|
||||
void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed)
|
||||
{
|
||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
||||
char timestamp[32];
|
||||
char full_comm[SULOG_COMM_LEN];
|
||||
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
get_timestamp(timestamp, sizeof(timestamp));
|
||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
||||
|
||||
sanitize_string(full_comm, sizeof(full_comm));
|
||||
|
||||
snprintf(log_buf, sizeof(log_buf),
|
||||
"[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n",
|
||||
timestamp, uid, full_comm, allowed ? "ALLOWED" : "DENIED", current->pid);
|
||||
|
||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_PERM_CHECK);
|
||||
}
|
||||
|
||||
void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid)
|
||||
{
|
||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
||||
char timestamp[32];
|
||||
char full_comm[SULOG_COMM_LEN];
|
||||
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
get_timestamp(timestamp, sizeof(timestamp));
|
||||
ksu_get_cmdline(full_comm, NULL, sizeof(full_comm));
|
||||
|
||||
sanitize_string(full_comm, sizeof(full_comm));
|
||||
|
||||
snprintf(log_buf, sizeof(log_buf),
|
||||
"[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d COMM=%s PID=%d\n",
|
||||
timestamp, operation ? operation : "unknown", manager_uid, target_uid, full_comm, current->pid);
|
||||
|
||||
sulog_add_entry(log_buf, strlen(log_buf), manager_uid, DEDUP_MANAGER_OP);
|
||||
}
|
||||
|
||||
void ksu_sulog_report_syscall(uid_t uid, const char *comm, const char *syscall, const char *args)
|
||||
{
|
||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
||||
char timestamp[32];
|
||||
char full_comm[SULOG_COMM_LEN];
|
||||
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
get_timestamp(timestamp, sizeof(timestamp));
|
||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
||||
|
||||
sanitize_string(full_comm, sizeof(full_comm));
|
||||
|
||||
snprintf(log_buf, sizeof(log_buf),
|
||||
"[%s] SYSCALL: UID=%d COMM=%s SYSCALL=%s ARGS=%s PID=%d\n",
|
||||
timestamp, uid, full_comm, syscall ? syscall : "unknown",
|
||||
args ? args : "none", current->pid);
|
||||
|
||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_SYSCALL);
|
||||
}
|
||||
|
||||
int ksu_sulog_init(void)
|
||||
{
|
||||
sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1);
|
||||
if (!sulog_workqueue) {
|
||||
pr_err("sulog: failed to create workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
INIT_WORK(&sulog_work, sulog_work_handler);
|
||||
pr_info("sulog: initialized successfully\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_sulog_exit(void)
|
||||
{
|
||||
struct sulog_entry *entry, *tmp;
|
||||
unsigned long flags;
|
||||
|
||||
sulog_enabled = false;
|
||||
|
||||
if (sulog_workqueue) {
|
||||
flush_workqueue(sulog_workqueue);
|
||||
destroy_workqueue(sulog_workqueue);
|
||||
sulog_workqueue = NULL;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dedup_lock, flags);
|
||||
list_for_each_entry_safe(entry, tmp, &sulog_queue, list) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
spin_unlock_irqrestore(&dedup_lock, flags);
|
||||
|
||||
pr_info("sulog: cleaned up successfully\n");
|
||||
}
|
||||
|
||||
#endif // __SULOG_GATE
|
||||
93
kernel/sulog.h
Normal file
93
kernel/sulog.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#ifndef __KSU_SULOG_H
|
||||
#define __KSU_SULOG_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/crc32.h> // needed for function dedup_calc_hash
|
||||
|
||||
#define __SULOG_GATE 1
|
||||
|
||||
#if __SULOG_GATE
|
||||
|
||||
extern struct timezone sys_tz;
|
||||
|
||||
#define SULOG_PATH "/data/adb/ksu/log/sulog.log"
|
||||
#define SULOG_MAX_SIZE (128 * 1024 * 1024) // 128MB
|
||||
#define SULOG_ENTRY_MAX_LEN 512
|
||||
#define SULOG_COMM_LEN 256
|
||||
#define DEDUP_SECS 10
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 10, 0)
|
||||
static inline size_t strlcpy(char *dest, const char *src, size_t size)
|
||||
{
|
||||
return strscpy(dest, src, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define KSU_STRSCPY(dst, src, size) \
|
||||
do { \
|
||||
if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)) { \
|
||||
strscpy(dst, src, size); \
|
||||
} else { \
|
||||
strlcpy(dst, src, size); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
|
||||
#include <linux/rtc.h>
|
||||
|
||||
static inline void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
|
||||
{
|
||||
struct rtc_time rtc_tm;
|
||||
rtc_time64_to_tm(totalsecs, &rtc_tm);
|
||||
|
||||
result->tm_sec = rtc_tm.tm_sec;
|
||||
result->tm_min = rtc_tm.tm_min;
|
||||
result->tm_hour = rtc_tm.tm_hour;
|
||||
result->tm_mday = rtc_tm.tm_mday;
|
||||
result->tm_mon = rtc_tm.tm_mon;
|
||||
result->tm_year = rtc_tm.tm_year;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct dedup_key {
|
||||
u32 crc;
|
||||
uid_t uid;
|
||||
u8 type;
|
||||
u8 _pad[1];
|
||||
};
|
||||
|
||||
struct dedup_entry {
|
||||
struct dedup_key key;
|
||||
u64 ts_ns;
|
||||
};
|
||||
|
||||
enum {
|
||||
DEDUP_SU_GRANT = 0,
|
||||
DEDUP_SU_ATTEMPT,
|
||||
DEDUP_PERM_CHECK,
|
||||
DEDUP_MANAGER_OP,
|
||||
DEDUP_SYSCALL,
|
||||
};
|
||||
|
||||
static inline u32 dedup_calc_hash(const char *content, size_t len)
|
||||
{
|
||||
return crc32(0, content, len);
|
||||
}
|
||||
|
||||
struct sulog_entry {
|
||||
struct list_head list;
|
||||
char content[SULOG_ENTRY_MAX_LEN];
|
||||
};
|
||||
|
||||
void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method);
|
||||
void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success);
|
||||
void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed);
|
||||
void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid);
|
||||
void ksu_sulog_report_syscall(uid_t uid, const char *comm, const char *syscall, const char *args);
|
||||
|
||||
int ksu_sulog_init(void);
|
||||
void ksu_sulog_exit(void);
|
||||
#endif // __SULOG_GATE
|
||||
|
||||
#endif /* __KSU_SULOG_H */
|
||||
935
kernel/supercalls.c
Normal file
935
kernel/supercalls.c
Normal file
|
|
@ -0,0 +1,935 @@
|
|||
#include "supercalls.h"
|
||||
|
||||
#include <linux/anon_inodes.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fdtable.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "arch.h"
|
||||
#include "allowlist.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "manager.h"
|
||||
#include "sulog.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "objsec.h"
|
||||
#include "file_wrapper.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "throne_comm.h"
|
||||
#include "dynamic_manager.h"
|
||||
#include "umount_manager.h"
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#include "manual_su.h"
|
||||
#endif
|
||||
|
||||
bool ksu_uid_scanner_enabled = false;
|
||||
|
||||
// Permission check functions
|
||||
bool only_manager(void)
|
||||
{
|
||||
return is_manager();
|
||||
}
|
||||
|
||||
bool only_root(void)
|
||||
{
|
||||
return current_uid().val == 0;
|
||||
}
|
||||
|
||||
bool manager_or_root(void)
|
||||
{
|
||||
return current_uid().val == 0 || is_manager();
|
||||
}
|
||||
|
||||
bool always_allow(void)
|
||||
{
|
||||
return true; // No permission check
|
||||
}
|
||||
|
||||
bool allowed_for_su(void)
|
||||
{
|
||||
bool is_allowed = is_manager() || ksu_is_allow_uid_for_current(current_uid().val);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_permission_check(current_uid().val, current->comm, is_allowed);
|
||||
#endif
|
||||
return is_allowed;
|
||||
}
|
||||
|
||||
static void init_uid_scanner(void)
|
||||
{
|
||||
ksu_uid_init();
|
||||
do_load_throne_state(NULL);
|
||||
|
||||
if (ksu_uid_scanner_enabled) {
|
||||
int ret = ksu_throne_comm_init();
|
||||
if (ret != 0) {
|
||||
pr_err("Failed to initialize throne communication: %d\n", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int do_grant_root(void __user *arg)
|
||||
{
|
||||
// we already check uid above on allowed_for_su()
|
||||
|
||||
pr_info("allow root for: %d\n", current_uid().val);
|
||||
escape_with_root_profile();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_info(void __user *arg)
|
||||
{
|
||||
struct ksu_get_info_cmd cmd = {.version = KERNEL_SU_VERSION, .flags = 0};
|
||||
|
||||
#ifdef MODULE
|
||||
cmd.flags |= 0x1;
|
||||
#endif
|
||||
if (is_manager()) {
|
||||
cmd.flags |= 0x2;
|
||||
}
|
||||
cmd.features = KSU_FEATURE_MAX;
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_version: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_report_event(void __user *arg)
|
||||
{
|
||||
struct ksu_report_event_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (cmd.event) {
|
||||
case EVENT_POST_FS_DATA: {
|
||||
static bool post_fs_data_lock = false;
|
||||
if (!post_fs_data_lock) {
|
||||
post_fs_data_lock = true;
|
||||
pr_info("post-fs-data triggered\n");
|
||||
on_post_fs_data();
|
||||
init_uid_scanner();
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_init();
|
||||
#endif
|
||||
ksu_dynamic_manager_init();
|
||||
}
|
||||
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");
|
||||
on_boot_completed();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVENT_MODULE_MOUNTED: {
|
||||
pr_info("module mounted!\n");
|
||||
on_module_mounted();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_set_sepolicy(void __user *arg)
|
||||
{
|
||||
struct ksu_set_sepolicy_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return handle_sepolicy(cmd.cmd, (void __user *)cmd.arg);
|
||||
}
|
||||
|
||||
static int do_check_safemode(void __user *arg)
|
||||
{
|
||||
struct ksu_check_safemode_cmd cmd;
|
||||
|
||||
cmd.in_safe_mode = ksu_is_safe_mode();
|
||||
|
||||
if (cmd.in_safe_mode) {
|
||||
pr_warn("safemode enabled!\n");
|
||||
}
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("check_safemode: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_allow_list(void __user *arg)
|
||||
{
|
||||
struct ksu_get_allow_list_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
bool success = ksu_get_allow_list((int *)cmd.uids, (int *)&cmd.count, true);
|
||||
|
||||
if (!success) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_allow_list: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_deny_list(void __user *arg)
|
||||
{
|
||||
struct ksu_get_allow_list_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
bool success = ksu_get_allow_list((int *)cmd.uids, (int *)&cmd.count, false);
|
||||
|
||||
if (!success) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_deny_list: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_uid_granted_root(void __user *arg)
|
||||
{
|
||||
struct ksu_uid_granted_root_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
cmd.granted = ksu_is_allow_uid_for_current(cmd.uid);
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("uid_granted_root: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_uid_should_umount(void __user *arg)
|
||||
{
|
||||
struct ksu_uid_should_umount_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
cmd.should_umount = ksu_uid_should_umount(cmd.uid);
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("uid_should_umount: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_manager_uid(void __user *arg)
|
||||
{
|
||||
struct ksu_get_manager_uid_cmd cmd;
|
||||
|
||||
cmd.uid = ksu_get_manager_uid();
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_manager_uid: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_app_profile(void __user *arg)
|
||||
{
|
||||
struct ksu_get_app_profile_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("get_app_profile: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!ksu_get_app_profile(&cmd.profile)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_app_profile: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_set_app_profile(void __user *arg)
|
||||
{
|
||||
struct ksu_set_app_profile_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("set_app_profile: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!ksu_set_app_profile(&cmd.profile, true)) {
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_manager_operation("SET_APP_PROFILE",
|
||||
current_uid().val, cmd.profile.current_uid);
|
||||
#endif
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_feature(void __user *arg)
|
||||
{
|
||||
struct ksu_get_feature_cmd cmd;
|
||||
bool supported;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("get_feature: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
ret = ksu_get_feature(cmd.feature_id, &cmd.value, &supported);
|
||||
cmd.supported = supported ? 1 : 0;
|
||||
|
||||
if (ret && supported) {
|
||||
pr_err("get_feature: failed for feature %u: %d\n", cmd.feature_id, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_feature: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_set_feature(void __user *arg)
|
||||
{
|
||||
struct ksu_set_feature_cmd cmd;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("set_feature: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
ret = ksu_set_feature(cmd.feature_id, cmd.value);
|
||||
if (ret) {
|
||||
pr_err("set_feature: failed for feature %u: %d\n", cmd.feature_id, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_wrapper_fd(void __user *arg) {
|
||||
if (!ksu_file_sid) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
struct ksu_get_wrapper_fd_cmd cmd;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("get_wrapper_fd: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
struct file* f = fget(cmd.fd);
|
||||
if (!f) {
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
struct ksu_file_wrapper *data = ksu_create_file_wrapper(f);
|
||||
if (data == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto put_orig_file;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
#define getfd_secure anon_inode_create_getfd
|
||||
#else
|
||||
#define getfd_secure anon_inode_getfd_secure
|
||||
#endif
|
||||
ret = getfd_secure("[ksu_fdwrapper]", &data->ops, data, f->f_flags, NULL);
|
||||
if (ret < 0) {
|
||||
pr_err("ksu_fdwrapper: getfd failed: %d\n", ret);
|
||||
goto put_wrapper_data;
|
||||
}
|
||||
struct file* pf = fget(ret);
|
||||
|
||||
struct inode* wrapper_inode = file_inode(pf);
|
||||
// copy original inode mode
|
||||
wrapper_inode->i_mode = file_inode(f)->i_mode;
|
||||
struct inode_security_struct *sec = selinux_inode(wrapper_inode);
|
||||
if (sec) {
|
||||
sec->sid = ksu_file_sid;
|
||||
}
|
||||
|
||||
fput(pf);
|
||||
goto put_orig_file;
|
||||
put_wrapper_data:
|
||||
ksu_delete_file_wrapper(data);
|
||||
put_orig_file:
|
||||
fput(f);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_manage_mark(void __user *arg)
|
||||
{
|
||||
struct ksu_manage_mark_cmd cmd;
|
||||
int ret = 0;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("manage_mark: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (cmd.operation) {
|
||||
case KSU_MARK_GET: {
|
||||
// Get task mark status
|
||||
ret = ksu_get_task_mark(cmd.pid);
|
||||
if (ret < 0) {
|
||||
pr_err("manage_mark: get failed for pid %d: %d\n", cmd.pid, ret);
|
||||
return ret;
|
||||
}
|
||||
cmd.result = (u32)ret;
|
||||
break;
|
||||
}
|
||||
case KSU_MARK_MARK: {
|
||||
if (cmd.pid == 0) {
|
||||
ksu_mark_all_process();
|
||||
} else {
|
||||
ret = ksu_set_task_mark(cmd.pid, true);
|
||||
if (ret < 0) {
|
||||
pr_err("manage_mark: set_mark failed for pid %d: %d\n", cmd.pid,
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KSU_MARK_UNMARK: {
|
||||
if (cmd.pid == 0) {
|
||||
ksu_unmark_all_process();
|
||||
} else {
|
||||
ret = ksu_set_task_mark(cmd.pid, false);
|
||||
if (ret < 0) {
|
||||
pr_err("manage_mark: set_unmark failed for pid %d: %d\n",
|
||||
cmd.pid, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KSU_MARK_REFRESH: {
|
||||
ksu_mark_running_process();
|
||||
pr_info("manage_mark: refreshed running processes\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_err("manage_mark: invalid operation %u\n", cmd.operation);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("manage_mark: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 100. GET_FULL_VERSION - Get full version string
|
||||
static int do_get_full_version(void __user *arg)
|
||||
{
|
||||
struct ksu_get_full_version_cmd cmd = {0};
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(cmd.version_full, KSU_VERSION_FULL, sizeof(cmd.version_full));
|
||||
#else
|
||||
strlcpy(cmd.version_full, KSU_VERSION_FULL, sizeof(cmd.version_full));
|
||||
#endif
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_full_version: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 101. HOOK_TYPE - Get hook type
|
||||
static int do_get_hook_type(void __user *arg)
|
||||
{
|
||||
struct ksu_hook_type_cmd cmd = {0};
|
||||
const char *type = "Tracepoint";
|
||||
|
||||
#if defined(KSU_MANUAL_HOOK)
|
||||
type = "Manual";
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(cmd.hook_type, type, sizeof(cmd.hook_type));
|
||||
#else
|
||||
strlcpy(cmd.hook_type, type, sizeof(cmd.hook_type));
|
||||
#endif
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_hook_type: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 102. ENABLE_KPM - Check if KPM is enabled
|
||||
static int do_enable_kpm(void __user *arg)
|
||||
{
|
||||
struct ksu_enable_kpm_cmd cmd;
|
||||
|
||||
cmd.enabled = IS_ENABLED(CONFIG_KPM);
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("enable_kpm: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_dynamic_manager(void __user *arg)
|
||||
{
|
||||
struct ksu_dynamic_manager_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("dynamic_manager: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
int ret = ksu_handle_dynamic_manager(&cmd.config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (cmd.config.operation == DYNAMIC_MANAGER_OP_GET &&
|
||||
copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("dynamic_manager: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_get_managers(void __user *arg)
|
||||
{
|
||||
struct ksu_get_managers_cmd cmd;
|
||||
|
||||
int ret = ksu_get_active_managers(&cmd.manager_info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("get_managers: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_enable_uid_scanner(void __user *arg)
|
||||
{
|
||||
struct ksu_enable_uid_scanner_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("enable_uid_scanner: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (cmd.operation) {
|
||||
case UID_SCANNER_OP_GET_STATUS: {
|
||||
bool status = ksu_uid_scanner_enabled;
|
||||
if (copy_to_user((void __user *)cmd.status_ptr, &status, sizeof(status))) {
|
||||
pr_err("enable_uid_scanner: copy status failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UID_SCANNER_OP_TOGGLE: {
|
||||
bool enabled = cmd.enabled;
|
||||
|
||||
if (enabled == ksu_uid_scanner_enabled) {
|
||||
pr_info("enable_uid_scanner: no need to change, already %s\n",
|
||||
enabled ? "enabled" : "disabled");
|
||||
break;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
// Enable UID scanner
|
||||
int ret = ksu_throne_comm_init();
|
||||
if (ret != 0) {
|
||||
pr_err("enable_uid_scanner: failed to initialize: %d\n", ret);
|
||||
return -EFAULT;
|
||||
}
|
||||
pr_info("enable_uid_scanner: enabled\n");
|
||||
} else {
|
||||
// Disable UID scanner
|
||||
ksu_throne_comm_exit();
|
||||
pr_info("enable_uid_scanner: disabled\n");
|
||||
}
|
||||
|
||||
ksu_uid_scanner_enabled = enabled;
|
||||
ksu_throne_comm_save_state();
|
||||
break;
|
||||
}
|
||||
case UID_SCANNER_OP_CLEAR_ENV: {
|
||||
// Clear environment (force exit)
|
||||
ksu_throne_comm_exit();
|
||||
ksu_uid_scanner_enabled = false;
|
||||
ksu_throne_comm_save_state();
|
||||
pr_info("enable_uid_scanner: environment cleared\n");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pr_err("enable_uid_scanner: invalid operation\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
static bool system_uid_check(void)
|
||||
{
|
||||
return current_uid().val <= 2000;
|
||||
}
|
||||
|
||||
static int do_manual_su(void __user *arg)
|
||||
{
|
||||
struct ksu_manual_su_cmd cmd;
|
||||
struct manual_su_request request;
|
||||
int res;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("manual_su: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
pr_info("manual_su request, option=%d, uid=%d, pid=%d\n",
|
||||
cmd.option, cmd.target_uid, cmd.target_pid);
|
||||
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.target_uid = cmd.target_uid;
|
||||
request.target_pid = cmd.target_pid;
|
||||
|
||||
if (cmd.option == MANUAL_SU_OP_GENERATE_TOKEN ||
|
||||
cmd.option == MANUAL_SU_OP_ESCALATE) {
|
||||
memcpy(request.token_buffer, cmd.token_buffer, sizeof(request.token_buffer));
|
||||
}
|
||||
|
||||
res = ksu_handle_manual_su_request(cmd.option, &request);
|
||||
|
||||
if (cmd.option == MANUAL_SU_OP_GENERATE_TOKEN && res == 0) {
|
||||
memcpy(cmd.token_buffer, request.token_buffer, sizeof(cmd.token_buffer));
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("manual_su: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int do_umount_manager(void __user *arg)
|
||||
{
|
||||
struct ksu_umount_manager_cmd cmd;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("umount_manager: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (cmd.operation) {
|
||||
case UMOUNT_OP_ADD: {
|
||||
return ksu_umount_manager_add(cmd.path, cmd.check_mnt, cmd.flags, false);
|
||||
}
|
||||
case UMOUNT_OP_REMOVE: {
|
||||
return ksu_umount_manager_remove(cmd.path);
|
||||
}
|
||||
case UMOUNT_OP_LIST: {
|
||||
struct ksu_umount_entry_info __user *entries =
|
||||
(struct ksu_umount_entry_info __user *)cmd.entries_ptr;
|
||||
return ksu_umount_manager_get_entries(entries, &cmd.count);
|
||||
}
|
||||
case UMOUNT_OP_CLEAR_CUSTOM: {
|
||||
return ksu_umount_manager_clear_custom();
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
// IOCTL handlers mapping table
|
||||
static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
|
||||
{ .cmd = KSU_IOCTL_GRANT_ROOT, .name = "GRANT_ROOT", .handler = do_grant_root, .perm_check = allowed_for_su },
|
||||
{ .cmd = KSU_IOCTL_GET_INFO, .name = "GET_INFO", .handler = do_get_info, .perm_check = always_allow },
|
||||
{ .cmd = KSU_IOCTL_REPORT_EVENT, .name = "REPORT_EVENT", .handler = do_report_event, .perm_check = only_root },
|
||||
{ .cmd = KSU_IOCTL_SET_SEPOLICY, .name = "SET_SEPOLICY", .handler = do_set_sepolicy, .perm_check = only_root },
|
||||
{ .cmd = KSU_IOCTL_CHECK_SAFEMODE, .name = "CHECK_SAFEMODE", .handler = do_check_safemode, .perm_check = always_allow },
|
||||
{ .cmd = KSU_IOCTL_GET_ALLOW_LIST, .name = "GET_ALLOW_LIST", .handler = do_get_allow_list, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_DENY_LIST, .name = "GET_DENY_LIST", .handler = do_get_deny_list, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_UID_GRANTED_ROOT, .name = "UID_GRANTED_ROOT", .handler = do_uid_granted_root, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_UID_SHOULD_UMOUNT, .name = "UID_SHOULD_UMOUNT", .handler = do_uid_should_umount, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_MANAGER_UID, .name = "GET_MANAGER_UID", .handler = do_get_manager_uid, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_APP_PROFILE, .name = "GET_APP_PROFILE", .handler = do_get_app_profile, .perm_check = only_manager },
|
||||
{ .cmd = KSU_IOCTL_SET_APP_PROFILE, .name = "SET_APP_PROFILE", .handler = do_set_app_profile, .perm_check = only_manager },
|
||||
{ .cmd = KSU_IOCTL_GET_FEATURE, .name = "GET_FEATURE", .handler = do_get_feature, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_feature, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_WRAPPER_FD, .name = "GET_WRAPPER_FD", .handler = do_get_wrapper_fd, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_MANAGE_MARK, .name = "MANAGE_MARK", .handler = do_manage_mark, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_FULL_VERSION,.name = "GET_FULL_VERSION", .handler = do_get_full_version, .perm_check = always_allow},
|
||||
{ .cmd = KSU_IOCTL_HOOK_TYPE,.name = "GET_HOOK_TYPE", .handler = do_get_hook_type, .perm_check = manager_or_root},
|
||||
{ .cmd = KSU_IOCTL_ENABLE_KPM, .name = "GET_ENABLE_KPM", .handler = do_enable_kpm, .perm_check = manager_or_root},
|
||||
{ .cmd = KSU_IOCTL_DYNAMIC_MANAGER, .name = "SET_DYNAMIC_MANAGER", .handler = do_dynamic_manager, .perm_check = manager_or_root},
|
||||
{ .cmd = KSU_IOCTL_GET_MANAGERS, .name = "GET_MANAGERS", .handler = do_get_managers, .perm_check = manager_or_root},
|
||||
{ .cmd = KSU_IOCTL_ENABLE_UID_SCANNER, .name = "SET_ENABLE_UID_SCANNER", .handler = do_enable_uid_scanner, .perm_check = manager_or_root},
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
{ .cmd = KSU_IOCTL_MANUAL_SU, .name = "MANUAL_SU", .handler = do_manual_su, .perm_check = system_uid_check},
|
||||
#endif
|
||||
#ifdef CONFIG_KPM
|
||||
{ .cmd = KSU_IOCTL_KPM, .name = "KPM_OPERATION", .handler = do_kpm, .perm_check = manager_or_root},
|
||||
#endif
|
||||
{ .cmd = KSU_IOCTL_UMOUNT_MANAGER, .name = "UMOUNT_MANAGER", .handler = do_umount_manager, .perm_check = manager_or_root},
|
||||
{ .cmd = 0, .name = NULL, .handler = NULL, .perm_check = NULL} // Sentine
|
||||
};
|
||||
|
||||
struct ksu_install_fd_tw {
|
||||
struct callback_head cb;
|
||||
int __user *outp;
|
||||
};
|
||||
|
||||
static void ksu_install_fd_tw_func(struct callback_head *cb)
|
||||
{
|
||||
struct ksu_install_fd_tw *tw = container_of(cb, struct ksu_install_fd_tw, cb);
|
||||
int fd = ksu_install_fd();
|
||||
pr_info("[%d] install ksu fd: %d\n", current->pid, fd);
|
||||
|
||||
if (copy_to_user(tw->outp, &fd, sizeof(fd))) {
|
||||
pr_err("install ksu fd reply err\n");
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
close_fd(fd);
|
||||
#else
|
||||
ksys_close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
kfree(tw);
|
||||
}
|
||||
|
||||
// downstream: make sure to pass arg as reference, this can allow us to extend things.
|
||||
int ksu_handle_sys_reboot(int magic1, int magic2, unsigned int cmd, void __user **arg)
|
||||
{
|
||||
struct ksu_install_fd_tw *tw;
|
||||
|
||||
if (magic1 != KSU_INSTALL_MAGIC1)
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("sys_reboot: intercepted call! magic: 0x%x id: %d\n", magic1, magic2);
|
||||
#endif
|
||||
|
||||
// Check if this is a request to install KSU fd
|
||||
if (magic2 == KSU_INSTALL_MAGIC2) {
|
||||
tw = kzalloc(sizeof(*tw), GFP_ATOMIC);
|
||||
if (!tw)
|
||||
return 0;
|
||||
|
||||
tw->outp = (int __user *)*arg;
|
||||
tw->cb.func = ksu_install_fd_tw_func;
|
||||
|
||||
if (task_work_add(current, &tw->cb, TWA_RESUME)) {
|
||||
kfree(tw);
|
||||
pr_warn("install fd add task_work failed\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// extensions
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef KSU_KPROBES_HOOK
|
||||
// Reboot hook for installing fd
|
||||
static int reboot_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int magic1 = (int)PT_REGS_PARM1(real_regs);
|
||||
int magic2 = (int)PT_REGS_PARM2(real_regs);
|
||||
int cmd = (int)PT_REGS_PARM3(real_regs);
|
||||
void __user **arg = (void __user **)&PT_REGS_SYSCALL_PARM4(real_regs);
|
||||
|
||||
return ksu_handle_sys_reboot(magic1, magic2, cmd, arg);
|
||||
}
|
||||
|
||||
static struct kprobe reboot_kp = {
|
||||
.symbol_name = REBOOT_SYMBOL,
|
||||
.pre_handler = reboot_handler_pre,
|
||||
};
|
||||
#endif
|
||||
|
||||
void ksu_supercalls_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
pr_info("KernelSU IOCTL Commands:\n");
|
||||
for (i = 0; ksu_ioctl_handlers[i].handler; i++) {
|
||||
pr_info(" %-18s = 0x%08x\n", ksu_ioctl_handlers[i].name, ksu_ioctl_handlers[i].cmd);
|
||||
}
|
||||
#ifdef KSU_KPROBES_HOOK
|
||||
int rc = register_kprobe(&reboot_kp);
|
||||
if (rc) {
|
||||
pr_err("reboot kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("reboot kprobe registered successfully\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_supercalls_exit(void) {
|
||||
#ifdef KSU_KPROBES_HOOK
|
||||
unregister_kprobe(&reboot_kp);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void ksu_ioctl_audit(unsigned int cmd, const char *cmd_name, uid_t uid, int ret)
|
||||
{
|
||||
#if __SULOG_GATE
|
||||
const char *result = (ret == 0) ? "SUCCESS" :
|
||||
(ret == -EPERM) ? "DENIED" : "FAILED";
|
||||
ksu_sulog_report_syscall(uid, NULL, cmd_name, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
// IOCTL dispatcher
|
||||
static long anon_ksu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
void __user *argp = (void __user *)arg;
|
||||
int i;
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("ksu ioctl: cmd=0x%x from uid=%d\n", cmd, current_uid().val);
|
||||
#endif
|
||||
|
||||
for (i = 0; ksu_ioctl_handlers[i].handler; i++) {
|
||||
if (cmd == ksu_ioctl_handlers[i].cmd) {
|
||||
// Check permission first
|
||||
if (ksu_ioctl_handlers[i].perm_check &&
|
||||
!ksu_ioctl_handlers[i].perm_check()) {
|
||||
pr_warn("ksu ioctl: permission denied for cmd=0x%x uid=%d\n",
|
||||
cmd, current_uid().val);
|
||||
ksu_ioctl_audit(cmd, ksu_ioctl_handlers[i].name,
|
||||
current_uid().val, -EPERM);
|
||||
return -EPERM;
|
||||
}
|
||||
// Execute handler
|
||||
int ret = ksu_ioctl_handlers[i].handler(argp);
|
||||
ksu_ioctl_audit(cmd, ksu_ioctl_handlers[i].name,
|
||||
current_uid().val, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pr_warn("ksu ioctl: unsupported command 0x%x\n", cmd);
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
// File release handler
|
||||
static int anon_ksu_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
pr_info("ksu fd released\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// File operations structure
|
||||
static const struct file_operations anon_ksu_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = anon_ksu_ioctl,
|
||||
.compat_ioctl = anon_ksu_ioctl,
|
||||
.release = anon_ksu_release,
|
||||
};
|
||||
|
||||
// Install KSU fd to current process
|
||||
int ksu_install_fd(void)
|
||||
{
|
||||
struct file *filp;
|
||||
int fd;
|
||||
|
||||
// Get unused fd
|
||||
fd = get_unused_fd_flags(O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
pr_err("ksu_install_fd: failed to get unused fd\n");
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Create anonymous inode file
|
||||
filp = anon_inode_getfile("[ksu_driver]", &anon_ksu_fops, NULL, O_RDWR | O_CLOEXEC);
|
||||
if (IS_ERR(filp)) {
|
||||
pr_err("ksu_install_fd: failed to create anon inode file\n");
|
||||
put_unused_fd(fd);
|
||||
return PTR_ERR(filp);
|
||||
}
|
||||
|
||||
// Install fd
|
||||
fd_install(fd, filp);
|
||||
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_permission_check(current_uid().val, current->comm, fd >= 0);
|
||||
#endif
|
||||
|
||||
pr_info("ksu fd installed: %d for pid %d\n", fd, current->pid);
|
||||
|
||||
return fd;
|
||||
}
|
||||
180
kernel/supercalls.h
Normal file
180
kernel/supercalls.h
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#ifndef __KSU_H_SUPERCALLS
|
||||
#define __KSU_H_SUPERCALLS
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include "ksu.h"
|
||||
#include "app_profile.h"
|
||||
|
||||
#ifdef CONFIG_KPM
|
||||
#include "kpm/kpm.h"
|
||||
#endif
|
||||
|
||||
// Magic numbers for reboot hook to install fd
|
||||
#define KSU_INSTALL_MAGIC1 0xDEADBEEF
|
||||
#define KSU_INSTALL_MAGIC2 0xCAFEBABE
|
||||
|
||||
// Command structures for ioctl
|
||||
|
||||
struct ksu_become_daemon_cmd {
|
||||
__u8 token[65]; // Input: daemon token (null-terminated)
|
||||
};
|
||||
|
||||
struct ksu_get_info_cmd {
|
||||
__u32 version; // Output: KERNEL_SU_VERSION
|
||||
__u32 flags; // Output: flags (bit 0: MODULE mode)
|
||||
__u32 features; // Output: max feature ID supported
|
||||
};
|
||||
|
||||
struct ksu_report_event_cmd {
|
||||
__u32 event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc.
|
||||
};
|
||||
|
||||
struct ksu_set_sepolicy_cmd {
|
||||
__u64 cmd; // Input: sepolicy command
|
||||
__aligned_u64 arg; // Input: sepolicy argument pointer
|
||||
};
|
||||
|
||||
struct ksu_check_safemode_cmd {
|
||||
__u8 in_safe_mode; // Output: true if in safe mode, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_get_allow_list_cmd {
|
||||
__u32 uids[128]; // Output: array of allowed/denied UIDs
|
||||
__u32 count; // Output: number of UIDs in array
|
||||
__u8 allow; // Input: true for allow list, false for deny list
|
||||
};
|
||||
|
||||
struct ksu_uid_granted_root_cmd {
|
||||
__u32 uid; // Input: target UID to check
|
||||
__u8 granted; // Output: true if granted, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_uid_should_umount_cmd {
|
||||
__u32 uid; // Input: target UID to check
|
||||
__u8 should_umount; // Output: true if should umount, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_get_manager_uid_cmd {
|
||||
__u32 uid; // Output: manager UID
|
||||
};
|
||||
|
||||
struct ksu_get_app_profile_cmd {
|
||||
struct app_profile profile; // Input/Output: app profile structure
|
||||
};
|
||||
|
||||
struct ksu_set_app_profile_cmd {
|
||||
struct app_profile profile; // Input: app profile structure
|
||||
};
|
||||
|
||||
struct ksu_get_feature_cmd {
|
||||
__u32 feature_id; // Input: feature ID (enum ksu_feature_id)
|
||||
__u64 value; // Output: feature value/state
|
||||
__u8 supported; // Output: true if feature is supported, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_set_feature_cmd {
|
||||
__u32 feature_id; // Input: feature ID (enum ksu_feature_id)
|
||||
__u64 value; // Input: feature value/state to set
|
||||
};
|
||||
|
||||
struct ksu_get_wrapper_fd_cmd {
|
||||
__u32 fd; // Input: userspace fd
|
||||
__u32 flags; // Input: flags of userspace fd
|
||||
};
|
||||
|
||||
struct ksu_manage_mark_cmd {
|
||||
__u32 operation; // Input: KSU_MARK_*
|
||||
__s32 pid; // Input: target pid (0 for all processes)
|
||||
__u32 result; // Output: for get operation - mark status or reg_count
|
||||
};
|
||||
|
||||
#define KSU_MARK_GET 1
|
||||
#define KSU_MARK_MARK 2
|
||||
#define KSU_MARK_UNMARK 3
|
||||
#define KSU_MARK_REFRESH 4
|
||||
|
||||
// Other command structures
|
||||
struct ksu_get_full_version_cmd {
|
||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
||||
};
|
||||
|
||||
struct ksu_hook_type_cmd {
|
||||
char hook_type[32]; // Output: hook type string
|
||||
};
|
||||
|
||||
struct ksu_enable_kpm_cmd {
|
||||
__u8 enabled; // Output: true if KPM is enabled
|
||||
};
|
||||
|
||||
struct ksu_dynamic_manager_cmd {
|
||||
struct dynamic_manager_user_config config; // Input/Output: dynamic manager config
|
||||
};
|
||||
|
||||
struct ksu_get_managers_cmd {
|
||||
struct manager_list_info manager_info; // Output: manager list information
|
||||
};
|
||||
|
||||
struct ksu_enable_uid_scanner_cmd {
|
||||
__u32 operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV)
|
||||
__u32 enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE)
|
||||
void __user *status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS)
|
||||
};
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
struct ksu_manual_su_cmd {
|
||||
__u32 option; // Input: operation type (MANUAL_SU_OP_GENERATE_TOKEN, MANUAL_SU_OP_ESCALATE, MANUAL_SU_OP_ADD_PENDING)
|
||||
__u32 target_uid; // Input: target UID
|
||||
__u32 target_pid; // Input: target PID
|
||||
char token_buffer[33]; // Input/Output: token buffer
|
||||
};
|
||||
#endif
|
||||
|
||||
// IOCTL command definitions
|
||||
#define KSU_IOCTL_GRANT_ROOT _IOC(_IOC_NONE, 'K', 1, 0)
|
||||
#define KSU_IOCTL_GET_INFO _IOC(_IOC_READ, 'K', 2, 0)
|
||||
#define KSU_IOCTL_REPORT_EVENT _IOC(_IOC_WRITE, 'K', 3, 0)
|
||||
#define KSU_IOCTL_SET_SEPOLICY _IOC(_IOC_READ|_IOC_WRITE, 'K', 4, 0)
|
||||
#define KSU_IOCTL_CHECK_SAFEMODE _IOC(_IOC_READ, 'K', 5, 0)
|
||||
#define KSU_IOCTL_GET_ALLOW_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 6, 0)
|
||||
#define KSU_IOCTL_GET_DENY_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 7, 0)
|
||||
#define KSU_IOCTL_UID_GRANTED_ROOT _IOC(_IOC_READ|_IOC_WRITE, 'K', 8, 0)
|
||||
#define KSU_IOCTL_UID_SHOULD_UMOUNT _IOC(_IOC_READ|_IOC_WRITE, 'K', 9, 0)
|
||||
#define KSU_IOCTL_GET_MANAGER_UID _IOC(_IOC_READ, 'K', 10, 0)
|
||||
#define KSU_IOCTL_GET_APP_PROFILE _IOC(_IOC_READ|_IOC_WRITE, 'K', 11, 0)
|
||||
#define KSU_IOCTL_SET_APP_PROFILE _IOC(_IOC_WRITE, 'K', 12, 0)
|
||||
#define KSU_IOCTL_GET_FEATURE _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0)
|
||||
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0)
|
||||
#define KSU_IOCTL_GET_WRAPPER_FD _IOC(_IOC_WRITE, 'K', 15, 0)
|
||||
#define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
|
||||
// Other IOCTL command definitions
|
||||
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
|
||||
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)
|
||||
#define KSU_IOCTL_ENABLE_KPM _IOC(_IOC_READ, 'K', 102, 0)
|
||||
#define KSU_IOCTL_DYNAMIC_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 103, 0)
|
||||
#define KSU_IOCTL_GET_MANAGERS _IOC(_IOC_READ|_IOC_WRITE, 'K', 104, 0)
|
||||
#define KSU_IOCTL_ENABLE_UID_SCANNER _IOC(_IOC_READ|_IOC_WRITE, 'K', 105, 0)
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#define KSU_IOCTL_MANUAL_SU _IOC(_IOC_READ|_IOC_WRITE, 'K', 106, 0)
|
||||
#endif
|
||||
#define KSU_IOCTL_UMOUNT_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 107, 0)
|
||||
|
||||
// IOCTL handler types
|
||||
typedef int (*ksu_ioctl_handler_t)(void __user *arg);
|
||||
typedef bool (*ksu_perm_check_t)(void);
|
||||
|
||||
// IOCTL command mapping
|
||||
struct ksu_ioctl_cmd_map {
|
||||
unsigned int cmd;
|
||||
const char *name;
|
||||
ksu_ioctl_handler_t handler;
|
||||
ksu_perm_check_t perm_check; // Permission check function
|
||||
};
|
||||
|
||||
// Install KSU fd to current process
|
||||
int ksu_install_fd(void);
|
||||
|
||||
void ksu_supercalls_init(void);
|
||||
void ksu_supercalls_exit(void);
|
||||
|
||||
#endif // __KSU_H_SUPERCALLS
|
||||
374
kernel/syscall_hook_manager.c
Normal file
374
kernel/syscall_hook_manager.c
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
#include "linux/compiler.h"
|
||||
#include "linux/cred.h"
|
||||
#include "linux/printk.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/tracepoint.h>
|
||||
#include <asm/syscall.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <trace/events/syscalls.h>
|
||||
#include <linux/namei.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "sucompat.h"
|
||||
#include "setuid_hook.h"
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
// Tracepoint registration count management
|
||||
// == 1: just us
|
||||
// > 1: someone else is also using syscall tracepoint e.g. ftrace
|
||||
static int tracepoint_reg_count = 0;
|
||||
static DEFINE_SPINLOCK(tracepoint_reg_lock);
|
||||
|
||||
void ksu_clear_task_tracepoint_flag_if_needed(struct task_struct *t)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
if (tracepoint_reg_count <= 1) {
|
||||
ksu_clear_task_tracepoint_flag(t);
|
||||
}
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
}
|
||||
|
||||
// Process marking management
|
||||
static void handle_process_mark(bool mark)
|
||||
{
|
||||
struct task_struct *p, *t;
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread(p, t) {
|
||||
if (mark)
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
else
|
||||
ksu_clear_task_tracepoint_flag(t);
|
||||
}
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
void ksu_mark_all_process(void)
|
||||
{
|
||||
handle_process_mark(true);
|
||||
pr_info("hook_manager: mark all user process done!\n");
|
||||
}
|
||||
|
||||
void ksu_unmark_all_process(void)
|
||||
{
|
||||
handle_process_mark(false);
|
||||
pr_info("hook_manager: unmark all user process done!\n");
|
||||
}
|
||||
|
||||
static void ksu_mark_running_process_locked()
|
||||
{
|
||||
struct task_struct *p, *t;
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread (p, t) {
|
||||
if (!t->mm) { // only user processes
|
||||
continue;
|
||||
}
|
||||
int uid = task_uid(t).val;
|
||||
const struct cred *cred = get_task_cred(t);
|
||||
bool ksu_root_process =
|
||||
uid == 0 && is_task_ksu_domain(cred);
|
||||
bool is_zygote_process = is_zygote(cred);
|
||||
bool is_shell = uid == 2000;
|
||||
// before boot completed, we shall mark init for marking zygote
|
||||
bool is_init = t->pid == 1;
|
||||
if (ksu_root_process || is_zygote_process || is_shell || is_init
|
||||
|| ksu_is_allow_uid(uid)) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
pr_info("hook_manager: mark process: pid:%d, uid: %d, comm:%s\n",
|
||||
t->pid, uid, t->comm);
|
||||
} else {
|
||||
ksu_clear_task_tracepoint_flag(t);
|
||||
pr_info("hook_manager: unmark process: pid:%d, uid: %d, comm:%s\n",
|
||||
t->pid, uid, t->comm);
|
||||
}
|
||||
put_cred(cred);
|
||||
}
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
void ksu_mark_running_process()
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
if (tracepoint_reg_count <= 1) {
|
||||
ksu_mark_running_process_locked();
|
||||
} else {
|
||||
pr_info("hook_manager: not mark running process since syscall tracepoint is in use\n");
|
||||
}
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
}
|
||||
|
||||
// Get task mark status
|
||||
// Returns: 1 if marked, 0 if not marked, -ESRCH if task not found
|
||||
int ksu_get_task_mark(pid_t pid)
|
||||
{
|
||||
struct task_struct *task;
|
||||
int marked = -ESRCH;
|
||||
|
||||
rcu_read_lock();
|
||||
task = find_task_by_vpid(pid);
|
||||
if (task) {
|
||||
get_task_struct(task);
|
||||
rcu_read_unlock();
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
marked = test_task_syscall_work(task, SYSCALL_TRACEPOINT) ? 1 : 0;
|
||||
#else
|
||||
marked = test_tsk_thread_flag(task, TIF_SYSCALL_TRACEPOINT) ? 1 : 0;
|
||||
#endif
|
||||
put_task_struct(task);
|
||||
} else {
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
return marked;
|
||||
}
|
||||
|
||||
// Set task mark status
|
||||
// Returns: 0 on success, -ESRCH if task not found
|
||||
int ksu_set_task_mark(pid_t pid, bool mark)
|
||||
{
|
||||
struct task_struct *task;
|
||||
int ret = -ESRCH;
|
||||
|
||||
rcu_read_lock();
|
||||
task = find_task_by_vpid(pid);
|
||||
if (task) {
|
||||
get_task_struct(task);
|
||||
rcu_read_unlock();
|
||||
if (mark) {
|
||||
ksu_set_task_tracepoint_flag(task);
|
||||
pr_info("hook_manager: marked task pid=%d comm=%s\n", pid, task->comm);
|
||||
} else {
|
||||
ksu_clear_task_tracepoint_flag(task);
|
||||
pr_info("hook_manager: unmarked task pid=%d comm=%s\n", pid, task->comm);
|
||||
}
|
||||
put_task_struct(task);
|
||||
ret = 0;
|
||||
} else {
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
|
||||
static struct kretprobe *init_kretprobe(const char *name,
|
||||
kretprobe_handler_t handler)
|
||||
{
|
||||
struct kretprobe *rp = kzalloc(sizeof(struct kretprobe), GFP_KERNEL);
|
||||
if (!rp)
|
||||
return NULL;
|
||||
rp->kp.symbol_name = name;
|
||||
rp->handler = handler;
|
||||
rp->data_size = 0;
|
||||
rp->maxactive = 0;
|
||||
|
||||
int ret = register_kretprobe(rp);
|
||||
pr_info("hook_manager: register_%s kretprobe: %d\n", name, ret);
|
||||
if (ret) {
|
||||
kfree(rp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return rp;
|
||||
}
|
||||
|
||||
static void destroy_kretprobe(struct kretprobe **rp_ptr)
|
||||
{
|
||||
struct kretprobe *rp = *rp_ptr;
|
||||
if (!rp)
|
||||
return;
|
||||
unregister_kretprobe(rp);
|
||||
synchronize_rcu();
|
||||
kfree(rp);
|
||||
*rp_ptr = NULL;
|
||||
}
|
||||
|
||||
static int syscall_regfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
if (tracepoint_reg_count < 1) {
|
||||
// while install our tracepoint, mark our processes
|
||||
ksu_mark_running_process_locked();
|
||||
} else if (tracepoint_reg_count == 1) {
|
||||
// while other tracepoint first added, mark all processes
|
||||
ksu_mark_all_process();
|
||||
}
|
||||
tracepoint_reg_count++;
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int syscall_unregfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
tracepoint_reg_count--;
|
||||
if (tracepoint_reg_count <= 0) {
|
||||
// while no tracepoint left, unmark all processes
|
||||
ksu_unmark_all_process();
|
||||
} else if (tracepoint_reg_count == 1) {
|
||||
// while just our tracepoint left, unmark disallowed processes
|
||||
ksu_mark_running_process_locked();
|
||||
}
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kretprobe *syscall_regfunc_rp = NULL;
|
||||
static struct kretprobe *syscall_unregfunc_rp = NULL;
|
||||
#endif
|
||||
|
||||
static inline bool check_syscall_fastpath(int nr)
|
||||
{
|
||||
switch (nr) {
|
||||
case __NR_newfstatat:
|
||||
case __NR_faccessat:
|
||||
case __NR_execve:
|
||||
case __NR_setresuid:
|
||||
case __NR_clone:
|
||||
case __NR_clone3:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Unmark init's child that are not zygote, adbd or ksud
|
||||
int ksu_handle_init_mark_tracker(const char __user **filename_user)
|
||||
{
|
||||
char path[64];
|
||||
|
||||
if (unlikely(!filename_user))
|
||||
return 0;
|
||||
|
||||
memset(path, 0, sizeof(path));
|
||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
if (likely(strstr(path, "/app_process") == NULL && strstr(path, "/adbd") == NULL && strstr(path, "/ksud") == NULL)) {
|
||||
pr_info("hook_manager: unmark %d exec %s", current->pid, path);
|
||||
ksu_clear_task_tracepoint_flag_if_needed(current);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#include "manual_su.h"
|
||||
static inline void ksu_handle_task_alloc(struct pt_regs *regs)
|
||||
{
|
||||
ksu_try_escalate_for_uid(current_uid().val);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
|
||||
// Generic sys_enter handler that dispatches to specific handlers
|
||||
static void ksu_sys_enter_handler(void *data, struct pt_regs *regs, long id)
|
||||
{
|
||||
if (unlikely(check_syscall_fastpath(id))) {
|
||||
#ifdef KSU_TP_HOOK
|
||||
if (ksu_su_compat_enabled) {
|
||||
// Handle newfstatat
|
||||
if (id == __NR_newfstatat) {
|
||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM2(regs);
|
||||
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(regs);
|
||||
ksu_handle_stat(dfd, filename_user, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle faccessat
|
||||
if (id == __NR_faccessat) {
|
||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM2(regs);
|
||||
int *mode = (int *)&PT_REGS_PARM3(regs);
|
||||
ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle execve
|
||||
if (id == __NR_execve) {
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM1(regs);
|
||||
if (current->pid != 1 && is_init(get_current_cred())) {
|
||||
ksu_handle_init_mark_tracker(filename_user);
|
||||
} else {
|
||||
ksu_handle_execve_sucompat(filename_user, NULL, NULL, NULL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handle setresuid
|
||||
if (id == __NR_setresuid) {
|
||||
uid_t ruid = (uid_t)PT_REGS_PARM1(regs);
|
||||
uid_t euid = (uid_t)PT_REGS_PARM2(regs);
|
||||
uid_t suid = (uid_t)PT_REGS_PARM3(regs);
|
||||
ksu_handle_setresuid(ruid, euid, suid);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
// Handle task_alloc via clone/fork
|
||||
if (id == __NR_clone || id == __NR_clone3)
|
||||
return ksu_handle_task_alloc(regs);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ksu_syscall_hook_manager_init(void)
|
||||
{
|
||||
int ret;
|
||||
pr_info("hook_manager: ksu_hook_manager_init called\n");
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
// Register kretprobe for syscall_regfunc
|
||||
syscall_regfunc_rp = init_kretprobe("syscall_regfunc", syscall_regfunc_handler);
|
||||
// Register kretprobe for syscall_unregfunc
|
||||
syscall_unregfunc_rp = init_kretprobe("syscall_unregfunc", syscall_unregfunc_handler);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
|
||||
ret = register_trace_sys_enter(ksu_sys_enter_handler, NULL);
|
||||
#ifndef CONFIG_KRETPROBES
|
||||
ksu_mark_running_process_locked();
|
||||
#endif
|
||||
if (ret) {
|
||||
pr_err("hook_manager: failed to register sys_enter tracepoint: %d\n", ret);
|
||||
} else {
|
||||
pr_info("hook_manager: sys_enter tracepoint registered\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
ksu_setuid_hook_init();
|
||||
ksu_sucompat_init();
|
||||
}
|
||||
|
||||
void ksu_syscall_hook_manager_exit(void)
|
||||
{
|
||||
pr_info("hook_manager: ksu_hook_manager_exit called\n");
|
||||
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
|
||||
unregister_trace_sys_enter(ksu_sys_enter_handler, NULL);
|
||||
tracepoint_synchronize_unregister();
|
||||
pr_info("hook_manager: sys_enter tracepoint unregistered\n");
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
destroy_kretprobe(&syscall_regfunc_rp);
|
||||
destroy_kretprobe(&syscall_unregfunc_rp);
|
||||
#endif
|
||||
|
||||
ksu_sucompat_exit();
|
||||
ksu_setuid_hook_exit();
|
||||
}
|
||||
47
kernel/syscall_hook_manager.h
Normal file
47
kernel/syscall_hook_manager.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef __KSU_H_HOOK_MANAGER
|
||||
#define __KSU_H_HOOK_MANAGER
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/fs.h>
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
// Hook manager initialization and cleanup
|
||||
void ksu_syscall_hook_manager_init(void);
|
||||
void ksu_syscall_hook_manager_exit(void);
|
||||
|
||||
// Process marking for tracepoint
|
||||
void ksu_mark_all_process(void);
|
||||
void ksu_unmark_all_process(void);
|
||||
void ksu_mark_running_process(void);
|
||||
|
||||
// Per-task mark operations
|
||||
int ksu_get_task_mark(pid_t pid);
|
||||
int ksu_set_task_mark(pid_t pid, bool mark);
|
||||
|
||||
|
||||
static inline void ksu_set_task_tracepoint_flag(struct task_struct *t)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
set_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
||||
#else
|
||||
set_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void ksu_clear_task_tracepoint_flag(struct task_struct *t)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
clear_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
||||
#else
|
||||
clear_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_clear_task_tracepoint_flag_if_needed(struct task_struct *t);
|
||||
|
||||
#endif
|
||||
|
|
@ -7,119 +7,208 @@
|
|||
|
||||
#include "klog.h"
|
||||
#include "throne_comm.h"
|
||||
#include "ksu.h"
|
||||
|
||||
#define PROC_UID_SCANNER "ksu_uid_scanner"
|
||||
#define UID_SCANNER_STATE_FILE "/data/adb/ksu/.uid_scanner"
|
||||
|
||||
static struct proc_dir_entry *proc_entry = NULL;
|
||||
static struct workqueue_struct *scanner_wq = NULL;
|
||||
static struct work_struct scan_work;
|
||||
static struct work_struct ksu_state_save_work;
|
||||
static struct work_struct ksu_state_load_work;
|
||||
|
||||
|
||||
// Signal userspace to rescan
|
||||
static bool need_rescan = false;
|
||||
|
||||
static void rescan_work_fn(struct work_struct *work)
|
||||
{
|
||||
// Signal userspace through proc interface
|
||||
need_rescan = true;
|
||||
pr_info("requested userspace uid rescan\n");
|
||||
// Signal userspace through proc interface
|
||||
need_rescan = true;
|
||||
pr_info("requested userspace uid rescan\n");
|
||||
}
|
||||
|
||||
void ksu_request_userspace_scan(void)
|
||||
{
|
||||
if (scanner_wq) {
|
||||
queue_work(scanner_wq, &scan_work);
|
||||
}
|
||||
if (scanner_wq) {
|
||||
queue_work(scanner_wq, &scan_work);
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_handle_userspace_update(void)
|
||||
{
|
||||
// Called when userspace notifies update complete
|
||||
need_rescan = false;
|
||||
pr_info("userspace uid list updated\n");
|
||||
// Called when userspace notifies update complete
|
||||
need_rescan = false;
|
||||
pr_info("userspace uid list updated\n");
|
||||
}
|
||||
|
||||
static void do_save_throne_state(struct work_struct *work)
|
||||
{
|
||||
struct file *fp;
|
||||
char state_char = ksu_uid_scanner_enabled ? '1' : '0';
|
||||
loff_t off = 0;
|
||||
|
||||
fp = filp_open(UID_SCANNER_STATE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_throne_state create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
if (kernel_write(fp, &state_char, sizeof(state_char), &off) != sizeof(state_char)) {
|
||||
pr_err("save_throne_state write failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pr_info("throne state saved: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
|
||||
|
||||
exit:
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
void do_load_throne_state(struct work_struct *work)
|
||||
{
|
||||
struct file *fp;
|
||||
char state_char;
|
||||
loff_t off = 0;
|
||||
ssize_t ret;
|
||||
|
||||
fp = filp_open(UID_SCANNER_STATE_FILE, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_info("throne state file not found, using default: disabled\n");
|
||||
ksu_uid_scanner_enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ret = kernel_read(fp, &state_char, sizeof(state_char), &off);
|
||||
if (ret != sizeof(state_char)) {
|
||||
pr_err("load_throne_state read err: %zd\n", ret);
|
||||
ksu_uid_scanner_enabled = false;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ksu_uid_scanner_enabled = (state_char == '1');
|
||||
pr_info("throne state loaded: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
|
||||
|
||||
exit:
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
bool ksu_throne_comm_load_state(void)
|
||||
{
|
||||
return ksu_queue_work(&ksu_state_load_work);
|
||||
}
|
||||
|
||||
void ksu_throne_comm_save_state(void)
|
||||
{
|
||||
ksu_queue_work(&ksu_state_save_work);
|
||||
}
|
||||
|
||||
static int uid_scanner_show(struct seq_file *m, void *v)
|
||||
{
|
||||
if (need_rescan) {
|
||||
seq_puts(m, "RESCAN\n");
|
||||
} else {
|
||||
seq_puts(m, "OK\n");
|
||||
}
|
||||
return 0;
|
||||
if (need_rescan) {
|
||||
seq_puts(m, "RESCAN\n");
|
||||
} else {
|
||||
seq_puts(m, "OK\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uid_scanner_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, uid_scanner_show, NULL);
|
||||
return single_open(file, uid_scanner_show, NULL);
|
||||
}
|
||||
|
||||
static ssize_t uid_scanner_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
char cmd[16];
|
||||
|
||||
if (count >= sizeof(cmd))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(cmd, buffer, count))
|
||||
return -EFAULT;
|
||||
|
||||
cmd[count] = '\0';
|
||||
|
||||
// Remove newline if present
|
||||
if (count > 0 && cmd[count-1] == '\n')
|
||||
cmd[count-1] = '\0';
|
||||
|
||||
if (strcmp(cmd, "UPDATED") == 0) {
|
||||
ksu_handle_userspace_update();
|
||||
pr_info("received userspace update notification\n");
|
||||
}
|
||||
|
||||
return count;
|
||||
char cmd[16];
|
||||
|
||||
if (count >= sizeof(cmd))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(cmd, buffer, count))
|
||||
return -EFAULT;
|
||||
|
||||
cmd[count] = '\0';
|
||||
|
||||
// Remove newline if present
|
||||
if (count > 0 && cmd[count-1] == '\n')
|
||||
cmd[count-1] = '\0';
|
||||
|
||||
if (strcmp(cmd, "UPDATED") == 0) {
|
||||
ksu_handle_userspace_update();
|
||||
pr_info("received userspace update notification\n");
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#ifdef KSU_COMPAT_HAS_PROC_OPS
|
||||
static const struct proc_ops uid_scanner_proc_ops = {
|
||||
.proc_open = uid_scanner_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_write = uid_scanner_write,
|
||||
.proc_write = uid_scanner_write,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = single_release,
|
||||
};
|
||||
#else
|
||||
static const struct file_operations uid_scanner_proc_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = uid_scanner_open,
|
||||
.read = seq_read,
|
||||
.write = uid_scanner_write,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
#endif
|
||||
|
||||
int ksu_throne_comm_init(void)
|
||||
{
|
||||
// Create workqueue
|
||||
scanner_wq = alloc_workqueue("ksu_scanner", WQ_UNBOUND, 1);
|
||||
if (!scanner_wq) {
|
||||
pr_err("failed to create scanner workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
INIT_WORK(&scan_work, rescan_work_fn);
|
||||
|
||||
// Create proc entry
|
||||
proc_entry = proc_create(PROC_UID_SCANNER, 0600, NULL, &uid_scanner_proc_ops);
|
||||
if (!proc_entry) {
|
||||
pr_err("failed to create proc entry\n");
|
||||
destroy_workqueue(scanner_wq);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pr_info("throne communication initialized\n");
|
||||
return 0;
|
||||
// Create workqueue
|
||||
scanner_wq = alloc_workqueue("ksu_scanner", WQ_UNBOUND, 1);
|
||||
if (!scanner_wq) {
|
||||
pr_err("failed to create scanner workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
INIT_WORK(&scan_work, rescan_work_fn);
|
||||
|
||||
// Create proc entry
|
||||
proc_entry = proc_create(PROC_UID_SCANNER, 0600, NULL, &uid_scanner_proc_ops);
|
||||
if (!proc_entry) {
|
||||
pr_err("failed to create proc entry\n");
|
||||
destroy_workqueue(scanner_wq);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pr_info("throne communication initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_throne_comm_exit(void)
|
||||
{
|
||||
if (proc_entry) {
|
||||
proc_remove(proc_entry);
|
||||
proc_entry = NULL;
|
||||
}
|
||||
|
||||
if (scanner_wq) {
|
||||
destroy_workqueue(scanner_wq);
|
||||
scanner_wq = NULL;
|
||||
}
|
||||
|
||||
pr_info("throne communication cleaned up\n");
|
||||
if (proc_entry) {
|
||||
proc_remove(proc_entry);
|
||||
proc_entry = NULL;
|
||||
}
|
||||
|
||||
if (scanner_wq) {
|
||||
destroy_workqueue(scanner_wq);
|
||||
scanner_wq = NULL;
|
||||
}
|
||||
|
||||
pr_info("throne communication cleaned up\n");
|
||||
}
|
||||
|
||||
int ksu_uid_init(void)
|
||||
{
|
||||
INIT_WORK(&ksu_state_save_work, do_save_throne_state);
|
||||
INIT_WORK(&ksu_state_load_work, do_load_throne_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_uid_exit(void)
|
||||
{
|
||||
do_save_throne_state(NULL);
|
||||
}
|
||||
|
|
@ -9,4 +9,14 @@ int ksu_throne_comm_init(void);
|
|||
|
||||
void ksu_throne_comm_exit(void);
|
||||
|
||||
int ksu_uid_init(void);
|
||||
|
||||
void ksu_uid_exit(void);
|
||||
|
||||
bool ksu_throne_comm_load_state(void);
|
||||
|
||||
void ksu_throne_comm_save_state(void);
|
||||
|
||||
void do_load_throne_state(struct work_struct *work);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,6 @@ void ksu_throne_tracker_init();
|
|||
|
||||
void ksu_throne_tracker_exit();
|
||||
|
||||
void track_throne();
|
||||
void track_throne(bool prune_only);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
278
kernel/umount_manager.c
Normal file
278
kernel/umount_manager.c
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/cred.h>
|
||||
|
||||
#include "klog.h"
|
||||
#include "kernel_umount.h"
|
||||
#include "umount_manager.h"
|
||||
|
||||
static struct umount_manager g_umount_mgr = {
|
||||
.entry_count = 0,
|
||||
.max_entries = 64,
|
||||
};
|
||||
|
||||
static void try_umount_path(struct umount_entry *entry)
|
||||
{
|
||||
try_umount(entry->path, entry->check_mnt, entry->flags);
|
||||
}
|
||||
|
||||
static struct umount_entry *find_entry_locked(const char *path)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (strcmp(entry->path, path) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int init_default_entries(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
const struct {
|
||||
const char *path;
|
||||
bool check_mnt;
|
||||
int flags;
|
||||
} defaults[] = {
|
||||
{ "/odm", true, 0 },
|
||||
{ "/system", true, 0 },
|
||||
{ "/vendor", true, 0 },
|
||||
{ "/product", true, 0 },
|
||||
{ "/system_ext", true, 0 },
|
||||
{ "/data/adb/modules", false, MNT_DETACH },
|
||||
{ "/debug_ramdisk", false, MNT_DETACH },
|
||||
};
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(defaults); i++) {
|
||||
ret = ksu_umount_manager_add(defaults[i].path,
|
||||
defaults[i].check_mnt,
|
||||
defaults[i].flags,
|
||||
true); // is_default = true
|
||||
if (ret) {
|
||||
pr_err("Failed to add default entry: %s, ret=%d\n",
|
||||
defaults[i].path, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pr_info("Initialized %zu default umount entries\n", ARRAY_SIZE(defaults));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_umount_manager_init(void)
|
||||
{
|
||||
INIT_LIST_HEAD(&g_umount_mgr.entry_list);
|
||||
spin_lock_init(&g_umount_mgr.lock);
|
||||
|
||||
return init_default_entries();
|
||||
}
|
||||
|
||||
void ksu_umount_manager_exit(void)
|
||||
{
|
||||
struct umount_entry *entry, *tmp;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry_safe(entry, tmp, &g_umount_mgr.entry_list, list) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
g_umount_mgr.entry_count--;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
|
||||
pr_info("Umount manager cleaned up\n");
|
||||
}
|
||||
|
||||
int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_default)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
unsigned long irqflags;
|
||||
int ret = 0;
|
||||
|
||||
if (flags == -1)
|
||||
flags = MNT_DETACH;
|
||||
|
||||
if (!path || strlen(path) == 0 || strlen(path) >= 256) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, irqflags);
|
||||
|
||||
if (g_umount_mgr.entry_count >= g_umount_mgr.max_entries) {
|
||||
pr_err("Umount manager: max entries reached\n");
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (find_entry_locked(path)) {
|
||||
pr_warn("Umount manager: path already exists: %s\n", path);
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
if (!entry) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
strncpy(entry->path, path, sizeof(entry->path) - 1);
|
||||
entry->check_mnt = check_mnt;
|
||||
entry->flags = flags;
|
||||
entry->state = UMOUNT_STATE_IDLE;
|
||||
entry->is_default = is_default;
|
||||
entry->ref_count = 0;
|
||||
|
||||
list_add_tail(&entry->list, &g_umount_mgr.entry_list);
|
||||
g_umount_mgr.entry_count++;
|
||||
|
||||
pr_info("Umount manager: added %s entry: %s\n",
|
||||
is_default ? "default" : "custom", path);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, irqflags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ksu_umount_manager_remove(const char *path)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (!path) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
entry = find_entry_locked(path);
|
||||
if (!entry) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (entry->is_default) {
|
||||
pr_err("Umount manager: cannot remove default entry: %s\n", path);
|
||||
ret = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (entry->state == UMOUNT_STATE_BUSY || entry->ref_count > 0) {
|
||||
pr_err("Umount manager: entry is busy: %s\n", path);
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
list_del(&entry->list);
|
||||
g_umount_mgr.entry_count--;
|
||||
kfree(entry);
|
||||
|
||||
pr_info("Umount manager: removed entry: %s\n", path);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ksu_umount_manager_execute_all(const struct cred *cred)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (entry->state == UMOUNT_STATE_IDLE) {
|
||||
entry->ref_count++;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (entry->ref_count > 0 && entry->state == UMOUNT_STATE_IDLE) {
|
||||
try_umount_path(entry);
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (entry->ref_count > 0) {
|
||||
entry->ref_count--;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
}
|
||||
|
||||
int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
struct ksu_umount_entry_info info;
|
||||
unsigned long flags;
|
||||
u32 idx = 0;
|
||||
u32 max_count = *count;
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (idx >= max_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
strncpy(info.path, entry->path, sizeof(info.path) - 1);
|
||||
info.check_mnt = entry->check_mnt;
|
||||
info.flags = entry->flags;
|
||||
info.is_default = entry->is_default;
|
||||
info.state = entry->state;
|
||||
info.ref_count = entry->ref_count;
|
||||
|
||||
if (copy_to_user(&entries[idx], &info, sizeof(info))) {
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
*count = idx;
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_umount_manager_clear_custom(void)
|
||||
{
|
||||
struct umount_entry *entry, *tmp;
|
||||
unsigned long flags;
|
||||
u32 cleared = 0;
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry_safe(entry, tmp, &g_umount_mgr.entry_list, list) {
|
||||
if (!entry->is_default && entry->state == UMOUNT_STATE_IDLE && entry->ref_count == 0) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
g_umount_mgr.entry_count--;
|
||||
cleared++;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
|
||||
pr_info("Umount manager: cleared %u custom entries\n", cleared);
|
||||
return 0;
|
||||
}
|
||||
66
kernel/umount_manager.h
Normal file
66
kernel/umount_manager.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef __KSU_H_UMOUNT_MANAGER
|
||||
#define __KSU_H_UMOUNT_MANAGER
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
struct cred;
|
||||
|
||||
enum umount_entry_state {
|
||||
UMOUNT_STATE_IDLE = 0,
|
||||
UMOUNT_STATE_ACTIVE = 1,
|
||||
UMOUNT_STATE_BUSY = 2,
|
||||
};
|
||||
|
||||
struct umount_entry {
|
||||
struct list_head list;
|
||||
char path[256];
|
||||
bool check_mnt;
|
||||
int flags;
|
||||
enum umount_entry_state state;
|
||||
bool is_default;
|
||||
u32 ref_count;
|
||||
};
|
||||
|
||||
struct umount_manager {
|
||||
struct list_head entry_list;
|
||||
spinlock_t lock;
|
||||
u32 entry_count;
|
||||
u32 max_entries;
|
||||
};
|
||||
|
||||
enum umount_manager_op {
|
||||
UMOUNT_OP_ADD = 0,
|
||||
UMOUNT_OP_REMOVE = 1,
|
||||
UMOUNT_OP_LIST = 2,
|
||||
UMOUNT_OP_CLEAR_CUSTOM = 3,
|
||||
};
|
||||
|
||||
struct ksu_umount_manager_cmd {
|
||||
__u32 operation;
|
||||
char path[256];
|
||||
__u8 check_mnt;
|
||||
__s32 flags;
|
||||
__u32 count;
|
||||
__aligned_u64 entries_ptr;
|
||||
};
|
||||
|
||||
struct ksu_umount_entry_info {
|
||||
char path[256];
|
||||
__u8 check_mnt;
|
||||
__s32 flags;
|
||||
__u8 is_default;
|
||||
__u32 state;
|
||||
__u32 ref_count;
|
||||
};
|
||||
|
||||
int ksu_umount_manager_init(void);
|
||||
void ksu_umount_manager_exit(void);
|
||||
int ksu_umount_manager_add(const char *path, bool check_mnt, int flags, bool is_default);
|
||||
int ksu_umount_manager_remove(const char *path);
|
||||
void ksu_umount_manager_execute_all(const struct cred *cred);
|
||||
int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count);
|
||||
int ksu_umount_manager_clear_custom(void);
|
||||
|
||||
#endif // __KSU_H_UMOUNT_MANAGER
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.app)
|
||||
|
|
@ -17,6 +16,7 @@ plugins {
|
|||
|
||||
val managerVersionCode: Int by rootProject.extra
|
||||
val managerVersionName: String by rootProject.extra
|
||||
val androidCmakeVersion: String by rootProject.extra
|
||||
|
||||
apksign {
|
||||
storeFileProperty = "KEYSTORE_FILE"
|
||||
|
|
@ -51,15 +51,12 @@ android {
|
|||
}
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
buildConfig = true
|
||||
compose = true
|
||||
prefab = true
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
|
|
@ -77,7 +74,8 @@ android {
|
|||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("src/main/cpp/CMakeLists.txt")
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
version = androidCmakeVersion
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,6 +123,7 @@ dependencies {
|
|||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.foundation)
|
||||
implementation(libs.androidx.documentfile)
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
|
|
|
|||
4
manager/app/proguard-rules.pro
vendored
4
manager/app/proguard-rules.pro
vendored
|
|
@ -43,4 +43,6 @@
|
|||
-keep class com.dergoogler.mmrl.webui.interfaces.** { *; }
|
||||
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }
|
||||
|
||||
-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; }
|
||||
-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; }
|
||||
|
||||
-keep interface com.sukisu.zako.** { *; }
|
||||
|
|
@ -18,31 +18,82 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.KernelSU"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
tools:targetApi="34">
|
||||
<!-- 专门为小米手机桌面卸载添加了提示,提升用户体验 -->
|
||||
<meta-data
|
||||
android:name="app_description_title"
|
||||
android:resource="@string/miui_uninstall_title" />
|
||||
<meta-data
|
||||
android:name="app_description_content"
|
||||
android:resource="@string/miui_uninstall_content" />
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:launchMode="standard"
|
||||
android:documentLaunchMode="intoExisting"
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:theme="@style/Theme.KernelSU">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- 切换图标 -->
|
||||
<activity-alias
|
||||
android:name=".ui.MainActivityAlias"
|
||||
android:targetActivity=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/ic_launcher_alt"
|
||||
android:roundIcon="@mipmap/ic_launcher_alt_round"
|
||||
android:targetActivity=".ui.MainActivity">
|
||||
android:roundIcon="@mipmap/ic_launcher_alt_round">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
|
|
|
|||
10
manager/app/src/main/aidl/com/sukisu/zako/IKsuInterface.aidl
Normal file
10
manager/app/src/main/aidl/com/sukisu/zako/IKsuInterface.aidl
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// IKsuInterface.aidl
|
||||
package com.sukisu.zako;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import java.util.List;
|
||||
|
||||
interface IKsuInterface {
|
||||
int getPackageCount();
|
||||
List<PackageInfo> getPackages(int start, int maxCount);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
manager/app/src/main/assets/ksu_susfs_2.0.0
Normal file
BIN
manager/app/src/main/assets/ksu_susfs_2.0.0
Normal file
Binary file not shown.
|
|
@ -6,10 +6,11 @@ cmake_minimum_required(VERSION 3.18.1)
|
|||
|
||||
project("kernelsu")
|
||||
|
||||
add_library(zako
|
||||
add_library(kernelsu
|
||||
SHARED
|
||||
jni.c
|
||||
ksu.c
|
||||
legacy.c
|
||||
)
|
||||
|
||||
find_library(log-lib log)
|
||||
|
|
@ -21,7 +22,7 @@ elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
|
|||
endif()
|
||||
|
||||
if(ANDROID_ABI STREQUAL "arm64-v8a" OR ANDROID_ABI STREQUAL "armeabi-v7a")
|
||||
target_link_libraries(zako ${log-lib} ${zakosign-lib})
|
||||
target_link_libraries(kernelsu ${log-lib} ${zakosign-lib})
|
||||
else()
|
||||
target_link_libraries(zako ${log-lib})
|
||||
target_link_libraries(kernelsu ${log-lib})
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -5,430 +5,440 @@
|
|||
#include <sys/prctl.h>
|
||||
#include <android/log.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
NativeBridge(becomeManager, jboolean, jstring pkg) {
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, JNI_FALSE);
|
||||
bool result = become_manager(cpkg);
|
||||
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
|
||||
return result;
|
||||
}
|
||||
#include <linux/capability.h>
|
||||
#include <pwd.h>
|
||||
|
||||
NativeBridgeNP(getVersion, jint) {
|
||||
return get_version();
|
||||
uint32_t version = get_version();
|
||||
if (version > 0) {
|
||||
return (jint)version;
|
||||
}
|
||||
// try legacy method as fallback
|
||||
return legacy_get_info().version;
|
||||
}
|
||||
|
||||
// get VERSION FULL
|
||||
NativeBridgeNP(getFullVersion, jstring) {
|
||||
char buff[255] = { 0 };
|
||||
get_full_version((char *) &buff);
|
||||
return GetEnvironment()->NewStringUTF(env, buff);
|
||||
char buff[255] = { 0 };
|
||||
get_full_version((char *) &buff);
|
||||
return GetEnvironment()->NewStringUTF(env, buff);
|
||||
}
|
||||
|
||||
NativeBridgeNP(getAllowList, jintArray) {
|
||||
int uids[1024];
|
||||
int size = 0;
|
||||
bool result = get_allow_list(uids, &size);
|
||||
struct ksu_get_allow_list_cmd cmd = {};
|
||||
bool result = get_allow_list(&cmd);
|
||||
|
||||
LogDebug("getAllowList: %d, size: %d", result, size);
|
||||
if (result) {
|
||||
jsize array_size = (jsize)cmd.count;
|
||||
if (array_size < 0 || (unsigned int)array_size != cmd.count) {
|
||||
LogDebug("Invalid array size: %u", cmd.count);
|
||||
return GetEnvironment()->NewIntArray(env, 0);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
jintArray array = GetEnvironment()->NewIntArray(env, size);
|
||||
GetEnvironment()->SetIntArrayRegion(env, array, 0, size, uids);
|
||||
jintArray array = GetEnvironment()->NewIntArray(env, array_size);
|
||||
GetEnvironment()->SetIntArrayRegion(env, array, 0, array_size, (const jint *)(cmd.uids));
|
||||
|
||||
return array;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
return GetEnvironment()->NewIntArray(env, 0);
|
||||
return GetEnvironment()->NewIntArray(env, 0);
|
||||
}
|
||||
|
||||
NativeBridgeNP(isSafeMode, jboolean) {
|
||||
return is_safe_mode();
|
||||
return is_safe_mode();
|
||||
}
|
||||
|
||||
NativeBridgeNP(isLkmMode, jboolean) {
|
||||
return is_lkm_mode();
|
||||
return is_lkm_mode();
|
||||
}
|
||||
|
||||
NativeBridgeNP(isManager, jboolean) {
|
||||
return is_manager();
|
||||
}
|
||||
|
||||
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, data[i]);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
}
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, data[i]);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
}
|
||||
}
|
||||
|
||||
static void addIntToList(JNIEnv *env, jobject list, int ele) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, ele);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, ele);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
}
|
||||
|
||||
static uint64_t capListToBits(JNIEnv *env, jobject list) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
jint listSize = GetEnvironment()->CallIntMethod(env, list, size);
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < listSize; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
int data = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
jint listSize = GetEnvironment()->CallIntMethod(env, list, size);
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < listSize; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
int data = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
|
||||
if (cap_valid(data)) {
|
||||
result |= (1ULL << data);
|
||||
}
|
||||
}
|
||||
if (cap_valid(data)) {
|
||||
result |= (1ULL << data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getListSize(JNIEnv *env, jobject list) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
return GetEnvironment()->CallIntMethod(env, list, size);
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
return GetEnvironment()->CallIntMethod(env, list, size);
|
||||
}
|
||||
|
||||
static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
data[i] = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
}
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
data[i] = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
}
|
||||
}
|
||||
|
||||
NativeBridge(getAppProfile, jobject, jstring pkg, jint uid) {
|
||||
if (GetEnvironment()->GetStringLength(env, pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||
return NULL;
|
||||
}
|
||||
if (GetEnvironment()->GetStringLength(env, pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, nullptr);
|
||||
strcpy(key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
|
||||
char key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, nullptr);
|
||||
strcpy(key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
|
||||
|
||||
struct app_profile profile = { 0 };
|
||||
profile.version = KSU_APP_PROFILE_VER;
|
||||
struct app_profile profile = { 0 };
|
||||
profile.version = KSU_APP_PROFILE_VER;
|
||||
|
||||
strcpy(profile.key, key);
|
||||
profile.current_uid = uid;
|
||||
strcpy(profile.key, key);
|
||||
profile.current_uid = uid;
|
||||
|
||||
bool useDefaultProfile = !get_app_profile(key, &profile);
|
||||
bool useDefaultProfile = get_app_profile(&profile) != 0;
|
||||
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
||||
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
||||
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
|
||||
GetEnvironment()->SetObjectField(env, obj, keyField, GetEnvironment()->NewStringUTF(env, profile.key));
|
||||
GetEnvironment()->SetIntField(env, obj, currentUidField, profile.current_uid);
|
||||
GetEnvironment()->SetObjectField(env, obj, keyField, GetEnvironment()->NewStringUTF(env, profile.key));
|
||||
GetEnvironment()->SetIntField(env, obj, currentUidField, profile.current_uid);
|
||||
|
||||
if (useDefaultProfile) {
|
||||
// no profile found, so just use default profile:
|
||||
// don't allow root and use default profile!
|
||||
LogDebug("use default profile for: %s, %d", key, uid);
|
||||
if (useDefaultProfile) {
|
||||
// no profile found, so just use default profile:
|
||||
// don't allow root and use default profile!
|
||||
LogDebug("use default profile for: %s, %d", key, uid);
|
||||
|
||||
// allow_su = false
|
||||
// non root use default = true
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, false);
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, true);
|
||||
// allow_su = false
|
||||
// non root use default = true
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, false);
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, true);
|
||||
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool allowSu = profile.allow_su;
|
||||
bool allowSu = profile.allow_su;
|
||||
|
||||
if (allowSu) {
|
||||
GetEnvironment()->SetBooleanField(env, obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
|
||||
if (strlen(profile.rp_config.template_name) > 0) {
|
||||
GetEnvironment()->SetObjectField(env, obj, rootTemplateField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.template_name));
|
||||
}
|
||||
if (allowSu) {
|
||||
GetEnvironment()->SetBooleanField(env, obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
|
||||
if (strlen(profile.rp_config.template_name) > 0) {
|
||||
GetEnvironment()->SetObjectField(env, obj, rootTemplateField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.template_name));
|
||||
}
|
||||
|
||||
GetEnvironment()->SetIntField(env, obj, uidField, profile.rp_config.profile.uid);
|
||||
GetEnvironment()->SetIntField(env, obj, gidField, profile.rp_config.profile.gid);
|
||||
GetEnvironment()->SetIntField(env, obj, uidField, profile.rp_config.profile.uid);
|
||||
GetEnvironment()->SetIntField(env, obj, gidField, profile.rp_config.profile.gid);
|
||||
|
||||
jobject groupList = GetEnvironment()->GetObjectField(env, obj, groupsField);
|
||||
int groupCount = profile.rp_config.profile.groups_count;
|
||||
if (groupCount > KSU_MAX_GROUPS) {
|
||||
LogDebug("kernel group count too large: %d???", groupCount);
|
||||
groupCount = KSU_MAX_GROUPS;
|
||||
}
|
||||
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
|
||||
jobject groupList = GetEnvironment()->GetObjectField(env, obj, groupsField);
|
||||
int groupCount = profile.rp_config.profile.groups_count;
|
||||
if (groupCount > KSU_MAX_GROUPS) {
|
||||
LogDebug("kernel group count too large: %d???", groupCount);
|
||||
groupCount = KSU_MAX_GROUPS;
|
||||
}
|
||||
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
|
||||
|
||||
jobject capList = GetEnvironment()->GetObjectField(env, obj, capabilitiesField);
|
||||
for (int i = 0; i <= CAP_LAST_CAP; i++) {
|
||||
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
|
||||
addIntToList(env, capList, i);
|
||||
}
|
||||
}
|
||||
jobject capList = GetEnvironment()->GetObjectField(env, obj, capabilitiesField);
|
||||
for (int i = 0; i <= CAP_LAST_CAP; i++) {
|
||||
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
|
||||
addIntToList(env, capList, i);
|
||||
}
|
||||
}
|
||||
|
||||
GetEnvironment()->SetObjectField(env, obj, domainField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.profile.selinux_domain));
|
||||
GetEnvironment()->SetIntField(env, obj, namespacesField, profile.rp_config.profile.namespaces);
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, profile.allow_su);
|
||||
} else {
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, profile.nrp_config.use_default);
|
||||
GetEnvironment()->SetBooleanField(env, obj, umountModulesField, profile.nrp_config.profile.umount_modules);
|
||||
}
|
||||
GetEnvironment()->SetObjectField(env, obj, domainField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.profile.selinux_domain));
|
||||
GetEnvironment()->SetIntField(env, obj, namespacesField, profile.rp_config.profile.namespaces);
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, profile.allow_su);
|
||||
} else {
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, profile.nrp_config.use_default);
|
||||
GetEnvironment()->SetBooleanField(env, obj, umountModulesField, profile.nrp_config.profile.umount_modules);
|
||||
}
|
||||
|
||||
return obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
NativeBridge(setAppProfile, jboolean, jobject profile) {
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
|
||||
jobject key = GetEnvironment()->GetObjectField(env, profile, keyField);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
if (GetEnvironment()->GetStringLength(env, (jstring) key) > KSU_MAX_PACKAGE_NAME) {
|
||||
return false;
|
||||
}
|
||||
jobject key = GetEnvironment()->GetObjectField(env, profile, keyField);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
if (GetEnvironment()->GetStringLength(env, (jstring) key) > KSU_MAX_PACKAGE_NAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, (jstring) key, nullptr);
|
||||
char p_key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
strcpy(p_key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) key, cpkg);
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, (jstring) key, nullptr);
|
||||
char p_key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
strcpy(p_key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) key, cpkg);
|
||||
|
||||
jint currentUid = GetEnvironment()->GetIntField(env, profile, currentUidField);
|
||||
jint currentUid = GetEnvironment()->GetIntField(env, profile, currentUidField);
|
||||
|
||||
jint uid = GetEnvironment()->GetIntField(env, profile, uidField);
|
||||
jint gid = GetEnvironment()->GetIntField(env, profile, gidField);
|
||||
jobject groups = GetEnvironment()->GetObjectField(env, profile, groupsField);
|
||||
jobject capabilities = GetEnvironment()->GetObjectField(env, profile, capabilitiesField);
|
||||
jobject domain = GetEnvironment()->GetObjectField(env, profile, domainField);
|
||||
jboolean allowSu = GetEnvironment()->GetBooleanField(env, profile, allowSuField);
|
||||
jboolean umountModules = GetEnvironment()->GetBooleanField(env, profile, umountModulesField);
|
||||
jint uid = GetEnvironment()->GetIntField(env, profile, uidField);
|
||||
jint gid = GetEnvironment()->GetIntField(env, profile, gidField);
|
||||
jobject groups = GetEnvironment()->GetObjectField(env, profile, groupsField);
|
||||
jobject capabilities = GetEnvironment()->GetObjectField(env, profile, capabilitiesField);
|
||||
jobject domain = GetEnvironment()->GetObjectField(env, profile, domainField);
|
||||
jboolean allowSu = GetEnvironment()->GetBooleanField(env, profile, allowSuField);
|
||||
jboolean umountModules = GetEnvironment()->GetBooleanField(env, profile, umountModulesField);
|
||||
|
||||
struct app_profile p = { 0 };
|
||||
p.version = KSU_APP_PROFILE_VER;
|
||||
struct app_profile p = { 0 };
|
||||
p.version = KSU_APP_PROFILE_VER;
|
||||
|
||||
strcpy(p.key, p_key);
|
||||
p.allow_su = allowSu;
|
||||
p.current_uid = currentUid;
|
||||
strcpy(p.key, p_key);
|
||||
p.allow_su = allowSu;
|
||||
p.current_uid = currentUid;
|
||||
|
||||
if (allowSu) {
|
||||
p.rp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, rootUseDefaultField);
|
||||
jobject templateName = GetEnvironment()->GetObjectField(env, profile, rootTemplateField);
|
||||
if (templateName) {
|
||||
const char* ctemplateName = GetEnvironment()->GetStringUTFChars(env, (jstring) templateName, nullptr);
|
||||
strcpy(p.rp_config.template_name, ctemplateName);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) templateName, ctemplateName);
|
||||
}
|
||||
if (allowSu) {
|
||||
p.rp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, rootUseDefaultField);
|
||||
jobject templateName = GetEnvironment()->GetObjectField(env, profile, rootTemplateField);
|
||||
if (templateName) {
|
||||
const char* ctemplateName = GetEnvironment()->GetStringUTFChars(env, (jstring) templateName, nullptr);
|
||||
strcpy(p.rp_config.template_name, ctemplateName);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) templateName, ctemplateName);
|
||||
}
|
||||
|
||||
p.rp_config.profile.uid = uid;
|
||||
p.rp_config.profile.gid = gid;
|
||||
p.rp_config.profile.uid = uid;
|
||||
p.rp_config.profile.gid = gid;
|
||||
|
||||
int groups_count = getListSize(env, groups);
|
||||
if (groups_count > KSU_MAX_GROUPS) {
|
||||
LogDebug("groups count too large: %d", groups_count);
|
||||
return false;
|
||||
}
|
||||
p.rp_config.profile.groups_count = groups_count;
|
||||
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
|
||||
int groups_count = getListSize(env, groups);
|
||||
if (groups_count > KSU_MAX_GROUPS) {
|
||||
LogDebug("groups count too large: %d", groups_count);
|
||||
return false;
|
||||
}
|
||||
p.rp_config.profile.groups_count = groups_count;
|
||||
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
|
||||
|
||||
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
|
||||
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
|
||||
|
||||
const char* cdomain = GetEnvironment()->GetStringUTFChars(env, (jstring) domain, nullptr);
|
||||
strcpy(p.rp_config.profile.selinux_domain, cdomain);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) domain, cdomain);
|
||||
const char* cdomain = GetEnvironment()->GetStringUTFChars(env, (jstring) domain, nullptr);
|
||||
strcpy(p.rp_config.profile.selinux_domain, cdomain);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) domain, cdomain);
|
||||
|
||||
p.rp_config.profile.namespaces = GetEnvironment()->GetIntField(env, profile, namespacesField);
|
||||
} else {
|
||||
p.nrp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, nonRootUseDefaultField);
|
||||
p.nrp_config.profile.umount_modules = umountModules;
|
||||
}
|
||||
p.rp_config.profile.namespaces = GetEnvironment()->GetIntField(env, profile, namespacesField);
|
||||
} else {
|
||||
p.nrp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, nonRootUseDefaultField);
|
||||
p.nrp_config.profile.umount_modules = umountModules;
|
||||
}
|
||||
|
||||
return set_app_profile(&p);
|
||||
return set_app_profile(&p);
|
||||
}
|
||||
|
||||
NativeBridge(uidShouldUmount, jboolean, jint uid) {
|
||||
return uid_should_umount(uid);
|
||||
return uid_should_umount(uid);
|
||||
}
|
||||
|
||||
NativeBridgeNP(isSuEnabled, jboolean) {
|
||||
return is_su_enabled();
|
||||
return is_su_enabled();
|
||||
}
|
||||
|
||||
NativeBridge(setSuEnabled, jboolean, jboolean enabled) {
|
||||
return set_su_enabled(enabled);
|
||||
return set_su_enabled(enabled);
|
||||
}
|
||||
|
||||
NativeBridgeNP(isKernelUmountEnabled, jboolean) {
|
||||
return is_kernel_umount_enabled();
|
||||
}
|
||||
|
||||
NativeBridge(setKernelUmountEnabled, jboolean, jboolean enabled) {
|
||||
return set_kernel_umount_enabled(enabled);
|
||||
}
|
||||
|
||||
NativeBridgeNP(isEnhancedSecurityEnabled, jboolean) {
|
||||
return is_enhanced_security_enabled();
|
||||
}
|
||||
|
||||
NativeBridge(setEnhancedSecurityEnabled, jboolean, jboolean enabled) {
|
||||
return set_enhanced_security_enabled(enabled);
|
||||
}
|
||||
|
||||
NativeBridge(getUserName, jstring, jint uid) {
|
||||
struct passwd *pw = getpwuid((uid_t) uid);
|
||||
if (pw && pw->pw_name && pw->pw_name[0] != '\0') {
|
||||
return GetEnvironment()->NewStringUTF(env, pw->pw_name);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check if KPM is enabled
|
||||
NativeBridgeNP(isKPMEnabled, jboolean) {
|
||||
return is_KPM_enable();
|
||||
return is_KPM_enable();
|
||||
}
|
||||
|
||||
// Get HOOK type
|
||||
NativeBridgeNP(getHookType, jstring) {
|
||||
char hook_type[16];
|
||||
get_hook_type(hook_type, sizeof(hook_type));
|
||||
return GetEnvironment()->NewStringUTF(env, hook_type);
|
||||
}
|
||||
|
||||
// SuSFS Related Function Status
|
||||
NativeBridgeNP(getSusfsFeatureStatus, jobject) {
|
||||
struct susfs_feature_status status;
|
||||
bool result = get_susfs_feature_status(&status);
|
||||
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$SusfsFeatureStatus");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
||||
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
|
||||
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusSusPath, status.status_sus_path);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusSusMount, status.status_sus_mount);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusAutoDefaultMount, status.status_auto_default_mount);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusAutoBindMount, status.status_auto_bind_mount);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusSusKstat, status.status_sus_kstat);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusTryUmount, status.status_try_umount);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusAutoTryUmountBind, status.status_auto_try_umount_bind);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusSpoofUname, status.status_spoof_uname);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusEnableLog, status.status_enable_log);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusHideSymbols, status.status_hide_symbols);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusSpoofCmdline, status.status_spoof_cmdline);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusOpenRedirect, status.status_open_redirect);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusMagicMount, status.status_magic_mount);
|
||||
SET_BOOLEAN_FIELD(obj, cls, statusSusSu, status.status_sus_su);
|
||||
|
||||
return obj;
|
||||
char hook_type[32] = { 0 };
|
||||
get_hook_type((char *) &hook_type);
|
||||
return GetEnvironment()->NewStringUTF(env, hook_type);
|
||||
}
|
||||
|
||||
// dynamic manager
|
||||
NativeBridge(setDynamicManager, jboolean, jint size, jstring hash) {
|
||||
if (!hash) {
|
||||
LogDebug("setDynamicManager: hash is null");
|
||||
return false;
|
||||
}
|
||||
if (!hash) {
|
||||
LogDebug("setDynamicManager: hash is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
|
||||
bool result = set_dynamic_manager((unsigned int)size, chash);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
|
||||
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
|
||||
bool result = set_dynamic_manager((unsigned int)size, chash);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
|
||||
|
||||
LogDebug("setDynamicManager: size=0x%x, result=%d", size, result);
|
||||
return result;
|
||||
LogDebug("setDynamicManager: size=0x%x, result=%d", size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
NativeBridgeNP(getDynamicManager, jobject) {
|
||||
struct dynamic_manager_user_config config;
|
||||
bool result = get_dynamic_manager(&config);
|
||||
struct dynamic_manager_user_config config;
|
||||
bool result = get_dynamic_manager(&config);
|
||||
|
||||
if (!result) {
|
||||
LogDebug("getDynamicManager: failed to get dynamic manager config");
|
||||
return NULL;
|
||||
}
|
||||
if (!result) {
|
||||
LogDebug("getDynamicManager: failed to get dynamic manager config");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
|
||||
SET_INT_FIELD(obj, cls, size, (jint)config.size);
|
||||
SET_STRING_FIELD(obj, cls, hash, config.hash);
|
||||
SET_INT_FIELD(obj, cls, size, (jint)config.size);
|
||||
SET_STRING_FIELD(obj, cls, hash, config.hash);
|
||||
|
||||
LogDebug("getDynamicManager: size=0x%x, hash=%.16s...", config.size, config.hash);
|
||||
return obj;
|
||||
LogDebug("getDynamicManager: size=0x%x, hash=%.16s...", config.size, config.hash);
|
||||
return obj;
|
||||
}
|
||||
|
||||
NativeBridgeNP(clearDynamicManager, jboolean) {
|
||||
bool result = clear_dynamic_manager();
|
||||
LogDebug("clearDynamicManager: result=%d", result);
|
||||
return result;
|
||||
bool result = clear_dynamic_manager();
|
||||
LogDebug("clearDynamicManager: result=%d", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get a list of active managers
|
||||
NativeBridgeNP(getManagersList, jobject) {
|
||||
struct manager_list_info managerListInfo;
|
||||
bool result = get_managers_list(&managerListInfo);
|
||||
struct manager_list_info managerListInfo;
|
||||
bool result = get_managers_list(&managerListInfo);
|
||||
|
||||
if (!result) {
|
||||
LogDebug("getManagersList: failed to get active managers list");
|
||||
return NULL;
|
||||
}
|
||||
if (!result) {
|
||||
LogDebug("getManagersList: failed to get active managers list");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$ManagersList");
|
||||
jclass managerListCls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$ManagersList");
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$ManagersList");
|
||||
jclass managerListCls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$ManagersList");
|
||||
|
||||
SET_INT_FIELD(obj, managerListCls, count, (jint)managerListInfo.count);
|
||||
SET_INT_FIELD(obj, managerListCls, count, (jint)managerListInfo.count);
|
||||
|
||||
jobject managersList = CREATE_ARRAYLIST();
|
||||
jobject managersList = CREATE_ARRAYLIST();
|
||||
|
||||
for (int i = 0; i < managerListInfo.count; i++) {
|
||||
jobject managerInfo = CREATE_JAVA_OBJECT_WITH_PARAMS(
|
||||
"com/sukisu/ultra/Natives$ManagerInfo",
|
||||
"(II)V",
|
||||
(jint)managerListInfo.managers[i].uid,
|
||||
(jint)managerListInfo.managers[i].signature_index
|
||||
);
|
||||
ADD_TO_LIST(managersList, managerInfo);
|
||||
}
|
||||
for (int i = 0; i < managerListInfo.count; i++) {
|
||||
jobject managerInfo = CREATE_JAVA_OBJECT_WITH_PARAMS(
|
||||
"com/sukisu/ultra/Natives$ManagerInfo",
|
||||
"(II)V",
|
||||
(jint)managerListInfo.managers[i].uid,
|
||||
(jint)managerListInfo.managers[i].signature_index
|
||||
);
|
||||
ADD_TO_LIST(managersList, managerInfo);
|
||||
}
|
||||
|
||||
SET_OBJECT_FIELD(obj, managerListCls, managers, managersList);
|
||||
SET_OBJECT_FIELD(obj, managerListCls, managers, managersList);
|
||||
|
||||
LogDebug("getManagersList: count=%d", managerListInfo.count);
|
||||
return obj;
|
||||
LogDebug("getManagersList: count=%d", managerListInfo.count);
|
||||
return obj;
|
||||
}
|
||||
|
||||
NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||
if (!modulePath) {
|
||||
LogDebug("verifyModuleSignature: modulePath is null");
|
||||
return false;
|
||||
}
|
||||
if (!modulePath) {
|
||||
LogDebug("verifyModuleSignature: modulePath is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* cModulePath = GetEnvironment()->GetStringUTFChars(env, modulePath, nullptr);
|
||||
bool result = verify_module_signature(cModulePath);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, modulePath, cModulePath);
|
||||
const char* cModulePath = GetEnvironment()->GetStringUTFChars(env, modulePath, nullptr);
|
||||
bool result = verify_module_signature(cModulePath);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, modulePath, cModulePath);
|
||||
|
||||
LogDebug("verifyModuleSignature: path=%s, result=%d", cModulePath, result);
|
||||
return result;
|
||||
LogDebug("verifyModuleSignature: path=%s, result=%d", cModulePath, result);
|
||||
return result;
|
||||
#else
|
||||
LogDebug("verifyModuleSignature: not supported on non-ARM architecture");
|
||||
return false;
|
||||
LogDebug("verifyModuleSignature: not supported on non-ARM architecture");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
NativeBridgeNP(isUidScannerEnabled, jboolean) {
|
||||
return is_uid_scanner_enabled();
|
||||
}
|
||||
|
||||
NativeBridge(setUidScannerEnabled, jboolean, jboolean enabled) {
|
||||
return set_uid_scanner_enabled(enabled);
|
||||
}
|
||||
|
||||
NativeBridgeNP(clearUidScannerEnvironment, jboolean) {
|
||||
return clear_uid_scanner_environment();
|
||||
}
|
||||
|
|
@ -2,11 +2,14 @@
|
|||
// Created by weishu on 2022/12/9.
|
||||
//
|
||||
|
||||
#include <sys/prctl.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <android/log.h>
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "prelude.h"
|
||||
#include "ksu.h"
|
||||
|
|
@ -21,232 +24,367 @@ extern const char* zako_file_verrcidx2str(uint8_t index);
|
|||
|
||||
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
|
||||
|
||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||
static int fd = -1;
|
||||
|
||||
#define CMD_GRANT_ROOT 0
|
||||
static inline int scan_driver_fd() {
|
||||
const char *kName = "[ksu_driver]";
|
||||
DIR *fd_dir = opendir("/proc/self/fd");
|
||||
if (!fd_dir) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define CMD_BECOME_MANAGER 1
|
||||
#define CMD_GET_VERSION 2
|
||||
#define CMD_ALLOW_SU 3
|
||||
#define CMD_DENY_SU 4
|
||||
#define CMD_GET_SU_LIST 5
|
||||
#define CMD_GET_DENY_LIST 6
|
||||
#define CMD_CHECK_SAFEMODE 9
|
||||
int found = -1;
|
||||
struct dirent *de;
|
||||
char path[64];
|
||||
char target[PATH_MAX];
|
||||
|
||||
#define CMD_GET_APP_PROFILE 10
|
||||
#define CMD_SET_APP_PROFILE 11
|
||||
while ((de = readdir(fd_dir)) != NULL) {
|
||||
if (de->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
char *endptr = nullptr;
|
||||
long fd_long = strtol(de->d_name, &endptr, 10);
|
||||
if (!de->d_name[0] || *endptr != '\0' || fd_long < 0 || fd_long > INT_MAX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#define CMD_GET_VERSION_FULL 0xC0FFEE1A
|
||||
snprintf(path, sizeof(path), "/proc/self/fd/%s", de->d_name);
|
||||
ssize_t n = readlink(path, target, sizeof(target) - 1);
|
||||
if (n < 0) {
|
||||
continue;
|
||||
}
|
||||
target[n] = '\0';
|
||||
|
||||
#define CMD_ENABLE_KPM 100
|
||||
#define CMD_HOOK_TYPE 101
|
||||
#define CMD_GET_SUSFS_FEATURE_STATUS 102
|
||||
#define CMD_DYNAMIC_MANAGER 103
|
||||
#define CMD_GET_MANAGERS 104
|
||||
const char *base = strrchr(target, '/');
|
||||
base = base ? base + 1 : target;
|
||||
|
||||
#define DYNAMIC_MANAGER_OP_SET 0
|
||||
#define DYNAMIC_MANAGER_OP_GET 1
|
||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||
if (strstr(base, kName)) {
|
||||
found = (int)fd_long;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
int32_t result = 0;
|
||||
int32_t rtn = prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
|
||||
|
||||
return result == KERNEL_SU_OPTION && rtn == -1;
|
||||
closedir(fd_dir);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool become_manager(const char* pkg) {
|
||||
char param[128];
|
||||
uid_t uid = getuid();
|
||||
uint32_t userId = uid / 100000;
|
||||
if (userId == 0) {
|
||||
sprintf(param, "/data/data/%s", pkg);
|
||||
} else {
|
||||
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
|
||||
static int ksuctl(unsigned long op, void* arg) {
|
||||
if (fd < 0) {
|
||||
fd = scan_driver_fd();
|
||||
}
|
||||
return ioctl(fd, op, arg);
|
||||
}
|
||||
|
||||
static struct ksu_get_info_cmd g_version = {0};
|
||||
|
||||
struct ksu_get_info_cmd get_info() {
|
||||
if (!g_version.version) {
|
||||
ksuctl(KSU_IOCTL_GET_INFO, &g_version);
|
||||
}
|
||||
return g_version;
|
||||
}
|
||||
|
||||
uint32_t get_version() {
|
||||
auto info = get_info();
|
||||
return info.version;
|
||||
}
|
||||
|
||||
bool get_allow_list(struct ksu_get_allow_list_cmd *cmd) {
|
||||
if (ksuctl(KSU_IOCTL_GET_ALLOW_LIST, cmd) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ksuctl(CMD_BECOME_MANAGER, param, NULL);
|
||||
}
|
||||
|
||||
// cache the result to avoid unnecessary syscall
|
||||
static bool is_lkm;
|
||||
int get_version() {
|
||||
int32_t version = -1;
|
||||
int32_t flags = 0;
|
||||
ksuctl(CMD_GET_VERSION, &version, &flags);
|
||||
if (!is_lkm && (flags & 0x1)) {
|
||||
is_lkm = true;
|
||||
// fallback to legacy
|
||||
int size = 0;
|
||||
int uids[1024];
|
||||
if (legacy_get_allow_list(uids, &size)) {
|
||||
cmd->count = size;
|
||||
memcpy(cmd->uids, uids, sizeof(int) * size);
|
||||
return true;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
void get_full_version(char* buff) {
|
||||
ksuctl(CMD_GET_VERSION_FULL, buff, NULL);
|
||||
}
|
||||
|
||||
bool get_allow_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_safe_mode() {
|
||||
return ksuctl(CMD_CHECK_SAFEMODE, NULL, NULL);
|
||||
struct ksu_check_safemode_cmd cmd = {};
|
||||
if (ksuctl(KSU_IOCTL_CHECK_SAFEMODE, &cmd) == 0) {
|
||||
return cmd.in_safe_mode;
|
||||
}
|
||||
// fallback
|
||||
return legacy_is_safe_mode();
|
||||
}
|
||||
|
||||
bool is_lkm_mode() {
|
||||
// you should call get_version first!
|
||||
return is_lkm;
|
||||
auto info = get_info();
|
||||
if (info.version > 0) {
|
||||
return (info.flags & 0x1) != 0;
|
||||
}
|
||||
// Legacy Compatible
|
||||
return (legacy_get_info().flags & 0x1) != 0;
|
||||
}
|
||||
|
||||
bool is_manager() {
|
||||
auto info = get_info();
|
||||
if (info.version > 0) {
|
||||
return (info.flags & 0x2) != 0;
|
||||
}
|
||||
// Legacy Compatible
|
||||
return legacy_get_info().version > 0;
|
||||
}
|
||||
|
||||
bool uid_should_umount(int uid) {
|
||||
int should;
|
||||
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should;
|
||||
struct ksu_uid_should_umount_cmd cmd = {};
|
||||
cmd.uid = uid;
|
||||
if (ksuctl(KSU_IOCTL_UID_SHOULD_UMOUNT, &cmd) == 0) {
|
||||
return cmd.should_umount;
|
||||
}
|
||||
return legacy_uid_should_umount(uid);
|
||||
}
|
||||
|
||||
bool set_app_profile(const struct app_profile* profile) {
|
||||
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL);
|
||||
bool set_app_profile(const struct app_profile *profile) {
|
||||
struct ksu_set_app_profile_cmd cmd = {};
|
||||
cmd.profile = *profile;
|
||||
if (ksuctl(KSU_IOCTL_SET_APP_PROFILE, &cmd) == 0) {
|
||||
return true;
|
||||
}
|
||||
return legacy_set_app_profile(profile);
|
||||
}
|
||||
|
||||
bool get_app_profile(char* key, struct app_profile* profile) {
|
||||
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL);
|
||||
int get_app_profile(struct app_profile *profile) {
|
||||
struct ksu_get_app_profile_cmd cmd = {.profile = *profile};
|
||||
int ret = ksuctl(KSU_IOCTL_GET_APP_PROFILE, &cmd);
|
||||
if (ret == 0) {
|
||||
*profile = cmd.profile;
|
||||
return 0;
|
||||
}
|
||||
return legacy_get_app_profile(profile->key, profile) ? 0 : -1;
|
||||
}
|
||||
|
||||
bool set_su_enabled(bool enabled) {
|
||||
return ksuctl(CMD_ENABLE_SU, (void*) enabled, NULL);
|
||||
struct ksu_set_feature_cmd cmd = {};
|
||||
cmd.feature_id = KSU_FEATURE_SU_COMPAT;
|
||||
cmd.value = enabled ? 1 : 0;
|
||||
if (ksuctl(KSU_IOCTL_SET_FEATURE, &cmd) == 0) {
|
||||
return true;
|
||||
}
|
||||
return legacy_set_su_enabled(enabled);
|
||||
}
|
||||
|
||||
bool is_su_enabled() {
|
||||
int enabled = true;
|
||||
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
|
||||
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL);
|
||||
return enabled;
|
||||
struct ksu_get_feature_cmd cmd = {};
|
||||
cmd.feature_id = KSU_FEATURE_SU_COMPAT;
|
||||
if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) == 0 && cmd.supported) {
|
||||
return cmd.value != 0;
|
||||
}
|
||||
return legacy_is_su_enabled();
|
||||
}
|
||||
|
||||
bool is_KPM_enable() {
|
||||
int enabled = false;
|
||||
ksuctl(CMD_ENABLE_KPM, &enabled, NULL);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool get_hook_type(char* hook_type, size_t size) {
|
||||
if (hook_type == NULL || size == 0) {
|
||||
static inline bool get_feature(uint32_t feature_id, uint64_t *out_value, bool *out_supported) {
|
||||
struct ksu_get_feature_cmd cmd = {};
|
||||
cmd.feature_id = feature_id;
|
||||
if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static char cached_hook_type[16] = {0};
|
||||
if (cached_hook_type[0] == '\0') {
|
||||
if (!ksuctl(CMD_HOOK_TYPE, cached_hook_type, NULL)) {
|
||||
strcpy(cached_hook_type, "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(hook_type, cached_hook_type, size);
|
||||
hook_type[size - 1] = '\0';
|
||||
if (out_value) *out_value = cmd.value;
|
||||
if (out_supported) *out_supported = cmd.supported;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_susfs_feature_status(struct susfs_feature_status* status) {
|
||||
if (status == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ksuctl(CMD_GET_SUSFS_FEATURE_STATUS, status, NULL);
|
||||
static inline bool set_feature(uint32_t feature_id, uint64_t value) {
|
||||
struct ksu_set_feature_cmd cmd = {};
|
||||
cmd.feature_id = feature_id;
|
||||
cmd.value = value;
|
||||
return ksuctl(KSU_IOCTL_SET_FEATURE, &cmd) == 0;
|
||||
}
|
||||
|
||||
bool set_dynamic_manager(unsigned int size, const char* hash) {
|
||||
if (hash == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dynamic_manager_user_config config;
|
||||
config.operation = DYNAMIC_MANAGER_OP_SET;
|
||||
config.size = size;
|
||||
strncpy(config.hash, hash, sizeof(config.hash) - 1);
|
||||
config.hash[sizeof(config.hash) - 1] = '\0';
|
||||
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||
bool set_kernel_umount_enabled(bool enabled) {
|
||||
return set_feature(KSU_FEATURE_KERNEL_UMOUNT, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
bool get_dynamic_manager(struct dynamic_manager_user_config* config) {
|
||||
if (config == NULL) {
|
||||
bool is_kernel_umount_enabled() {
|
||||
uint64_t value = 0;
|
||||
bool supported = false;
|
||||
if (!get_feature(KSU_FEATURE_KERNEL_UMOUNT, &value, &supported)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config->operation = DYNAMIC_MANAGER_OP_GET;
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
|
||||
}
|
||||
|
||||
bool clear_dynamic_manager() {
|
||||
struct dynamic_manager_user_config config;
|
||||
config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||
}
|
||||
|
||||
bool get_managers_list(struct manager_list_info* info) {
|
||||
if (info == NULL) {
|
||||
if (!supported) {
|
||||
return false;
|
||||
}
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
return ksuctl(CMD_GET_MANAGERS, info, NULL);
|
||||
bool set_enhanced_security_enabled(bool enabled) {
|
||||
return set_feature(KSU_FEATURE_ENHANCED_SECURITY, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
bool is_enhanced_security_enabled() {
|
||||
uint64_t value = 0;
|
||||
bool supported = false;
|
||||
if (!get_feature(KSU_FEATURE_ENHANCED_SECURITY, &value, &supported)) {
|
||||
return false;
|
||||
}
|
||||
if (!supported) {
|
||||
return false;
|
||||
}
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
void get_full_version(char* buff) {
|
||||
struct ksu_get_full_version_cmd cmd = {0};
|
||||
if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) {
|
||||
strncpy(buff, cmd.version_full, KSU_FULL_VERSION_STRING - 1);
|
||||
buff[KSU_FULL_VERSION_STRING - 1] = '\0';
|
||||
} else {
|
||||
return legacy_get_full_version(buff);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_KPM_enable(void) {
|
||||
struct ksu_enable_kpm_cmd cmd = {};
|
||||
if (ksuctl(KSU_IOCTL_ENABLE_KPM, &cmd) == 0 && cmd.enabled) {
|
||||
return true;
|
||||
}
|
||||
return legacy_is_KPM_enable();
|
||||
}
|
||||
|
||||
void get_hook_type(char *buff) {
|
||||
struct ksu_hook_type_cmd cmd = {0};
|
||||
if (ksuctl(KSU_IOCTL_HOOK_TYPE, &cmd) == 0) {
|
||||
strncpy(buff, cmd.hook_type, 32 - 1);
|
||||
buff[32 - 1] = '\0';
|
||||
} else {
|
||||
legacy_get_hook_type(buff, 32);
|
||||
}
|
||||
}
|
||||
|
||||
bool set_dynamic_manager(unsigned int size, const char *hash)
|
||||
{
|
||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
||||
cmd.config.operation = DYNAMIC_MANAGER_OP_SET;
|
||||
cmd.config.size = size;
|
||||
strlcpy(cmd.config.hash, hash, sizeof(cmd.config.hash));
|
||||
|
||||
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
|
||||
}
|
||||
|
||||
bool get_dynamic_manager(struct dynamic_manager_user_config *cfg)
|
||||
{
|
||||
if (!cfg)
|
||||
return false;
|
||||
|
||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
||||
cmd.config.operation = DYNAMIC_MANAGER_OP_GET;
|
||||
|
||||
if (ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) != 0)
|
||||
return false;
|
||||
|
||||
*cfg = cmd.config;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool clear_dynamic_manager(void)
|
||||
{
|
||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
||||
cmd.config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
||||
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
|
||||
}
|
||||
|
||||
bool get_managers_list(struct manager_list_info *info)
|
||||
{
|
||||
if (!info)
|
||||
return false;
|
||||
struct ksu_get_managers_cmd cmd = {0};
|
||||
if (ksuctl(KSU_IOCTL_GET_MANAGERS, &cmd) != 0)
|
||||
return false;
|
||||
|
||||
*info = cmd.manager_info;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_uid_scanner_enabled(void)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
||||
.operation = UID_SCANNER_OP_GET_STATUS,
|
||||
.status_ptr = (__u64)(uintptr_t)&status
|
||||
};
|
||||
|
||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd) == 0 != 0 && status;
|
||||
}
|
||||
|
||||
bool set_uid_scanner_enabled(bool enabled)
|
||||
{
|
||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
||||
.operation = UID_SCANNER_OP_TOGGLE,
|
||||
.enabled = enabled
|
||||
};
|
||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
||||
}
|
||||
|
||||
bool clear_uid_scanner_environment(void)
|
||||
{
|
||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
||||
.operation = UID_SCANNER_OP_CLEAR_ENV
|
||||
};
|
||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
||||
}
|
||||
|
||||
bool verify_module_signature(const char* input) {
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||
if (input == NULL) {
|
||||
LogDebug("verify_module_signature: input path is null");
|
||||
return false;
|
||||
}
|
||||
if (input == NULL) {
|
||||
LogDebug("verify_module_signature: input path is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd = zako_sys_file_open(input);
|
||||
if (fd < 0) {
|
||||
LogDebug("verify_module_signature: failed to open file: %s", input);
|
||||
return false;
|
||||
}
|
||||
int file_fd = zako_sys_file_open(input);
|
||||
if (file_fd < 0) {
|
||||
LogDebug("verify_module_signature: failed to open file: %s", input);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t results = zako_file_verify_esig(fd, 0);
|
||||
uint32_t results = zako_file_verify_esig(file_fd, 0);
|
||||
|
||||
if (results != 0) {
|
||||
/* If important error occured, verification process should
|
||||
be considered as failed due to unexpected modification
|
||||
potentially happened. */
|
||||
if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) {
|
||||
LogDebug("verify_module_signature: Verification failed! (important error)");
|
||||
} else {
|
||||
/* This is for manager that doesn't want to do certificate checks */
|
||||
LogDebug("verify_module_signature: Verification partially passed");
|
||||
}
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Verification passed!");
|
||||
goto exit;
|
||||
}
|
||||
if (results != 0) {
|
||||
/* If important error occured, verification process should
|
||||
be considered as failed due to unexpected modification
|
||||
potentially happened. */
|
||||
if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) {
|
||||
LogDebug("verify_module_signature: Verification failed! (important error)");
|
||||
} else {
|
||||
/* This is for manager that doesn't want to do certificate checks */
|
||||
LogDebug("verify_module_signature: Verification partially passed");
|
||||
}
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Verification passed!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Go through all bit fields */
|
||||
for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) {
|
||||
if ((results & (1 << i)) == 0) {
|
||||
continue;
|
||||
}
|
||||
/* Go through all bit fields */
|
||||
for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) {
|
||||
if ((results & (1 << i)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Convert error bit field index into human readable string */
|
||||
const char* message = zako_file_verrcidx2str((uint8_t)i);
|
||||
// Error message: message
|
||||
if (message != NULL) {
|
||||
LogDebug("verify_module_signature: Error bit %zu: %s", i, message);
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Error bit %zu: Unknown error", i);
|
||||
}
|
||||
}
|
||||
/* Convert error bit field index into human readable string */
|
||||
const char* message = zako_file_verrcidx2str((uint8_t)i);
|
||||
// Error message: message
|
||||
if (message != NULL) {
|
||||
LogDebug("verify_module_signature: Error bit %zu: %s", i, message);
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Error bit %zu: Unknown error", i);
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
close(fd);
|
||||
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
|
||||
input, results, (results == 0) ? "true" : "false");
|
||||
return results == 0;
|
||||
exit:
|
||||
close(file_fd);
|
||||
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
|
||||
input, results, (results == 0) ? "true" : "false");
|
||||
return results == 0;
|
||||
#else
|
||||
LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null");
|
||||
return false;
|
||||
LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,15 @@
|
|||
#define KERNELSU_KSU_H
|
||||
|
||||
#include "prelude.h"
|
||||
#include <linux/capability.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
bool become_manager(const char *);
|
||||
#define KSU_FULL_VERSION_STRING 255
|
||||
|
||||
void get_full_version(char* buff);
|
||||
|
||||
int get_version();
|
||||
|
||||
bool get_allow_list(int *uids, int *size);
|
||||
uint32_t get_version();
|
||||
|
||||
bool uid_should_umount(int uid);
|
||||
|
||||
|
|
@ -23,6 +22,10 @@ bool is_safe_mode();
|
|||
|
||||
bool is_lkm_mode();
|
||||
|
||||
bool is_manager();
|
||||
|
||||
void get_full_version(char* buff);
|
||||
|
||||
#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.
|
||||
|
|
@ -33,99 +36,80 @@ bool is_lkm_mode();
|
|||
#define DYNAMIC_MANAGER_OP_GET 1
|
||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||
|
||||
#define UID_SCANNER_OP_GET_STATUS 0
|
||||
#define UID_SCANNER_OP_TOGGLE 1
|
||||
#define UID_SCANNER_OP_CLEAR_ENV 2
|
||||
|
||||
struct dynamic_manager_user_config {
|
||||
unsigned int operation;
|
||||
unsigned int size;
|
||||
char hash[65];
|
||||
unsigned int operation;
|
||||
unsigned int size;
|
||||
char hash[65];
|
||||
};
|
||||
|
||||
// SUSFS Functional State Structures
|
||||
struct susfs_feature_status {
|
||||
bool status_sus_path;
|
||||
bool status_sus_mount;
|
||||
bool status_auto_default_mount;
|
||||
bool status_auto_bind_mount;
|
||||
bool status_sus_kstat;
|
||||
bool status_try_umount;
|
||||
bool status_auto_try_umount_bind;
|
||||
bool status_spoof_uname;
|
||||
bool status_enable_log;
|
||||
bool status_hide_symbols;
|
||||
bool status_spoof_cmdline;
|
||||
bool status_open_redirect;
|
||||
bool status_magic_mount;
|
||||
bool status_sus_su;
|
||||
};
|
||||
|
||||
struct root_profile {
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
uint64_t effective;
|
||||
uint64_t permitted;
|
||||
uint64_t inheritable;
|
||||
} capabilities;
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
uint64_t effective;
|
||||
uint64_t permitted;
|
||||
uint64_t inheritable;
|
||||
} capabilities;
|
||||
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
|
||||
int32_t namespaces;
|
||||
int32_t namespaces;
|
||||
};
|
||||
|
||||
struct non_root_profile {
|
||||
bool umount_modules;
|
||||
bool umount_modules;
|
||||
};
|
||||
|
||||
struct app_profile {
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
uint32_t version;
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
uint32_t 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;
|
||||
// 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];
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
|
||||
struct {
|
||||
bool use_default;
|
||||
struct {
|
||||
bool use_default;
|
||||
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
};
|
||||
|
||||
struct manager_list_info {
|
||||
int count;
|
||||
struct {
|
||||
uid_t uid;
|
||||
int signature_index;
|
||||
} managers[2];
|
||||
int count;
|
||||
struct {
|
||||
uid_t uid;
|
||||
int signature_index;
|
||||
} managers[2];
|
||||
};
|
||||
|
||||
bool set_app_profile(const struct app_profile* profile);
|
||||
|
||||
bool get_app_profile(char* key, struct app_profile* profile);
|
||||
|
||||
bool set_su_enabled(bool enabled);
|
||||
|
||||
bool is_su_enabled();
|
||||
int get_app_profile(struct app_profile* profile);
|
||||
|
||||
bool is_KPM_enable();
|
||||
|
||||
bool get_hook_type(char* hook_type, size_t size);
|
||||
|
||||
bool get_susfs_feature_status(struct susfs_feature_status* status);
|
||||
void get_hook_type(char* hook_type);
|
||||
|
||||
bool set_dynamic_manager(unsigned int size, const char* hash);
|
||||
|
||||
|
|
@ -137,4 +121,176 @@ bool get_managers_list(struct manager_list_info* info);
|
|||
|
||||
bool verify_module_signature(const char* input);
|
||||
|
||||
bool is_uid_scanner_enabled();
|
||||
|
||||
bool set_uid_scanner_enabled(bool enabled);
|
||||
|
||||
bool clear_uid_scanner_environment();
|
||||
|
||||
// Feature IDs
|
||||
enum ksu_feature_id {
|
||||
KSU_FEATURE_SU_COMPAT = 0,
|
||||
KSU_FEATURE_KERNEL_UMOUNT = 1,
|
||||
KSU_FEATURE_ENHANCED_SECURITY = 2,
|
||||
};
|
||||
|
||||
// Generic feature API
|
||||
struct ksu_get_feature_cmd {
|
||||
uint32_t feature_id; // Input: feature ID
|
||||
uint64_t value; // Output: feature value/state
|
||||
uint8_t supported; // Output: whether the feature is supported
|
||||
};
|
||||
|
||||
struct ksu_set_feature_cmd {
|
||||
uint32_t feature_id; // Input: feature ID
|
||||
uint64_t value; // Input: feature value/state to set
|
||||
};
|
||||
|
||||
struct ksu_become_daemon_cmd {
|
||||
uint8_t token[65]; // Input: daemon token (null-terminated)
|
||||
};
|
||||
|
||||
struct ksu_get_info_cmd {
|
||||
uint32_t version; // Output: KERNEL_SU_VERSION
|
||||
uint32_t flags; // Output: flags (bit 0: MODULE mode)
|
||||
uint32_t features; // Output: max feature ID supported (KSU_FEATURE_MAX)
|
||||
};
|
||||
|
||||
struct ksu_report_event_cmd {
|
||||
uint32_t event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc.
|
||||
};
|
||||
|
||||
struct ksu_set_sepolicy_cmd {
|
||||
uint64_t cmd; // Input: sepolicy command
|
||||
uint64_t arg; // Input: sepolicy argument pointer
|
||||
};
|
||||
|
||||
struct ksu_check_safemode_cmd {
|
||||
uint8_t in_safe_mode; // Output: true if in safe mode, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_get_allow_list_cmd {
|
||||
uint32_t uids[128]; // Output: array of allowed/denied UIDs
|
||||
uint32_t count; // Output: number of UIDs in array
|
||||
uint8_t allow; // Input: true for allow list, false for deny list
|
||||
};
|
||||
|
||||
struct ksu_uid_granted_root_cmd {
|
||||
uint32_t uid; // Input: target UID to check
|
||||
uint8_t granted; // Output: true if granted, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_uid_should_umount_cmd {
|
||||
uint32_t uid; // Input: target UID to check
|
||||
uint8_t should_umount; // Output: true if should umount, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_get_manager_uid_cmd {
|
||||
uint32_t uid; // Output: manager UID
|
||||
};
|
||||
|
||||
struct ksu_set_manager_uid_cmd {
|
||||
uint32_t uid; // Input: new manager UID
|
||||
};
|
||||
|
||||
struct ksu_get_app_profile_cmd {
|
||||
struct app_profile profile; // Input/Output: app profile structure
|
||||
};
|
||||
|
||||
struct ksu_set_app_profile_cmd {
|
||||
struct app_profile profile; // Input: app profile structure
|
||||
};
|
||||
|
||||
// Su compat
|
||||
bool set_su_enabled(bool enabled);
|
||||
bool is_su_enabled();
|
||||
|
||||
// Kernel umount
|
||||
bool set_kernel_umount_enabled(bool enabled);
|
||||
bool is_kernel_umount_enabled();
|
||||
|
||||
// Enhanced security
|
||||
bool set_enhanced_security_enabled(bool enabled);
|
||||
|
||||
bool is_enhanced_security_enabled();
|
||||
|
||||
// Other command structures
|
||||
struct ksu_get_full_version_cmd {
|
||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
||||
};
|
||||
|
||||
struct ksu_hook_type_cmd {
|
||||
char hook_type[32]; // Output: hook type string
|
||||
};
|
||||
|
||||
struct ksu_enable_kpm_cmd {
|
||||
uint8_t enabled; // Output: true if KPM is enabled
|
||||
};
|
||||
|
||||
struct ksu_dynamic_manager_cmd {
|
||||
struct dynamic_manager_user_config config; // Input/Output: dynamic manager config
|
||||
};
|
||||
|
||||
struct ksu_get_managers_cmd {
|
||||
struct manager_list_info manager_info; // Output: manager list information
|
||||
};
|
||||
|
||||
struct ksu_enable_uid_scanner_cmd {
|
||||
uint32_t operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV)
|
||||
uint32_t enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE)
|
||||
uint64_t status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS)
|
||||
};
|
||||
|
||||
// IOCTL command definitions
|
||||
#define KSU_IOCTL_GRANT_ROOT _IOC(_IOC_NONE, 'K', 1, 0)
|
||||
#define KSU_IOCTL_GET_INFO _IOC(_IOC_READ, 'K', 2, 0)
|
||||
#define KSU_IOCTL_REPORT_EVENT _IOC(_IOC_WRITE, 'K', 3, 0)
|
||||
#define KSU_IOCTL_SET_SEPOLICY _IOC(_IOC_READ|_IOC_WRITE, 'K', 4, 0)
|
||||
#define KSU_IOCTL_CHECK_SAFEMODE _IOC(_IOC_READ, 'K', 5, 0)
|
||||
#define KSU_IOCTL_GET_ALLOW_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 6, 0)
|
||||
#define KSU_IOCTL_GET_DENY_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 7, 0)
|
||||
#define KSU_IOCTL_UID_GRANTED_ROOT _IOC(_IOC_READ|_IOC_WRITE, 'K', 8, 0)
|
||||
#define KSU_IOCTL_UID_SHOULD_UMOUNT _IOC(_IOC_READ|_IOC_WRITE, 'K', 9, 0)
|
||||
#define KSU_IOCTL_GET_MANAGER_UID _IOC(_IOC_READ, 'K', 10, 0)
|
||||
#define KSU_IOCTL_GET_APP_PROFILE _IOC(_IOC_READ|_IOC_WRITE, 'K', 11, 0)
|
||||
#define KSU_IOCTL_SET_APP_PROFILE _IOC(_IOC_WRITE, 'K', 12, 0)
|
||||
#define KSU_IOCTL_GET_FEATURE _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0)
|
||||
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0)
|
||||
|
||||
// Other IOCTL command definitions
|
||||
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
|
||||
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)
|
||||
#define KSU_IOCTL_ENABLE_KPM _IOC(_IOC_READ, 'K', 102, 0)
|
||||
#define KSU_IOCTL_DYNAMIC_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 103, 0)
|
||||
#define KSU_IOCTL_GET_MANAGERS _IOC(_IOC_READ|_IOC_WRITE, 'K', 104, 0)
|
||||
#define KSU_IOCTL_ENABLE_UID_SCANNER _IOC(_IOC_READ|_IOC_WRITE, 'K', 105, 0)
|
||||
|
||||
bool get_allow_list(struct ksu_get_allow_list_cmd *);
|
||||
|
||||
// Legacy Compatible
|
||||
struct ksu_version_info legacy_get_info();
|
||||
|
||||
struct ksu_version_info {
|
||||
int32_t version;
|
||||
int32_t flags;
|
||||
};
|
||||
|
||||
bool legacy_get_allow_list(int *uids, int *size);
|
||||
bool legacy_is_safe_mode();
|
||||
bool legacy_uid_should_umount(int uid);
|
||||
bool legacy_set_app_profile(const struct app_profile* profile);
|
||||
bool legacy_get_app_profile(char* key, struct app_profile* profile);
|
||||
bool legacy_set_su_enabled(bool enabled);
|
||||
bool legacy_is_su_enabled();
|
||||
bool legacy_is_KPM_enable();
|
||||
bool legacy_get_hook_type(char* hook_type, size_t size);
|
||||
void legacy_get_full_version(char* buff);
|
||||
bool legacy_set_dynamic_manager(unsigned int size, const char* hash);
|
||||
bool legacy_get_dynamic_manager(struct dynamic_manager_user_config* config);
|
||||
bool legacy_clear_dynamic_manager();
|
||||
bool legacy_get_managers_list(struct manager_list_info* info);
|
||||
bool legacy_is_uid_scanner_enabled();
|
||||
bool legacy_set_uid_scanner_enabled(bool enabled);
|
||||
bool legacy_clear_uid_scanner_environment();
|
||||
|
||||
#endif //KERNELSU_KSU_H
|
||||
163
manager/app/src/main/cpp/legacy.c
Normal file
163
manager/app/src/main/cpp/legacy.c
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// Created by shirkneko on 2025/11/3.
|
||||
//
|
||||
// Legacy Compatible
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <android/log.h>
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "prelude.h"
|
||||
#include "ksu.h"
|
||||
|
||||
#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_SU_LIST 5
|
||||
#define CMD_GET_DENY_LIST 6
|
||||
#define CMD_CHECK_SAFEMODE 9
|
||||
|
||||
#define CMD_GET_APP_PROFILE 10
|
||||
#define CMD_SET_APP_PROFILE 11
|
||||
|
||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
|
||||
#define CMD_GET_VERSION_FULL 0xC0FFEE1A
|
||||
|
||||
#define CMD_ENABLE_KPM 100
|
||||
#define CMD_HOOK_TYPE 101
|
||||
#define CMD_DYNAMIC_MANAGER 103
|
||||
#define CMD_GET_MANAGERS 104
|
||||
#define CMD_ENABLE_UID_SCANNER 105
|
||||
|
||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
int32_t result = 0;
|
||||
int32_t rtn = prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
|
||||
return result == KERNEL_SU_OPTION && rtn == -1;
|
||||
}
|
||||
|
||||
struct ksu_version_info legacy_get_info()
|
||||
{
|
||||
int32_t version = -1;
|
||||
int32_t flags = 0;
|
||||
ksuctl(CMD_GET_VERSION, &version, &flags);
|
||||
return (struct ksu_version_info){version, flags};
|
||||
}
|
||||
|
||||
bool legacy_get_allow_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||
}
|
||||
|
||||
bool legacy_is_safe_mode() {
|
||||
return ksuctl(CMD_CHECK_SAFEMODE, NULL, NULL);
|
||||
}
|
||||
|
||||
bool legacy_uid_should_umount(int uid) {
|
||||
int should;
|
||||
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should;
|
||||
}
|
||||
|
||||
bool legacy_set_app_profile(const struct app_profile* profile) {
|
||||
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL);
|
||||
}
|
||||
|
||||
bool legacy_get_app_profile(char* key, struct app_profile* profile) {
|
||||
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL);
|
||||
}
|
||||
|
||||
bool legacy_set_su_enabled(bool enabled) {
|
||||
return ksuctl(CMD_ENABLE_SU, (void*) enabled, NULL);
|
||||
}
|
||||
|
||||
bool legacy_is_su_enabled() {
|
||||
int enabled = true;
|
||||
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
|
||||
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool legacy_is_KPM_enable() {
|
||||
int enabled = false;
|
||||
ksuctl(CMD_ENABLE_KPM, &enabled, NULL);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool legacy_get_hook_type(char* hook_type, size_t size) {
|
||||
if (hook_type == NULL || size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static char cached_hook_type[16] = {0};
|
||||
if (cached_hook_type[0] == '\0') {
|
||||
if (!ksuctl(CMD_HOOK_TYPE, cached_hook_type, NULL)) {
|
||||
strcpy(cached_hook_type, "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(hook_type, cached_hook_type, size - 1);
|
||||
hook_type[size - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
void legacy_get_full_version(char* buff) {
|
||||
ksuctl(CMD_GET_VERSION_FULL, buff, NULL);
|
||||
}
|
||||
|
||||
bool legacy_set_dynamic_manager(unsigned int size, const char* hash) {
|
||||
if (hash == NULL) {
|
||||
return false;
|
||||
}
|
||||
struct dynamic_manager_user_config config;
|
||||
config.operation = DYNAMIC_MANAGER_OP_SET;
|
||||
config.size = size;
|
||||
strncpy(config.hash, hash, sizeof(config.hash) - 1);
|
||||
config.hash[sizeof(config.hash) - 1] = '\0';
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||
}
|
||||
|
||||
bool legacy_get_dynamic_manager(struct dynamic_manager_user_config* config) {
|
||||
if (config == NULL) {
|
||||
return false;
|
||||
}
|
||||
config->operation = DYNAMIC_MANAGER_OP_GET;
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
|
||||
}
|
||||
|
||||
bool legacy_clear_dynamic_manager() {
|
||||
struct dynamic_manager_user_config config;
|
||||
config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||
}
|
||||
|
||||
bool legacy_get_managers_list(struct manager_list_info* info) {
|
||||
if (info == NULL) {
|
||||
return false;
|
||||
}
|
||||
return ksuctl(CMD_GET_MANAGERS, info, NULL);
|
||||
}
|
||||
|
||||
bool legacy_is_uid_scanner_enabled() {
|
||||
bool status = false;
|
||||
ksuctl(CMD_ENABLE_UID_SCANNER, (void*)0, &status);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool legacy_set_uid_scanner_enabled(bool enabled) {
|
||||
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)1, (void*)enabled);
|
||||
}
|
||||
|
||||
bool legacy_clear_uid_scanner_environment() {
|
||||
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)2, NULL);
|
||||
}
|
||||
|
|
@ -14,51 +14,51 @@
|
|||
|
||||
// Macros to simplify field setup
|
||||
#define SET_BOOLEAN_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Z"); \
|
||||
GetEnvironment()->SetBooleanField(env, obj, field, value); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Z"); \
|
||||
GetEnvironment()->SetBooleanField(env, obj, field, value); \
|
||||
} while(0)
|
||||
|
||||
#define SET_INT_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "I"); \
|
||||
GetEnvironment()->SetIntField(env, obj, field, value); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "I"); \
|
||||
GetEnvironment()->SetIntField(env, obj, field, value); \
|
||||
} while(0)
|
||||
|
||||
#define SET_STRING_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/lang/String;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, GetEnvironment()->NewStringUTF(env, value)); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/lang/String;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, GetEnvironment()->NewStringUTF(env, value)); \
|
||||
} while(0)
|
||||
|
||||
#define SET_OBJECT_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/util/List;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, value); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/util/List;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, value); \
|
||||
} while(0)
|
||||
|
||||
// Macros for creating Java objects
|
||||
#define CREATE_JAVA_OBJECT(className) ({ \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor); \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor); \
|
||||
})
|
||||
|
||||
// Macros for creating ArrayList
|
||||
#define CREATE_ARRAYLIST() ({ \
|
||||
jclass arrayListCls = GetEnvironment()->FindClass(env, "java/util/ArrayList"); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, arrayListCls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, arrayListCls, constructor); \
|
||||
jclass arrayListCls = GetEnvironment()->FindClass(env, "java/util/ArrayList"); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, arrayListCls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, arrayListCls, constructor); \
|
||||
})
|
||||
|
||||
// Macros for adding elements to an ArrayList
|
||||
#define ADD_TO_LIST(list, item) do { \
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list); \
|
||||
jmethodID addMethod = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z"); \
|
||||
GetEnvironment()->CallBooleanMethod(env, list, addMethod, item); \
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list); \
|
||||
jmethodID addMethod = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z"); \
|
||||
GetEnvironment()->CallBooleanMethod(env, list, addMethod, item); \
|
||||
} while(0)
|
||||
|
||||
// Macros for creating Java objects with parameter constructors
|
||||
#define CREATE_JAVA_OBJECT_WITH_PARAMS(className, signature, ...) ({ \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", signature); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor, __VA_ARGS__); \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", signature); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor, __VA_ARGS__); \
|
||||
})
|
||||
|
||||
#ifdef NDEBUG
|
||||
|
|
|
|||
|
|
@ -1,94 +1,40 @@
|
|||
package com.sukisu.ultra
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.ActivityOptions
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.system.Os
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import com.dergoogler.mmrl.platform.Platform
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
lateinit var ksuApp: KernelSUApplication
|
||||
|
||||
class KernelSUApplication : Application() {
|
||||
private var currentActivity: Activity? = null
|
||||
class KernelSUApplication : Application(), ViewModelStoreOwner {
|
||||
|
||||
private val activityLifecycleCallbacks = object : ActivityLifecycleCallbacks {
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
currentActivity = activity
|
||||
}
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
currentActivity = activity
|
||||
}
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
currentActivity = activity
|
||||
}
|
||||
override fun onActivityPaused(activity: Activity) {}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
if (currentActivity == activity) {
|
||||
currentActivity = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
val prefs = base.getSharedPreferences("settings", MODE_PRIVATE)
|
||||
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||
|
||||
var context = base
|
||||
if (languageCode.isNotEmpty()) {
|
||||
val locale = Locale.forLanguageTag(languageCode)
|
||||
Locale.setDefault(locale)
|
||||
|
||||
val config = Configuration(base.resources.configuration)
|
||||
config.setLocale(locale)
|
||||
|
||||
context = base.createConfigurationContext(config)
|
||||
}
|
||||
|
||||
super.attachBaseContext(context)
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
override fun getResources(): Resources {
|
||||
val resources = super.getResources()
|
||||
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||
|
||||
if (languageCode.isNotEmpty()) {
|
||||
val locale = Locale.forLanguageTag(languageCode)
|
||||
val config = Configuration(resources.configuration)
|
||||
config.setLocale(locale)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return createConfigurationContext(config).resources
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
resources.updateConfiguration(config, resources.displayMetrics)
|
||||
}
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
lateinit var okhttpClient: OkHttpClient
|
||||
private val appViewModelStore by lazy { ViewModelStore() }
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ksuApp = this
|
||||
|
||||
// 注册Activity生命周期回调
|
||||
registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
|
||||
// For faster response when first entering superuser or webui activity
|
||||
val superUserViewModel = ViewModelProvider(this)[SuperUserViewModel::class.java]
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
superUserViewModel.fetchAppList()
|
||||
}
|
||||
|
||||
Platform.setHiddenApiExemptions()
|
||||
|
||||
|
|
@ -107,45 +53,20 @@ class KernelSUApplication : Application() {
|
|||
if (!webroot.exists()) {
|
||||
webroot.mkdir()
|
||||
}
|
||||
|
||||
// Provide working env for rust's temp_dir()
|
||||
Os.setenv("TMPDIR", cacheDir.absolutePath, true)
|
||||
|
||||
okhttpClient =
|
||||
OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024))
|
||||
.addInterceptor { block ->
|
||||
block.proceed(
|
||||
block.request().newBuilder()
|
||||
.header("User-Agent", "SukiSU/${BuildConfig.VERSION_CODE}")
|
||||
.header("Accept-Language", Locale.getDefault().toLanguageTag()).build()
|
||||
)
|
||||
}.build()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
applyLanguageSetting()
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
private fun applyLanguageSetting() {
|
||||
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||
|
||||
if (languageCode.isNotEmpty()) {
|
||||
val locale = Locale.forLanguageTag(languageCode)
|
||||
Locale.setDefault(locale)
|
||||
|
||||
val resources = resources
|
||||
val config = Configuration(resources.configuration)
|
||||
config.setLocale(locale)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
createConfigurationContext(config)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
resources.updateConfiguration(config, resources.displayMetrics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加刷新当前Activity的方法
|
||||
fun refreshCurrentActivity() {
|
||||
currentActivity?.let { activity ->
|
||||
val intent = activity.intent
|
||||
activity.finish()
|
||||
|
||||
val options = ActivityOptions.makeCustomAnimation(
|
||||
activity, android.R.anim.fade_in, android.R.anim.fade_out
|
||||
)
|
||||
activity.startActivity(intent, options.toBundle())
|
||||
}
|
||||
}
|
||||
}
|
||||
override val viewModelStore: ViewModelStore
|
||||
get() = appViewModelStore
|
||||
}
|
||||
|
|
@ -16,23 +16,22 @@ object Natives {
|
|||
// 10946: add capabilities
|
||||
// 10977: change groups_count and groups to avoid overflow write
|
||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.5"
|
||||
|
||||
// 11640: Support query working mode, LKM or GKI
|
||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||
// 12143: breaking: new supercall impl
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 12143
|
||||
|
||||
// 12040: Support disable sucompat mode
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
|
||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||
|
||||
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.8"
|
||||
|
||||
const val MINIMAL_SUPPORTED_KPM = 12800
|
||||
|
||||
const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215
|
||||
|
||||
const val MINIMAL_SUPPORTED_UID_SCANNER = 13347
|
||||
|
||||
const val MINIMAL_NEW_IOCTL_KERNEL = 13490
|
||||
|
||||
const val ROOT_UID = 0
|
||||
const val ROOT_GID = 0
|
||||
|
||||
|
|
@ -63,11 +62,9 @@ object Natives {
|
|||
|
||||
init {
|
||||
System.loadLibrary("zakosign")
|
||||
System.loadLibrary("zako")
|
||||
System.loadLibrary("kernelsu")
|
||||
}
|
||||
|
||||
// become root manager, return true if success.
|
||||
external fun becomeManager(pkg: String?): Boolean
|
||||
val version: Int
|
||||
external get
|
||||
|
||||
|
|
@ -81,6 +78,9 @@ object Natives {
|
|||
val isLkmMode: Boolean
|
||||
external get
|
||||
|
||||
val isManager: Boolean
|
||||
external get
|
||||
|
||||
external fun uidShouldUmount(uid: Int): Boolean
|
||||
|
||||
/**
|
||||
|
|
@ -99,6 +99,25 @@ object Natives {
|
|||
*/
|
||||
external fun isSuEnabled(): Boolean
|
||||
external fun setSuEnabled(enabled: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Kernel module umount can be disabled temporarily.
|
||||
* 0: disabled
|
||||
* 1: enabled
|
||||
* negative : error
|
||||
*/
|
||||
external fun isKernelUmountEnabled(): Boolean
|
||||
external fun setKernelUmountEnabled(enabled: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Enhanced security can be enabled/disabled.
|
||||
* 0: disabled
|
||||
* 1: enabled
|
||||
* negative : error
|
||||
*/
|
||||
external fun isEnhancedSecurityEnabled(): Boolean
|
||||
external fun setEnhancedSecurityEnabled(enabled: Boolean): Boolean
|
||||
|
||||
external fun isKPMEnabled(): Boolean
|
||||
external fun getHookType(): String
|
||||
|
||||
|
|
@ -106,7 +125,6 @@ object Natives {
|
|||
* Get SUSFS feature status from kernel
|
||||
* @return SusfsFeatureStatus object containing all feature states, or null if failed
|
||||
*/
|
||||
external fun getSusfsFeatureStatus(): SusfsFeatureStatus?
|
||||
|
||||
/**
|
||||
* Set dynamic managerature configuration
|
||||
|
|
@ -138,6 +156,28 @@ object Natives {
|
|||
// 模块签名验证
|
||||
external fun verifyModuleSignature(modulePath: String): Boolean
|
||||
|
||||
/**
|
||||
* Check if UID scanner is currently enabled
|
||||
* @return true if UID scanner is enabled, false otherwise
|
||||
*/
|
||||
external fun isUidScannerEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Enable or disable UID scanner
|
||||
* @param enabled true to enable, false to disable
|
||||
* @return true if operation was successful, false otherwise
|
||||
*/
|
||||
external fun setUidScannerEnabled(enabled: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Clear UID scanner environment (force exit)
|
||||
* This will forcefully stop all UID scanner operations and clear the environment
|
||||
* @return true if operation was successful, false otherwise
|
||||
*/
|
||||
external fun clearUidScannerEnvironment(): Boolean
|
||||
|
||||
external fun getUserName(uid: Int): String?
|
||||
|
||||
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
||||
private const val NOBODY_UID = 9999
|
||||
|
||||
|
|
@ -159,31 +199,10 @@ object Natives {
|
|||
}
|
||||
|
||||
fun requireNewKernel(): Boolean {
|
||||
if (version < MINIMAL_SUPPORTED_KERNEL) return true
|
||||
if (version != -1 && version < MINIMAL_SUPPORTED_KERNEL) return true
|
||||
return isVersionLessThan(getFullVersion(), MINIMAL_SUPPORTED_KERNEL_FULL)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
@Keep
|
||||
data class SusfsFeatureStatus(
|
||||
val statusSusPath: Boolean = false,
|
||||
val statusSusMount: Boolean = false,
|
||||
val statusAutoDefaultMount: Boolean = false,
|
||||
val statusAutoBindMount: Boolean = false,
|
||||
val statusSusKstat: Boolean = false,
|
||||
val statusTryUmount: Boolean = false,
|
||||
val statusAutoTryUmountBind: Boolean = false,
|
||||
val statusSpoofUname: Boolean = false,
|
||||
val statusEnableLog: Boolean = false,
|
||||
val statusHideSymbols: Boolean = false,
|
||||
val statusSpoofCmdline: Boolean = false,
|
||||
val statusOpenRedirect: Boolean = false,
|
||||
val statusMagicMount: Boolean = false,
|
||||
val statusOverlayfsAutoKstat: Boolean = false,
|
||||
val statusSusSu: Boolean = false
|
||||
) : Parcelable
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
@Keep
|
||||
|
|
|
|||
|
|
@ -1,137 +1,75 @@
|
|||
package com.sukisu.ultra.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import rikka.parcelablelist.ParcelableListSlice
|
||||
import java.lang.reflect.Method
|
||||
import com.sukisu.zako.IKsuInterface
|
||||
|
||||
/**
|
||||
* @author ShirkNeko
|
||||
* @date 2025/7/2.
|
||||
* @date 2025/10/17.
|
||||
*/
|
||||
class KsuService : RootService() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "KsuService"
|
||||
private const val DESCRIPTOR = "com.sukisu.ultra.IKsuInterface"
|
||||
private const val TRANSACTION_GET_PACKAGES = IBinder.FIRST_CALL_TRANSACTION + 0
|
||||
private val TAG = "KsuService"
|
||||
|
||||
private val cacheLock = Object()
|
||||
private var _all: List<PackageInfo>? = null
|
||||
private val allPackages: List<PackageInfo>
|
||||
get() = synchronized(cacheLock) {
|
||||
_all ?: loadAllPackages().also { _all = it }
|
||||
}
|
||||
|
||||
private fun loadAllPackages(): List<PackageInfo> {
|
||||
val tmp = arrayListOf<PackageInfo>()
|
||||
for (user in (getSystemService(USER_SERVICE) as UserManager).userProfiles) {
|
||||
val userId = user.getUserIdCompat()
|
||||
tmp += getInstalledPackagesAsUser(userId)
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
interface IKsuInterface : IInterface {
|
||||
fun getPackages(flags: Int): ParcelableListSlice<PackageInfo>
|
||||
}
|
||||
internal inner class Stub : IKsuInterface.Stub() {
|
||||
override fun getPackageCount(): Int = allPackages.size
|
||||
|
||||
abstract class Stub : Binder(), IKsuInterface {
|
||||
init {
|
||||
attachInterface(this, DESCRIPTOR)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun asInterface(obj: IBinder?): IKsuInterface? {
|
||||
if (obj == null) return null
|
||||
val iin = obj.queryLocalInterface(DESCRIPTOR)
|
||||
return if (iin != null && iin is IKsuInterface) {
|
||||
iin
|
||||
} else {
|
||||
Proxy(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun asBinder(): IBinder = this
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
val descriptor = DESCRIPTOR
|
||||
when (code) {
|
||||
INTERFACE_TRANSACTION -> {
|
||||
reply?.writeString(descriptor)
|
||||
return true
|
||||
}
|
||||
TRANSACTION_GET_PACKAGES -> {
|
||||
data.enforceInterface(descriptor)
|
||||
val flagsArg = data.readInt()
|
||||
val result = getPackages(flagsArg)
|
||||
reply?.writeNoException()
|
||||
reply?.writeInt(1)
|
||||
result.writeToParcel(reply!!, Parcelable.PARCELABLE_WRITE_RETURN_VALUE)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
private class Proxy(private val mRemote: IBinder) : IKsuInterface {
|
||||
override fun getPackages(flags: Int): ParcelableListSlice<PackageInfo> {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
return try {
|
||||
data.writeInterfaceToken(DESCRIPTOR)
|
||||
data.writeInt(flags)
|
||||
mRemote.transact(TRANSACTION_GET_PACKAGES, data, reply, 0)
|
||||
reply.readException()
|
||||
if (reply.readInt() != 0) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
ParcelableListSlice.CREATOR.createFromParcel(reply) as ParcelableListSlice<PackageInfo>
|
||||
} else {
|
||||
ParcelableListSlice(emptyList<PackageInfo>())
|
||||
}
|
||||
} finally {
|
||||
reply.recycle()
|
||||
data.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun asBinder(): IBinder = mRemote
|
||||
override fun getPackages(start: Int, maxCount: Int): List<PackageInfo> {
|
||||
val list = allPackages
|
||||
val end = (start + maxCount).coerceAtMost(list.size)
|
||||
return if (start >= list.size) emptyList()
|
||||
else list.subList(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
inner class KsuInterfaceImpl : Stub() {
|
||||
override fun getPackages(flags: Int): ParcelableListSlice<PackageInfo> {
|
||||
val list = getInstalledPackagesAll(flags)
|
||||
Log.i(TAG, "getPackages: ${list.size}")
|
||||
return ParcelableListSlice(list)
|
||||
}
|
||||
}
|
||||
override fun onBind(intent: Intent): IBinder = Stub()
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return KsuInterfaceImpl()
|
||||
}
|
||||
|
||||
private fun getUserIds(): List<Int> {
|
||||
val result = mutableListOf<Int>()
|
||||
val um = getSystemService(USER_SERVICE) as UserManager
|
||||
val userProfiles = um.userProfiles
|
||||
for (userProfile in userProfiles) {
|
||||
result.add(userProfile.hashCode())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getInstalledPackagesAll(flags: Int): ArrayList<PackageInfo> {
|
||||
val packages = ArrayList<PackageInfo>()
|
||||
for (userId in getUserIds()) {
|
||||
Log.i(TAG, "getInstalledPackagesAll: $userId")
|
||||
packages.addAll(getInstalledPackagesAsUser(flags, userId))
|
||||
}
|
||||
return packages
|
||||
}
|
||||
|
||||
private fun getInstalledPackagesAsUser(flags: Int, userId: Int): List<PackageInfo> {
|
||||
@SuppressLint("PrivateApi")
|
||||
private fun getInstalledPackagesAsUser(userId: Int): List<PackageInfo> {
|
||||
return try {
|
||||
val pm = packageManager
|
||||
val getInstalledPackagesAsUser: Method = pm.javaClass.getDeclaredMethod(
|
||||
val m = pm.javaClass.getDeclaredMethod(
|
||||
"getInstalledPackagesAsUser",
|
||||
Int::class.java,
|
||||
Int::class.java
|
||||
)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
getInstalledPackagesAsUser.invoke(pm, flags, userId) as List<PackageInfo>
|
||||
m.invoke(pm, 0, userId) as List<PackageInfo>
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "err", e)
|
||||
ArrayList()
|
||||
Log.e(TAG, "getInstalledPackagesAsUser", e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserHandle.getUserIdCompat(): Int {
|
||||
return try {
|
||||
javaClass.getDeclaredField("identifier").apply { isAccessible = true }.getInt(this)
|
||||
} catch (_: NoSuchFieldException) {
|
||||
javaClass.getDeclaredMethod("getIdentifier").invoke(this) as Int
|
||||
} catch (e: Throwable) {
|
||||
Log.e("KsuService", "getUserIdCompat", e)
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
package com.sukisu.ultra.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
|
|
@ -9,13 +10,10 @@ import androidx.activity.compose.setContent
|
|||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
|
|
@ -26,6 +24,8 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty
|
|||
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
||||
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import zako.zako.zako.zakoui.screen.moreSettings.util.LocaleHelper
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||
import com.sukisu.ultra.ui.theme.KernelSUTheme
|
||||
|
|
@ -34,11 +34,11 @@ import com.sukisu.ultra.ui.util.install
|
|||
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
import com.sukisu.ultra.ui.webui.initPlatform
|
||||
import io.sukisu.ultra.UltraToolInstall
|
||||
import com.sukisu.ultra.ui.component.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import zako.zako.zako.zakoui.activity.component.BottomBar
|
||||
import zako.zako.zako.zakoui.activity.util.*
|
||||
import com.sukisu.ultra.ui.activity.component.BottomBar
|
||||
import com.sukisu.ultra.ui.activity.util.*
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var superUserViewModel: SuperUserViewModel
|
||||
|
|
@ -50,21 +50,18 @@ class MainActivity : ComponentActivity() {
|
|||
val showKpmInfo: Boolean = false
|
||||
)
|
||||
|
||||
private lateinit var themeChangeObserver: ThemeChangeContentObserver
|
||||
private var showConfirmationDialog = mutableStateOf(false)
|
||||
private var pendingZipFiles = mutableStateOf<List<ZipFileInfo>>(emptyList())
|
||||
|
||||
// 添加标记避免重复初始化
|
||||
private lateinit var themeChangeObserver: ThemeChangeContentObserver
|
||||
private var isInitialized = false
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
val context = LocaleUtils.applyLocale(newBase)
|
||||
super.attachBaseContext(context)
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(newBase?.let { LocaleHelper.applyLanguage(it) })
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
try {
|
||||
// 确保应用正确的语言设置
|
||||
LocaleUtils.applyLanguageSetting(this)
|
||||
|
||||
// 应用自定义 DPI
|
||||
DisplayUtils.applyCustomDpi(this)
|
||||
|
||||
|
|
@ -77,6 +74,11 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val isManager = Natives.isManager
|
||||
if (isManager && !Natives.requireNewKernel()) {
|
||||
install()
|
||||
}
|
||||
|
||||
// 使用标记控制初始化流程
|
||||
if (!isInitialized) {
|
||||
initializeViewModels()
|
||||
|
|
@ -84,6 +86,39 @@ class MainActivity : ComponentActivity() {
|
|||
isInitialized = true
|
||||
}
|
||||
|
||||
// Check if launched with a ZIP file
|
||||
val zipUri: ArrayList<Uri>? = when (intent?.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
uri?.let { arrayListOf(it) }
|
||||
}
|
||||
|
||||
Intent.ACTION_SEND_MULTIPLE -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
}
|
||||
|
||||
else -> when {
|
||||
intent?.data != null -> arrayListOf(intent.data!!)
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
|
||||
intent.getParcelableArrayListExtra("uris", Uri::class.java)
|
||||
}
|
||||
else -> {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableArrayListExtra("uris")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setContent {
|
||||
KernelSUTheme {
|
||||
val navController = rememberNavController()
|
||||
|
|
@ -94,6 +129,38 @@ class MainActivity : ComponentActivity() {
|
|||
BottomBarDestination.entries.map { it.direction.route }.toSet()
|
||||
}
|
||||
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
|
||||
InstallConfirmationDialog(
|
||||
show = showConfirmationDialog.value,
|
||||
zipFiles = pendingZipFiles.value,
|
||||
onConfirm = { confirmedFiles ->
|
||||
showConfirmationDialog.value = false
|
||||
UltraActivityUtils.navigateToFlashScreen(this, confirmedFiles, navigator)
|
||||
},
|
||||
onDismiss = {
|
||||
showConfirmationDialog.value = false
|
||||
pendingZipFiles.value = emptyList()
|
||||
finish()
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(zipUri) {
|
||||
if (!zipUri.isNullOrEmpty()) {
|
||||
// 检测 ZIP 文件类型并显示确认对话框
|
||||
lifecycleScope.launch {
|
||||
UltraActivityUtils.detectZipTypeAndShowConfirmation(this@MainActivity, zipUri) { infos ->
|
||||
if (infos.isNotEmpty()) {
|
||||
pendingZipFiles.value = infos
|
||||
showConfirmationDialog.value = true
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val showBottomBar = when (currentDestination?.route) {
|
||||
ExecuteModuleActionScreenDestination.route -> false
|
||||
else -> true
|
||||
|
|
@ -187,32 +254,17 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
homeViewModel.initializeData()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// 数据刷新协程
|
||||
DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope)
|
||||
DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
|
||||
|
||||
// 初始化主题相关设置
|
||||
ThemeUtils.initializeThemeSettings(this, settingsStateFlow)
|
||||
|
||||
val isManager = Natives.becomeManager(packageName)
|
||||
if (isManager) {
|
||||
install()
|
||||
UltraToolInstall.tryToInstall()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
try {
|
||||
super.onResume()
|
||||
LocaleUtils.applyLanguageSetting(this)
|
||||
ThemeUtils.onActivityResume()
|
||||
|
||||
// 仅在需要时刷新数据
|
||||
|
|
@ -228,7 +280,6 @@ class MainActivity : ComponentActivity() {
|
|||
lifecycleScope.launch {
|
||||
try {
|
||||
superUserViewModel.fetchAppList()
|
||||
homeViewModel.initializeData()
|
||||
DataRefreshUtils.refreshData(lifecycleScope)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
@ -253,13 +304,4 @@ class MainActivity : ComponentActivity() {
|
|||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
try {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
LocaleUtils.applyLanguageSetting(this)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package zako.zako.zako.zakoui.activity.component
|
||||
package com.sukisu.ultra.ui.activity.component
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -15,21 +15,20 @@ import com.ramcosta.composedestinations.spec.RouteOrDirection
|
|||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.MainActivity
|
||||
import com.sukisu.ultra.ui.activity.util.*
|
||||
import com.sukisu.ultra.ui.activity.util.AppData.getKpmVersionUse
|
||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||
import zako.zako.zako.zakoui.activity.util.AppData
|
||||
import zako.zako.zako.zakoui.activity.util.AppData.DataRefreshManager
|
||||
import zako.zako.zako.zakoui.activity.util.AppData.getKpmVersionUse
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
|
||||
@SuppressLint("ContextCastToActivity")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BottomBar(navController: NavHostController) {
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
val isFullFeatured = AppData.isFullFeatured(ksuApp.packageName)
|
||||
val isFullFeatured = AppData.isFullFeatured()
|
||||
val kpmVersion = getKpmVersionUse()
|
||||
val cardColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
val activity = LocalContext.current as MainActivity
|
||||
|
|
@ -40,9 +39,9 @@ fun BottomBar(navController: NavHostController) {
|
|||
val showKpmInfo = settings.showKpmInfo
|
||||
|
||||
// 收集计数数据
|
||||
val superuserCount by DataRefreshManager.superuserCount.collectAsState()
|
||||
val moduleCount by DataRefreshManager.moduleCount.collectAsState()
|
||||
val kpmModuleCount by DataRefreshManager.kpmModuleCount.collectAsState()
|
||||
val superuserCount by AppData.DataRefreshManager.superuserCount.collectAsState()
|
||||
val moduleCount by AppData.DataRefreshManager.moduleCount.collectAsState()
|
||||
val kpmModuleCount by AppData.DataRefreshManager.kpmModuleCount.collectAsState()
|
||||
|
||||
|
||||
NavigationBar(
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package zako.zako.zako.zakoui.activity.util
|
||||
package com.sukisu.ultra.ui.activity.util
|
||||
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.os.Handler
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.edit
|
||||
import com.sukisu.ultra.ui.MainActivity
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
|
|
@ -56,7 +57,7 @@ object ThemeUtils {
|
|||
}
|
||||
|
||||
activity.contentResolver.registerContentObserver(
|
||||
android.provider.Settings.System.getUriFor("ui_night_mode"),
|
||||
Settings.System.getUriFor("ui_night_mode"),
|
||||
false,
|
||||
contentObserver
|
||||
)
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
package com.sukisu.ultra.ui.activity.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.MainActivity
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.sukisu.ultra.ui.component.ZipFileDetector
|
||||
import com.sukisu.ultra.ui.component.ZipFileInfo
|
||||
import com.sukisu.ultra.ui.component.ZipType
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||
import com.sukisu.ultra.ui.screen.FlashIt
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.core.content.edit
|
||||
|
||||
object AnimatedBottomBar {
|
||||
@Composable
|
||||
fun AnimatedBottomBarWrapper(
|
||||
showBottomBar: Boolean,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = showBottomBar,
|
||||
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object UltraActivityUtils {
|
||||
|
||||
suspend fun detectZipTypeAndShowConfirmation(
|
||||
activity: MainActivity,
|
||||
zipUris: ArrayList<Uri>,
|
||||
onResult: (List<ZipFileInfo>) -> Unit
|
||||
) {
|
||||
val infos = ZipFileDetector.detectAndParseZipFiles(activity, zipUris)
|
||||
withContext(Dispatchers.Main) { onResult(infos) }
|
||||
}
|
||||
|
||||
fun navigateToFlashScreen(
|
||||
activity: MainActivity,
|
||||
zipFiles: List<ZipFileInfo>,
|
||||
navigator: DestinationsNavigator
|
||||
) {
|
||||
activity.lifecycleScope.launch {
|
||||
val moduleUris = zipFiles.filter { it.type == ZipType.MODULE }.map { it.uri }
|
||||
val kernelUris = zipFiles.filter { it.type == ZipType.KERNEL }.map { it.uri }
|
||||
|
||||
when {
|
||||
kernelUris.isNotEmpty() && moduleUris.isEmpty() -> {
|
||||
if (kernelUris.size == 1 && rootAvailable()) {
|
||||
navigator.navigate(
|
||||
InstallScreenDestination(
|
||||
preselectedKernelUri = kernelUris.first().toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
setAutoExitAfterFlash(activity)
|
||||
}
|
||||
|
||||
moduleUris.isNotEmpty() -> {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashModules(ArrayList(moduleUris))
|
||||
)
|
||||
)
|
||||
setAutoExitAfterFlash(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAutoExitAfterFlash(activity: Context) {
|
||||
activity.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
.edit {
|
||||
putBoolean("auto_exit_after_flash", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AppData {
|
||||
object DataRefreshManager {
|
||||
// 私有状态流
|
||||
private val _superuserCount = MutableStateFlow(0)
|
||||
private val _moduleCount = MutableStateFlow(0)
|
||||
private val _kpmModuleCount = MutableStateFlow(0)
|
||||
|
||||
// 公开的只读状态流
|
||||
val superuserCount: StateFlow<Int> = _superuserCount.asStateFlow()
|
||||
val moduleCount: StateFlow<Int> = _moduleCount.asStateFlow()
|
||||
val kpmModuleCount: StateFlow<Int> = _kpmModuleCount.asStateFlow()
|
||||
|
||||
/**
|
||||
* 刷新所有数据计数
|
||||
*/
|
||||
fun refreshData() {
|
||||
_superuserCount.value = getSuperuserCountUse()
|
||||
_moduleCount.value = getModuleCountUse()
|
||||
_kpmModuleCount.value = getKpmModuleCountUse()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取超级用户应用计数
|
||||
*/
|
||||
fun getSuperuserCountUse(): Int {
|
||||
return try {
|
||||
if (!rootAvailable()) return 0
|
||||
getSuperuserCount()
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块计数
|
||||
*/
|
||||
fun getModuleCountUse(): Int {
|
||||
return try {
|
||||
if (!rootAvailable()) return 0
|
||||
getModuleCount()
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取KPM模块计数
|
||||
*/
|
||||
fun getKpmModuleCountUse(): Int {
|
||||
return try {
|
||||
if (!rootAvailable()) return 0
|
||||
val kpmVersion = getKpmVersionUse()
|
||||
if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) return 0
|
||||
getKpmModuleCount()
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取KPM版本
|
||||
*/
|
||||
fun getKpmVersionUse(): String {
|
||||
return try {
|
||||
if (!rootAvailable()) return ""
|
||||
val version = getKpmVersion()
|
||||
version.ifEmpty { "" }
|
||||
} catch (e: Exception) {
|
||||
"Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否是完整功能模式
|
||||
*/
|
||||
fun isFullFeatured(): Boolean {
|
||||
val isManager = Natives.isManager
|
||||
return isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
object DataRefreshUtils {
|
||||
fun startDataRefreshCoroutine(scope: LifecycleCoroutineScope) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
while (isActive) {
|
||||
AppData.DataRefreshManager.refreshData()
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startSettingsMonitorCoroutine(
|
||||
scope: LifecycleCoroutineScope,
|
||||
activity: MainActivity,
|
||||
settingsStateFlow: MutableStateFlow<MainActivity.SettingsState>
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
while (isActive) {
|
||||
val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
settingsStateFlow.value = MainActivity.SettingsState(
|
||||
isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false),
|
||||
showKpmInfo = prefs.getBoolean("show_kpm_info", false)
|
||||
)
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshData(scope: LifecycleCoroutineScope) {
|
||||
scope.launch {
|
||||
AppData.DataRefreshManager.refreshData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DisplayUtils {
|
||||
fun applyCustomDpi(context: Context) {
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val customDpi = prefs.getInt("app_dpi", 0)
|
||||
|
||||
if (customDpi > 0) {
|
||||
try {
|
||||
val resources = context.resources
|
||||
val metrics = resources.displayMetrics
|
||||
metrics.density = customDpi / 160f
|
||||
@Suppress("DEPRECATION")
|
||||
metrics.scaledDensity = customDpi / 160f
|
||||
metrics.densityDpi = customDpi
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,16 @@ import android.text.method.LinkMovementMethod
|
|||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
|
|
@ -428,27 +433,36 @@ private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, di
|
|||
@Composable
|
||||
private fun MarkdownContent(content: String) {
|
||||
val contentColor = LocalContentColor.current
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
TextView(context).apply {
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
setSpannableFactory(NoCopySpannableFactory.getInstance())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
breakStrategy = LineBreaker.BREAK_STRATEGY_SIMPLE
|
||||
}
|
||||
hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
},
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
update = {
|
||||
Markwon.create(it.context).setMarkdown(it, content)
|
||||
it.setTextColor(contentColor.toArgb())
|
||||
}
|
||||
)
|
||||
}
|
||||
.verticalScroll(
|
||||
state = scrollState,
|
||||
flingBehavior = ScrollableDefaults.flingBehavior()
|
||||
)
|
||||
.padding(12.dp)
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
TextView(context).apply {
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
setSpannableFactory(NoCopySpannableFactory.getInstance())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
breakStrategy = LineBreaker.BREAK_STRATEGY_SIMPLE
|
||||
}
|
||||
hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
},
|
||||
update = {
|
||||
Markwon.create(it.context).setMarkdown(it, content)
|
||||
it.setTextColor(contentColor.toArgb())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,223 +0,0 @@
|
|||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Fullscreen
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.max
|
||||
|
||||
@Composable
|
||||
fun ImageEditorDialog(
|
||||
imageUri: Uri,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (Uri) -> Unit
|
||||
) {
|
||||
var scale by remember { mutableFloatStateOf(1f) }
|
||||
var offsetX by remember { mutableFloatStateOf(0f) }
|
||||
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var lastScale by remember { mutableFloatStateOf(1f) }
|
||||
var lastOffsetX by remember { mutableFloatStateOf(0f) }
|
||||
var lastOffsetY by remember { mutableFloatStateOf(0f) }
|
||||
var imageSize by remember { mutableStateOf(Size.Zero) }
|
||||
var screenSize by remember { mutableStateOf(Size.Zero) }
|
||||
val animatedScale by animateFloatAsState(
|
||||
targetValue = scale,
|
||||
label = "ScaleAnimation"
|
||||
)
|
||||
val animatedOffsetX by animateFloatAsState(
|
||||
targetValue = offsetX,
|
||||
label = "OffsetXAnimation"
|
||||
)
|
||||
val animatedOffsetY by animateFloatAsState(
|
||||
targetValue = offsetY,
|
||||
label = "OffsetYAnimation"
|
||||
)
|
||||
val updateTransformation = remember {
|
||||
{ newScale: Float, newOffsetX: Float, newOffsetY: Float ->
|
||||
val scaleDiff = kotlin.math.abs(newScale - lastScale)
|
||||
val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX)
|
||||
val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY)
|
||||
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
|
||||
scale = newScale
|
||||
offsetX = newOffsetX
|
||||
offsetY = newOffsetY
|
||||
lastScale = newScale
|
||||
lastOffsetX = newOffsetX
|
||||
lastOffsetY = newOffsetY
|
||||
}
|
||||
}
|
||||
}
|
||||
val scaleToFullScreen = remember {
|
||||
{
|
||||
if (imageSize.height > 0 && screenSize.height > 0) {
|
||||
val newScale = screenSize.height / imageSize.height
|
||||
updateTransformation(newScale, 0f, 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = false,
|
||||
usePlatformDefaultWidth = false
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.9f))
|
||||
.onSizeChanged { size ->
|
||||
screenSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||
}
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(imageUri)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = stringResource(R.string.settings_custom_background),
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer(
|
||||
scaleX = animatedScale,
|
||||
scaleY = animatedScale,
|
||||
translationX = animatedOffsetX,
|
||||
translationY = animatedOffsetY
|
||||
)
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures { _, pan, zoom, _ ->
|
||||
scope.launch {
|
||||
try {
|
||||
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
|
||||
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
|
||||
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
|
||||
val newOffsetX = if (maxOffsetX > 0) {
|
||||
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val newOffsetY = if (maxOffsetY > 0) {
|
||||
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
updateTransformation(newScale, newOffsetX, newOffsetY)
|
||||
} catch (_: Exception) {
|
||||
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onSizeChanged { size ->
|
||||
imageSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||
}
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.align(Alignment.TopCenter),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(R.string.cancel),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { scaleToFullScreen() },
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Fullscreen,
|
||||
contentDescription = stringResource(R.string.reprovision),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
try {
|
||||
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
|
||||
val savedUri = context.saveTransformedBackground(imageUri, transformation)
|
||||
savedUri?.let { onConfirm(it) }
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.confirm),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
.padding(16.dp)
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.image_editor_hint),
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Help
|
||||
import androidx.compose.material.icons.filled.Extension
|
||||
import androidx.compose.material.icons.filled.GetApp
|
||||
import androidx.compose.material.icons.filled.Memory
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sukisu.ultra.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
enum class ZipType {
|
||||
MODULE,
|
||||
KERNEL,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
data class ZipFileInfo(
|
||||
val uri: Uri,
|
||||
val type: ZipType,
|
||||
val name: String = "",
|
||||
val version: String = "",
|
||||
val versionCode: String = "",
|
||||
val author: String = "",
|
||||
val description: String = "",
|
||||
val kernelVersion: String = "",
|
||||
val supported: String = ""
|
||||
)
|
||||
|
||||
object ZipFileDetector {
|
||||
|
||||
fun detectZipType(context: Context, uri: Uri): ZipType {
|
||||
return try {
|
||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zipStream ->
|
||||
var hasModuleProp = false
|
||||
var hasToolsFolder = false
|
||||
var hasAnykernelSh = false
|
||||
|
||||
var entry = zipStream.nextEntry
|
||||
while (entry != null) {
|
||||
val entryName = entry.name.lowercase()
|
||||
|
||||
when {
|
||||
entryName == "module.prop" || entryName.endsWith("/module.prop") -> {
|
||||
hasModuleProp = true
|
||||
}
|
||||
entryName.startsWith("tools/") || entryName == "tools" -> {
|
||||
hasToolsFolder = true
|
||||
}
|
||||
entryName == "anykernel.sh" || entryName.endsWith("/anykernel.sh") -> {
|
||||
hasAnykernelSh = true
|
||||
}
|
||||
}
|
||||
|
||||
zipStream.closeEntry()
|
||||
entry = zipStream.nextEntry
|
||||
}
|
||||
|
||||
when {
|
||||
hasModuleProp -> ZipType.MODULE
|
||||
hasToolsFolder && hasAnykernelSh -> ZipType.KERNEL
|
||||
else -> ZipType.UNKNOWN
|
||||
}
|
||||
}
|
||||
} ?: ZipType.UNKNOWN
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
ZipType.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
fun parseModuleInfo(context: Context, uri: Uri): ZipFileInfo {
|
||||
var zipInfo = ZipFileInfo(uri = uri, type = ZipType.MODULE)
|
||||
|
||||
try {
|
||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zipStream ->
|
||||
var entry = zipStream.nextEntry
|
||||
while (entry != null) {
|
||||
if (entry.name.lowercase() == "module.prop" || entry.name.endsWith("/module.prop")) {
|
||||
val reader = BufferedReader(InputStreamReader(zipStream))
|
||||
val props = mutableMapOf<String, String>()
|
||||
|
||||
var line = reader.readLine()
|
||||
while (line != null) {
|
||||
if (line.contains("=") && !line.startsWith("#")) {
|
||||
val parts = line.split("=", limit = 2)
|
||||
if (parts.size == 2) {
|
||||
props[parts[0].trim()] = parts[1].trim()
|
||||
}
|
||||
}
|
||||
line = reader.readLine()
|
||||
}
|
||||
|
||||
zipInfo = zipInfo.copy(
|
||||
name = props["name"] ?: context.getString(R.string.unknown_module),
|
||||
version = props["version"] ?: "",
|
||||
versionCode = props["versionCode"] ?: "",
|
||||
author = props["author"] ?: "",
|
||||
description = props["description"] ?: ""
|
||||
)
|
||||
break
|
||||
}
|
||||
zipStream.closeEntry()
|
||||
entry = zipStream.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return zipInfo
|
||||
}
|
||||
|
||||
fun parseKernelInfo(context: Context, uri: Uri): ZipFileInfo {
|
||||
var zipInfo = ZipFileInfo(uri = uri, type = ZipType.KERNEL)
|
||||
|
||||
try {
|
||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zipStream ->
|
||||
var entry = zipStream.nextEntry
|
||||
while (entry != null) {
|
||||
if (entry.name.lowercase() == "anykernel.sh" || entry.name.endsWith("/anykernel.sh")) {
|
||||
val reader = BufferedReader(InputStreamReader(zipStream))
|
||||
val props = mutableMapOf<String, String>()
|
||||
|
||||
var inPropertiesBlock = false
|
||||
var line = reader.readLine()
|
||||
while (line != null) {
|
||||
if (line.contains("properties()")) {
|
||||
inPropertiesBlock = true
|
||||
} else if (inPropertiesBlock && line.contains("'; }")) {
|
||||
inPropertiesBlock = false
|
||||
} else if (inPropertiesBlock) {
|
||||
val propertyLine = line.trim()
|
||||
if (propertyLine.contains("=") && !propertyLine.startsWith("#")) {
|
||||
val parts = propertyLine.split("=", limit = 2)
|
||||
if (parts.size == 2) {
|
||||
val key = parts[0].trim()
|
||||
val value = parts[1].trim().removeSurrounding("'").removeSurrounding("\"")
|
||||
when (key) {
|
||||
"kernel.string" -> props["name"] = value
|
||||
"supported.versions" -> props["supported"] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析普通变量定义
|
||||
if (line.contains("kernel.string=") && !inPropertiesBlock) {
|
||||
val value = line.substringAfter("kernel.string=").trim().removeSurrounding("\"")
|
||||
props["name"] = value
|
||||
}
|
||||
if (line.contains("supported.versions=") && !inPropertiesBlock) {
|
||||
val value = line.substringAfter("supported.versions=").trim().removeSurrounding("\"")
|
||||
props["supported"] = value
|
||||
}
|
||||
if (line.contains("kernel.version=") && !inPropertiesBlock) {
|
||||
val value = line.substringAfter("kernel.version=").trim().removeSurrounding("\"")
|
||||
props["version"] = value
|
||||
}
|
||||
if (line.contains("kernel.author=") && !inPropertiesBlock) {
|
||||
val value = line.substringAfter("kernel.author=").trim().removeSurrounding("\"")
|
||||
props["author"] = value
|
||||
}
|
||||
|
||||
line = reader.readLine()
|
||||
}
|
||||
|
||||
zipInfo = zipInfo.copy(
|
||||
name = props["name"] ?: context.getString(R.string.unknown_kernel),
|
||||
version = props["version"] ?: "",
|
||||
author = props["author"] ?: "",
|
||||
supported = props["supported"] ?: "",
|
||||
kernelVersion = props["version"] ?: ""
|
||||
)
|
||||
break
|
||||
}
|
||||
zipStream.closeEntry()
|
||||
entry = zipStream.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return zipInfo
|
||||
}
|
||||
|
||||
suspend fun detectAndParseZipFiles(context: Context, zipUris: List<Uri>): List<ZipFileInfo> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val zipFileInfos = mutableListOf<ZipFileInfo>()
|
||||
|
||||
for (uri in zipUris) {
|
||||
val zipType = detectZipType(context, uri)
|
||||
val zipInfo = when (zipType) {
|
||||
ZipType.MODULE -> parseModuleInfo(context, uri)
|
||||
ZipType.KERNEL -> parseKernelInfo(context, uri)
|
||||
ZipType.UNKNOWN -> ZipFileInfo(
|
||||
uri = uri,
|
||||
type = ZipType.UNKNOWN,
|
||||
name = context.getString(R.string.unknown_file)
|
||||
)
|
||||
}
|
||||
zipFileInfos.add(zipInfo)
|
||||
}
|
||||
|
||||
zipFileInfos.filter { it.type != ZipType.UNKNOWN }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InstallConfirmationDialog(
|
||||
show: Boolean,
|
||||
zipFiles: List<ZipFileInfo>,
|
||||
onConfirm: (List<ZipFileInfo>) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
if (show && zipFiles.isNotEmpty()) {
|
||||
val context = LocalContext.current
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (zipFiles.any { it.type == ZipType.KERNEL })
|
||||
Icons.Default.Memory else Icons.Default.Extension,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = if (zipFiles.size == 1) {
|
||||
context.getString(R.string.confirm_installation)
|
||||
} else {
|
||||
context.getString(R.string.confirm_multiple_installation, zipFiles.size)
|
||||
},
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 400.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(zipFiles.size) { index ->
|
||||
val zipFile = zipFiles[index]
|
||||
InstallItemCard(zipFile = zipFile)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = { onConfirm(zipFiles) },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.GetApp,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(context.getString(R.string.install_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(
|
||||
context.getString(android.R.string.cancel),
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.widthIn(min = 320.dp, max = 560.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InstallItemCard(zipFile: ZipFileInfo) {
|
||||
val context = LocalContext.current
|
||||
|
||||
ElevatedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = when (zipFile.type) {
|
||||
ZipType.MODULE -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
||||
ZipType.KERNEL -> MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.3f)
|
||||
else -> MaterialTheme.colorScheme.surfaceVariant
|
||||
}
|
||||
),
|
||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 0.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = when (zipFile.type) {
|
||||
ZipType.MODULE -> Icons.Default.Extension
|
||||
ZipType.KERNEL -> Icons.Default.Memory
|
||||
else -> Icons.AutoMirrored.Filled.Help
|
||||
},
|
||||
contentDescription = null,
|
||||
tint = when (zipFile.type) {
|
||||
ZipType.MODULE -> MaterialTheme.colorScheme.primary
|
||||
ZipType.KERNEL -> MaterialTheme.colorScheme.tertiary
|
||||
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
},
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = zipFile.name.ifEmpty {
|
||||
when (zipFile.type) {
|
||||
ZipType.MODULE -> context.getString(R.string.unknown_module)
|
||||
ZipType.KERNEL -> context.getString(R.string.unknown_kernel)
|
||||
else -> context.getString(R.string.unknown_file)
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
text = when (zipFile.type) {
|
||||
ZipType.MODULE -> context.getString(R.string.module_package)
|
||||
ZipType.KERNEL -> context.getString(R.string.kernel_package)
|
||||
else -> context.getString(R.string.unknown_package)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 详细信息
|
||||
if (zipFile.version.isNotEmpty() || zipFile.author.isNotEmpty() ||
|
||||
zipFile.description.isNotEmpty() || zipFile.supported.isNotEmpty()) {
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
|
||||
thickness = 0.5.dp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// 版本信息
|
||||
if (zipFile.version.isNotEmpty()) {
|
||||
InfoRow(
|
||||
label = context.getString(R.string.version),
|
||||
value = zipFile.version + if (zipFile.versionCode.isNotEmpty()) " (${zipFile.versionCode})" else ""
|
||||
)
|
||||
}
|
||||
|
||||
// 作者信息
|
||||
if (zipFile.author.isNotEmpty()) {
|
||||
InfoRow(
|
||||
label = context.getString(R.string.author),
|
||||
value = zipFile.author
|
||||
)
|
||||
}
|
||||
|
||||
// 描述信息 (仅模块)
|
||||
if (zipFile.description.isNotEmpty() && zipFile.type == ZipType.MODULE) {
|
||||
InfoRow(
|
||||
label = context.getString(R.string.description),
|
||||
value = zipFile.description
|
||||
)
|
||||
}
|
||||
|
||||
// 支持设备 (仅内核)
|
||||
if (zipFile.supported.isNotEmpty() && zipFile.type == ZipType.KERNEL) {
|
||||
InfoRow(
|
||||
label = context.getString(R.string.supported_devices),
|
||||
value = zipFile.supported
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InfoRow(label: String, value: String) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
Text(
|
||||
text = "$label:",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.widthIn(min = 60.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.sukisu.ultra.ksuApp
|
|||
fun KsuIsValid(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val isManager = Natives.isManager
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
if (ksuVersion != null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,250 @@
|
|||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SuperDropdown(
|
||||
items: List<String>,
|
||||
selectedIndex: Int,
|
||||
title: String,
|
||||
summary: String? = null,
|
||||
icon: ImageVector? = null,
|
||||
enabled: Boolean = true,
|
||||
showValue: Boolean = true,
|
||||
maxHeight: Dp? = 400.dp,
|
||||
colors: SuperDropdownColors = SuperDropdownDefaults.colors(),
|
||||
leftAction: (@Composable () -> Unit)? = null,
|
||||
onSelectedIndexChange: (Int) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
val selectedItemText = items.getOrNull(selectedIndex) ?: ""
|
||||
val itemsNotEmpty = items.isNotEmpty()
|
||||
val actualEnabled = enabled && itemsNotEmpty
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = actualEnabled) { showDialog = true }
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
if (leftAction != null) {
|
||||
leftAction()
|
||||
} else if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = if (actualEnabled) colors.iconColor else colors.disabledIconColor,
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = if (actualEnabled) colors.titleColor else colors.disabledTitleColor
|
||||
)
|
||||
|
||||
if (summary != null) {
|
||||
Spacer(modifier = Modifier.height(3.dp))
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (actualEnabled) colors.summaryColor else colors.disabledSummaryColor
|
||||
)
|
||||
}
|
||||
|
||||
if (showValue && itemsNotEmpty) {
|
||||
Spacer(modifier = Modifier.height(3.dp))
|
||||
Text(
|
||||
text = selectedItemText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (actualEnabled) colors.valueColor else colors.disabledValueColor,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
|
||||
contentDescription = null,
|
||||
tint = if (actualEnabled) colors.arrowColor else colors.disabledArrowColor,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (showDialog && itemsNotEmpty) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
title = {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
},
|
||||
text = {
|
||||
val dialogMaxHeight = maxHeight ?: 400.dp
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = dialogMaxHeight),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
items(items.size) { index ->
|
||||
DropdownItem(
|
||||
text = items[index],
|
||||
isSelected = selectedIndex == index,
|
||||
colors = colors,
|
||||
onClick = {
|
||||
onSelectedIndexChange(index)
|
||||
showDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showDialog = false }) {
|
||||
Text(text = stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
containerColor = colors.dialogBackgroundColor,
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
tonalElevation = 4.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DropdownItem(
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
colors: SuperDropdownColors,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val backgroundColor = if (isSelected) {
|
||||
colors.selectedBackgroundColor
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
|
||||
val contentColor = if (isSelected) {
|
||||
colors.selectedContentColor
|
||||
} else {
|
||||
colors.contentColor
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(backgroundColor)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 12.dp, horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = isSelected,
|
||||
onClick = null,
|
||||
colors = RadioButtonDefaults.colors(
|
||||
selectedColor = colors.selectedContentColor,
|
||||
unselectedColor = colors.contentColor
|
||||
)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = contentColor,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
tint = colors.selectedContentColor,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class SuperDropdownColors(
|
||||
val titleColor: Color,
|
||||
val summaryColor: Color,
|
||||
val valueColor: Color,
|
||||
val iconColor: Color,
|
||||
val arrowColor: Color,
|
||||
val disabledTitleColor: Color,
|
||||
val disabledSummaryColor: Color,
|
||||
val disabledValueColor: Color,
|
||||
val disabledIconColor: Color,
|
||||
val disabledArrowColor: Color,
|
||||
val dialogBackgroundColor: Color,
|
||||
val contentColor: Color,
|
||||
val selectedContentColor: Color,
|
||||
val selectedBackgroundColor: Color
|
||||
)
|
||||
|
||||
object SuperDropdownDefaults {
|
||||
@Composable
|
||||
fun colors(
|
||||
titleColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
summaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
valueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
iconColor: Color = MaterialTheme.colorScheme.primary,
|
||||
arrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
disabledTitleColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||
disabledSummaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
disabledValueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
disabledIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
disabledArrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
dialogBackgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
selectedContentColor: Color = MaterialTheme.colorScheme.primary,
|
||||
selectedBackgroundColor: Color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
||||
): SuperDropdownColors {
|
||||
return SuperDropdownColors(
|
||||
titleColor = titleColor,
|
||||
summaryColor = summaryColor,
|
||||
valueColor = valueColor,
|
||||
iconColor = iconColor,
|
||||
arrowColor = arrowColor,
|
||||
disabledTitleColor = disabledTitleColor,
|
||||
disabledSummaryColor = disabledSummaryColor,
|
||||
disabledValueColor = disabledValueColor,
|
||||
disabledIconColor = disabledIconColor,
|
||||
disabledArrowColor = disabledArrowColor,
|
||||
dialogBackgroundColor = dialogBackgroundColor,
|
||||
contentColor = contentColor,
|
||||
selectedContentColor = selectedContentColor,
|
||||
selectedBackgroundColor = selectedBackgroundColor
|
||||
)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue