Skip to contentSkip to conversation

Longstanding elf fix (2.4.3 fix)

PATCH In review Eric W. Biederman 5 participants 9 messages Apr 23, 2001 – Apr 24, 2001

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Longstanding elf fix (2.4.3 fix)

A little while ago I was playing with building an elf self extracting binary. In doing so I discovered that the linux kernel does not handle elf program headers with multiple BSS segments.

In building a patch for 2.4.3 I also discovered that we are not taking the mmap_sem around do_brk in the exec paths.

Attached is a patch that corrects, both of these problems.

Eric

arch/mips/kernel/irixelf.c +12 -3
diff -uNrX linux-exclude-files linux-2.4.3/arch/mips/kernel/irixelf.c linux-2.4.3.elf-fix2/arch/mips/kernel/irixelf.c
@@ -130,7 +130,9 @@
 	end = PAGE_ALIGN(end);
 	if (end <= start) 
 		return;
+	down_write(&current->mm->mmap_sem);
 	do_brk(start, end - start);
+	up_write(&current->mm->mmap_sem);
 }
 
 
@@ -379,7 +381,9 @@
 
 	/* Map the last of the bss segment */
 	if (last_bss > len) {
+		down_write(&current->mm->mmap_sem);
 		do_brk(len, (last_bss - len));
+		up_write(&current->mm->mmap_sem);
 	}
 	kfree(elf_phdata);
 
@@ -567,8 +571,10 @@
 	unsigned long v;
 	struct prda *pp;
 
+	down_write(&current->mm->mmap_sem);
 	v =  do_brk (PRDA_ADDRESS, PAGE_SIZE);
-	
+	up_write(&current->mm->mmap_sem);
+		
 	if (v < 0)
 		return;
 
@@ -858,8 +864,11 @@
 
 	len = (elf_phdata->p_filesz + elf_phdata->p_vaddr+ 0xfff) & 0xfffff000;
 	bss = elf_phdata->p_memsz + elf_phdata->p_vaddr;
-	if (bss > len)
-	  do_brk(len, bss-len);
+	if (bss > len) {
+		down_write(&current->mm->mmap_sem);
+		do_brk(len, bss-len);
+		up_write(&current->mm->mmap_sem);
+	}
 	kfree(elf_phdata);
 	return 0;
 }
arch/s390x/kernel/binfmt_elf32.c +18 -5
diff -uNrX linux-exclude-files linux-2.4.3/arch/s390x/kernel/binfmt_elf32.c linux-2.4.3.elf-fix2/arch/s390x/kernel/binfmt_elf32.c
@@ -188,16 +188,29 @@
 static unsigned long
 elf_map32 (struct file *filep, unsigned long addr, struct elf_phdr *eppnt, int prot, int type)
 {
+	unsigned long start, data_len, mem_len, offset;
 	unsigned long map_addr;
 
 	if(!addr)
 		addr = 0x40000000;
 
-	down_write(&current->mm->mmap_sem);
-	map_addr = do_mmap(filep, ELF_PAGESTART(addr),
-			   eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr), prot, type,
-			   eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr));
-	up_write(&current->mm->mmap_sem);
+	start = ELF_PAGESTART(addr);
+	data_len = ELF_PAGEALIGN(eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr));
+	mem_len = ELF_PAGEALIGN(eppnt->p_memsz + ELF_PAGEOFFSET(eppnt->p_vaddr));
+	offset = eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr);
+
+	if (eppnt->p_filesz) {
+		down_write(&current->mm->mmap_sem);
+		map_addr = do_mmap(filep, start, data_len, prot, type, offset);
+		do_mmap(NULL, map_addr + data_len, mem_len - data_len, prot,
+			MAP_FIXED | MAP_PRIVATE, 0);
+		up_write(&current->mm->mmap_sem);
+		padzero(map_addr + eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr));
+	} else {
+		down_write(&current->mm->mmap_sem);
+		map_addr = do_mmap(NULL, start, mem_len, prot, MAP_PRIVATE, 0);
+		up_write(&current->mm->mmap_sem);
+	}
 	return(map_addr);
 }
 
arch/sparc64/kernel/binfmt_aout32.c +15 -0
diff -uNrX linux-exclude-files linux-2.4.3/arch/sparc64/kernel/binfmt_aout32.c linux-2.4.3.elf-fix2/arch/sparc64/kernel/binfmt_aout32.c
@@ -49,7 +49,9 @@
 	end = PAGE_ALIGN(end);
 	if (end <= start)
 		return;
+	down_write(&current->mm->mmap_sem);
 	do_brk(start, end - start);
+	up_write(&current->mm->mmap_sem);
 }
 
 /*
@@ -245,10 +247,17 @@
 	if (N_MAGIC(ex) == NMAGIC) {
 		loff_t pos = fd_offset;
 		/* Fuck me plenty... */
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_TXTADDR(ex), ex.a_text);
+		up_write(&current->mm->mmap_sem);
+
 		bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
 			  ex.a_text, &pos);
+
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_DATADDR(ex), ex.a_data);
+		up_write(&current->mm->mmap_sem);
+
 		bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex),
 			  ex.a_data, &pos);
 		goto beyond_if;
@@ -256,8 +265,10 @@
 
 	if (N_MAGIC(ex) == OMAGIC) {
 		loff_t pos = fd_offset;
+		down_write(&current->mm->mmap_sem);
 		do_brk(N_TXTADDR(ex) & PAGE_MASK,
 			ex.a_text+ex.a_data + PAGE_SIZE - 1);
+		up_write(&current->mm->mmap_sem);
 		bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
 			  ex.a_text+ex.a_data, &pos);
 	} else {
@@ -271,7 +282,9 @@
 
 		if (!bprm->file->f_op->mmap) {
 			loff_t pos = fd_offset;
+			down_write(&current->mm->mmap_sem);
 			do_brk(0, ex.a_text+ex.a_data);
+			up_write(&current->mm->mmap_sem);
 			bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex),
 				  ex.a_text+ex.a_data, &pos);
 			goto beyond_if;
@@ -382,7 +395,9 @@
 	len = PAGE_ALIGN(ex.a_text + ex.a_data);
 	bss = ex.a_text + ex.a_data + ex.a_bss;
 	if (bss > len) {
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(start_addr + len, bss - len);
+		up_write(&current->mm->mmap_sem);
 		retval = error;
 		if (error != start_addr + len)
 			goto out;
fs/binfmt_aout.c +14 -0
diff -uNrX linux-exclude-files linux-2.4.3/fs/binfmt_aout.c linux-2.4.3.elf-fix2/fs/binfmt_aout.c
@@ -45,7 +45,9 @@
 	end = PAGE_ALIGN(end);
 	if (end <= start)
 		return;
+	down_write(&current->mm->mmap_sem);
 	do_brk(start, end - start);
+	up_write(&current->mm->mmap_sem);
 }
 
 /*
@@ -310,10 +312,14 @@
 		loff_t pos = fd_offset;
 		/* Fuck me plenty... */
 		/* <AOL></AOL> */
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_TXTADDR(ex), ex.a_text);
+		up_write(&current->mm->mmap_sem);
 		bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
 			  ex.a_text, &pos);
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_DATADDR(ex), ex.a_data);
+		up_write(&current->mm->mmap_sem);
 		bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex),
 			  ex.a_data, &pos);
 		goto beyond_if;
@@ -334,7 +340,9 @@
 		map_size = ex.a_text+ex.a_data;
 #endif
 
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(text_addr & PAGE_MASK, map_size);
+		up_write(&current->mm->mmap_sem);
 		if (error != (text_addr & PAGE_MASK)) {
 			send_sig(SIGKILL, current, 0);
 			return error;
@@ -368,7 +376,9 @@
 
 		if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) {
 			loff_t pos = fd_offset;
+			down_write(&current->mm->mmap_sem);
 			do_brk(N_TXTADDR(ex), ex.a_text+ex.a_data);
+			up_write(&current->mm->mmap_sem);
 			bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex),
 					ex.a_text+ex.a_data, &pos);
 			flush_icache_range((unsigned long) N_TXTADDR(ex),
@@ -465,7 +475,9 @@
 			error_time = jiffies;
 		}
 
+		down_write(&current->mm->mmap_sem);
 		do_brk(start_addr, ex.a_text + ex.a_data + ex.a_bss);
+		up_write(&current->mm->mmap_sem);
 		
 		file->f_op->read(file, (char *)start_addr,
 			ex.a_text + ex.a_data, &pos);
@@ -489,7 +501,9 @@
 	len = PAGE_ALIGN(ex.a_text + ex.a_data);
 	bss = ex.a_text + ex.a_data + ex.a_bss;
 	if (bss > len) {
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(start_addr + len, bss - len);
+		up_write(&current->mm->mmap_sem);
 		retval = error;
 		if (error != start_addr + len)
 			goto out;
fs/binfmt_elf.c +26 -39

115 lines not on this page · raw message

#1 lore raw
Quoted 2 lines · Eric W. Biederman

In building a patch for 2.4.3 I also discovered that we are not taking the mmap_sem around do_brk in the exec paths.

Does that really matter? Who else can get at the address space? We are a singly referenced address space at that point... perhaps ptrace?

Later,
David S. Miller
[email protected]

#2 lore raw
In reply to Eric W. Biederman
Quoted 5 lines · David S. Miller
Quoted 2 lines · Eric W. Biederman

In building a patch for 2.4.3 I also discovered that we are not taking the mmap_sem around do_brk in the exec paths.

Does that really matter?

In the library loader I can certainly see it making a difference.

Quoted 2 lines

Who else can get at the address space?
We are a singly referenced address space at that point... perhaps ptrace?

In practice I don't see it being a big deal. But reliable code is made by closing all of the little loop holes.

It also improves consistency as all of the calls to do_mmap are already protected in the exec paths.

And of course since much of the code in the kernel is built on the copy a good example neglecting the locking without a big comment, invites trouble elsewhere like in elf_load_library. Where we could have multiple threads running.

Eric

#3 lore raw
Quoted 4 lines

And of course since much of the code in the kernel is built on the copy a good example neglecting the locking without a big comment, invites trouble elsewhere like in elf_load_library. Where we could have multiple threads running.

Out of interest: does anything, anywhere, actually use elf_load_library any more?

p.

#4 lore raw
In reply to Eric W. Biederman
Quoted 6 lines · David S. Miller
Quoted 2 lines · Eric W. Biederman

In building a patch for 2.4.3 I also discovered that we are not taking the mmap_sem around do_brk in the exec paths.

Does that really matter? Who else can get at the address space? We are a singly referenced address space at that point... perhaps ptrace?

Well looking a little more closely than I did last night it looks like access_process_vm (called from ptrace) can cause what amounts to a page fault at pretty arbitrary times.

ptrace is protected by the big kernel lock, but exec isn't so that doesn't help. Hmm. ptrace does require that the process be stopped in all cases, before it does anything and that probably saves us. This is subtle enough I'd rather be locally correct, and not have to worry about someone enhancing ptrace...

I'm actually a little curious what the big kernel lock in ptrace buys us. I suspect it could be a performance issue with user mode linux. Where you have multiple processes being ptraced at the same time.

Eric

#5 lore raw
Quoted 4 lines · Eric W. Biederman

ptrace is protected by the big kernel lock, but exec isn't so that doesn't help. Hmm. ptrace does require that the process be stopped in all cases

Right. Ptrace definitely cannot access a process at "arbitrary" times. In fact, it is very serialized indeed, in that it can only access a process at "signal points", ie effectively when it is returning to user space.

With threads, of course, that doesn't help us. But with threads, the other threads could have caused the same page faults, so ptrace() isn't actually adding any "new" cases in that sense.

I'd be a lot more worried about /proc accesses.

execve() doesn't really need the mm semaphore, but on the other hand it would be cleaner to get it, and it won't really hurt (there can not be any real contention on it anyway - the only contention might come through /proc, and I haven't looked at what that might imply).

load-library should definitely get it. I thought it did already, but..

Did you have a patch? Maybe I missed it.

Linus

#6 lore raw
Quoted 10 lines · Linus Torvalds
Quoted 4 lines · Eric W. Biederman

ptrace is protected by the big kernel lock, but exec isn't so that doesn't help. Hmm. ptrace does require that the process be stopped in all cases

Right. Ptrace definitely cannot access a process at "arbitrary" times. In fact, it is very serialized indeed, in that it can only access a process at "signal points", ie effectively when it is returning to user space.

Quoted 5 lines

With threads, of course, that doesn't help us. But with threads, the other threads could have caused the same page faults, so ptrace() isn't actually adding any "new" cases in that sense.

I'd be a lot more worried about /proc accesses.

access_process_vm is also called from /proc to get the environment and the command line. I don't know if it has other locks it might serialize on, probably not. With execve it's a very small window...

Quoted 8 lines

execve() doesn't really need the mm semaphore, but on the other hand it would be cleaner to get it, and it won't really hurt (there can not be any real contention on it anyway - the only contention might come through /proc, and I haven't looked at what that might imply).

load-library should definitely get it. I thought it did already, but..

Did you have a patch? Maybe I missed it.

I'll include it again. I had it attached as a plain text attachment, I don't know if that is a problem or not.

The case I spotted it we were getting the mm semaphore for do_mmap but not for do_brk. So we only get it 50% of the time...

The other thing my patch does is update elf_map so we now handles elf files with multiple bss sections.

Eric

arch/mips/kernel/irixelf.c +12 -3
diff -uNrX linux-exclude-files linux-2.4.3/arch/mips/kernel/irixelf.c linux-2.4.3.elf-fix2/arch/mips/kernel/irixelf.c
@@ -130,7 +130,9 @@
 	end = PAGE_ALIGN(end);
 	if (end <= start) 
 		return;
+	down_write(&current->mm->mmap_sem);
 	do_brk(start, end - start);
+	up_write(&current->mm->mmap_sem);
 }
 
 
@@ -379,7 +381,9 @@
 
 	/* Map the last of the bss segment */
 	if (last_bss > len) {
+		down_write(&current->mm->mmap_sem);
 		do_brk(len, (last_bss - len));
+		up_write(&current->mm->mmap_sem);
 	}
 	kfree(elf_phdata);
 
@@ -567,8 +571,10 @@
 	unsigned long v;
 	struct prda *pp;
 
+	down_write(&current->mm->mmap_sem);
 	v =  do_brk (PRDA_ADDRESS, PAGE_SIZE);
-	
+	up_write(&current->mm->mmap_sem);
+		
 	if (v < 0)
 		return;
 
@@ -858,8 +864,11 @@
 
 	len = (elf_phdata->p_filesz + elf_phdata->p_vaddr+ 0xfff) & 0xfffff000;
 	bss = elf_phdata->p_memsz + elf_phdata->p_vaddr;
-	if (bss > len)
-	  do_brk(len, bss-len);
+	if (bss > len) {
+		down_write(&current->mm->mmap_sem);
+		do_brk(len, bss-len);
+		up_write(&current->mm->mmap_sem);
+	}
 	kfree(elf_phdata);
 	return 0;
 }
arch/s390x/kernel/binfmt_elf32.c +18 -5
diff -uNrX linux-exclude-files linux-2.4.3/arch/s390x/kernel/binfmt_elf32.c linux-2.4.3.elf-fix2/arch/s390x/kernel/binfmt_elf32.c
@@ -188,16 +188,29 @@
 static unsigned long
 elf_map32 (struct file *filep, unsigned long addr, struct elf_phdr *eppnt, int prot, int type)
 {
+	unsigned long start, data_len, mem_len, offset;
 	unsigned long map_addr;
 
 	if(!addr)
 		addr = 0x40000000;
 
-	down_write(&current->mm->mmap_sem);
-	map_addr = do_mmap(filep, ELF_PAGESTART(addr),
-			   eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr), prot, type,
-			   eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr));
-	up_write(&current->mm->mmap_sem);
+	start = ELF_PAGESTART(addr);
+	data_len = ELF_PAGEALIGN(eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr));
+	mem_len = ELF_PAGEALIGN(eppnt->p_memsz + ELF_PAGEOFFSET(eppnt->p_vaddr));
+	offset = eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr);
+
+	if (eppnt->p_filesz) {
+		down_write(&current->mm->mmap_sem);
+		map_addr = do_mmap(filep, start, data_len, prot, type, offset);
+		do_mmap(NULL, map_addr + data_len, mem_len - data_len, prot,
+			MAP_FIXED | MAP_PRIVATE, 0);
+		up_write(&current->mm->mmap_sem);
+		padzero(map_addr + eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr));
+	} else {
+		down_write(&current->mm->mmap_sem);
+		map_addr = do_mmap(NULL, start, mem_len, prot, MAP_PRIVATE, 0);
+		up_write(&current->mm->mmap_sem);
+	}
 	return(map_addr);
 }
 
arch/sparc64/kernel/binfmt_aout32.c +15 -0
diff -uNrX linux-exclude-files linux-2.4.3/arch/sparc64/kernel/binfmt_aout32.c linux-2.4.3.elf-fix2/arch/sparc64/kernel/binfmt_aout32.c
@@ -49,7 +49,9 @@
 	end = PAGE_ALIGN(end);
 	if (end <= start)
 		return;
+	down_write(&current->mm->mmap_sem);
 	do_brk(start, end - start);
+	up_write(&current->mm->mmap_sem);
 }
 
 /*
@@ -245,10 +247,17 @@
 	if (N_MAGIC(ex) == NMAGIC) {
 		loff_t pos = fd_offset;
 		/* Fuck me plenty... */
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_TXTADDR(ex), ex.a_text);
+		up_write(&current->mm->mmap_sem);
+
 		bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
 			  ex.a_text, &pos);
+
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_DATADDR(ex), ex.a_data);
+		up_write(&current->mm->mmap_sem);
+
 		bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex),
 			  ex.a_data, &pos);
 		goto beyond_if;
@@ -256,8 +265,10 @@
 
 	if (N_MAGIC(ex) == OMAGIC) {
 		loff_t pos = fd_offset;
+		down_write(&current->mm->mmap_sem);
 		do_brk(N_TXTADDR(ex) & PAGE_MASK,
 			ex.a_text+ex.a_data + PAGE_SIZE - 1);
+		up_write(&current->mm->mmap_sem);
 		bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
 			  ex.a_text+ex.a_data, &pos);
 	} else {
@@ -271,7 +282,9 @@
 
 		if (!bprm->file->f_op->mmap) {
 			loff_t pos = fd_offset;
+			down_write(&current->mm->mmap_sem);
 			do_brk(0, ex.a_text+ex.a_data);
+			up_write(&current->mm->mmap_sem);
 			bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex),
 				  ex.a_text+ex.a_data, &pos);
 			goto beyond_if;
@@ -382,7 +395,9 @@
 	len = PAGE_ALIGN(ex.a_text + ex.a_data);
 	bss = ex.a_text + ex.a_data + ex.a_bss;
 	if (bss > len) {
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(start_addr + len, bss - len);
+		up_write(&current->mm->mmap_sem);
 		retval = error;
 		if (error != start_addr + len)
 			goto out;
fs/binfmt_aout.c +14 -0
diff -uNrX linux-exclude-files linux-2.4.3/fs/binfmt_aout.c linux-2.4.3.elf-fix2/fs/binfmt_aout.c
@@ -45,7 +45,9 @@
 	end = PAGE_ALIGN(end);
 	if (end <= start)
 		return;
+	down_write(&current->mm->mmap_sem);
 	do_brk(start, end - start);
+	up_write(&current->mm->mmap_sem);
 }
 
 /*
@@ -310,10 +312,14 @@
 		loff_t pos = fd_offset;
 		/* Fuck me plenty... */
 		/* <AOL></AOL> */
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_TXTADDR(ex), ex.a_text);
+		up_write(&current->mm->mmap_sem);
 		bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
 			  ex.a_text, &pos);
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(N_DATADDR(ex), ex.a_data);
+		up_write(&current->mm->mmap_sem);
 		bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex),
 			  ex.a_data, &pos);
 		goto beyond_if;
@@ -334,7 +340,9 @@
 		map_size = ex.a_text+ex.a_data;
 #endif
 
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(text_addr & PAGE_MASK, map_size);
+		up_write(&current->mm->mmap_sem);
 		if (error != (text_addr & PAGE_MASK)) {
 			send_sig(SIGKILL, current, 0);
 			return error;
@@ -368,7 +376,9 @@
 
 		if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) {
 			loff_t pos = fd_offset;
+			down_write(&current->mm->mmap_sem);
 			do_brk(N_TXTADDR(ex), ex.a_text+ex.a_data);
+			up_write(&current->mm->mmap_sem);
 			bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex),
 					ex.a_text+ex.a_data, &pos);
 			flush_icache_range((unsigned long) N_TXTADDR(ex),
@@ -465,7 +475,9 @@
 			error_time = jiffies;
 		}
 
+		down_write(&current->mm->mmap_sem);
 		do_brk(start_addr, ex.a_text + ex.a_data + ex.a_bss);
+		up_write(&current->mm->mmap_sem);
 		
 		file->f_op->read(file, (char *)start_addr,
 			ex.a_text + ex.a_data, &pos);
@@ -489,7 +501,9 @@
 	len = PAGE_ALIGN(ex.a_text + ex.a_data);
 	bss = ex.a_text + ex.a_data + ex.a_bss;
 	if (bss > len) {
+		down_write(&current->mm->mmap_sem);
 		error = do_brk(start_addr + len, bss - len);
+		up_write(&current->mm->mmap_sem);
 		retval = error;
 		if (error != start_addr + len)
 			goto out;
fs/binfmt_elf.c +26 -39

115 lines not on this page · raw message

#7 lore raw
Quoted 2 lines · Eric W. Biederman

I'll include it again. I had it attached as a plain text attachment, I don't know if that is a problem or not.

Actually it was attached as text/x-patch, not as text/plain... so pine certainly refused to display it inline.

Ion

Signature · 2 lines
  It is better to keep your mouth shut and be thought a fool,
            than to open it and remove all doubt.
#8 lore raw
Quoted 7 lines · Ion Badulescu
Quoted 2 lines · Eric W. Biederman

I'll include it again. I had it attached as a plain text attachment, I don't know if that is a problem or not.

Actually it was attached as text/x-patch, not as text/plain... so pine certainly refused to display it inline.

Interesting I'll have to play with that some time. I really think for basic display it should only care about the text part. But oh well.

Eric