12

The following example show how to create a file with only read permissions. As we can see, when I try to write to this file using the echo command I get, Permission denied.

But why, in the case that we use vi, do we not get Permission denied? As can be seen here, we can write to the file even though the file is read-only.

What is happening here? Is this a vi bug?

[admin@madona-machine1 ~]$ touch test-file
[admin@madona-machine1 ~]$ ls -ltr
total 0
-rw-r--r-- 1 admin admin 0 Apr 13 07:32 test-file
[admin@madona-machine1 ~]$ chmod -w  test-file
[admin@madona-machine1 ~]$ ls -ltr
total 0
-r--r--r-- 1 admin admin 0 Apr 13 07:32 test-file
[admin@madona-machine1 ~]$ echo try_to_write > test-file
-bash: test-file: Permission denied
[admin@madona-machine1 ~]$ vi test-file

I am good singer,

 ~
 ~
 ~
 ~
 ~
 ~
 ~                                                
   "test-file" 1L, 4C written
1

2 Answers 2

29

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.

2
  • 2
    Holy smoke, that was thorough! Apr 13, 2015 at 18:38
  • 4
    @CamilleGoudeseune After I posted the first version of my answer, I did some experiments and ended up spending about an hour running Vim through strace to see what it was doing behind the scenes in different situations (different permutations of permissions and ownership of both file and parent directory). I sometimes get carried away but once I'd published an answer, I wanted to be sure that what I was saying was correct. Apr 13, 2015 at 19:19
5

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.

1
  • Hah! After I posted my answer, I did some experiments and ended up spending close to hour running Vim through strace to see what it was doing behind the scenes in different situations (different permutations of permissions and ownership of both file and parent directory). I only just saw your answer after I'd finished summarising the results of my experiments. It was a good learning experience. Apr 13, 2015 at 9:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .