Goal: Load .so or executable that has been verified to be signed (or verified against an arbitrary algorithm).
I want to be able to verify a .so/executable and then load/execute that .so/executable with dlopen/...
The wrench in this is that there seems to be no programmatic way to check-then-load. One could check the file manually and then load it after.. however there is a window-of-opportunity within which someone could swap out that file for another.
One possible solution that I can think of is to load the binary, check the signature and then dlopen/execvt the /proc/$PID/fd.... however I do not know if that is a viable solution.
Since filesystem locks are advisory in Linux they are not so useful for this purpose... (well, there's mount -o mand ... but this is something for userlevel, not root use).
Many dynamic linkers (including Glibc's) support setting LD_AUDIT environment variable to a colon-separated list of shared libraries. These libraries are allowed to hook into various locations in the dynamic library loading process.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
unsigned int la_version(unsigned int v) { return v; }
unsigned int la_objopen(struct link_map *l, Lmid_t lmid, uintptr_t *cookie) {
if (!some_custom_check_on_name_and_contents(l->l_name, l->l_addr))
abort();
return 0;
}
Compile this with cc -shared -fPIC -o test.so test.c or similar.
You can see glibc/elf/tst-auditmod1.c or latrace for more examples, or read the Linkers and Libraries Guide.
Very very specific to Glibc's internals, but you can still hook into libdl at runtime.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
extern struct dlfcn_hook {
void *(*dlopen)(const char *, int, void *);
int (*dlclose)(void *);
void *(*dlsym)(void *, const char *, void *);
void *(*dlvsym)(void *, const char *, const char *, void *);
char *(*dlerror)(void);
int (*dladdr)(const void *, Dl_info *);
int (*dladdr1)(const void *, Dl_info *, void **, int);
int (*dlinfo)(void *, int, void *, void *);
void *(*dlmopen)(Lmid_t, const char *, int, void *);
void *pad[4];
} *_dlfcn_hook;
static struct dlfcn_hook *old_dlfcn_hook, my_dlfcn_hook;
static int depth;
static void enter(void) { if (!depth++) _dlfcn_hook = old_dlfcn_hook; }
static void leave(void) { if (!--depth) _dlfcn_hook = &my_dlfcn_hook; }
void *my_dlopen(const char *file, int mode, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%s, %d, %p)\n", __func__, file, mode, dl_caller);
enter();
result = dlopen(file, mode);
leave();
return result;
}
int my_dlclose(void *handle) {
int result;
fprintf(stderr, "%s(%p)\n", __func__, handle);
enter();
result = dlclose(handle);
leave();
return result;
}
void *my_dlsym(void *handle, const char *name, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%p, %s, %p)\n", __func__, handle, name, dl_caller);
enter();
result = dlsym(handle, name);
leave();
return result;
}
void *my_dlvsym(void *handle, const char *name, const char *version, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%p, %s, %s, %p)\n", __func__, handle, name, version, dl_caller);
enter();
result = dlvsym(handle, name, version);
leave();
return result;
}
char *my_dlerror(void) {
char *result;
fprintf(stderr, "%s()\n", __func__);
enter();
result = dlerror();
leave();
return result;
}
int my_dladdr(const void *address, Dl_info *info) {
int result;
fprintf(stderr, "%s(%p, %p)\n", __func__, address, info);
enter();
result = dladdr(address, info);
leave();
return result;
}
int my_dladdr1(const void *address, Dl_info *info, void **extra_info, int flags) {
int result;
fprintf(stderr, "%s(%p, %p, %p, %d)\n", __func__, address, info, extra_info, flags);
enter();
result = dladdr1(address, info, extra_info, flags);
leave();
return result;
}
int my_dlinfo(void *handle, int request, void *arg, void *dl_caller) {
int result;
fprintf(stderr, "%s(%p, %d, %p, %p)\n", __func__, handle, request, arg, dl_caller);
enter();
result = dlinfo(handle, request, arg);
leave();
return result;
}
void *my_dlmopen(Lmid_t nsid, const char *file, int mode, void *dl_caller) {
void *result;
fprintf(stderr, "%s(%lu, %s, %d, %p)\n", __func__, nsid, file, mode, dl_caller);
enter();
result = dlmopen(nsid, file, mode);
leave();
return result;
}
static struct dlfcn_hook my_dlfcn_hook = {
.dlopen = my_dlopen,
.dlclose = my_dlclose,
.dlsym = my_dlsym,
.dlvsym = my_dlvsym,
.dlerror = my_dlerror,
.dladdr = my_dladdr,
.dlinfo = my_dlinfo,
.dlmopen = my_dlmopen,
.pad = {0, 0, 0, 0},
};
__attribute__((constructor))
static void init(void) {
old_dlfcn_hook = _dlfcn_hook;
_dlfcn_hook = &my_dlfcn_hook;
}
__attribute__((destructor))
static void fini(void) {
_dlfcn_hook = old_dlfcn_hook;
}
$ cc -shared -fPIC -o hook.so hook.c
$ cat > a.c
#include <dlfcn.h>
int main() { dlopen("./hook.so", RTLD_LAZY); dlopen("libm.so", RTLD_LAZY); }
^D
$ cc -ldl a.c
$ ./a.out
my_dlopen(libm.so, 1, 0x80484bd)
Unfortunately, my investigations are leading me to conclude that even if you could hook into glibc/elf/dl-load.c:open_verify() (which you can't), it's not possible to make this race-free against somebody writing over segments of your library.
The problem is essentially unsolvable in the form you've given, because shared objects are loaded by mmap()ing to process memory space. So even if you could make sure that the file that dlopen() operated on was the one you'd examined and declared OK, anyone who can write to the file can modify the loaded object at any time after you've loaded it. (This is why you don't upgrade running binaries by writing to them - instead you delete-then-install, because writing to them would likely crash any running instances).
Your best bet is to ensure that only the user you are running as can write to the file, then examine it, then dlopen() it. Your user (or root) can still sneak different code in, but processes with those permissions could just ptrace() you to do their bidding anyhow.
This project supposedly solves this on kernel level.
DigSig currently offers:
run time signature verification of ELF binaries and shared libraries.
support for file's signature revocation.
a signature caching mechanism to enhance performances.
I propose the following solution that should work without libraries *)
int memfd = memfd_create("for-debugging.library.so", MFD_CLOEXEC | MFD_ALLOW_SEALING);
assert(memfd != -1);
// Use any way to read the library from disk and copy the content into memfd
// e.g. write(2) or ftruncate(2) and mmap(2)
// Important! if you use mmap, you have to unmap it before the next step
// fcntl( , , F_SEAL_WRITE) will fail if there exists a writeable mapping
int seals_to_set = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL;
int sealing_err = fcntl(memfd, F_ADD_SEALS, seals_to_set);
assert(sealing_err == 0);
// Only now verify the contents of the loaded file
// then you can safely *) dlopen("/proc/self/fd/<memfd>");
*) Not actually tested it against attacks. Do not use in production without further investigation.
Related
I'm trying to complete a hooking sample attachment in a program for my uni assignment. The task requires to get a system call sys_rt_sigaction hooked when initiating a loadable module in Linux kernel (I use Ubuntu 18.04 LTS, kernel version is 5.0.0-23-generic). So, the case I'm struggling originates from an error could not insert module <module name>: Unknown symbol in module once I started sudo insmod <my module name>.ko.
After some googling, I see clear this problem arises due to missing sys_call_table export to run inserting as smoothly as well. Following this post, I want to cope that invoking kallsyms_lookup_name call before kicking off init procedure.
There is .c-file which provides with definitions of operations accessible by module (file name is buffer.c):
#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>
#include <linux/unistd.h>
void * sys_call_table = (void *) kallsyms_lookup_name("sys_call_table");// some wrongness here, but what exactly?
MODULE_LICENSE("GPL");
int (*real_rt_sigaction)(const char * path); // true syscall prototype
static int __init buffer_init_module(void);
static void __exit buffer_exit_module(void);
static int device_open(struct inode *, struct file *); // driver file opening
static int device_release(struct inode *, struct file *); // return of system resource control
static ssize_t device_read(struct file *, char *, size_t, loff_t *); // reading from driver file
static ssize_t device_write(struct file *, const char *, size_t, loff_t *); // writing into driver file
#define DEVICE_NAME "buffer"
#define BUF_LEN 80
// to be called instead
int alter_rt_sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact, size_t sigsetsize) {
printk(KERN_INFO "Syscall function hooked - you've lost control of your experience");
return 0;
}
static int Major;
static int Device_Open = 0;
static int total_open = 1;
static char Buf[BUF_LEN + 1] = "Buffer is empty, add some input\n";
static char *Msg_ptr;
static int Buf_Char = 50;
static int Bytes_Read = 0;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
static int __init buffer_init_module(void)
{
printk(KERN_INFO
"Device initializing in progress...");
Major = register_chrdev(0, DEVICE_NAME, &fops);
if(Major < 0) {
printk("Major number hasn't been assigned - Driver registration failed\n");
return Major;
}
printk(KERN_INFO "Registration success - device major number: %d\n", Major);
real_rt_sigaction=sys_call_table[__NR_rt_sigaction];
sys_call_table[__NR_rt_sigaction]=alter_rt_sigaction; // hooking implementation
return 0;
}
static void __exit buffer_exit_module(void)
{
unregister_chrdev(Major, DEVICE_NAME);
printk(KERN_INFO "Outside the module - exit successfully completed\n");
sys_call_table[__NR_rt_sigaction]=real_rt_sigaction; // original call reset
}
static int device_open(struct inode *inode, struct file *file)
{
if(Device_Open)
return -EBUSY;
Device_Open++;
printk(KERN_INFO "Device file has been accessed %d time(s)\n", total_open++);
Msg_ptr = Buf;
try_module_get(THIS_MODULE);
Bytes_Read = 0;
return 0;
}
static int device_release(struct inode * node, struct file * filep)
{
Device_Open--;
module_put(THIS_MODULE);
printk(KERN_INFO "Device file gets close\n");
return 0;
}
static ssize_t device_read(struct file * filep, char * buffer, size_t len, loff_t * offset)
{
int got_read = Bytes_Read;
if(Bytes_Read >= Buf_Char)
return 0;
while(len && (Bytes_Read < Buf_Char)) {
put_user(Msg_ptr[Bytes_Read], buffer+Bytes_Read);
len--;
Bytes_Read++;
}
return Bytes_Read-got_read;
}
static ssize_t device_write(struct file * filep, const char * buffer, size_t len, loff_t * offset)
{
Buf_Char = 0;
if(Buf_Char >= BUF_LEN) {
return 0;
}
while(len && (Buf_Char < BUF_LEN))
{
get_user(Msg_ptr[Buf_Char], buffer+Buf_Char);
len--;
Buf_Char++;
}
return Buf_Char;
}
module_init(buffer_init_module);
module_exit(buffer_exit_module);
Additively, there is code in Makefile:
obj-m += buffer.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
The painful moment here is an error message initializer element is not constant whenever I was trying to build module via sudo make in my project folder. As I follow the beginner's tutorials and need for some basic insight, it might be highly appreciable to see any help with solution or even some ideas how to handle the same problem more effectively, indeed.
I created the device file under /dev folder successfully, but writing to that device file makes kernel to throw following error messages.
kernel:[10090.943733] Internal error: : 1b [#3] PREEMPT SMP ARM
kernel:[10091.049020] Process echo (pid: 3728, stack limit = 0xdc40a218)
kernel:[10091.054880] Stack: (0xdc40be60 to 0xdc40c000)
kernel:[10091.059267] be60: c15491c6 00000022 dc5cb14c bf30430c dc40bedc dc40be88 c075312c c074fe5c
kernel:[10091.067488] be80: c0753018 ffffff04 ffff0a00 c140414c c0d407c8 bf30430c c140414c 40cfbcf3
kernel:[10091.075709] bea0: 00852878 ffffff04 ffff0a00 00040952 c01a7404 c140414c 00852878 00852878
Segmentation fault
I know very basic of Linux Device Drivers
can anyone help me regarding this??
I'm attaching the code for this character device driver
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/device.h>
MODULE_AUTHOR("RUCHA");
MODULE_DESCRIPTION("Character Driver First test");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.1");
static int MajorNum;
static struct class* RetValOfClassRegistration = NULL;
static struct device* RetVal = NULL;
static char message[1024];
static int openDev(struct inode * , struct file *);
static int closeDev(struct inode * , struct file *);
static ssize_t readDev(struct file *, char *, size_t, loff_t *);
static ssize_t writeDev(struct file *, const char *, size_t, loff_t *);
static struct file_operations FileOps = {
.owner = THIS_MODULE,
.open = openDev,
.read = readDev,
.write = writeDev,
.release = closeDev,
};
static int registerCharDev(void){
return register_chrdev(0,"MyDev",&FileOps);
}
static int __init Loaded(void){
// registering device
MajorNum = registerCharDev();
if(MajorNum < 0){
printk("Can not register device\n");
return -1;
}
printk("Driver Loaded with %d \n",MajorNum);
// registering device class
RetValOfClassRegistration = class_create(THIS_MODULE,"MyCharacterDriver");
if(RetValOfClassRegistration < 0){
printk("can not register class for driver number : %d\n",MajorNum);
return 0;
}
// register the driver
RetVal = device_create(RetValOfClassRegistration,NULL,MKDEV(MajorNum,0),NULL,"MyDev");
return 0;
}
static void __exit Removed(void){
device_destroy(RetValOfClassRegistration,MKDEV(MajorNum,0));
class_unregister(RetValOfClassRegistration);
class_destroy(RetValOfClassRegistration);
unregister_chrdev(MajorNum,"MyDev");
printk("Driver Removed\n");
}
module_init(Loaded);
module_exit(Removed);
static int openDev(struct inode *inodep , struct file *filep){
printk("Device is now open to read write operations\n");
return 0;
}
static int closeDev(struct inode *inodep , struct file *filep){
printk("Device Closed\n");
return 0;
}
static ssize_t readDev(struct file *filep, char *c, size_t v, loff_t *lp){
printk("Read From the device\n");
return 0;
}
static ssize_t writeDev(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
sprintf(message, "%s(%zu letters)", buf, len);
return 0;
}
and the user input is
echo '1' > /dev/MyDev
You can't access a user data pointer (buf) directly. You need to use copy_from_user to copy the data into kernel memory first. Also, use snprintf instead of sprintf to avoid buffer overflows.
Also, compile with debugging enabled, especially CONFIG_DEBUG_INFO, to get more helpful error messages in the kernel log.
I am a windows driver programmer, who is also a complete beginner in linux kernel development. im trying out simple linux kernel modules.
I have written a parallel port driver as in the following code and could successfully load this driver.
I have created a char device node using command mknod with same major and minor numbers as in the code. I have tried reading from the file using cat command and i found it was working fine. While i tried writing the same device using command cat in.txt>/dev/parallel_dev the prompt shows a message "killed" and in dmesg, it has shown unable to handle kernel NULL pointer exception.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h> //kprintf
#include<linux/module.h> //kmalloc
#include<linux/types.h>
#include<linux/fs.h> //fileoperations,register_chrdev
#include<linux/slab.h>
#include<linux/uaccess.h> //copy from/to user
#include<linux/ioport.h> //check_region
#include<linux/interrupt.h>
#include<linux/delay.h>
static int parallel_open(struct inode* p_node, struct file* p_file);
static int parallel_release(struct inode* p_node, struct file* p_file);
static ssize_t parallel_read(struct file* p_file, char* buf, size_t count, loff_t* f_pos);
static ssize_t parallel_write(struct file* p_file, const char* buf, size_t count, loff_t* f_pos);
static irqreturn_t parallel_interrupt(int irq,void* dev);
static const int major_no = 709;
static const char* dev_name = "parallel_tryout";
#define par_base 0x378
int port;
int* parallel_buffer;
struct file_operations fops = {
.read = parallel_read,
.write = parallel_write,
.open = parallel_open,
.release = parallel_release,
};
static int parallel_init(void)
{
int reg_result;
int irq_req_result;
unsigned int irq_num = 7;
printk("Entered init");
printk("Initializing parallelport dev with major %d",major_no);
reg_result = register_chrdev(major_no, dev_name, 0);
if(reg_result < 0 )
{
printk("Error registering char dev: %s",dev_name);
}
parallel_buffer = kmalloc(16,GFP_KERNEL);
if(parallel_buffer==NULL)
{
printk("Error allocating memory for: %s",dev_name);
}
printk("Port registration");
request_region(0x378,1,"parallelport");
if(port)
{
printk("Error allocating port");
}
parallel_buffer = inb(par_base);
if(parallel_buffer != NULL)
{
printk("Allocated buffer");
}
irq_req_result = request_irq(irq_num,parallel_interrupt,IRQF_SHARED,dev_name,parallel_buffer);
return 0;
}
static void parallel_exit(void)
{
unregister_chrdev(major_no, dev_name);
release_region(0x378,1);
}
static int parallel_open(struct inode* p_node, struct file* p_file)
{
printk("parallel driver opened from device: %s",dev_name);
return 0;
}
static int parallel_release(struct inode* p_node, struct file* p_file)
{
printk("Memory driver closed from device: %s",dev_name);
return 0;
}
static ssize_t parallel_read(struct file* p_file, char* buf, size_t count, loff_t* f_pos)
{
copy_to_user(buf,parallel_buffer,1);
printk("Copied data:%s ",buf);
printk("Memory driver read from device: %s",dev_name);
return 0;
}
static ssize_t parallel_write(struct file* p_file, const char* buf, size_t count, loff_t* f_pos)
{
copy_from_user(parallel_buffer,buf,1);
printk("Copied data:%s ",parallel_buffer);
printk("Memory driver written from device: %s",dev_name);
return 0;
}
static irqreturn_t parallel_interrupt(int irq,void* dev)
{
irqreturn_t return_value;
printk("parallel interrupt detected!!!!");
return return_value;
}
module_init(parallel_init);
module_exit(parallel_exit);
what could be the possible reason for this, and thanks in advance.
I am trying to implement a system call interception for sys_open() call via kernel module and for that I have defined a miscellaneous device driver MyDevice which can be inserted as kernel module. Below is the code for my kernel module:
#include <linux/version.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/highmem.h>
#include <asm/unistd.h>
MODULE_LICENSE("GPL");
// IOCTL commands
#define IOCTL_PATCH_TABLE 0x00000001
#define IOCTL_FIX_TABLE 0x00000004
//Global variables
int in_use = 0; //set to 1 in open handler and reset to zero in release handler
int is_set = 0; // flag to detect system call interception
unsigned long *sys_call_table = (unsigned long*)0xffffffff81801400; //hard coded address of sys_call_table from /boot/System.map
//function pointer to original sys_open
asmlinkage int (*real_open)(const char* __user, int, int);
//Replacement of original call with modified system call
asmlinkage int custom_open(const char* __user file_name, int flags, int mode)
{
printk("interceptor: open(\"%s\", %X, %X)\n", file_name,flags,mode);
return real_open(file_name,flags,mode);
}
/*
Make the memory page writable
This is little risky as directly arch level protection bit is changed
*/
int make_rw(unsigned 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 page write protected */
int make_ro(unsigned long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);
pte->pte = pte->pte &~ _PAGE_RW;
return 0;
}
/* This function will be invoked each time a user process attempts
to open my device. You should keep in mind that the prototype
of this function may change along different kernel versions. */
static int my_open(struct inode *inode, struct file *file)
{
/*Do not allow multiple processes to open this device*/
if(in_use)
return -EBUSY;
in_use++;
printk("MyDevice opened\n");
return 0;
}
/* This function, in turn, will be called when a process closes our device */
static int my_release(struct inode *inode, struct file *file)
{
in_use--;
printk("MyDevice closed\n");
return 0;
}
/*This static function handles ioctl calls performed on MyDevice*/
static int my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int retval = 0;
switch(cmd)
{
case IOCTL_PATCH_TABLE:
make_rw((unsigned long)sys_call_table);
real_open = (void*)*(sys_call_table + __NR_open);
*(sys_call_table + __NR_open) = (unsigned long)custom_open;
make_ro((unsigned long)sys_call_table);
is_set=1;
break;
case IOCTL_FIX_TABLE:
make_rw((unsigned long)sys_call_table);
*(sys_call_table + __NR_open) = (unsigned long)real_open;
make_ro((unsigned long)sys_call_table);
is_set=0;
break;
default:
printk("sys_open not executed\n");
break;
}
return retval;
}
//populate data struct for file operations
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = &my_open,
.release = &my_release,
.unlocked_ioctl = (void*)&my_ioctl,
.compat_ioctl = (void*)&my_ioctl
};
//populate miscdevice data structure
static struct miscdevice my_device = {
MISC_DYNAMIC_MINOR,
"MyDevice",
&my_fops
};
static int __init init_my_module(void)
{
int retval;
printk(KERN_INFO "Inside kernel space\n");
retval = misc_register(&my_device);
return retval;
}
static void __exit cleanup_my_module(void)
{
if (is_set)
{
make_rw((unsigned long)sys_call_table);
*(sys_call_table + __NR_open) = (unsigned long)real_open;
make_ro((unsigned long)sys_call_table);
}
misc_deregister(&my_device);
printk(KERN_INFO "Exiting kernel space\n");
return;
}
module_init(init_my_module);
module_exit(cleanup_my_module);
The code for my test file is as follows:
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* Define ioctl commands */
#define IOCTL_PATCH_TABLE 0x00000001
#define IOCTL_FIX_TABLE 0x00000004
int main(void)
{
int device = open("/dev/MyDevice", O_RDWR);
printf("%d\n",device);
ioctl(device, IOCTL_PATCH_TABLE);
sleep(2);
ioctl(device, IOCTL_FIX_TABLE);
close(device);
return 0;
}
The problem is that in my test file open("/dev/MyDevice", O_RDWR); is always returning -1, why is it so ? Where am I going wrong ? I checked with ls -l /dev/MyDevice, MyDevice has been successfully registered with following details: crw------- 1 root root 10, 56 Dec 9 19:33 /dev/MyDevice
Sorry, seriously stupid mistake, that's what happens when rookies do things. I just needed to grant the read and write permissions for my miscellaneous char device driver.
sudo chmod a+r+w /dev/MyDevice
I am getting the "implicit declaration of function 'proc_create'" error while compiling my driver module.I want to create a entry in /proc and print the number of programs which are using the module. Can you please let me know what is wrong in here?? Here is my code.
#include<linux/module.h>
#include<linux/fs.h>
#define HELLO_MAJOR 234
static int debug_enable = 0;
static int no_of_access;
module_param(debug_enable, int, 0);
MODULE_PARM_DESC(debug_enable, "Enable module debug mode.");
struct file_operations hello_fops;
struct proc_dir_entry *proc_file_entry;
<File operation functions...>
<Incremented global_counter in the file open operation.>
static int hello1_read_proc(char *buf, char **start, off_t offset,
int count, int *eof, void *data)
{
int len=0;
len += sprintf(buf+len, no_of_access);
*eof=1;
return len;
}
static int __init hello_init(void)
{
int ret;
proc_file_entry = proc_create("examples/hello1", 0,NULL, hello1_read_proc);
if(proc_file_entry == NULL)
return -ENOMEM;
printk("\nProc file entry for hello1 has been created !!!\n");
}
static void __exit hello_exit(void)
{
printk("Hello Example Exit\n");
remove_proc_entry("exmaples/hello1", NULL);
unregister_chrdev(HELLO_MAJOR,"hello1");
}
Thanks in advance.
You also need to include <linux/proc_fs.h>