Skip to content

Instantly share code, notes, and snippets.

@Theldus
Last active April 21, 2022 20:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Theldus/38ba08bfdbd17e9b5075d7b3806fbec7 to your computer and use it in GitHub Desktop.
Save Theldus/38ba08bfdbd17e9b5075d7b3806fbec7 to your computer and use it in GitHub Desktop.
Get infos from inodes/symlinks directly from the kernel, because why not?
// 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");
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