Merge pull request #184 from linD026/master

syscall: Use openat() instead of open()
This commit is contained in:
Jim Huang 2022-12-25 20:24:10 +08:00 committed by GitHub
commit 53f4b4c640
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 25 deletions

View File

@ -15,6 +15,8 @@
#include <linux/module.h>
#include <linux/moduleparam.h> /* which will have params */
#include <linux/unistd.h> /* The list of system calls */
#include <linux/cred.h> /* For current_uid() */
#include <linux/uidgid.h> /* For __kuid_val() */
#include <linux/version.h>
/* For the current (process) structure, we need this to know who the
@ -62,22 +64,26 @@ module_param(sym, ulong, 0644);
static unsigned long **sys_call_table;
/* UID we want to spy on - will be filled from the command line. */
static int uid;
static uid_t uid = -1;
module_param(uid, int, 0644);
/* A pointer to the original system call. The reason we keep this, rather
* than call the original function (sys_open), is because somebody else
* than call the original function (sys_openat), is because somebody else
* might have replaced the system call before us. Note that this is not
* 100% safe, because if another module replaced sys_open before us,
* 100% safe, because if another module replaced sys_openat before us,
* then when we are inserted, we will call the function in that module -
* and it might be removed before we are.
*
* Another reason for this is that we can not get sys_open.
* Another reason for this is that we can not get sys_openat.
* It is a static variable, so it is not exported.
*/
static asmlinkage int (*original_call)(const char __user *, int, umode_t);
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long (*original_call)(const struct pt_regs *);
#else
static asmlinkage long (*original_call)(int, const char __user *, int, umode_t);
#endif
/* The function we will replace sys_open (the function called when you
/* The function we will replace sys_openat (the function called when you
* call the open system call) with. To find the exact prototype, with
* the number and type of arguments, we find the original function first
* (it is at fs/open.c).
@ -87,25 +93,41 @@ static asmlinkage int (*original_call)(const char __user *, int, umode_t);
* wreck havoc and require programs to be recompiled, since the system
* calls are the interface between the kernel and the processes).
*/
static asmlinkage int our_sys_open(const char __user *filename, int flags,
umode_t mode)
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long our_sys_openat(const struct pt_regs *regs)
#else
static asmlinkage long our_sys_openat(int dfd, const char __user *filename,
int flags, umode_t mode)
#endif
{
int i = 0;
char ch;
if (__kuid_val(current_uid()) != uid)
goto orig_call;
/* Report the file, if relevant */
pr_info("Opened file by %d: ", uid);
do {
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
get_user(ch, (char __user *)regs->si + i);
#else
get_user(ch, (char __user *)filename + i);
#endif
i++;
pr_info("%c", ch);
} while (ch != 0);
pr_info("\n");
/* Call the original sys_open - otherwise, we lose the ability to
orig_call:
/* Call the original sys_openat - otherwise, we lose the ability to
* open files.
*/
return original_call(filename, flags, mode);
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
return original_call(regs);
#else
return original_call(dfd, filename, flags, mode);
#endif
}
static unsigned long **acquire_sys_call_table(void)
@ -192,10 +214,10 @@ static int __init syscall_start(void)
disable_write_protection();
/* keep track of the original open function */
original_call = (void *)sys_call_table[__NR_open];
original_call = (void *)sys_call_table[__NR_openat];
/* use our open function instead */
sys_call_table[__NR_open] = (unsigned long *)our_sys_open;
/* use our openat function instead */
sys_call_table[__NR_openat] = (unsigned long *)our_sys_openat;
enable_write_protection();
@ -210,7 +232,7 @@ static void __exit syscall_end(void)
return;
/* Return the system call back to normal */
if (sys_call_table[__NR_open] != (unsigned long *)our_sys_open) {
if (sys_call_table[__NR_openat] != (unsigned long *)our_sys_openat) {
pr_alert("Somebody else also played with the ");
pr_alert("open system call\n");
pr_alert("The system may be left in ");
@ -218,7 +240,7 @@ static void __exit syscall_end(void)
}
disable_write_protection();
sys_call_table[__NR_open] = (unsigned long *)original_call;
sys_call_table[__NR_openat] = (unsigned long *)original_call;
enable_write_protection();
msleep(2000);

View File

@ -1529,24 +1529,24 @@ For more information, check out the following:
The source code here is an example of such a kernel module.
We want to ``spy'' on a certain user, and to \cpp|pr_info()| a message whenever that user opens a file.
Towards this end, we replace the system call to open a file with our own function, called \cpp|our_sys_open|.
Towards this end, we replace the system call to open a file with our own function, called \cpp|our_sys_openat|.
This function checks the uid (user's id) of the current process, and if it is equal to the uid we spy on, it calls \cpp|pr_info()| to display the name of the file to be opened.
Then, either way, it calls the original \cpp|open()| function with the same parameters, to actually open the file.
Then, either way, it calls the original \cpp|openat()| function with the same parameters, to actually open the file.
The \cpp|init_module| function replaces the appropriate location in \cpp|sys_call_table| and keeps the original pointer in a variable.
The \cpp|cleanup_module| function uses that variable to restore everything back to normal.
This approach is dangerous, because of the possibility of two kernel modules changing the same system call.
Imagine we have two kernel modules, A and B. A's open system call will be \cpp|A_open| and B's will be \cpp|B_open|.
Now, when A is inserted into the kernel, the system call is replaced with \cpp|A_open|, which will call the original \cpp|sys_open| when it is done.
Next, B is inserted into the kernel, which replaces the system call with \cpp|B_open|, which will call what it thinks is the original system call, \cpp|A_open|, when it's done.
Imagine we have two kernel modules, A and B. A's openat system call will be \cpp|A_openat| and B's will be \cpp|B_openat|.
Now, when A is inserted into the kernel, the system call is replaced with \cpp|A_openat|, which will call the original \cpp|sys_openat| when it is done.
Next, B is inserted into the kernel, which replaces the system call with \cpp|B_openat|, which will call what it thinks is the original system call, \cpp|A_openat|, when it's done.
Now, if B is removed first, everything will be well --- it will simply restore the system call to \cpp|A_open|, which calls the original.
Now, if B is removed first, everything will be well --- it will simply restore the system call to \cpp|A_openat|, which calls the original.
However, if A is removed and then B is removed, the system will crash.
A's removal will restore the system call to the original, \cpp|sys_open|, cutting B out of the loop.
Then, when B is removed, it will restore the system call to what it thinks is the original, \cpp|A_open|, which is no longer in memory.
A's removal will restore the system call to the original, \cpp|sys_openat|, cutting B out of the loop.
Then, when B is removed, it will restore the system call to what it thinks is the original, \cpp|A_openat|, which is no longer in memory.
At first glance, it appears we could solve this particular problem by checking if the system call is equal to our open function and if so not changing it at all (so that B won't change the system call when it is removed), but that will cause an even worse problem.
When A is removed, it sees that the system call was changed to \cpp|B_open| so that it is no longer pointing to \cpp|A_open|, so it will not restore it to \cpp|sys_open| before it is removed from memory.
Unfortunately, \cpp|B_open| will still try to call \cpp|A_open| which is no longer there, so that even without removing B the system would crash.
When A is removed, it sees that the system call was changed to \cpp|B_openat| so that it is no longer pointing to \cpp|A_openat|, so it will not restore it to \cpp|sys_openat| before it is removed from memory.
Unfortunately, \cpp|B_openat| will still try to call \cpp|A_openat| which is no longer there, so that even without removing B the system would crash.
Note that all the related problems make syscall stealing unfeasible for production use.
In order to keep people from doing potential harmful things \cpp|sys_call_table| is no longer exported.