Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZFS refuses to private read/write map of immutable files on Linux #15344

Merged

Conversation

Low-power
Copy link
Contributor

Motivation and Context

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1052472

Description

Private read/write mapping can't be used to modify the underlying files, such
mapping shouldn't be rejected because the files having the immutable flag.

How Has This Been Tested?

This change has been tested with Linux 6.5 (linux-image-6.5.0-1-powerpc64 from
Debian) in a root-on-ZFS configuration. Immtuable files are still appear being
immutable.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Library ABI change (libzfs, libzfs_core, libnvpair, libuutil and libzfsbootenv)
  • Documentation (a change to man pages or other documentation)

Checklist:

@Low-power Low-power force-pushed the linux-private-rw-mmap-immutable branch from 66fadf0 to bacef45 Compare October 3, 2023 07:40
@behlendorf behlendorf added the Status: Code Review Needed Ready for review and testing label Oct 3, 2023
@behlendorf
Copy link
Contributor

@amotin this appears to be a linux specific issue, but can you double check the FreeBSD side.

@amotin
Copy link
Member

amotin commented Oct 3, 2023

@behlendorf I am not familiar with the mmap() code, but I hope that in case of MAP_PRIVATE no write requests will ever reach ZFS to allow modification of immutable files.

@behlendorf
Copy link
Contributor

Well you're right this isn't safe. I doubled checked and sure enough the generic filemap_page_mkwrite() we're registering doesn't check for this. I believe we'll need to register our own custom struct generic_file_vm_ops and callbacks to properly support this.

@behlendorf behlendorf self-requested a review October 3, 2023 23:30
@behlendorf behlendorf added Status: Revision Needed Changes are required for the PR to be accepted and removed Status: Code Review Needed Ready for review and testing labels Oct 20, 2023
Private read/write mapping can't be used to modify the mapped files, so
they will remain be immutable. Private read/write mappings are usually
used to load the data segument of executable files, rejecting them will
rendering immutable executable files to stop working.

Signed-off-by: WHR <msl0000023508@gmail.com>
@Low-power
Copy link
Contributor Author

Just found #2724.

@Low-power Low-power force-pushed the linux-private-rw-mmap-immutable branch from bacef45 to 0cf4aad Compare October 31, 2023 08:54
@behlendorf
Copy link
Contributor

Private read/write mapping can't be used to modify the mapped files, so they will remain be immutable.

Where does this get enforced if we remove this check?

@behlendorf behlendorf linked an issue Nov 1, 2023 that may be closed by this pull request
@behlendorf behlendorf added Status: Code Review Needed Ready for review and testing and removed Status: Revision Needed Changes are required for the PR to be accepted labels Nov 1, 2023
@Low-power
Copy link
Contributor Author

From my testings, ext4 doesn't check this on mmap(2) call at all.

If a process successfully R/W **open(2)**ed a file from ext4, and the file was later made immutable, the process can still mmap(2) the file with MAP_SHARED; but any attempt to write the mapped memory will fail and the process will receive a SIGBUS.

I'm not sure whether will this get checked in ZFS too.

@behlendorf
Copy link
Contributor

I'm not sure whether will this get checked in ZFS too.

Based on my reading of the code it currently won't be, so for now we have to keep this check. To do this right we'll need to register our own custom struct generic_file_vm_ops and callbacks. This is all doable but a bit more work.

@Low-power
Copy link
Contributor Author

I think returning an EPREM for MAP_SHARED R/W mmap(2) request is good enough if the file was later made immutable. The only issue with this check I can imagine is that will make appending an append-only file with mmap(2) impossible; but I can't come up a real world usecase for it, so let's keep this check just yet.

On other hand this bug of rejecting MAP_PRIVATE R/W request is quiet annoying, as it breaks my usecase where I had on other kernels/filesystems for years. I really hope this simple fix be applied, and be backported into a stable branch ASAP.

@Low-power
Copy link
Contributor Author

Some more testings I have done with the fix applied

R/W and MAP_SHARED

Because for this type of mmap(2) to work, the file descriptor must be available for writing, the file must be made immutable between open(2) and mmap(2) for the corresponding ZFS code be reached.

root@debiansid-be:~/src# grep -F zfs /proc/mounts 
zr/ROOT/debiansid-be / zfs rw,relatime,xattr,noacl 0 0
zr/usr/home /usr/home zfs rw,noatime,xattr,noacl 0 0
zr /zr zfs rw,noatime,xattr,noacl 0 0
zr/usr/src/openzfs /usr/src/openzfs zfs rw,noatime,xattr,noacl 0 0
zr/ROOT/debianwheezy-i386 /i386 zfs rw,noatime,xattr,noacl 0 0
root@debiansid-be:~/src# cat mmap-rw-test.c 
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
	if(argc != 2) {
		return -1;
	}
	int fd = open(argv[1], O_RDWR);
	if(fd == -1) {
		perror(argv[1]);
		return 1;
	}
	sleep(10);
	void *map = mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if(map == MAP_FAILED) {
		perror("mmap");
		return 1;
	}
	fprintf(stderr, "%p\n", map);
	strcpy(map, "Hello world\n");
	sleep(10);
	fputs("written\n", stderr);
	return 0;
}
root@debiansid-be:~/src# gcc -Wall -O1 mmap-rw-test.c -o mmap-rw-test
root@debiansid-be:~/src# echo "This file is immutable" > /test-file
root@debiansid-be:~/src# chattr +i /test-file 
root@debiansid-be:~/src# lsattr /test-file 
----i----------------- /test-file
root@debiansid-be:~/src# ./mmap-rw-test /test-file 
/test-file: Operation not permitted
root@debiansid-be:~/src# chattr -i /test-file 
root@debiansid-be:~/src# ./mmap-rw-test /test-file &
[1] 179397
root@debiansid-be:~/src# chattr +i /test-file 
root@debiansid-be:~/src# mmap: Operation not permitted

[1]+  Exit 1                  ./mmap-rw-test /test-file

This shows the mmap(2) failed with EPERM as expected.

R/W and MAP_PRIVATE

Private R/W mapping didn't require the file descriptor be available for writing, so this tested is being performed with both O_RDWR and O_RDONLY to open(2) call.

root@debiansid-be:~/src# cat mmap-rw-test.c 
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
	if(argc != 2) {
		return -1;
	}
	int fd = open(argv[1], O_RDWR);
	if(fd == -1) {
		perror(argv[1]);
		return 1;
	}
	sleep(10);
	void *map = mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
	if(map == MAP_FAILED) {
		perror("mmap");
		return 1;
	}
	fprintf(stderr, "%p\n", map);
	strcpy(map, "Hello world\n");
	sleep(10);
	fputs("written\n", stderr);
	return 0;
}
root@debiansid-be:~/src# gcc -Wall -O1 mmap-rw-test.c -o mmap-rw-test
root@debiansid-be:~/src# ./mmap-rw-test /test-file 
/test-file: Operation not permitted
root@debiansid-be:~/src# chattr -i /test-file 
root@debiansid-be:~/src# ./mmap-rw-test /test-file &
[1] 3654
root@debiansid-be:~/src# chattr +i /test-file 
root@debiansid-be:~/src# 0x7fff86f20000
written

[1]+  Done                    ./mmap-rw-test /test-file
root@debiansid-be:~/src# cat /test-file 
This file is immutable
root@debiansid-be:~/src# sed -i s/O_RDWR/O_RDONLY/ mmap-rw-test.c
root@debiansid-be:~/src# gcc -Wall -O1 mmap-rw-test.c -o mmap-rw-test
root@debiansid-be:~/src# ./mmap-rw-test /test-file 
0x7fff978e0000
written
root@debiansid-be:~/src# cat /test-file 
This file is immutable

R/W and MAP_PRIVATE on buggy ZFS version

This test shows that in current unfixed versions of ZFS, even the file was **open(2)**ed with O_RDONLY (no reason to use O_RDWR with MAP_PRIVATE anyways), private R/W mapping is still unnecessarily rejected for no good.

root@debiansid-be:~/src# cat mmap-rw-test.c
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
	if(argc != 2) {
		return -1;
	}
	int fd = open(argv[1], O_RDONLY);
	if(fd == -1) {
		perror(argv[1]);
		return 1;
	}
	sleep(10);
	void *map = mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
	if(map == MAP_FAILED) {
		perror("mmap");
		return 1;
	}
	fprintf(stderr, "%p\n", map);
	strcpy(map, "Hello world\n");
	sleep(10);
	fputs("written\n", stderr);
	return 0;
}
root@debiansid-be:~/src# gcc -Wall -O1 mmap-rw-test.c -o mmap-rw-test
root@debiansid-be:~/src# lsattr /test-file 
----i----------------- /test-file
root@debiansid-be:~/src# ./mmap-rw-test /test-file 
mmap: Operation not permitted

Copy link
Contributor

@behlendorf behlendorf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Low-power thanks for whipping up those test cases. I ran through it myself locally and was confirmed that in the VM_PRIVATE case nothing written to the mapping hits disk. That addresses my concern above so this LGTM. Thanks.

@behlendorf behlendorf merged commit a160c15 into openzfs:master Nov 8, 2023
23 of 25 checks passed
@behlendorf behlendorf added this to To do in 2.2-release Nov 8, 2023
@tonyhutter tonyhutter moved this from To do to In progress in 2.2-release Nov 13, 2023
ixhamza pushed a commit to truenas/zfs that referenced this pull request Nov 20, 2023
Private read/write mapping can't be used to modify the mapped files, so
they will remain be immutable. Private read/write mappings are usually
used to load the data segment of executable files, rejecting them will
rendering immutable executable files to stop working.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: WHR <msl0000023508@gmail.com>
Closes openzfs#15344
@behlendorf behlendorf moved this from In progress to Done in 2.2-release Nov 27, 2023
lundman pushed a commit to openzfsonwindows/openzfs that referenced this pull request Dec 12, 2023
Private read/write mapping can't be used to modify the mapped files, so
they will remain be immutable. Private read/write mappings are usually
used to load the data segment of executable files, rejecting them will
rendering immutable executable files to stop working.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: WHR <msl0000023508@gmail.com>
Closes openzfs#15344
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Code Review Needed Ready for review and testing
Projects
Development

Successfully merging this pull request may close these issues.

mmap of immutable file behaves differently on zfs vs. ext4/btrfs
3 participants