ETXTBUSY on shared libs (patch included)

pacman (pacman-kernel@cqc.com)
Sat, 22 Aug 1998 00:18:00 -0500 (EST)


Here's a patch against 2.0.35 to make ETXTBUSY work on ELF shared libs. I
hope this counts as a bugfix, since overwriting a .so that is in use causes
bad things to happen to the process using it. Also because it makes the ELF
shared lib behavior match the a.out shared lib behavior.

This patch works by changing mmap to always set the MAP_DENYWRITE flag
whenever PROT_EXEC is requested. To prevent that from being used as an
annoyance attack by people PROT_EXEC'ing things they don't have execute
permission on, mmap also had to be changed to check for execute permission.

I've been running it for a day or so now, and haven't run into any bad side
effects yet. I did find 2 shared libs on my system that were mode 644 and
should have been 755, so you probably want to check up on your permissions at
least in /lib before booting with this patch.

This is my first non-trivial[1] kernel hack, so your verbose criticism will
be appreciated.

While studying this issue, I found that MAP_DENYWRITE is ignored by mmap when
passed in from outside the kernel, and I assume that's intentional, but it
seems odd that it's done in the arch/* code, and even more odd that
arch/sparc doesn't do it. What's going on there?

[1] Affecting more than one line of code

=== CUT HERE ===
diff -ruN linux/drivers/block/amiflop.c linux.pac/drivers/block/amiflop.c
--- linux/drivers/block/amiflop.c Thu May 16 01:05:10 1996
+++ linux.pac/drivers/block/amiflop.c Thu Aug 20 23:33:08 1998
@@ -1617,7 +1617,7 @@
if (old_dev && old_dev != inode->i_rdev)
invalidate_buffers(old_dev);

- if (filp && filp->f_mode)
+ if (filp && (filp->f_mode & 3))
check_disk_change(inode->i_rdev);

if (filp && (filp->f_flags & (O_WRONLY|O_RDWR))) {
diff -ruN linux/drivers/cdrom/cdrom.c linux.pac/drivers/cdrom/cdrom.c
--- linux/drivers/cdrom/cdrom.c Wed Jun 3 17:17:47 1998
+++ linux.pac/drivers/cdrom/cdrom.c Thu Aug 20 22:38:19 1998
@@ -19,6 +19,7 @@
#include <linux/cdrom.h>
#include <linux/ucdrom.h>

+/* FMODE_WRITE not good enough for you? */
#define FM_WRITE 0x2 /* file mode write bit */

#define VERSION "$Id: cdrom.c,v 0.8 1996/08/10 10:52:11 david Exp $"
diff -ruN linux/fs/exec.c linux.pac/fs/exec.c
--- linux/fs/exec.c Mon Jul 13 15:47:34 1998
+++ linux.pac/fs/exec.c Thu Aug 20 23:32:28 1998
@@ -126,7 +126,7 @@
return -ENFILE;
}
f->f_flags = mode;
- f->f_mode = (mode+1) & O_ACCMODE;
+ f->f_mode = ((mode+1) & O_ACCMODE) | FMODE_EXEC;
f->f_inode = inode;
f->f_pos = 0;
f->f_reada = 0;
diff -ruN linux/fs/fifo.c linux.pac/fs/fifo.c
--- linux/fs/fifo.c Mon Mar 11 04:23:22 1996
+++ linux.pac/fs/fifo.c Thu Aug 20 22:53:02 1998
@@ -15,7 +15,7 @@
int retval = 0;
unsigned long page;

- switch( filp->f_mode ) {
+ switch( filp->f_mode & 3 ) {

case 1:
/*
diff -ruN linux/fs/open.c linux.pac/fs/open.c
--- linux/fs/open.c Sat Nov 30 05:21:19 1996
+++ linux.pac/fs/open.c Thu Aug 20 19:18:09 1998
@@ -522,6 +522,8 @@
if (error)
goto cleanup_inode;
}
+ if (!permission(inode, MAY_EXEC))
+ f->f_mode |= FMODE_EXEC;

f->f_inode = inode;
f->f_pos = 0;
diff -ruN linux/fs/pipe.c linux.pac/fs/pipe.c
--- linux/fs/pipe.c Wed Aug 6 19:52:01 1997
+++ linux.pac/fs/pipe.c Thu Aug 6 19:33:19 1998
@@ -413,7 +413,7 @@
int error;
int i,j;

- error = ENFILE;
+ error = -ENFILE;
f1 = get_empty_filp();
if (!f1)
goto no_files;
diff -ruN linux/include/linux/fs.h linux.pac/include/linux/fs.h
--- linux/include/linux/fs.h Mon Jul 13 15:47:39 1998
+++ linux.pac/include/linux/fs.h Thu Aug 20 18:38:13 1998
@@ -45,6 +45,7 @@

#define FMODE_READ 1
#define FMODE_WRITE 2
+#define FMODE_EXEC 4

#define READ 0
#define WRITE 1
diff -ruN linux/mm/mmap.c linux.pac/mm/mmap.c
--- linux/mm/mmap.c Wed Jun 3 17:17:50 1998
+++ linux.pac/mm/mmap.c Thu Aug 20 23:56:27 1998
@@ -1,3 +1,4 @@
+/* vim:set noet ts=8 sw=8: */
/*
* linux/mm/mmap.c
*
@@ -189,6 +190,27 @@

default:
return -EINVAL;
+ }
+ if (prot & PROT_EXEC) {
+/* There are 2 ways to go on this permission check: set a FMODE_EXEC flag
+ * when the fd is created, or do it now. I prefer the former but it is a bit
+ * more complicated, so I can't be sure I haven't caused any bad side
+ * effects. Switch this #if around to do it the other way.
+ *
+ * The reason I don't like calling permission() from here because it means
+ * that a chmod can revoke permissions from an existing fd, and that doesn't
+ * occur for read and write permissions, so why should it for exec? It does
+ * look like fchdir does it this way, and that should probably be fixed too. */
+#if 1
+ if (!(file->f_mode & FMODE_EXEC))
+ return -EACCES;
+#else
+ int error;
+ error = permission(file->f_inode, MAY_EXEC);
+ if (error)
+ return error;
+#endif
+ flags |= MAP_DENYWRITE;
}
if (flags & MAP_DENYWRITE) {
if (file->f_inode->i_writecount > 0)
=== CUT HERE ===

-- 
Alan Curry

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.altern.org/andrebalsa/doc/lkml-faq.html