shithub: plan9front

Download patch

ref: a6af3069b32670ef0902625424070087a06f8c1b
parent: 3092ac09e43d2eaeb33be3ec37dba2e8477418e6
author: Igor Böhm <igor@9lab.org>
date: Mon Oct 10 12:28:44 EDT 2022

upas/fs/imap.c: additional sanity checking during `Expunge` to avoid suicide

We have to ensure the size we compute for memmove(...) in `Expunge` is
properly bounded.  For certain combinations of inputs we compute an
illegal size causing a suicide:

	upas/fs: imap: fetchrsp: fetchrsp: bad idx 7
	upas/fs: user: igor; note: sys: trap: fault read addr=0x0 pc=0x214f92
	fs 531: suicide: sys: trap: fault read addr=0x0 pc=0x214f92

Stack trace including state of data-structures:

	term% acid -l /sys/src/cmd/upas/fs/imap.acid 531
	/proc/531/text:amd64 plan 9 executable
	/sys/lib/acid/port
	/sys/lib/acid/amd64
	acid: lstk()
	memmove(p2=0x8e5928,n=0xffffffffffffffe8)+0x42 /sys/src/libc/amd64/memmove.s:36
	imap4resp0(imap=0x423c80,mb=0x0,m=0x0)+0x448 /sys/src/cmd/upas/fs/imap.c:522
		unexp=0x58b00000000
		p=0x425d17
		ep=0x425d17
		line=0x425d0b
		n=0x425d100000058c
		verb=0x425d10
		op=0x2e6d6f63
	imap4resp()+0x1b /sys/src/cmd/upas/fs/imap.c:556
	imap4read(imap=0x423c80,mb=0x4239e0)+0x30 /sys/src/cmd/upas/fs/imap.c:928
		s=0x425d29
		f=0x425d29
		n=0x58b0000058c
		ll=0x5008a34b0
		i=0x425d290000058b
		m=0x171bd1f85150e2a6
	imap4sync(mb=0x4239e0)+0x62 /sys/src/cmd/upas/fs/imap.c:1059
		imap=0x423c80
		err=0x0
	syncmbox(mb=0x4239e0,doplumb=0x70616d6900000001)+0x5c /sys/src/cmd/upas/fs/mbox.c:76
		a=0x58f
		n=0x400bc800000000
		d=0x0
		y=0x0
		next=0x171bd206499a9366
		m=0x66939a4906d21b17
	reader()+0xde /sys/src/cmd/upas/fs/fs.c:1488
		t=0x204ca163404153
		mb=0x692e6d68656f622f
	io()+0x212 /sys/src/cmd/upas/fs/fs.c:1416
		n=0x0
	main(argc=0x0,argv=0x7fffffffef58)+0x31e /sys/src/cmd/upas/fs/fs.c:353
		mboxfile=0x7fffffffef7a
		nodflt=0x300000000
		srvpost=0x0
		v=0x7fffffffef38
		_argc=0x6d
		_args=0x40d7bd
		p=0x400000003
		maildir=0x0
		mbox=0x0
		srvfile=0x0
	_main+0x40 /sys/src/libc/amd64/main9.s:15
	acid: Imap(0x423c80)
		mbox	0x0000000000428b90
		freep	0x0000000000423c20
		host	0x0000000000423c27
		user	0x0000000000423c36
		refreshtime	60
		cap	0x00
		flags	0x05
		tag	872
		validity	605040277
		newvalidity	605040277
		nmsg	1419
		size	0
		f	0x00000000008dd408
		nuid	1420
		muid	1420
	Biobuf bin {
	Biobufhdr {
		icount	-28
		ocount	0
		rdline	16
		runesize	0
		state	1
		fid	10
		flag	0
		offset	280380
		bsize	8192
		bbuf	0x0000000000423d35
		ebuf	0x0000000000425d35
		gbuf	0x0000000000425cd1
		errorf	0x0000000000000000
		iof	0x0000000000228d17
		aux	0x0000000000000000
	}
		b	end+0x388
	}
	Biobuf bout {
	Biobufhdr {
		icount	0
		ocount	-8192
		rdline	0
		runesize	0
		state	2
		fid	10
		flag	0
		offset	33362
		bsize	8192
		bbuf	0x0000000000425d9d
		ebuf	0x0000000000427d9d
		gbuf	0x0000000000427d9d
		errorf	0x0000000000000000
		iof	0x0000000000228d3a
		aux	0x0000000000000000
	}
		b	0x425d98
	}
		binit	1
		fd	10

The root cause is an integer underflow where we subtract -1 from 0
using an unsigned type.

The above acid trace shows the following values for key local variables:

• nmsg ... 1419
• muid ... 1420
• n    ... 1420
• idx  ... 1419

The key section of code is /sys/src/cmd/upas/fs/imap.c:516,522

				case Expunge:
					if(n < 1 || n > imap->muid){
						snprint(error, sizeof(error), "bad expunge %d (nmsg %d)", n, imap->nuid);
						return error;
					}
					idx = n - 1;
					memmove(&imap->f[idx], &imap->f[idx + 1], (imap->nmsg - idx - 1)*sizeof(imap->f[0]));

Plugging the above values into the call to memmove(...) demonstrates the
issue:

	memmove(&imap->f[1419], &imap->f[1419 + 1], (1419 - 1419 - 1)*sizeof(imap->f[0]));
	                                             ^^^^^^^^^^^^^^^

The third argument of memmove(...) is an unsigned size type causing
the size to be a value that is way too large (the stack trace shows
the size to be the unsigned value 0xffffffffffffffe8).

The `Expunge` case can be fixed by amending the bounds checking
condition before the memmove(...):

			case Expunge:
				if(n < 1 || n > imap->muid || (n - 1) >= imap->nmsg){
                                              ^^^^^^^^^^^^^^^^^^^^^
					snprint(error, sizeof(error), "bad expunge %d (nmsg %d)", n, imap->nuid);
					return error;
				}
				idx = n - 1;
				memmove(&imap->f[idx], &imap->f[idx + 1], (imap->nmsg - idx - 1)*sizeof(imap->f[0]));

To add some additional context, this issue has been introduced in
revision:

	term% git/export 84c4c81ceecfa8f51949787fc2dbe7b14164a353
	Date: Mon, 02 Dec 2019 01:12:19 +0000
	Subject: [PATCH] upas/fs imap fixes and improvements

	do incremental imap fetches after startup, fixes validity handling,
	record flags correctly when we aren't in the process of directly
	updating a message, fixes off by one in flag parsing, fixes
	mis-indexing messages in sync when we get an unsolicited fetch
	response.
    ...

--- a/sys/src/cmd/upas/fs/imap.c
+++ b/sys/src/cmd/upas/fs/imap.c
@@ -514,7 +514,7 @@
 					imap->nuid = n;
 				break;
 			case Expunge:
-				if(n < 1 || n > imap->muid){
+				if(n < 1 || n > imap->muid || (n - 1) >= imap->nmsg){
 					snprint(error, sizeof(error), "bad expunge %d (nmsg %d)", n, imap->nuid);
 					return error;
 				}