Vi can write to file despite file being read-only
Solution 1:
Note: Due to legacy licensing reasons, most GNU/Linux distributions don’t include the original vi program as written by Bill Joy. Instead, the vi command is provided by running Vim in vi-compatibility mode. The following answer is based on running Vim with its vi-compatibility mode.
Modifying a read-only file
Vim warns the user if they modify the buffer of a read-only file, W10: Warning: Changing a readonly file
. If the user tries writing to this file, they get the following error message, 'readonly' option is set (add ! to override)
.
When the parent directory is writeable by the Vim user
Vim, being helpful, lets the user know that they can forcefully insist on writing by appending an exclamation mark, !
to the w
command. If this forceful version of the write command is used, Vim deletes the original file (if using Vim with the Vim-only backup
option set, the original file is actually renamed to be the same as the backup file). It then opens (creates) a new file with the same name as the original and writes the contents of its buffer to this new file. This can be observed by checking the inode of the file before and after running Vim:
$ ls -l --inode t
131529 -r--r--r-- 1 anthony anthony 0 Apr 13 09:23 t
$ vi t
$ ls -l --inode t
131649 -r--r--r-- 1 anthony anthony 4 Apr 13 09:23 t
Note: This may also change the permission and ownership of the file and break (symbolic) links, e.g., if the original file was owned by another user, the new file would be owned by the user running Vim.
A process can only do this if it has write permission for the file’s parent directory. In general, to ensure that a program can't modify a file, the permissions of both the file itself and its parent directory should be secured.
When the parent directory is not writeable by the Vim user
However, even in this case, Vim still does its best to help the insistent user to over-write the file. If the Vim user has ownership of the file, Vim can get around the read-only parent directory restriction by temporarily changing the permission of the file (using the chmod
system call), writing the buffer to the file, closing the file and then changing the permissions back. Here’s an extract of the system calls made while running vi through strace, strace -o ../vi.trace vi t
:
getuid() = 501
chmod("t", 0100644) = 0
open("t", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 4
write(4, "I am good singer,\n", 18) = 18
fsync(4) = 0
close(4) = 0
chmod("t", 0100444) = 0
Note: This doesn’t happen if the Vim user is editing a file that they don’t have ownership of as Vim won’t be able to change the file permissions.
Addendum
To be really certain that a file can’t be modified (on a GNU/Linux system), run the chattr
command as the superuser:
sudo chattr +i filename
From man chattr
:
A file with the ‘i’ attribute cannot be modified: it cannot be deleted or renamed, no link can be created to this file and no data can be written to the file. Only the superuser or a process possessing the CAP_LINUX_IMMUTABLE capability can set or clear this attribute.
Solution 2:
Most if not all vi
implementations prevent you to write the file if you use a regular save command like either ZZ
, :w
, :wq
or :x
, eg with vim
:
:w
E45: 'readonly' option is set (add ! to override)
On the other hand, if you tell vi
to write the file despite its permissions, with using something like :x!
or :wq!
, the editor is temporarily relaxing the permissions to allow the file to be written:
...
stat("test-file", {st_mode=S_IFREG|0444, st_size=7, ...}) = 0
getuid() = 1000
chmod("test-file", 0100644) = 0
...
open("test-file", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 4
write(4, "I am good singer,\n", 18) = 18
fsync(4) = 0
close(4) = 0
chmod("test-file", 0100444) = 0
....
In that case, the inode number is left unchanged.
Finally, this is not a bug as if you are not allowed to change the file permissions, you cannot modify it through vi
.