|
|
Subscribe / Log in / New account

Implementing fully immutable files

By Jonathan Corbet
April 19, 2019
Like all Unix-like systems, Linux implements the traditional protection bits controlling who can access files in a filesystem (and what access they have). Fewer users, perhaps, are aware of a set of additional permission bits hidden away behind the chattr and lsattr commands. Among other things, these bits can make a file append-only, mark a file to be excluded from backups, cause a file's data to be automatically overwritten on deletion, or make a file immutable. The implementation of many of these features is incomplete at best, so perhaps it's not surprising that immutable files can still be changed in certain limited circumstances. Darrick Wong has posted a patch set changing this behavior, implementing a user-visible behavioral change that he describes as "an extraordinary way to destroy everything".

The chattr man page is clear on what happens when the immutable bit is set:

A file with the 'i' attribute cannot be modified: it cannot be deleted or renamed, no link can be created to this file, most of the file's metadata can not be modified, and the file can not be opened in write mode.

This description is true for the most part (at least on filesystems supporting this bit), but Wong noticed an important exception: a process that opens a file for writing prior to the setting of the immutable bit will still be able to write to the file after the bit is set for as long as it holds the file descriptor open. So while many operations on an immutable file are blocked, modifying the data in the file using open file descriptors is still allowed. The file is not yet, in other words, fully immutable.

That behavior is both inconsistent and surprising; Wong set out to change it by making the system actually behave the way the man page says it will. Doing that requires two types of changes, the first of which is easier than the second. Whenever a process attempts to write to a file descriptor, a call is made to generic_write_checks() to ensure that the operation can be allowed. Adding a check for immutability to that function will cause write() calls to fail immediately once a file has been marked immutable. A similar check needs to be added to do_mmap() to prevent the creation of a writable memory mapping from a file descriptor. Those changes close off the most obvious ways to change an immutable file.

The remaining problem is that writable memory mappings of the file may already exist, and those, too, can be used to modify a file that has since been marked immutable. User-space code need not make any system calls to write to a memory-mapped region, so there isn't a single, simple place to add a check like there is with write() and mmap(). The good news is that most of the needed machinery to prevent such writes is already in place, thanks to how filesystems manage writable mappings now.

The problem that a filesystem implementation has to solve is that it, too, will get no notification when a process writes to a region of memory that has a file mapped into it; user space simply dereferences a pointer and stores data there. But the filesystem needs to know when that happens so it can ensure that the newly written data eventually finds its way to persistent storage. The trick that is used here is to write-protect the pages in memory. When user space attempts to write to one of those pages, a page fault will result; the kernel can then make the page writable and notify the filesystem that the page has been written to. When the filesystem code eventually writes the modified page(s) back to disk, it can once again write-protect those pages to mark them as being clean and to catch any subsequent modifications.

One obvious place, then, to prevent modification of an immutable file is when that page fault occurs; rather than allow the modification to proceed, the kernel can fail the operation and deliver a SIGBUS signal to user space. But even that doesn't catch the case where pages have already been marked as being writable; user space could continue to make changes to those.

Closing that last hole requires making changes to every filesystem that is to support the new behavior; that is the object of the bulk of the patches in Wong's set. Filesystem implementations already have an ioctl() handler that will be called when the immutable bit is set, so that is the logical place to respond when the status of a file is changed. A number of things need to happen at that point. If there are direct I/O operations outstanding, they must be allowed to conclude before marking the file immutable. Then, any pages that are currently dirty need to be flushed out to permanent storage; those changes were made prior to the file being marked immutable, so they should persist. Finally, all pages mapped from the file in question can be marked read-only, at which point they cannot be modified further. In most filesystems, flushing dirty pages and write-protecting them is already implemented as a single operation, so it's just a matter of calling the right function.

The end result is that, once a file is marked immutable, it truly cannot be changed further — at least, until a privileged user clears the immutable bit. This is, of course, a change in the kernel's behavior; any application that relies on the ability to write to an open file descriptor for an immutable file will break. Hyrum's law says that this is certain to happen somewhere; that would likely lead to the reversion of this patch set. In practice, though, it seems entirely possible that nobody actually depends on this obscure behavior, so Wong's patch set will fail to destroy everything as advertised.

Index entries for this article
KernelFilesystems


to post comments

Implementing fully immutable files

Posted Apr 19, 2019 15:46 UTC (Fri) by nivedita76 (subscriber, #121790) [Link]

The man page on my system at least does note this under BUGS AND LIMITATIONS:
"Setting 'a' and 'i' attributes will not affect the ability to write to already existing file descriptors."

Some other attributes are also noted to not be implemented yet.

Also, even for chmod(2) isn't it filesystem-dependent what happens to already open file descriptors if a file is made read-only for eg? The manpage notes that NFS may apply that permission immediately to open files, and presumably local filesystems wouldn't.

Implementing fully immutable files

Posted Apr 19, 2019 16:58 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (4 responses)

In practice, though, it seems entirely possible that nobody actually depends on this obscure behavior, so Wong's patch set will fail to destroy everything as advertised.
Postulate that the process writing through mmap() knows nothing of the immutable bit, and does not own the file in question. Then the patch is taking an entirely legal pointer dereference and turning it into a segfault. A segfault caused not only by a different process, but by a different user (who cannot call kill(pid, SIGBUS) directly). Maybe that's improbable, but I find it rather frightening all the same.

Implementing fully immutable files

Posted Apr 19, 2019 17:36 UTC (Fri) by derobert (subscriber, #89569) [Link] (2 responses)

Immutable can only be set with by root or something with CAP_LINUX_IMMUTABLE (which normal users typically do not have). So typically the only other user able to cause this is root.

Implementing fully immutable files

Posted Apr 19, 2019 22:25 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (1 responses)

Good point. So I can mentally move this from the "non-root can kill other user's process" bucket into the (far more niche) "capability X can be used to accomplish non-obvious result Y" bucket. I suppose that's obscure enough that no one is likely to care (capabilities are a rather questionable security boundary anyway).

Implementing fully immutable files

Posted Apr 22, 2019 12:55 UTC (Mon) by bandrami (guest, #94229) [Link]

> capabilities are a rather questionable security boundary anyway

Honestly I despise them from a security standpoint, because of the change in reasoning required you demonstrated. More complex is bad, and non-obviously more complex is even worse.

Implementing fully immutable files

Posted Apr 20, 2019 20:43 UTC (Sat) by luto (guest, #39314) [Link]

Truncating the file already does this, and it doesn’t need any special permissions.

Implementing fully immutable files

Posted Apr 19, 2019 17:08 UTC (Fri) by augustz (guest, #37348) [Link] (5 responses)

Given how frequent writes are and how infrequent setting the immutable bit on a file is, is there no way to tie in some machinery to the bit setting process to close open file handles to the file and/or mmaps? A check on every write seems like to would impact a broader range of users than something tied to setting immutable bit.

Implementing fully immutable files

Posted Apr 19, 2019 17:47 UTC (Fri) by epa (subscriber, #39769) [Link] (4 responses)

Or better still, setting the file immutable will fail if there are open write file handles or writable memory mappings for it. It's a matter of policy whether the user chooses to break these existing open handles, or to wait for them to be closed before safely setting the immutable bit.

Implementing fully immutable files

Posted Apr 19, 2019 19:03 UTC (Fri) by augustz (guest, #37348) [Link]

I like it, even simpler. I can close file, or kill process with file open if needed myself in this approach and keeps code even simpler it would seem, no write check on every write, no hooking into file systems / memory allocation.

Implementing fully immutable files

Posted Apr 19, 2019 19:47 UTC (Fri) by mb (subscriber, #50428) [Link]

Refusing to do certain filesystem operations (e.g. renaming, deletion) if the file is open is one of the most annoying things Windows does.
Please avoid this on Linux wherever possible.

Implementing fully immutable files

Posted Apr 21, 2019 11:19 UTC (Sun) by tao (subscriber, #17563) [Link] (1 responses)

Ideally you'd want to be able to do "set immutable then close" to avoid race conditions, hence being able to set the immutable flag on an open file seems reasonable. Maybe some kind of "apply on close" behaviour?

Implementing fully immutable files

Posted Apr 21, 2019 18:11 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

Even better, seal-on-close that can fail if the file still has other open handles. Then it's up to the calling process to either propagate the error or kill all other openers and retry.

Implementing fully immutable files

Posted Apr 19, 2019 21:47 UTC (Fri) by wahern (subscriber, #37304) [Link] (9 responses)

Seems like a ton of complexity for very meager benefits. Why not just change the man page? While both Solaris and BSD have similar concepts, their documentation (perhaps wisely) doesn't seem to precisely define the behavior.

It's also inconsistent. You can continue writing to a file through an existing file descriptor after removing write permissions from the file. Why should immutability be any different? And in my limited experience using extended attributes like immutability, the principal use case seems to be making backup and restore procedures more fail-safe. For the security case, that an existing process already had write permissions suggests the ability to revoke after the fact is a niche feature that doesn't contribute much from a systems engineering standpoint.

Implementing fully immutable files

Posted Apr 20, 2019 2:44 UTC (Sat) by epa (subscriber, #39769) [Link] (8 responses)

It comes back to the two meanings of ‘read-only’ which are often confused. There is read-only in the negative sense: you are not allowed to write to this file. That is provided by the permission bits. But there is also a positive contract sense of ‘read-only’: the file will not change, now or in the future, because neither you nor anyone else can change it. So while it’s conceptually okay to tighten permissions for new open() calls and yet have existing file handles keep the access that was originally granted, it’s not okay to break a promise that the file would be forever immutable — code relies on that property being honoured.

(You can compare the different meanings of ‘const’ or ‘readonly’ in languages like C++, Java and C# for another example of how an object can be read-only for you, or have a positive guarantee that it won’t be mutated while you hold a reference to it. Locking in database systems is another place where you have to separate the two meanings.)

You may be right that in practice it makes little difference. Immutable files are a specialized feature. But if you’re going to have them at all, surely they should be implemented properly. A guarantee of immutability isn’t worth much unless it holds all of the time.

Implementing fully immutable files

Posted Apr 20, 2019 5:59 UTC (Sat) by lkundrak (subscriber, #43452) [Link] (7 responses)

> But there is also a positive contract sense of ‘read-only’: the file will not change, now or in the future, because neither you nor anyone else can change it.

Unless you, of course, turn off the immutable bit.

I'm quite honestly having trouble finding it hard to understand the use case where the immutable files provide any sort of useful guarantee.

Implementing fully immutable files

Posted Apr 20, 2019 12:21 UTC (Sat) by mikemol (guest, #83507) [Link]

These start to come up when you talk about regulatory compliance, and guarantees like these are just the sort of thing one is occasionally asked to make.

I could easily construct a high-confidence system that relied on the audit framework to tell me when someone is playing with the immutable bit without the overhead of logging on every write attempt to the file in question, for example. Then, I can rely on the immutable bit (with some restrictions) as I make an assertion that a file does not, shall not change.

I can see use cases in antivirus frameworks, configuration management frameworks, logging and auditing frameworks, and so on. Effectively, any system where demonstrable, positive control over a system accessible to untrusted individuals.

It's not a magic bullet, but a useful armor layer.

Implementing fully immutable files

Posted Apr 20, 2019 13:01 UTC (Sat) by epa (subscriber, #39769) [Link] (3 responses)

Good point — I would have expected the immutable bit to be permanent, if it were to be useful in practice. (To turn it off you would have to copy the contents to a new file with a new inode number.) Maybe given that only root can turn it off, it still provides some useful property in a system where normal operation is all non-root?

Implementing fully immutable files

Posted Apr 22, 2019 16:39 UTC (Mon) by wahern (subscriber, #37304) [Link] (2 responses)

It *is* useful, even with the way it's currently implemented. For example, exploding a tarball--normally you *want* it to be able to overwrite files lacking write permission bits. Such cases are rather niche, but the complexity is negligible so worth it.

I'm sure some people would find the proposed behavior of revoking mmap access useful, too. But the additional complexity is *tremendous* and, IMO, not worth the marginal benefit, even if there are a handful of organizations that *must* have the feature. I mean, if they really need such behavior they can always just terminate all processes with open file handles after making a file immutable. Messy, but at least the mess doesn't become a long-term maintenance burden for everybody else. It's a dubious guarantee, anyhow, considering how easy it will be to accidentally break the invariant.

Implementing fully immutable files

Posted Apr 22, 2019 19:44 UTC (Mon) by wahern (subscriber, #37304) [Link]

Correction: I meant, normally when exploding a tarball you want it to overwrite files without write permission bits because you're replacing the whole tree, but sometimes you don't and setting a file immutable makes the untar operation blow up. It's a fail-safe. There's some cleaning up to do but less than if that critical file was removed.

Implementing fully immutable files

Posted Apr 22, 2019 20:15 UTC (Mon) by rweikusat2 (subscriber, #117920) [Link]

This feature has existed in its present form for at least 18 years, probably more. It even used to be documented incorrectly as

"no data can be written to the file"
(Debian 7 man page)

someone apparently noted that this wasn't accurate and corrected to documentation to

"the file can not be opened in write mode."
(http://man7.org/linux/man-pages/man1/chattr.1.html)

I seriously doubt that there's any organisation on this planet which suddenly "must" have this feature. Methinks this is more something like a bored Oracle guy making undirected changes to a codebase (possibly "a sufficiently well-connected, bored Oracle guy that such undirected changes actually get accepted instead of being stonewalled").

Implementing fully immutable files

Posted Apr 21, 2019 0:30 UTC (Sun) by mm7323 (subscriber, #87386) [Link]

Searching lead me to one interesting use case - marking mount point directories immutable to prevent accidental creation of files when the filesystem isn't mounted there. It seems a bit niche though.

As for security guarantees, wouldn't something like SElinux be more appropriate, fine grained and auditable than this mechanism? That said, I have no idea if SElinux or similar behave sanely if policy is changed while files are already opened or memory mapped...

Implementing fully immutable files

Posted Apr 22, 2019 15:15 UTC (Mon) by janfrode (subscriber, #244) [Link]

Spectrum Scale/GPFS has a more useful immutability function, where files are made immutable for a given period, and you can’t reset it without deleting the filesystem/fileset:

https://www.ibm.com/developerworks/community/wikis/home?l...

Implementing fully immutable files

Posted Apr 19, 2019 22:08 UTC (Fri) by Freeaqingme (subscriber, #103259) [Link] (1 responses)

Is there a specific reason why this cannot be solved in VFS, but has to be solved in all individual file systems instead? I would think - or rather, postulate - that VFS has an index per FS to keep track of what fd links to what inode. If such index exists, you could simply update the FD in VFS level when an attribute is changed?

Implementing fully immutable files

Posted Apr 20, 2019 1:34 UTC (Sat) by Paf (subscriber, #91811) [Link]

As the article says, the fs-specific changes are not for the file descriptor case, but rather the memory mapped file case.

DAX

Posted Apr 20, 2019 10:53 UTC (Sat) by TheGopher (subscriber, #59256) [Link]

Doesn't this all fall apart on DAX (Direct Memory Access)? I think messing with access controls on open file descriptors is in general problematic, I'd like to hear about use cases.

Implementing fully immutable files

Posted Apr 20, 2019 15:44 UTC (Sat) by rweikusat2 (subscriber, #117920) [Link] (9 responses)

This is documented behaviour and entirely consistent with the way all file permissions work: Access checks are done on open. Could we perhaps patch the guy who apprently didn't know this instead?

Implementing fully immutable files

Posted Apr 20, 2019 16:16 UTC (Sat) by gus3 (guest, #61103) [Link] (5 responses)

1. User's program P opens file A for write.

2. Root checks for open descriptors on A, terminates User's process.

3. The parent process of P notes abnormal termination, re-launches P, which re-opens A.

4. Root sets immutable bit on A, but... too late, the file isn't immutable for User.

Or, just take the system down to single-user, to make sure *nobody* has access except Root; set the immutable bit(s) in question; then return to multi-user.

The current implementation of immutable (and append-only) files forces a sysadmin to choose between guaranteed behavior and uptime, a choice that the sysadmin's boss might not (will not) understand.

Implementing fully immutable files

Posted Apr 20, 2019 22:09 UTC (Sat) by epa (subscriber, #39769) [Link]

1a. Root sets the permission bits on file A to a-w so that no new file opens can happen unless read-only.

Implementing fully immutable files

Posted Apr 21, 2019 21:01 UTC (Sun) by rweikusat2 (subscriber, #117920) [Link]

This is exactly the same as for all file configuration bits in an i-node: They have no retroactive effects, ie, code using a valid file descriptor according to the 'contract' the kernel agreed to at time of the open won't suddenly experience failures because this or that i-node change happened to be made.

Implementing fully immutable files

Posted Apr 23, 2019 16:42 UTC (Tue) by sorokin (guest, #88478) [Link] (2 responses)

> 2. Root checks for open descriptors on A, terminates User's process.
> 4. Root sets immutable bit on A, but... too late, the file isn't immutable for User.

I would say that the problem is the order of the operations. Root should set the immutable bit first and then terminate processes that use the file.

Implementing fully immutable files

Posted Apr 24, 2019 16:46 UTC (Wed) by epa (subscriber, #39769) [Link] (1 responses)

Right, but then there's an unsafe period in between when the file is marked immutable, but isn't actually immutable (processes could change it before they eventually get killed). Which is what the change described in the article is intending to fix.

Implementing fully immutable files

Posted Apr 25, 2019 16:10 UTC (Thu) by rweikusat2 (subscriber, #117920) [Link]

This is still exactly the same as for all other file mode bits: They don't change the past. An immutable file cannot be opened for writing, not even by a privileged process. This doesn't and cannot mean the data in the file isn't going to change. Eg, someone could change it via the device file. Or with a filesystem debugger. Or by reformatting the partition. Or with a custom kernel module disabling the check. There are presumably more possibilities here.

The change described in the article doesn't "fix" anything, it just replaces the documented semantics with something else.

Implementing fully immutable files

Posted Apr 21, 2019 9:06 UTC (Sun) by matthias (subscriber, #94967) [Link] (2 responses)

This is only consistent, if the immutable bit is regarded as a permission, just as the well known rwx flags. If the immutable bit is regarded as a file property, then it is not consistent. Maybe one should look at the use cases for this bit. They should be a bit special, as most use cases should be fine with chown root and chmod go-w.

I would rather think that immutable and append only are not file permissions and therefore it might be sensible to treat them differently.

Implementing fully immutable files

Posted Apr 22, 2019 11:44 UTC (Mon) by rweikusat2 (subscriber, #117920) [Link] (1 responses)

This is only consistent if it's viewed in a consistent way and becomes inconsisten when regarded as ... well ... inconsistent, ie, "But I want this to be different and special!". The feature hasn't been implemented in this way and has existed for a long time in the way it has been implemented. The implementation is consistent because all 'inode bits' affecting file access are treated in the same way. This is easier to see for append-only. Marking a file as append-only means that future opens for writing will only succeed if done in append mode.

'Immutable' is a bit messy because it both affects accesses to the file data and changes to the i-node. But "this file cannot be opened for writing" is a perfectly well-defined effect. It's also different from the ordinary write permissions because files marked as immutable also cannot be opened for writing by privileged processes while the bit is set. *If* some kind of retroactively effective access permission change is actually needed for something, ie, this is not just about an impedance mismatch between someone's idea of the English meaning of immutable and the technical term 'immutable file', it would be better to implement this as a different property than to change the semantics of an existing one.

There's also no documented error code which could be returned by write to indicate "your access has been revoked", hence, existing code supposed to handle transient file system misconfiguration automatically is pretty much guaranteed to be broken by this.

Implementing fully immutable files

Posted Apr 22, 2019 15:04 UTC (Mon) by epa (subscriber, #39769) [Link]

Given the model you describe, it appears that the whole design wart of an immutable bit would go away if there were a fourth set of permission bits on a file: a mask revoking read, write, or execute permission, and applying even to the root user.

Implementing fully immutable files

Posted Apr 21, 2019 19:24 UTC (Sun) by ncm (guest, #165) [Link] (6 responses)

This calls attention to a bigger problem that has been noted frequently: the Posix file operations are all unavoidably racy. We need a better file management interface.

The fundamental problem here is that between creating, writing, and marking the file, another process can slip in and alter the file, so that after marking it, you still have to go back and check that it was not corrupted before it was marked. This last step is likely to be omitted in real code.

Alternatively, you make an unreadable directory and a file with an unguessable name, write the file, mark it, and then mv it into place. But does mv even work on a file so marked, if I can't link() it?

Implementing fully immutable files

Posted Apr 21, 2019 19:33 UTC (Sun) by ncm (guest, #165) [Link]

Oops, it also says "cannot be renamed". So is there no non-racy way to do this, short of re-reading the file to make sure it has in it what it should, and (if not) deleting it and starting over?

Implementing fully immutable files

Posted Apr 21, 2019 22:35 UTC (Sun) by luto (guest, #39314) [Link]

Maybe use O_TMPFILE?

Implementing fully immutable files

Posted Apr 22, 2019 14:44 UTC (Mon) by rweikusat2 (subscriber, #117920) [Link] (3 responses)

This is not a problem with anything-POSIX, it's a necessary consequence of the fact that the system supports multiprogramming and that all processes have access to a shared filesystem namespace. Just like address space shared among multiple threads, this is supposed to enable processes coordinating their activities to cooperate easily. It's not supposed to isolate hostile applications from each other. Facilities for dealing with this to some degree exist, eg, namespaces, but due to the possibility of bugs in the code providing these facilities, these isolation efforts will always end up being somewhat futile: A sufficiently hostile application will be able to get around them somehow.

Implementing fully immutable files

Posted Apr 23, 2019 16:41 UTC (Tue) by matthias (subscriber, #94967) [Link] (2 responses)

No, this is not a necessary consequence. One could specify operations that are not racy in this way, just POSIX does not do so. For example one could add a version of open that creates a file with zero links and just an open file descriptor. Just as if the file would have been deleted after opening it. And one could add a system call that creates a new link to a given file descriptor (i.e. add a directory entry). Then it would be possible to create a file, write to it, change all necessary attributes and permission bits and just create the link after everything is done. For other processes this would look like an atomic operation, as the finished file appears out of nowhere. And in a way it would be an atomic operation. If the process crashes before creating the link to the file, the file would not be there at all. It would be deleted like every other file with zero links and no open file descriptors.

Implementing fully immutable files

Posted Apr 23, 2019 17:01 UTC (Tue) by rweikusat2 (subscriber, #117920) [Link]

POSIX doesn't deal with Linux i-node attributes like the immutable bit.

BTW, concluding that the racyness is not a necessary consequence of the shared filesystem namespace by coming up with a contrived example built on avoiding use of the shared filesystem namespace is 'interesting' reasoning. Why is it supposed to be avoided if it's not the cause of the problem?

Implementing fully immutable files

Posted Apr 25, 2019 8:06 UTC (Thu) by thithib (guest, #115203) [Link]

Isn't this exactly one of the two main use cases of O_TMPFILE?

And what about DMA

Posted Apr 22, 2019 16:28 UTC (Mon) by meuh (guest, #22042) [Link] (1 responses)

After reading the thread (1, 2, 3, 4) about get_user_pages() and corruptions/crashes happening when a mmap()ed file is writing to through DMA, I wonder what will happen when a peripheral will try to DMA-write to a memory mapped file toggled immutable ...

And what about DMA

Posted Apr 22, 2019 17:48 UTC (Mon) by rweikusat2 (subscriber, #117920) [Link]

It's going to be swallowed by an impromptu created, super-miniature black hole.

Implementing fully immutable files

Posted Apr 23, 2019 17:53 UTC (Tue) by xorbe (guest, #3165) [Link] (1 responses)

Perhaps it's better to fail setting the immutable bit if there are open write handles.

Implementing fully immutable files

Posted Apr 24, 2019 19:04 UTC (Wed) by nix (subscriber, #2304) [Link]

This certainly seems nicer than flushing all dirty data (bound for that inode? on that fs? on the *entire system*?) whenever the immutable bit is set. Saying that chattr +i implies a sync() is extremely counterintuitive.


Copyright © 2019, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds