Arguments in syscall intercept using loadable kernel module seem to be broken - c

First post so I apologize for the possibly low quality explanation.
I was trying to write a loadable kernel module that does nothing but intercept syscalls to SYS_open, print the arguments to KERN_INFO and then forward the arguments to the real syscall.
The forwarding part seems to be working just fine, but I'm having issues with the printing, arguments seem to be broken, from the syscall interceptor function's perspective.
Following are the pointer to the real open syscall as well as the interceptor definition.
asmlinkage int (*real_open) (const char __user *, int, umode_t);
asmlinkage int fake_open(const char __user *filename, int flags, umode_t mode)
{
printk(KERN_INFO "interceptor: open() with flags = %d\n", flags);
return real_open(filename, flags, mode);
}
This is the syscall I'm testing:
syscall(SYS_open, argv[1], 3187236);
Which leads to the following call, according to strace:
open("test", O_RDONLY|O_TRUNC|__O_SYNC|O_LARGEFILE|O_PATH|FASYNC|0x24) = -1 ENOENT (No such file or directory)
And the information printed by the interceptor:
[18191.407899] interceptor: open() with flags = 0
As you can see, the flags argument is equal to 0, even though I passed 3187236 as flags.
What's even weirder, the real open syscall seems to have no issue in dealing with the arguments.
Any kind of help is appreciated since I'm pretty much stuck here.
Here's the full module code in case it's of any help:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/futex.h>
#include <linux/highmem.h>
#include <asm/unistd.h>
#include <linux/slab.h>
/*
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
*/
unsigned long long *sys_call_table = (unsigned long long*) 0xffffffffaf800260; //sudo cat /proc/kallsyms | grep sys_call_table (/boot/System.map)
asmlinkage int (*real_open) (const char __user *, int, umode_t);
asmlinkage int fake_open(const char __user *filename, int flags, umode_t mode)
{
printk("interceptor: open() with flags = %d\n", flags);
return real_open(filename, flags, mode);
}
//make the memory page writable
int make_rw(unsigned long long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);
if(pte->pte & ~_PAGE_RW)
pte->pte |= _PAGE_RW;
return 0;
}
//make the memory page read only
int make_ro(unsigned long long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);
pte->pte &= ~_PAGE_RW;
return 0;
}
static int __init init(void)
{
printk(KERN_INFO "Attempting to install hook.\n");
make_rw((unsigned long long) sys_call_table);
real_open = (void*) sys_call_table[__NR_open];
sys_call_table[__NR_open] = (unsigned long long) fake_open;
make_ro((unsigned long long) sys_call_table);
return 0; //no error
}
static void __exit clean(void)
{
printk(KERN_INFO "Uninstalling hook.\n");
make_rw((unsigned long long) sys_call_table);
sys_call_table[__NR_open] = (unsigned long long) real_open;
make_ro((unsigned long long) sys_call_table);
}
module_init(init);
module_exit(clean);
MODULE_LICENSE("GPL");

UPDATE:
Kernel version 4.17 and up requires parameters to be passed through a pt_regs struct. Previous code was good up to 4.16.
asmlinkage long (*real_open) (const struct pt_regs *);
asmlinkage long fake_open(const struct pt_regs *regs)
{
printk("interceptor: open() with flags = %ld\n", regs->si);
return real_open(regs);
}
More information: https://github.com/milabs/khook/issues/3
Thanks for everyone who contributed in the comments!

Related

How to call system call in kernel space?

I'm trying to trigger system call in kernel space and it works fine if the system call does not take arguments such as getpid().
The method how I do it:
get the address of system table
static void **syscall_table;
use it with system call number you want and as a function pointer:
typedef long (*sys_call_ptr_t)(const struct __user pt_regs *);
// call system call
((sys_call_ptr_t *)syscall_table)[system_call_number](reg);
if system call have argument, store them into regs before calling it:
struct __user pt_regs *reg = kmalloc....;
reg->di = ...
reg->si = ...
Currently, I'm trying to use write but it fails.
write(int fd, const void *buf, size_t count);
For buf, I've tried both user space address and kernel space address. count may not be a problem. So, I guess problem maybe occur in file descriptor (maybe fd is different between in lower level's and user space's). For basic testing, I only want to write text into terminal, so fd should be 1 (at least in user space).
There're two questions here:
In some reason, I need to stick to the method calling syscall described above. Is it reasonable or any step I miss and cause failure of using write?
If something wrong when I called write? Does the problem come from fd? If so, how do I get the corresponding fd with 1 in user space?
Foreword
By definition, a system call is a service offered by the system to the user space applications. When one is running inside the system, he should not call
a service destined to user space. Hence, this is unadvised to make it.
First try with a kernel space buffer
The write() system call is defined in fs/read_write.c. It calls ksys_write() which calls vfs_write():
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
if (unlikely(!access_ok(buf, count)))
return -EFAULT;
ret = rw_verify_area(WRITE, file, pos, count);
if (!ret) {
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT;
file_start_write(file);
ret = __vfs_write(file, buf, count, pos);
if (ret > 0) {
fsnotify_modify(file);
add_wchar(current, ret);
}
inc_syscw(current);
file_end_write(file);
}
return ret;
}
[...]
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos, *ppos = file_ppos(f.file);
if (ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_write(f.file, buf, count, ppos);
if (ret >= 0 && ppos)
f.file->f_pos = pos;
fdput_pos(f);
}
return ret;
}
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
return ksys_write(fd, buf, count);
}
The file descriptor passed as first parameter is not a problem. The value passed from user space is used to retrieve the file structure of the output file (in ksys_write()). But the second parameter must reference a user space memory area.
In vfs_write(), a check is done on the second parameter:
if (unlikely(!access_ok(buf, count)))
return -EFAULT;
access_ok() checks if the buffer is in the user-level space. Hence, if you
pass an address referencing the kernel space, the returned code from read() will be -EFAULT (-14).
The example below is a simple module calling the write() system call with a kernel space buffer. On x86_64, the convention for the parameters of the system calls are:
RDI = arg#0
RSI = arg#1
RDX = arg#2
R10 = arg#3
R8 = arg#4
R9 = arg#5
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/ptrace.h>
#include <linux/socket.h>
#include <linux/kallsyms.h>
MODULE_LICENSE("GPL");
typedef int (* syscall_wrapper)(struct pt_regs *);
unsigned long sys_call_table_addr;
#define DEV_NAME "[DEVICE2]"
#define DEV_STR DEV_NAME "String from driver"
static char buf[1024];
static int __init device2_init(void) {
syscall_wrapper write_syscall;
int rc;
struct pt_regs param;
printk(KERN_INFO DEV_NAME "module has been loaded\n");
sys_call_table_addr = kallsyms_lookup_name("sys_call_table");
printk(KERN_INFO DEV_NAME "sys_call_table#%lx\n", sys_call_table_addr);
write_syscall = ((syscall_wrapper *)sys_call_table_addr)[__NR_write];
/*
Call to write() system call with a kernel space buffer
*/
snprintf(buf, sizeof(buf), "%s\n", DEV_STR);
param.di = 1;
param.si = (unsigned long)buf;
param.dx = strlen(buf);
rc = (* write_syscall)(&param);
printk(KERN_INFO DEV_NAME "write() with a kernel space buffer = %d\n", rc);
return 0;
}
static void __exit device2_exit(void) {
printk(KERN_INFO DEV_NAME "module has been unloaded\n");
}
module_init(device2_init);
module_exit(device2_exit);
At module insertion time, we can verify that the system call returns -EFAULT:
$ sudo insmod ./device2.ko
$ dmesg
[15716.262977] [DEVICE2]module has been loaded
[15716.270566] [DEVICE2]sys_call_table#ffffffff926013a0
[15716.270568] [DEVICE2]write() with a kernel space buffer = -14
But the same module with a system call like dup() which involves a file descriptor but no user space buffers, this works. Let's change the previous code with:
static int __init device2_init(void) {
syscall_wrapper write_syscall;
syscall_wrapper dup_syscall;
syscall_wrapper close_syscall;
int rc;
struct pt_regs param;
printk(KERN_INFO DEV_NAME "module has been loaded\n");
sys_call_table_addr = kallsyms_lookup_name("sys_call_table");
printk(KERN_INFO DEV_NAME "sys_call_table#%lx\n", sys_call_table_addr);
write_syscall = ((syscall_wrapper *)sys_call_table_addr)[__NR_write];
dup_syscall = ((syscall_wrapper *)sys_call_table_addr)[__NR_dup];
close_syscall = ((syscall_wrapper *)sys_call_table_addr)[__NR_close];
/*
Call to write() system call with a kernel space buffer
*/
snprintf(buf, sizeof(buf), "%s\n", DEV_STR);
param.di = 1;
param.si = (unsigned long)buf;
param.dx = strlen(buf);
rc = (* write_syscall)(&param);
printk(KERN_INFO DEV_NAME "write() with a kernel space buffer = %d\n", rc);
/*
Call to dup() system call
*/
param.di = 1;
rc = (* dup_syscall)(&param);
printk(KERN_INFO DEV_NAME "dup() = %d\n", rc);
/*
Call to close() system call
*/
param.di = 0;
rc = (* close_syscall)(&param);
printk(KERN_INFO DEV_NAME "close() = %d\n", rc);
/*
Call to dup() system call ==> Must return 0 as it is available
*/
param.di = 1;
rc = (* dup_syscall)(&param);
printk(KERN_INFO DEV_NAME "dup() = %d\n", rc);
return 0;
}
The result of dup() is OK:
$ sudo insmod ./device2.ko
$ dmesg
[17444.098469] [DEVICE2]module has been loaded
[17444.106935] [DEVICE2]sys_call_table#ffffffff926013a0
[17444.106937] [DEVICE2]write() with a kernel space buffer = -14
[17444.106939] [DEVICE2]dup() = 4
[17444.106940] [DEVICE2]close() = 0
[17444.106940] [DEVICE2]dup() = 0
The first call to dup() returns 4 because the current process is insmod. The latter opened the module file and got file descriptor 3. Hence, the first available file descriptor is 4. The second call to dup() returns 0 because we closed the file descriptor 0.
Second try with a user space buffer
To use a user space buffer, let's add some file operations to the kernel module (open(), release() and write()). In the write() entry point we echo back what is passed from user space into stderr (file descriptor 2) using the user space buffer passed to the write() entry point:
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/ptrace.h>
#include <linux/socket.h>
#include <linux/kallsyms.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
typedef int (* syscall_wrapper)(struct pt_regs *);
static unsigned long sys_call_table_addr;
#define DEV_NAME "[DEVICE2]"
static syscall_wrapper write_syscall;
static ssize_t device2_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
struct pt_regs param;
int rc;
printk(KERN_INFO DEV_NAME "write %p, %zu\n", buff, len);
/*
Call to write() system call to echo the write to stderr
*/
param.di = 2;
param.si = (unsigned long)buff;
param.dx = len;
rc = (* write_syscall)(&param);
printk(KERN_INFO DEV_NAME "write() = %d\n", rc);
return len; // <-------------- To stop the write
}
static int device2_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO DEV_NAME "open\n");
return 0;
}
static int device2_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO DEV_NAME "released\n");
return 0;
}
static const struct file_operations fops =
{
.owner= THIS_MODULE,
.write=device2_write,
.open= device2_open,
.release= device2_release
};
struct cdev *device_cdev;
dev_t deviceNumbers;
static int __init device2_init(void) {
int rc;
printk(KERN_INFO DEV_NAME "module has been loaded\n");
// This returns the major number chosen dynamically in deviceNumbers
rc = alloc_chrdev_region(&deviceNumbers, 0, 1, DEV_NAME);
if (rc < 0) {
printk(KERN_ALERT DEV_NAME "Error registering: %d\n", rc);
return -1;
}
device_cdev = cdev_alloc();
cdev_init(device_cdev, &fops);
cdev_add(device_cdev, deviceNumbers, 1);
printk(KERN_INFO DEV_NAME "initialized (major number is %d)\n", MAJOR(deviceNumbers));
sys_call_table_addr = kallsyms_lookup_name("sys_call_table");
printk(KERN_INFO DEV_NAME "sys_call_table#%lx\n", sys_call_table_addr);
write_syscall = ((syscall_wrapper *)sys_call_table_addr)[__NR_write];
printk(KERN_INFO DEV_NAME "write_syscall#%p\n", write_syscall);
return 0;
}
static void __exit device2_exit(void) {
printk(KERN_INFO DEV_NAME "module has been unloaded\n");
}
module_init(device2_init);
module_exit(device2_exit);
The loading of the module:
$ sudo insmod device2.ko
$ dmesg
[ 2255.183196] [DEVICE2]module has been loaded
[ 2255.183202] [DEVICE2]initialized (major number is 508)
[ 2255.193255] [DEVICE2]sys_call_table#ffffffffbcc013a0
[ 2255.193256] [DEVICE2]write_syscall#0000000030394929
Make the device entry in the file system to be able to write into it:
$ sudo mknod /dev/device2 c 508 0
$ sudo chmod 666 /dev/device2
$ sudo ls -l /dev/device2
crw-rw-rw- 1 root root 508, 0 janv. 24 16:55 /dev/device2
The writing into the device triggers the expected echo on stderr:
$ echo "qwerty for test purposes" > /dev/device2
qwerty for test purposes
$ echo "another string" > /dev/device2
another string
$ dmesg
[ 2255.183196] [DEVICE2]module has been loaded
[ 2255.183202] [DEVICE2]initialized (major number is 508)
[ 2255.193255] [DEVICE2]sys_call_table#ffffffffbcc013a0
[ 2255.193256] [DEVICE2]write_syscall#0000000030394929
[ 2441.674250] [DEVICE2]open
[ 2441.674268] [DEVICE2]write 0000000032fb5249, 25
[ 2441.674281] [DEVICE2]write() = 25
[ 2441.674286] [DEVICE2]released
[ 2475.538140] [DEVICE2]open
[ 2475.538159] [DEVICE2]write 0000000032fb5249, 15
[ 2475.538171] [DEVICE2]write() = 15
[ 2475.538175] [DEVICE2]released

Uneven behavior in different system calls hooking

I am working on a project in which i have hooked the system open call. When a user attempts to open a file i want sys_open to block the action if the current task (pid or tgid that "black listed" ) has potential to leak the file out of the host.
Any ways, the hooking itself worked fine on sys_read and sys_write (I have some printk inside the fake function as an indicator).
but, when i try the hooking on the sys_open function, nothing is printed out - means that the override not succeeded.
I printed out the address of the sys call before and after the override so this might not be the issue.
I am confused about what can cause that uneven behavior when hooking different functions.
will be glad for some input here.
thanks !
dmesg output examples:
when hooked write -
...
[ 2989.500485] in my write ...
[ 2989.500585] in my write ...
when hooked open, noting printed, but here some "debug" output -
[ 890.709696] address found 00000000103d42f6
[ 890.709697] Address before - 0000000006d29c3a
[ 890.709698] Address after - 00000000a5117c6a
[ 948.533339] BYE !!!
using lubuntu vm (kernel v 4.15.0.20).
here is the source code:
#include <linux/init.h> // Macros used to mark up functions e.g., __init __exit
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/syscalls.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <asm/page.h>
#include <linux/kallsyms.h>
#include <linux/semaphore.h>
#include <asm/cacheflush.h>
#include <linux/set_memory.h>
#include <linux/cred.h>
#include <linux/user.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ABC");
MODULE_VERSION("0.1");
asmlinkage long (*original_call)( char __user *filename, int flags, umode_t mode); // for read or write: (unsigned int fd, char __user *buf, size_t count);
asmlinkage long my_sys_READ(unsigned int fd, char __user *buf, size_t count);
asmlinkage long my_sys_WRITE(unsigned int fd, char __user *buf, size_t count);
asmlinkage long my_sys_OPEN( char __user *filename, int flags, umode_t mode);
unsigned long* find_sys_call_table(void);
void set_page_rw( unsigned long addr);
void set_page_ro( unsigned long addr);
const struct cred *_cred = NULL ;
struct user_struct *user =NULL ;
unsigned long* sys_call_table = NULL;
void set_page_rw(unsigned long addr)
{
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;
}
void set_page_ro( unsigned long addr)
{
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
pte->pte = pte->pte &~_PAGE_RW;
}
/*
asmlinkage long my_sys_READ(unsigned int fd, char __user *buf, size_t count)
{
//_cred = current_cred();
user = get_current_user();
if( (int)(*user).uid.val == uid )
{
printk(KERN_ALERT"in my read ... hacked !");
return original_call(fd, buf, count);
}
printk(KERN_ALERT"in my read ... hacked !");
return original_call(fd, buf, count);
}
asmlinkage long my_sys_WRITE(unsigned int fd, char __user *buf, size_t count)
{
//_cred = current_cred();
user = get_current_user();
if( (int)(*user).uid.val == uid )
{
printk(KERN_ALERT"in my write ... hacked !");
return original_call(fd, buf, count);
}
printk(KERN_ALERT"in my write ... hacked !");
return original_call(fd, buf, count);
}
*/
asmlinkage long my_sys_OPEN( char __user *filename, int flags, umode_t mode)
{
printk(KERN_ALERT"in my open ... hacked !");
return original_call(filename, flags, mode);
}
unsigned long* find_sys_call_table(void)
{
return (unsigned long *)kallsyms_lookup_name("sys_call_table");
}
int init_module()
{
printk(KERN_ALERT "I'm dangerous. I hope you did a ");
printk(KERN_ALERT "sync before you insmod'ed me.\n");
sys_call_table = find_sys_call_table();
printk(KERN_INFO"address found %p \n",sys_call_table);
original_call = (void *)sys_call_table[__NR_open];
set_page_rw((unsigned long)sys_call_table);
printk(KERN_INFO" Address before - %p", (void *)sys_call_table[__NR_open]);
sys_call_table[__NR_open] = (unsigned long)my_sys_OPEN;
printk(KERN_INFO" Address after - %p", (void *)sys_call_table[__NR_open]);
return 0;
}
/*
* Cleanup − unregister the appropriate file from /proc
*/
void cleanup_module()
{
/*
* Return the system call back to normal
*/
if (sys_call_table[__NR_open] != (unsigned long)my_sys_OPEN) {
printk(KERN_ALERT "Somebody else also played with the ");
printk(KERN_ALERT "open system call\n");
}
printk(KERN_ALERT "BYE !!!\n");
sys_call_table[__NR_open] = (unsigned long)original_call;
}
Your Ubuntu version is based on glibc 2.27.
glibc version 2.26 switched to implementing open with openat:
commit b41152d716ee9c5ba34495a54e64ea2b732139b5
Author: Adhemerval Zanella <adhemerval.zanella#linaro.org>
Date: Fri Nov 11 15:00:03 2016 -0200
Consolidate Linux open implementation
This patch consolidates the open Linux syscall implementation on
sysdeps/unix/sysv/linux/open{64}.c. The changes are:
1. Remove open{64} from auto-generation syscalls.list.
2. Add a new open{64}.c implementation. For architectures that
define __OFF_T_MATCHES_OFF64_T the default open64 will create
alias to required open symbols.
3. Use __NR_openat as default syscall for open{64}.
You will have to hook openat in addition to open as a result.
Note that the Linux kernel provides a proper interface for this, in the form of the fanotify interface. If you use that, you will not have to worry about such details.

How to print to console (Linux) without the standard library (libc)

I'm not using the standard library, since my target x86 Linux distro is very limited.
#include <unistd.h>
void _start () {
const char msg[] = "Hello world";
write( STDOUT_FILENO, msg, sizeof( msg ) - 1 );
}
I want to print text to console, but I can't, is there any other way to do this.
The code above wont work because it depends on standard library
gcc Test.cpp -o Test -nostdlib
If you don't have libc, then you need to craft a write() system call from scratch to write to the standard output.
See this resource for the details: http://weeb.ddns.net/0/programming/c_without_standard_library_linux.txt
Code example from the above link:
void* syscall5(
void* number,
void* arg1,
void* arg2,
void* arg3,
void* arg4,
void* arg5
);
typedef unsigned long int uintptr; /* size_t */
typedef long int intptr; /* ssize_t */
static
intptr write(int fd, void const* data, uintptr nbytes)
{
return (intptr)
syscall5(
(void*)1, /* SYS_write */
(void*)(intptr)fd,
(void*)data,
(void*)nbytes,
0, /* ignored */
0 /* ignored */
);
}
int main(int argc, char* argv[])
{
write(1, "hello\n", 6);
return 0;
}

IOCTL call not working with driver

I wrote a IOCTL driver and a corresponding ioctl app with a header file containing commands.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "myioctl.h"
#include <linux/ioctl.h>
#define NAME MyCharDevice
//Function Prototypes
int NAME_open(struct inode *inode, struct file *filp);
int NAME_release(struct inode *indoe, struct file *filp);
ssize_t NAME_write(struct file *filp, char __user *Ubuff, size_t count, loff_t *offp);
ssize_t NAME_read(struct file *filp, char __user *Ubuff, size_t count, loff_t *offp);
int NAME_flush (struct file *filp);
int NAME_IOCTL (struct inode *inode, struct file *filp, unsigned long cmd, unsigned long val);
//Structure that defines the operations that the driver provides
struct file_operations fops =
{
.owner = THIS_MODULE,
.open = NAME_open,
.read = NAME_read,
.write = NAME_write,
.unlocked_ioctl = NAME_IOCTL,
.release = NAME_release,
.flush = NAME_flush,
};
//Structure for a character driver
struct cdev *my_cdev;
//Init Module
static int __init CharDevice_init(void)
{
int result;
int MAJOR,MINOR;
dev_t Mydev;
Mydev = MKDEV(255,0);//Create a device number
MAJOR=MAJOR(Mydev);
MINOR=MINOR(Mydev);
printk("\nThe Major Number is %d...THe Minor Number is %d\n",MAJOR,MINOR);
result=register_chrdev_region(Mydev,1,"MyCharDevice");//register device region.....
if(result<0)
{
printk(KERN_ALERT "\nThe Region requested for is not obtainable\n");
return(-1);
}
my_cdev = cdev_alloc();//allocate memory to Char Device structure
my_cdev->ops = &fops;//link our file operations to the char device
result=cdev_add(my_cdev,Mydev,1);//Notify the kernel abt the new device
if(result<0)
{
printk(KERN_ALERT "\nThe Char Devide has not been created......\n");
return (-1);
}
return 0;
}
//Cleanup Module
void __exit CharDevice_exit(void)
{
dev_t Mydev;
int MAJOR,MINOR;
Mydev=MKDEV(255,0);
MAJOR=MAJOR(Mydev);
MINOR=MINOR(Mydev);
printk("\nThe Major Number is %d...THe Minor Number is %d\n",MAJOR,MINOR);
unregister_chrdev_region(Mydev,1);//unregister the device numbers and the device created
cdev_del(my_cdev);
printk(KERN_ALERT "\nI have unregistered the stuff that was allocated.....Goodbye for ever.....\n");
return;
}
int NAME_IOCTL (struct inode *inode, struct file *filp, unsigned long cmd, unsigned long val)
{
int BAUD=0, STOP;
char PARITY, CONFIG;
printk ("In IOCTL\n");
printk("command = %d %d val = %d\n", cmd, SET_BAUD, val);
switch (cmd) {
case SET_BAUD:
get_user (BAUD, (int *)val);
printk ("The baud is %d", BAUD);
case SET_PARITY:
case SET_STOP:
case READ_CONFIG:
default:
return -1;
}
return 0;
}
//Open System Call
int NAME_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "\nThis is the Kernel....Open Call.....I have nothing to do.....but YOU ALL HAVE....HAHAHAHA...\n");
return 0;
}
//Close System Call
int NAME_release(struct inode *indoe, struct file *filp)
{
printk(KERN_ALERT "\nThis is the release method of my Character Driver......Bye Dudes......\n");
return 0;
}
//Write Functionality
ssize_t NAME_write(struct file *filp, char __user *Ubuff, size_t count, loff_t *offp)
{
char Kbuff[80];
unsigned long result;
ssize_t retval;
//strcpy(Kbuff,Ubuff);
result=copy_from_user((char *)Kbuff,(char *)Ubuff,count); //get user data
if(result==0)
{
printk(KERN_ALERT "\nMessage from the user......\n>>>> %s <<<<\n",Kbuff);
printk(KERN_ALERT "\n Data Successfully Written.....\n");
retval=count;
return retval;
}
else
{
printk(KERN_ALERT "\n Error Writing Data\n");
retval=-EFAULT;
return retval;
}
}
//read Functionality
ssize_t NAME_read(struct file *filp, char __user *Ubuff, size_t count, loff_t *offp)
{
char Kbuff[]="THis is some date from the kernel to the user....User,ENJOY......";
unsigned long result;
ssize_t retval;
//strcpy(Kbuff,Ubuff);
result=copy_to_user((char *)Ubuff,(char *)Kbuff,sizeof(Kbuff)); //copy to user
if(result==0)
{
//printk("\nMessage from the user......\n>>>> %s <<<<\n");
printk(KERN_ALERT "\n Data Successfully read.....\n");
retval=count;
return retval;
}
else
{
printk(KERN_ALERT"\n Error Writing Data to User\n");
retval=-EFAULT;
return retval;
}
}
int NAME_flush (struct file *filp)
{
printk("\n This is the close function of the file....");
return 0;
}
//Module over ride functions
module_init(CharDevice_init);
module_exit(CharDevice_exit);
header file
#define MAGIC 'x'
#define SET_BAUD _IOW(MAGIC,0, int)
#define SET_PARITY _IOW(MAGIC, 1, char)
#define SET_STOP _IOW(MAGIC, 2, int)
#define READ_CONFIG _IOR(MAGIC, 3, int)
c file
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include "myioctl.h"
int main()
{
int FileDesc, Baud=9600;
// char Ubuff[]="THis is the User Buffer......Sending Data to the Kernel....";
// char Kbuff[100];
FileDesc=open("/dev/MyCharDevice",O_RDWR);
if(FileDesc <0)
{
printf("\nError Opening Device\n");
exit(1);
}
ioctl (FileDesc, SET_BAUD, &Baud);
printf("%d %d \n", SET_BAUD, &Baud);
// write(FileDesc,Ubuff,sizeof(Ubuff));
// read(FileDesc,Kbuff,sizeof(Ubuff));
// printf("\n The Data read from the Kernel is\n>>>> %s <<<<\n",Kbuff);
close(FileDesc);
}
I am printing in driver what are the command and value of the argument that print like this
command = 1622004312 1074034688 val = 1622004312
So sent command is equal to the argument I sent. Why this is happening?
I used older IOCTL prototype in my driver.
it should be of this type
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
static int my_ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg)
#else
static long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
#endif
In my case against my kernel
static long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
is the correct type.

endlessly looping when reading from character device

For a homework assignment, I have written a character device driver. It seems to work OK. I can read and write it. The problem is that when I read the device, it endlessly loops, printing out the contents of the message buffer over and over.
This seems like it should be fairly straight forward. Just use copy_to_user(), but it's proven to be very problematic.
Anyway, here is the code. I think the problem is in the gdev_read() function.
The printk's are there to serve as debugging as well as talking points, since I have to present the project in class.
/*
* Implement a generic character pseudo-device driver
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
/* you need these, or the kernel will be tainted */
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple sample character device driver");
/*
* function prototypes
*/
int init_module(void);
void cleanup_module(void);
static ssize_t gdev_read(struct file *, char *, size_t, loff_t *);
static ssize_t gdev_write(struct file *, const char *, size_t, loff_t *);
static int gdev_open(struct inode *, struct file *);
static int gdev_release(struct inode *, struct file *);
/* macros */
#define TRUE 1
#define FALSE 0
#define MAX_MSG_LEN 64
/*
* global variables
*/
static dev_t dev_num; /* device number, for new device */
static char *mesg; /* buffer for message */
/* file operations structure, so my device knows how to act */
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = gdev_read,
.write = gdev_write,
.open = gdev_open,
.release = gdev_release,
};
/* character device struct. Declaired here, but initialized elsewhere */
struct cdev *gdev;
int init_module(void)
{
int err;
printk(KERN_ALERT "in init_module\n");
if(alloc_chrdev_region(&dev_num, 0, 1, "/dev/gdev")){
printk(KERN_INFO "Could not allocate device numbers\n");
printk(KERN_INFO "Module gdev not loaded\n");
return -1;
}
/* now I need to make the device and register it */
gdev = cdev_alloc();
gdev->owner = THIS_MODULE;
gdev->ops = &fops;
err = cdev_add(gdev, dev_num, 1);
if(err){
printk(KERN_NOTICE "Error %d adding gdev", err);
return err;
}
mesg = (char *)vmalloc(MAX_MSG_LEN);
printk(KERN_INFO "Module gdev successfully loaded.\n");
printk(KERN_INFO "gdev Major Number: %d\n", MAJOR(dev_num));
return 0;
}
void cleanup_module(void)
{
printk(KERN_ALERT "in cleanup_module\n");
unregister_chrdev_region(dev_num, 3);
vfree(mesg);
cdev_del( gdev );
printk(KERN_INFO "Module gdev unregistered\n");
}
static ssize_t gdev_read(struct file *filp, char *page,
size_t len, loff_t *offset)
{
ssize_t bytes = len < MAX_MSG_LEN ? len : MAX_MSG_LEN;
printk(KERN_ALERT "in gdev_read\n");
if(copy_to_user(page, mesg, bytes)){
return -EFAULT;
}
return bytes;
}
static ssize_t gdev_write(struct file *filp, const char *page,
size_t len, loff_t *offset)
{
ssize_t bytes = len < MAX_MSG_LEN ? len : MAX_MSG_LEN;
printk(KERN_ALERT "in gdev_write\n");
if(copy_from_user(mesg, page, bytes)){
return -EFAULT;
}
return bytes;
}
static int gdev_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "in gdev_open\n");
return 0;
}
static int gdev_release(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "in gdev_release\n");
/* doesn't do anything because it doesn't need too */
return 0;
}
If zero is not returned from read() (in your case gdev_read()), the read function will be called again.
To stop this you use the loff_t *offset parameter. Increment it by how many bytes you have read using (*offset) += bytes; after copy_to_user(). Next time read() is called, offset will be what you have incremented it to. Now just check how many bytes you have previously sent, and only send what you still have remaining. Your function should look like this:
static ssize_t gdev_read(struct file *filp, char *page,
size_t len, loff_t *offset)
{
ssize_t bytes = len < (MAX_MSG_LEN-(*offset)) ? len : (MAX_MSG_LEN-(*offset));
printk(KERN_ALERT "in gdev_read\n");
if(copy_to_user(page, mesg, bytes)){
return -EFAULT;
}
(*offset) += bytes;
return bytes;
}
You could use 'simple_read_from_buffer' function from 'linux/fs.h':
static ssize_t gdev_read(struct file *filep, char __user *buff, size_t count, loff_t *offp)
{
return simple_read_from_buffer(buff, count, offp, my_buffer, buffer_len);
}
'my_buffer' and 'buffer_len' are defined in your module.

Resources