返回值是没有拷贝成功的字节数,拷贝成功返回0。
static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (likely(check_copy_size(to, n, false)))
n = _copy_from_user(to, from, n);
return n;
}
通过/proc/hook/hook_control
传入的命令行参数,总共count个字节,其中count包含命令行中输入的回车符,在做参数解析时需要将最后一个字节替换成00。
# 0a -> 00
[ 109.100142] Module init
[ 126.618392] raw data: 0000000037ecc164: 76 66 73 5f 72 65 61 64 20 31 20 32 20 33 0a vfs_read 1 2 3.
[ 772.658773] Module init
[ 775.256314] raw data: 000000007f19446e: 76 66 73 5f 72 65 61 64 20 31 20 32 20 33 00 vfs_read 1 2 3.
proc_writ
返回值为写成功的字节数,如果返回值为0,会一直重试写?
ssize_t (*proc_write)(struct file *, const char __user *, size_t, loff_t *);
Linux 内核中的字符串分割函数lib/string.c
#ifndef __HAVE_ARCH_STRSEP
/**
* strsep - Split a string into tokens
* @s: The string to be searched
* @ct: The characters to search for
*
* strsep() updates @s to point after the token, ready for the next call.
*
* It returns empty tokens, too, behaving exactly like the libc function
* of that name. In fact, it was stolen from glibc2 and de-fancy-fied.
* Same semantics, slimmer shape. ;)
*/
char *strsep(char **s, const char *ct)
{
char *sbegin = *s;
char *end;
if (sbegin == NULL)
return NULL;
end = strpbrk(sbegin, ct);
if (end)
*end++ = '\0';
*s = end;
return sbegin;
}
EXPORT_SYMBOL(strsep);
#endif
内存拷贝函数memcpy
#ifndef __HAVE_ARCH_MEMCPY
/**
* memcpy - Copy one area of memory to another
* @dest: Where to copy to
* @src: Where to copy from
* @count: The size of the area.
*
* You should not use this function to access IO space, use memcpy_toio()
* or memcpy_fromio() instead.
*/
void *memcpy(void *dest, const void *src, size_t count)
{
char *tmp = dest;
const char *s = src;
while (count--)
*tmp++ = *s++;
return dest;
}
EXPORT_SYMBOL(memcpy);
#endif
Linux 内核将字符类型转换成整型/lib/string.c
。
/**
* kstrtoint - convert a string to an int
* @s: The start of the string. The string must be null-terminated, and may also
* include a single newline before its terminating null. The first character
* may also be a plus sign or a minus sign.
* @base: The number base to use. The maximum supported base is 16. If base is
* given as 0, then the base of the string is automatically detected with the
* conventional semantics - If it begins with 0x the number will be parsed as a
* hexadecimal (case insensitive), if it otherwise begins with 0, it will be
* parsed as an octal number. Otherwise it will be parsed as a decimal.
* @res: Where to write the result of the conversion on success.
*
* Returns 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
* Preferred over simple_strtol(). Return code must be checked.
*/
int kstrtoint(const char *s, unsigned int base, int *res)
{
long long tmp;
int rv;
rv = kstrtoll(s, base, &tmp);
if (rv < 0)
return rv;
if (tmp != (int)tmp)
return -ERANGE;
*res = tmp;
return 0;
}
EXPORT_SYMBOL(kstrtoint);
proc_interface.c
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define MAX_PARAM 5
#define MAX_STRING_LEN 64
static struct proc_dir_entry *parent;
static struct proc_dir_entry *node;
// single_open的回调函数,实现了seq_operaions的show实例
static int show_proc_info(struct seq_file *p, void *v)
{
seq_printf(p, "%s\n", "Seq_printf test string");
return 0;
}
static int hook_open(struct inode *inode, struct file *file)
{
return single_open(file, show_proc_info, NULL);
}
static void get_param(char *buffer, int *argc, char (*argv)[MAX_STRING_LEN])
{
unsigned int index = 0;
char *token = NULL;
while ((token = strsep(&buffer, " ")) != NULL) {
memcpy(argv[index], token, MAX_STRING_LEN);
index++;
}
*argc = index;
}
static ssize_t hook_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos)
{
int number;
char *tmp = NULL;
int argc, ret = 0;
char argv[MAX_PARAM][MAX_STRING_LEN];
tmp = (char *)kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp) {
ret = -ENOMEM;
goto out;
}
memset((char*)tmp, 0, PAGE_SIZE);
ret = copy_from_user(tmp, buffer, count);
if (ret) {
ret = -EFAULT;
goto out;
}
// 用户态输入的buffer还包含回车符,替换成\0
tmp[count -1] = '\0';
print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
16, 1, tmp, count, true);
get_param(tmp, &argc, argv);
if (argc >= MAX_PARAM) {
ret = EINVAL;
goto out;
}
printk("argc -> %d argv[0] -> %s argv[1] -> %s\n",
argc, argv[0], argv[1]);
if (kstrtoint(argv[1], 0, &number)) {
printk("Trans string to int failed\n");
goto out;
}
printk("argc -> %d number -> %d\n", argc, number);
out:
kfree(tmp);
return count;
}
static int hook_release(struct inode *inode, struct file *file)
{
return single_release(inode, file);
}
// .proc_read初始化为seq_read,如果自己实现,将会导致seq_printf失效。
static const struct proc_ops hook_fops = {
.proc_open = hook_open,
.proc_read = seq_read,
.proc_write = hook_write,
.proc_release = hook_release,
};
int proc_interface_init(void)
{
parent = proc_mkdir("hook", NULL);
if (IS_ERR(parent)) {
printk("Create /proc/hook dir failed\n");
return -1;
}
node = proc_create("hook_control", 0, parent, &hook_fops);
if (IS_ERR(node)) {
printk("Create /proc/hook/hook_control failed\n");
goto out;
}
return 0;
out:
remove_proc_entry("hook", NULL);
return -1;
}
void proc_interface_exit(void)
{
proc_remove(parent);
}
module.c
#include <linux/module.h>
extern int proc_interface_init(void);
extern void proc_interface_exit(void);
static int __init proc_fs_init(void)
{
int ret = 0;
printk("Module init\n");
ret = proc_interface_init();
if (ret != 0) {
return -1;
}
return 0;
}
static void __exit proc_fs_exit(void)
{
proc_interface_exit();
printk("Module exit\n");
}
module_init(proc_fs_init);
module_exit(proc_fs_exit);
MODULE_LICENSE("GPL");
Makefile
obj-m := proc_control.o
proc_control-y += module.o
proc_control-y += proc_interface.o
BASEINCLUDE ?= /lib/modules/`uname -r`/build
all:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
clean:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) clean;
rm -f *.ko;
查看内核打印信息
[ 1562.028889] raw data: 00000000c77375f0: 76 66 73 5f 72 65 61 64 20 31 20 31 20 31 00 vfs_read 1 1 1.
[ 1562.028892] argc -> 4 argv[0] -> vfs_read argv[1] -> 1
[ 1562.028893] argc -> 4 number -> 1