Last active
April 21, 2022 20:57
-
-
Save Theldus/38ba08bfdbd17e9b5075d7b3806fbec7 to your computer and use it in GitHub Desktop.
Get infos from inodes/symlinks directly from the kernel, because why not?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-2.0 OR MIT | |
#include <linux/fs.h> | |
#include <linux/kernel.h> | |
#include <linux/module.h> | |
#include <linux/mount.h> | |
#include <linux/namei.h> | |
#include <linux/miscdevice.h> | |
#include <fs/ext4/ext4.h> | |
#define REVERSE_MODES 0 | |
/* | |
* Notes, please read: | |
* | |
* This kernel modules do two things: | |
* - extract the in-memory inode for some symlink | |
* - read the path that symlink points to | |
* | |
* Why? | |
* Because I was curious... curious about how symlinks | |
* works. Also, it seemed fun to me to get file inode | |
* and retrieve info from it directly =). | |
* | |
* I must note that, this is very hacky and should not | |
* be used for anything serious. This kernel module is | |
* basically a stripped down version of the readlinkat | |
* syscall, but with several limitations: | |
* | |
* - EXT4 only | |
* - Only works for ext4 unencrypted fast symlinks | |
* | |
* I opted to 'bypass' the VFS and assume ext4 for a few | |
* reasons: I'd like to see with my own eyes how the | |
* symlink contents were extracted from fs as simply and | |
* succinctly as possible, without the abstractions the | |
* kernel puts on top of. | |
* | |
* Tested w/ Linux v5.4.186 on Slackware 15 | |
*/ | |
/* My buffers. */ | |
static char symlink_name[256]; | |
/* Target symlink to check. */ | |
static char symlink_target[256]; | |
static ssize_t symlink_size; | |
/* Dump message. */ | |
static char miscfs_msg[1024]; | |
#if REVERSE_MODES == 1 | |
static int new_write = 0; | |
/** | |
* @brief If regular file, set to symlink, if symlink, | |
* set to regular file. | |
* | |
* @param inode Target inode. | |
*/ | |
static void reverse_modes(struct inode *inode) | |
{ | |
if (!new_write) | |
return; | |
if (S_ISLNK(inode->i_mode)) { | |
spin_lock(&inode->i_lock); | |
inode->i_mode &= ~S_IFLNK; | |
inode->i_mode |= S_IFREG; | |
spin_unlock(&inode->i_lock); | |
} | |
else if (S_ISREG(inode->i_mode)) { | |
spin_lock(&inode->i_lock); | |
inode->i_mode &= ~S_IFREG; | |
inode->i_mode |= S_IFLNK; | |
spin_unlock(&inode->i_lock); | |
} | |
else | |
return; | |
mark_inode_dirty(inode); | |
} | |
#endif | |
/** | |
* @brief Conver a given timespec64 @p ts to seconds. | |
* | |
* @param ts Timespec to be converted. | |
* | |
* @return Time converted to seconds. | |
*/ | |
static long long timespec64_to_s(const struct timespec64 *ts) | |
{ | |
return ts->tv_sec + ts->tv_nsec/NSEC_PER_SEC; | |
} | |
/** | |
* @brief For a previously saved symlink name, recover some info | |
* about it and send to the userspace. | |
* | |
* @param file File to be written. | |
* @param buf Userspace buffer. | |
* @param size Userspace buffer size. | |
* @param ppos Current position. | |
* | |
* @param If success, return the amount of bytes transmitted, | |
* otherwise, a negative number. | |
*/ | |
static ssize_t misc_read(struct file *file, char __user *buf, | |
size_t size, loff_t *ppos) | |
{ | |
struct ext4_inode_info *i_info; | |
struct inode *inode; | |
mm_segment_t oldfs; | |
struct path path; | |
size_t amnt; | |
int error; | |
int empty; | |
int ret; | |
empty = 0; | |
oldfs = get_fs(); | |
set_fs(KERNEL_DS); | |
error = user_path_at_empty(AT_FDCWD, symlink_target, | |
LOOKUP_EMPTY, &path, &empty); | |
set_fs(oldfs); | |
if (error) { | |
pr_err("Unable to read symlink!\n"); | |
return -EINVAL; | |
} | |
inode = d_backing_inode(path.dentry); | |
i_info = EXT4_I(inode); | |
#if REVERSE_MODES == 1 | |
/* | |
* Reverse link modes, because its cool? | |
*/ | |
reverse_modes(inode); | |
#endif | |
if (!S_ISLNK(inode->i_mode)) { | |
pr_err("Not a symlink!\n"); | |
path_put(&path); | |
return -EINVAL; | |
} | |
amnt = min(sizeof(i_info->i_data), sizeof(symlink_name) - 1); | |
memset(symlink_name, 0, sizeof(symlink_name)); | |
memcpy(symlink_name, (char *)&i_info->i_data, amnt); | |
memset(miscfs_msg, 0, sizeof(miscfs_msg)); | |
ret = | |
snprintf(miscfs_msg, sizeof(miscfs_msg) - 1, | |
"File info: \n" | |
" i_mode: %x\n" | |
" i_ino: %lu\n" | |
" i_size: %lld\n" | |
" i_data: (%s)\n" | |
" i_uid: %u\n" | |
" i_gid: %u\n" | |
" i_flags: %u\n" | |
" i_nlink: %u\n" | |
" i_atime: %lld\n" | |
" i_mtime: %lld\n" | |
" i_ctime: %lld\n", | |
inode->i_mode, inode->i_ino, inode->i_size, | |
symlink_name, inode->i_uid.val, inode->i_gid.val, | |
inode->i_flags, inode->i_nlink, | |
timespec64_to_s(&inode->i_atime), | |
timespec64_to_s(&inode->i_mtime), | |
timespec64_to_s(&inode->i_ctime) | |
); | |
#if REVERSE_MODES == 1 | |
new_write = 0; | |
#endif | |
path_put(&path); | |
return simple_read_from_buffer(buf, size, ppos, miscfs_msg, | |
ret); | |
} | |
/** | |
* @brief Read a given string from @p buf relative to a symbolic | |
* link and save into the kernel memory. | |
* | |
* @param file File to be read. | |
* @param buf Userspace buffer. | |
* @param size Buffer size. | |
* @param ppos Current buffer position. | |
* | |
* @return If success, returns the amount of bytes written, otherwise, | |
* a negative number. | |
*/ | |
static ssize_t misc_write(struct file *file, const char __user *buf, | |
size_t size, loff_t *ppos) | |
{ | |
ssize_t ret = -EINVAL; | |
loff_t pos; | |
if (size > sizeof(symlink_target) - 1) | |
goto out; | |
pos = 0; | |
symlink_size = 0; | |
memset(symlink_target, 0, sizeof(symlink_target)); | |
ret = simple_write_to_buffer(symlink_target, | |
sizeof(symlink_target) - 1, &pos, buf, size); | |
if (ret >= 0) | |
symlink_size = ret; | |
#if REVERSE_MODES == 1 | |
new_write = 1; | |
#endif | |
out: | |
return ret; | |
} | |
static const struct file_operations misc_fops = { | |
.read = misc_read, | |
.write = misc_write | |
}; | |
/* | |
* My inode reader device =) | |
* you can find it at: /dev/inode_reader | |
*/ | |
static struct miscdevice misc_dev = { | |
.minor = MISC_DYNAMIC_MINOR, | |
.name = "inode_reader", | |
.fops = &misc_fops | |
}; | |
/* | |
* Initialize the device. | |
*/ | |
static int inode_reader_init(void) | |
{ | |
int ret; | |
ret = misc_register(&misc_dev); | |
if (ret != 0) { | |
pr_err("Unable to initialize misc_device\n"); | |
return ret; | |
} | |
pr_info("Initializing inode_reader...\n"); | |
return 0; | |
} | |
/* | |
* Finish the device. | |
*/ | |
static void inode_reader_exit(void) | |
{ | |
misc_deregister(&misc_dev); | |
pr_info("Exiting inode_reader...\n"); | |
} | |
module_init(inode_reader_init); | |
module_exit(inode_reader_exit); | |
MODULE_LICENSE("Dual MIT/GPL"); | |
MODULE_AUTHOR("Davidson Francis <davidsondfgl@gmail.com>"); | |
MODULE_DESCRIPTION("Attempt to read a symlink inside kernel, just 4fun"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CFLAGS_inodereader.o := -DDEBUG | |
obj-m += inodereader.o | |
# | |
# Kernel source directory, default or not | |
# | |
# Idea taken from: Documentation/kbuild/modules.txt | |
# | |
KDIR ?= /lib/modules/`uname -r`/build | |
# ext4 path | |
EXTRA_CFLAGS += -I$(KDIR)/ | |
all: | |
make -C $(KDIR) M=$(PWD) modules | |
clean: | |
make -C $(KDIR) M=$(PWD) clean |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment