TOCTTOU code vulnerability - security

I have a piece of code in C, and I need to know where I have the TOCTTOU vulnerability and why. Does somebody know where it is and how I can correct it?
int process(char *filename)
{
struct stat aux;
char buffer[1024];
printf("Input to be appended: ");
fgets(buffer, sizeof(buffer), stdin);
if((lstat(filename, &aux) == 0) && !S_ISLNK(aux.st_mode))
{
printf("[+] Opening\n", filename);
int fd = open(filename, O_RDWR | O_APPEND), nb;
nb = write(fd, buffer, strlen(buffer));
printf("[+] Done!\n");
return 0;
}else
printf("[-] ERROR\n", filename);
return 1;
}
int main(int argc, char * argv[])
{
if(argc != 2){
fprintf(stderr, "usage: %s filename\n", argv[0]);
exit(1);
}
return process(argv[1]);
}
Thanks!!

The use of lstat() provides a TOCTOU vulnerability because the file may be deleted after the lstat() and before the open(). Use open() instead and test the return value is a simple solution for this.

Related

Using fread on Linux is returning 0

I have written the code below, but I am getting 0 returned from fread. perror returns success so I guess its working OK. But I dont understand why I am not reading the data written to the file.
int main(int argc, char **argv)
{
FILE *fp;
char wr_buf[4096];
char rd_buf[4096];
int i;
size_t num;
printf("v1\n");
fp = fopen("/run/media/nvme/test", "w+");
if (fp == NULL)
{
printf("FAIL\n");
return -1;
}
for (i=0; i<4096; i++)
{
wr_buf[i] = i;
rd_buf[i] = 0;
}
num = fwrite(wr_buf , 1 , sizeof(wr_buf) , fp);
printf("WR num %d\n", num);
num = fread(rd_buf , 1 , sizeof(rd_buf) , fp);
printf("RD num %d\n", num);
perror("fread");
for (i=0; i<4096; i++)
{
if (wr_buf[i] != rd_buf[i])
{
printf("ERR %x != %x\n", wr_buf[i], rd_buf[i]);
}
}
fclose(fp);
printf("DONE\n");
return 0;
}
Call rewind(fp); between the fwrite and the fread, to seek back to the beginning of the file. To seek to an arbitrary byte offset, use fseek instead of rewind.

How to get unbuffered output from popen & fgets

I'm using popen to execute a command and read the output. I'm setting the file descriptor to non-blocking mode so that I can put in my own timeout, as follows:
auto stream = popen(cmd.c_str(), "r");
int fd = fileno(stream);
int flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
while(!feof(stream)) {
if(fgets(buffer, MAX_BUF, stream) != NULL) {
// do something with buffer...
}
sleep(10);
}
pclose(stream);
This works just fine, except that fgets keeps returning NULL, until the program has finished executing, at which time it returns all the output as expected.
In other words, even if the program immediately outputs some text and a newline to the stdout, my loop doesn't read it immediately; it only sees it later.
In the documentation for popen I see:
Note that output popen() streams are block buffered by default.
I've tried a few things to turn off buffering (ex. setvbuf(stream, NULL, _IONBF, 0)) , but so far no luck.
How do I turn off buffering so that I can read the output in real-time?
Thank you!
A solution based on something like select() would be more accurate and flexible. Try this :
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/select.h>
void read_cmd(const char *cmd)
{
FILE *stream;
int fd;
int flags;
char buffer[1024];
fd_set fdset;
struct timeval timeout;
int rc;
int eof;
stream = popen(cmd, "r");
fd = fileno(stream);
eof = 0;
while(!eof) {
timeout.tv_sec = 10; // 10 seconds
timeout.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
rc = select(fd + 1, &fdset, 0, 0, &timeout);
switch(rc) {
case -1: {
// Error
if (errno != EINTR) {
fprintf(stderr, "select(): error '%m' (%d)\n", errno);
}
return;
}
break;
case 0: {
// Timeout
printf("Timeout\n");
}
break;
case 1: {
// Something to read
rc = read(fd, buffer, sizeof(buffer) - 1);
if (rc > 0) {
buffer[rc] = '\0';
printf("%s", buffer);
fflush(stdout);
}
if (rc < 0) {
fprintf(stderr, "read(): error '%m' (%d)\n", errno);
eof = 1;
}
if (0 == rc) {
// End of file
eof = 1;
}
}
break;
} // End switch
} // End while
pclose(stream);
}
int main(int ac, char *av[])
{
read_cmd(av[1]);
return 0;
} // main

can we perform the operation using functions like fgets(), fputs(), feof(),etc. for the fifo file like we use for the normal file?

I have an assignment where I have to transfer the file from a client process to server process using fifo.I have tried to deal with fifo file as the other files we create in the system. It compiled without any error but it didn't execute properly.Can someone please give me an idea about the fifo file structure inside the computer system? What processes and functions are present for it ?Till now, I know how to use create(),read(),write(), open() function for fifo file.Also, I would be grateful if someone could help me to correct my program?
My client and server program are as follows:-
Client Program:-
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int fd;
char *myfifo ="/tmp/myfifo";
char str[80];
FILE *fp;
char filename[20];
printf("\nEnter filename: ");
gets(filename);
mkfifo(myfifo,0666);
fp = fopen(filename,"r");
if(fp == NULL)
{
printf("\nError opening the file");
exit(1);
}
fd = open(myfifo, O_WRONLY);
while(fgets(str,80,fp)!=NULL)
{
write(fd,str,strlen(str)+1);
}
close(fd);
fclose(fp);
return 0;
}
Client Program:-
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int fd1;
char *myfifo ="/tmp/myfifo";
char str1[80], filename[20];
FILE *fp1, *fp2;
fd1= open(myfifo, O_RDONLY);
fp1 = fopen(filename,"r");
fp2 = fopen(filename,"w");
while(!feof(fp1))
{
read(fd1,str1,strlen(str1)+1);
fputs(str1,fp2);
}
return 0;
}
Yes, but you have a few small problems in your programs. in the first:
write(fd, str, strlen(str)+1);
is a bit unconventional. This sends the string plus its end-of-string delimiter (\0) into the fd. One doesn't normally do this with strings, strlen(str) is probably what you want.
in the second:
fp1 = fopen(filename,"r");
fp2 = fopen(filename,"w");
filename has not been assigned a value, so both of these opens will almost certainly fail. When they do, they return a NULL pointer, so the first attempt to use them:
while(!feof(fp1))
will likely cause a segment violation. Also, you don't use fp1 anyways, so if feof(fp1) returned 1, it would always return 1. You want to base this loop on when the fifo is exhausted, which means there is no data in it, and nobody has it open for write. So changing this program around a bit yields:
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int fd1;
char *myfifo ="/tmp/myfifo";
char str1[80];
ssize_t n;
fd1= open(myfifo, O_RDONLY);
while ((n=read(fd1,str1,sizeof str1)) > 0)
{
fwrite(str1, 1, n, stdout);
}
return 0;
}
While this set of changes works, it doesn't address your other question, about using stdio functions with pipes. The answer is yes, and here is another functional rewrite of your second program:
#include<stdio.h>
int main()
{
char *myfifo ="/tmp/myfifo";
FILE *fp;
int c;
if ((fp = fopen(myfifo, "r")) != NULL) {
while ((c = getc(fp)) != EOF) {
putchar(c);
}
fclose(fp);
}
return 0;
}
Also, in the first, the critical bit with stdio:
...
FILE *fi = fopen(myfifo, "a");
while(fgets(str,80,fp)!=NULL)
{
fputs(str, fi);
}
fclose(fi);
...
as in the second, the loop could have been implemented with getc, putc.
A general refinement might be functions like these:
ssize_t FCopy(FILE *in, FILE *out) {
int c;
ssize_t len = 0;
while ((c = getc(in)) != EOF) {
len++;
if (putc(c, out) != c) {
return -len;
}
}
return len;
}
ssize_t FileAppend(char *from, char *to) {
FILE *in, *out;
ssize_t n = 0;
if ((in = fopen(from, "rb")) != NULL) {
if ((out = fopen(to, "ab")) != NULL) {
n = FCopy(in, out);
fclose(out);
} else {
n = -1;
}
fclose(in);
} else {
n = -1;
}
return n;
}
so your main would look more like:
...
char filename[80];
printf("Enter a file to store the data in: ");
if (fgets(filename, sizeof filename, stdin)) {
filename[strlen(filename)-1] = '\0';
if (FileAppend(myfifo, filename) < 0) {
printf("Error: could not save data to %s\n", filename);
}
}
....

Reuse char * pointer needs to free and malloc again?

I would like to implement a main function such as in order to execute system commands. The following code is currently used :
int main(int argc, char *argv[])
{
size_t cmd_length;
char *cmd_buffer = NULL;
char *file = NULL;
char *ip = NULL;
int size;
if(argc == 3)
{
size = strlen(argv[1]);
file = (char*)malloc((size + 1)*sizeof(char));
strcpy(file, argv[1]);
size = strlen(argv[2]);
ip = (char*)malloc((size + 1)*sizeof(char));
strcpy(ip, argv[2]);
}
cmd_length = snprintf(NULL, 0, "tftp -g -r %s %s", file, ip);
cmd_buffer = malloc(cmd_length + 1);
if (cmd_buffer == NULL)
{
return -1;
}
snprintf(cmd_buffer, cmd_length + 1, "tftp -g -r %s %s", file, ip);
if(system(cmd_buffer) == 0)
{
then ...
}
{
return -1;
}
free(cmd_buffer);
cmd_buffer = NULL;
cmd_length = snprintf(NULL, 0, "tftp -g -r %s %s", DFT_FILE, DFT_IP);
cmd_buffer = malloc(cmd_length + 1);
if (cmd_buffer == NULL)
{
return -1;
}
snprintf(cmd_buffer, cmd_length + 1, "tftp -g -r %s %s", DFT_FILE, DFT_IP);
if(system(cmd_buffer) == 0)
{
then ...
}
{
return -1;
}
free(cmd_buffer);
free(file);
free(ip);
cmd_buffer = NULL;
file = NULL;
ip = NULL;
return 0;
}
Because I need to enter other commands, I am currently using the same cmd_buffer by using free() before reallocating memory. Is it the right way to do ? Some other commands might be required in the future.
Your program can be be greatly simplified if you use a common function to execute the system call. It doesn't even need to use malloc at all. Here's a partial implementation [Please pardon the gratuitous style cleanup]:
#include <stdarg.h>
int
execute_command(const char *fmt,...)
{
char cmd_buffer[5000];
int cmd_length;
va_list ap;
// NOTE: much simpler to used a fixed size buffer that is larger than
// needed
va_start(ap,fmt);
cmd_length = vsnprintf(cmd_buffer,sizeof(cmd_buffer),fmt,ap);
va_end(ap);
if (system(cmd_buffer) != 0)
return -1;
return 0;
}
int
main(int argc, char *argv[])
{
char *file = NULL;
char *ip = NULL;
// NOTE: I had to guess the intent if fewer arguments are passed (e.g. just
// skip as I did here, print message and abort?)
if (argc == 3) {
// NOTE: no need to malloc these, but if you did, see strdup(3)
file = argv[1];
ip = argv[2];
execute_command("tftp -g -r %s %s", file, ip);
}
execute_command("tftp -g -r %s %s", DFT_FILE, DFT_IP);
return 0;
}
Yes, you are essentially just re-using the pointer variable cmd_buffer which is fine. And for every malloc() there is a matching free(), which is good.
You should factor our common code into a function, for example runCommand(const char *command, ...) (using varargs).

How to implement memory map feature in device drivers in linux?

I am trying to learn device drivers and I started with char device driver. I implemented a small program which is able to read/write from/to kernel buffer. Further, I tried to implement memory mapping and this is not working properly. When I am trying to read through a simple process by mapping my kernel module, it is giving me garbage value. Can any one help with this?
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h> //printk()
#include<linux/errno.h>
#include<linux/types.h>
#include<linux/proc_fs.h>
#include<asm/uaccess.h> //copy_from,to_user
#include<linux/mm.h> //remap_pfn_range
//#include<linux/mman.h> //private_mapping_ok
#define BUFF_SIZE 128
#define DEV_NAME "MyDevice"
MODULE_LICENSE("GPL");
//Method declarations
int mod_open(struct inode *,struct file *);
int mod_release(struct inode *,struct file *);
ssize_t mod_read(struct file *,char *,size_t ,loff_t *);
ssize_t mod_write(struct file *,char *,size_t ,loff_t *);
int mod_mmap(struct file *, struct vm_area_struct *);
void mod_exit(void);
int mod_init(void);
//Structure that declares the usual file access functions
struct file_operations mod_fops = {
read: mod_read,
write: mod_write,
open: mod_open,
release: mod_release,
mmap: mod_mmap
};
static const struct vm_operations_struct mod_mem_ops = {
};
module_init(mod_init);
module_exit(mod_exit);
char *read_buf;
char *write_buf;
static int Major;
//static int Device_Open = 0;
int buffsize = 0;
int mod_init(void)
{
Major = register_chrdev(0,DEV_NAME,&mod_fops);
if(Major < 0)
{
printk(KERN_ALERT "Can not register %s. No major number alloted",DEV_NAME);
return Major;
}
//allocate memory to buffers
read_buf = kmalloc(BUFF_SIZE, GFP_KERNEL);
write_buf = kmalloc(BUFF_SIZE, GFP_KERNEL);
if(!read_buf || !write_buf)
{
mod_exit();
return -ENOMEM;
}
//reset buffers
memset(read_buf,0, BUFF_SIZE);
memset(write_buf,0, BUFF_SIZE);
printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
printk(KERN_INFO "the driver, create a dev file with\n");
printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n",DEV_NAME, Major);
printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
printk(KERN_INFO "the device file.\n");
printk(KERN_INFO "Remove the device file and module when done.\n");
return 0;
}
void mod_exit(void)
{
unregister_chrdev(Major,"memory");
if(read_buf) kfree(read_buf);
if(write_buf) kfree(write_buf);
printk(KERN_INFO "removing module\n");
}
int mod_mmap(struct file *filp, struct vm_area_struct *vma)
{
size_t size = vma->vm_end - vma->vm_start;
vma->vm_ops = &mod_mem_ops;
/* Remap-pfn-range will mark the range VM_IO */
if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot)) {
return -EAGAIN;
}
printk(KERN_INFO "VMA Open. Virt_addr: %lx, phy_addr: %lx\n",vma->vm_start, vma->vm_pgoff<<PAGE_SHIFT);
return 0;
}
ssize_t mod_read(struct file *filp, char *buf, size_t len, loff_t *f_pos)
{
ssize_t bytes;
if(buffsize < len)
bytes = buffsize;
else
bytes = len;
printk(KERN_INFO "Buffer size availabe: %d\n", buffsize);
printk(KERN_INFO "VMA Open. read buffer initial: %lx\n",read_buf);
if(bytes == 0)
return 0;
int retval = copy_to_user(buf,read_buf,bytes);
if(retval)
{
printk(KERN_INFO "copy_to_user fail");
return -EFAULT;
}
else
{
printk(KERN_INFO "copy_to_user succeeded\n");
buffsize -= bytes;
return bytes;
}
}
ssize_t mod_write( struct file *filp,char *buf, size_t len, loff_t *f_pos)
{
memset(read_buf,0,BUFF_SIZE);
memset(write_buf,0,BUFF_SIZE);
if(len > BUFF_SIZE)
{
printk(KERN_ALERT "Buffer not available. Writing only %d bytes.\n",BUFF_SIZE);
len = BUFF_SIZE;
}
printk(KERN_INFO "User space msg size: %d\n",len);
int retval = copy_from_user(read_buf,buf,len);
printk(KERN_INFO "read %d bytes as: %s\n", retval,read_buf);
// memcpy(write_buf,read_buf,len);
// printk(KERN_INFO "written: %s\n", write_buf);
buffsize = len;
return len;
}
int mod_open(struct inode *inode, struct file *filp){return 0;}
int mod_release(struct inode *inode, struct file *filp) {return 0;}
The program which is trying to access this device driver:
#include<stdio.h>
#include<sys/fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/mman.h>
int main(int argc,char *argv[])
{
int fd,n,len;
char *buff;
if(argc != 3)
{
printf("Too few arguments.\n");
exit(EXIT_FAILURE);
}
buff = (char *)malloc(128);
if(strcmp(argv[1],"read")==0)
{
if(-1 == (fd = open("/dev/MyDevice",O_RDONLY)))
{
printf("Device open fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
memset(buff,0,128);
if(-1 == (buff = mmap(0,128,PROT_READ,MAP_SHARED | MAP_NORESERVE,fd,0)))
{
printf("Mapping failed. Error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* if(-1 == (n = read(fd,buff,128)))
{
printf("Device read fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
*/
printf("Read from device:\n%s\n",buff);
close(fd);
}
else if(strcmp(argv[1],"write")==0)
{
len = strlen(argv[2]);
if(-1 == (fd = open("/dev/MyDevice",O_WRONLY)))
{
printf("Device open fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
if(-1 == (n = write(fd,argv[2],len)))
{
printf("Device write fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
printf("Written %d bytes successfully.\n",n);
close(fd);
}
else
{
printf("Invalid argument..\n");
exit(EXIT_FAILURE);
}
return 0;
}
I got the error in my code. I was not mapping my buffer to vma->vm_pgoff. Just add following code before calling rmap_pfn_range, then this code will work fine
vma->vm_pgoff = virt_to_phys(read_buff)>>PAGE_SHIFT;
There are still several potential issues in your code although you found the root cause.
"vma->vm_pgoff = virt_to_phys(read_buff)>>PAGE_SHIFT"
It is not very good practice to program in this example, as basically you are overwriting a user file offset (in PAGE size unit). If your driver need to support mmap a memory offset, then obvious there is a issue. In this case, you can just pass virt_to_phys(read_buff)>>PAGE_SHIFT in place.
It is not recommended to use kmalloc to allocate the memory for remap purpose, as it is required to be page aligned, you can just use the kernel page APIs, like get_free_page to allocate the memory, further more, it is better to remap the memory in units of PAGE size, rather than 128 bytes here.

Resources