copy_to_user fails to copy data to mmap user from kernel? - c

In the user space programm I am allocating some memory via mmap as the following function call:
void *memory;
int fd;
fd = open(filepath, O_RDWR);
if (fd < 0)
return errno;
memory = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED, fd, 0);
if (memory == MAP_FAILED)
return -1;
//syscall() goes here
In the kernel space in my system call I am trying to copy data to the memory mapped region as follows:
copy_to_user(memory,src,4096);
EDIT: added error checking code to the post for clarification
The copy_to_user() call is repeatedly failing in this case, whereas if I would have done a memory = malloc() it was succeeding always.
Am I getting some permission flags wrong in this case for mmap ?

Does the open succeed? What about mmap? Is the target file big enough? Can you write to the file through the mapping in userspace?
Also, the repeated 4096 is a strong hit your code is wrong. Userspace should pass the expected size instead.

Related

mmap() causing segmentation fault in C

I'm pretty sure my mistake is very evident, but I just can't seem to find where the problem is.
I'm learning how to use mmap() in C, everything looks correct to me, but I get a segmentation fault.
Here is my code:
int n=50;
char * tab = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED, -1, 0);
for(int i=0; i<n; i++)
{
tab[i] = 1;
}
Using valgrind, I get an error saying "Invalid write of size 1" at the line where I do tab[i]=1, (I have tried replacing 1 by '1' thinking that maybe a char has a smaller size than an int, but still get the same error), followed by "Address 0xfffff..ff is not stack'd, malloc'd, or (recently) free'd".
I have no idea where my mistake is. Can somebody help me find it?
From man 2 mmap:
The contents of a file mapping (as opposed to an anonymous mapping;
see MAP_ANONYMOUS below), are initialized using length bytes starting
at offset offset in the file (or other object) referred to by the
file descriptor fd.
I suppose that you are trying to create an anonymous mapping (i.e. not backed by a file). In such case, you need to add MAP_ANONYMOUS to the flags, otherwise the system will try to read from the specified fd, which is invalid (-1) and will fail.
The correct code is:
char *tab = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (tab == MAP_FAILED) {
perror("mmap");
exit(1);
}
For the future, note that you can easily detect the error like I did above with a simple call to perror() in case the returned value indicates failure. In your case it should have printed the following:
mmap: Bad file descriptor
Checking the manual again you can see in the "ERRORS" section:
EBADF: fd is not a valid file descriptor (and MAP_ANONYMOUS was not set).

mmap fails for newly created file

My program creates a file using
HANDLE_ERROR(fd = open(path/to/file,O_WRONLY|O_CREAT,0640)))
Mapped it
uint8_t *output_file_addr = (uint8_t *)mmap ( NULL, size , PROT_WRITE, MAP_SHARED, fd, 0 )
if(output_dic_addr == MAP_FAILED){
fprintf(stderr,"%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
The mmap responds with Permission denied
The problem is solved by allocating space for file using posix_fallocate(...).
I thought the mmap will do the allocation as well (wrong judgment)
It is impossible to create a write only mapping on typical hardware and the file has been opened without read permission.

why does mmap fail?

I'm given a physical address, specifically 0x000000368d76c0. I'm trying to mmap it into my program. The code that I'm using is
void *mmap64;
off_t offset = 0x000000368d76c0;
int memFd = open("/dev/mem", O_RDWR);
if (-1 == memFd)
perror("Error ");
mmap64 = mmap(0, sizeof(uint64_t), PROT_WRITE | PROT_READ, MAP_SHARED, memFd, offset);
if (MAP_FAILED == mmap64) {
perror("Error ");
return -1;
}
For some reason when I run this code I get a failure on mmap. Specifically it says Error Invalid argument. I'm pretty sure it is because of the offset value, but I don't know what is wrong with it.
I would appreciate any help on it.
According to mmap(2) - Linux manual page,
offset must be a multiple of the page size as
returned by sysconf(_SC_PAGE_SIZE).
When the page size is 4096 (a page size used in x86 CPU), 0x000000368d76c0 is not a multiple of 4096 and will be considered as invalid.
For that reason, you will have to adjust the offset.

Invalid argument for read-write mmap?

I'm getting -EINVAL for some reason, and it's not clear to me why. Here's where I open and attempt to mmap the file:
if ((fd = open(argv[1], O_RDWR)) < 0)
{
fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno));
return 1;
}
struct stat statbuf;
if (fstat(fd, &statbuf))
{
fprintf(stderr, "stat filed: %s\n", strerror(errno));
return 1;
}
char* fbase = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (fbase == MAP_FAILED)
{
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
return 1;
}
EDIT: I should add, the error is occurring in the mmap.
Turns out changing the MAP_SHARED to MAP_PRIVATE allows this to succeed.
This reason this was failing is subtle: My code is running inside a VirtualBox VM, and the file I was attempting to mmap was in a shared directory on my host machine. The VirtualBox virtual filesystem apparently doesn't implement mmap with the MAP_SHARED option across the boundary of the hypervisor.
If you'll read jxh's helpful comments on both my question and on his answer, it turns out that this code was working for him because he was likely attempting to mmap a host filesystem file into the host memory.
My observation that switching from MAP_SHARED to MAP_PRIVATE is also consistent with this: since privately mapped memory is invisible to other processes, the virtual filesystem driver will probably have no objection to mapping the memory.
The solution was to move the file I wanted to map into the guest's hard drive and perform manipulation from there.
Your statbuf.st_size is 0. mmap() will fail if the length parameter is 0.
There are 3 listed reasons for EINVAL error mmap():
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
...
We don't like addr, length, or offset (e.g., they are too large, or not aligned on a page boundary).
(since Linux 2.6.12) length was 0.
flags contained neither MAP_PRIVATE or MAP_SHARED, or contained both of these values.
edit grub to add iomem=relaxed and reboot, make sure cat /proc/cmdline shows entry for iomem=relaxed after boot, re-run your program and check
[root#fedora ~]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.18.19-200.fc36.x86_64 root=/dev/mapper/fedora_fedora-root ro rd.lvm.lv=fedora_fedora/root iomem=relaxed rhgb quiet

Share process memory with mmap

I have a C program that generates large amounts of data in memory, and I need to share one particular section of this data in memory, so that another process can have read access to it.
I'm attempting to use mmap to do this, but I'm not having much success. Here is my code:
//Code above generates a pointer to the memory section I need to share, named addr
if (infoBlock->memory_size == 1073741824) { //This is the data block I need to share
int err, fd;
fd = open("/tmp/testOutput", (0_RDWR | 0_CREAT), S_IWUSR);
if (fd < 0) {
perror("Couldn't create output file\n");
goto failedExit;
}
unsigned *p = mmap(addr, 1073741824, PROT_READ, (MAP_SHARED | MAP_FIXED), fd, 0);
if (!p) {perror("mmap failed"); goto failedExit; }
printf("p is now: %p\n", p); //This should point to the shared mapping
printf("%u\n", *p); //Try to print out some data from the mapping
}
After running the program, I can see the file /tmp/testOutput is there, but it's size is 0. I'm not sure if that's a normal thing with memory mappings, as it's not technically a file. Also all of the output within my program points to the same memory address.
I can also see the memory map present within the /proc/PID/maps, with a reference to /tmp/testOutput.
Everything seems to run, however when it comes to dereferencing the pointer, the program exits, I'm assuming this is because I've done the mapping wrong, and the pointer is pointing to something it shouldn't be.
If anyone can spot what I'm doing wrong, or can offer some advice, it would be greatly appreciated.
Thanks!
You've mapped the storage associated with that file (or tried to) into your process, and you've insisted that it be mapped at an address you're already using for something else (presumably, addr was allocated somehow).
You don't say whether p actually does have the address you requested, and as suspectus points out, your error checking is broken.
Your Confusion:
You can't associate arbitrary heap or other process memory pages with a file after the fact. You have to allocate them in the filesystem, and then map them. (There is a way to associate them with a UNIX pipe using vmsplice, although it isn't exactly what you asked for).
Note the MMAP_FIXED flag will just replace the page which was occupied by your data, with the new pages associated with the file. Without that flag, the address hint would be ignored and the mapping placed elsewhere.
The Solution:
ftruncate the file to your desired size before mapping it (this allocates storage in the filesystem)
map it and then populate it
fix your mmap error checking
If you can't change your allocation scheme, the best you can manage is to copy your process-local memory into the mapping, in which case you might as well just write it to the file.
The ideal case would look something like this:
void *alloc_mmap(const char *filename, size_t length)
{
int fd;
fd = open(filename, (0_RDWR | 0_CREAT), S_IWUSR);
if (fd < 0) {
perror("Couldn't create output file\n");
return NULL;
}
if (ftruncate(fd, length)) {
perror("Couldn't grow output file\n");
close(fd);
return NULL;
}
void *p = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
if (p == -1) {
perror("mmap failed");
close(fd);
return NULL;
}
close(fd);
return p;
}
// now you've allocated your memory, you can populate it and it will be
// reflected in the file
Here is an extract from the mmap man page.
On success, mmap() returns a pointer to the mapped area. On error, the value
MAP_FAILED (that is, (void *) -1) is returned, and errno is set appropriately.
On success, munmap() returns 0, on failure -1, and errno is set (probably to
EINVAL).
The test for success should be changed to test for -1 return value of mmap. Then check the errno
value. HTH.

Resources