linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] dcache: fix dpath buffer corruption for too small buffers
@ 2014-03-12 23:29 Imre Deak
  2014-03-23  4:36 ` Al Viro
  2014-03-24 12:17 ` [PATCH v2] " Imre Deak
  0 siblings, 2 replies; 4+ messages in thread
From: Imre Deak @ 2014-03-12 23:29 UTC (permalink / raw)
  To: Alexander Viro, linux-fsdevel, linux-kernel

During dentry path lookups we can end up corrupting memory if the
destination path buffer is too small. This is because prepend_path()
and prepend() adjust the passed buffer length unconditionally, allowing
for the buffer length to go negative. Then a later prepend_name() call
will receive a negative length and convert this to unsigned before
comparing it against the source string length leading to a possible
memory corruption preceeding the destination buffer.

This fixes the following core dumping for me:

evolution[16162]: segfault at 0 ip 00007faadd0e7609 sp 00007ffffd63e780 error 4 in libevolution-mail.so.0.0.0[7faadd05a000+c7000]
BUG: unable to handle kernel paging request at ffffc900000f5000
IP: [<ffffffff812e04fa>] memmove+0x4a/0x1a0
PGD 41d80e067 PUD 41d80f067 PMD 41d820067 PTE 0
Oops: 0002 [#1] PREEMPT SMP
Modules linked in: nbd vfat fat nls_utf8 fuse rfcomm bnep bluetooth rfkill snd_hda_codec_hdmi snd_hda_codec_realtek kvm_intel kvm crc32c_intel snd_hda_intel snd_hda_codec snd_hwdep snd_pcm_oss snd_mixer_oss ftdi_sio usbserial snd_pcm cdc_acm snd_seq_dummy snd_page_alloc snd_seq_oss microcode serio_raw i915 snd_seq_midi snd_rawmidi snd_seq_midi_event snd_seq snd_seq_device snd_timer snd cfbfillrect soundcore cfbimgblt i2c_algo_bit cfbcopyarea drm_kms_helper lpc_ich drm mfd_core usb_storage usbhid
CPU: 1 PID: 16162 Comm: evolution Tainted: G        W    3.13.0-imre+ #327
Hardware name: Dell Inc. XPS 8300  /0Y2MRG, BIOS A03 02/15/2011
task: ffff8804157a8000 ti: ffff8803e0afa000 task.ti: ffff8803e0afa000
RIP: 0010:[<ffffffff812e04fa>]  [<ffffffff812e04fa>] memmove+0x4a/0x1a0
RSP: 0000:ffff8803e0afba70  EFLAGS: 00010283
RAX: ffffc900000f5000 RBX: 00000000ffffffdd RCX: ffffc900000f5000
RDX: ffffffffffffffe3 RSI: ffffc900000f4ffd RDI: ffffc900000f5000
RBP: ffff8803e0afbc30 R08: 312e302e6f732e30 R09: 2e332d6b74677469
R10: 6b62657762696c2f R11: 62696c2f7273752f R12: 0000000000000023
R13: ffff880415cb7790 R14: ffffc900000f5023 R15: ffffc900000e7370
FS:  00007faafb750a40(0000) GS:ffff88042fa40000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffc900000f5000 CR3: 0000000043388000 CR4: 00000000000407e0
Stack:
 ffffffff811e4a3d ffffffff811e4757 ffff8804157a8000 0000000000007558
 000004e5000004e5 ffff880400014000 ffff880100007558 ffff8801668bfb00
 ffffffff81c226a0 ffff8803e0afbce0 ffffc900000e8558 ffffc900000e1000
Call Trace:
 [<ffffffff811e4a3d>] ? elf_core_dump+0xb3d/0x1400
 [<ffffffff811e4757>] ? elf_core_dump+0x857/0x1400
 [<ffffffff811e9b9a>] do_coredump+0xbda/0xf90
 [<ffffffff8109890d>] ? trace_hardirqs_off+0xd/0x10
 [<ffffffff81061ec6>] get_signal_to_deliver+0x5c6/0x6b0
 [<ffffffff81002378>] do_signal+0x48/0x560
 [<ffffffff811d359f>] ? fsnotify+0x8f/0x340
 [<ffffffff816ac7d1>] ? retint_signal+0x11/0x90
 [<ffffffff810028c5>] do_notify_resume+0x35/0x80
 [<ffffffff816ac806>] retint_signal+0x46/0x90
Code: 00 00 48 81 fa a8 02 00 00 72 05 40 38 fe 74 41 48 83 ea 20 48 83 ea 20 4c 8b 1e 4c 8b 56 08 4c 8b 4e 10 4c 8b 46 18 48 8d 76 20 <4c> 89 1f 4c 89 57 08 4c 89 4f 10 4c 89 47 18 48 8d 7f 20 73 d4
RIP  [<ffffffff812e04fa>] memmove+0x4a/0x1a0
 RSP <ffff8803e0afba70>
CR2: ffffc900000f5000
---[ end trace cc7a046285294005 ]---

Signed-off-by: Imre Deak <imre.deak@intel.com>
---
 fs/dcache.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 265e0ce..4015fd9 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -2833,7 +2833,8 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
 	u32 dlen = ACCESS_ONCE(name->len);
 	char *p;
 
-	if (*buflen < dlen + 1)
+	/* make sure we don't convert a negative value to unsigned int */
+	if (*buflen < 0 || *buflen < dlen + 1)
 		return -ENAMETOOLONG;
 	*buflen -= dlen + 1;
 	p = *buffer -= dlen + 1;
-- 
1.8.3.2


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] dcache: fix dpath buffer corruption for too small buffers
  2014-03-12 23:29 [PATCH] dcache: fix dpath buffer corruption for too small buffers Imre Deak
@ 2014-03-23  4:36 ` Al Viro
  2014-03-23 16:07   ` Imre Deak
  2014-03-24 12:17 ` [PATCH v2] " Imre Deak
  1 sibling, 1 reply; 4+ messages in thread
From: Al Viro @ 2014-03-23  4:36 UTC (permalink / raw)
  To: Imre Deak; +Cc: linux-fsdevel, linux-kernel

On Thu, Mar 13, 2014 at 01:29:46AM +0200, Imre Deak wrote:
> During dentry path lookups we can end up corrupting memory if the
> destination path buffer is too small. This is because prepend_path()
> and prepend() adjust the passed buffer length unconditionally, allowing
> for the buffer length to go negative. Then a later prepend_name() call
> will receive a negative length and convert this to unsigned before
> comparing it against the source string length leading to a possible
> memory corruption preceeding the destination buffer.
> 
> diff --git a/fs/dcache.c b/fs/dcache.c
> index 265e0ce..4015fd9 100644
> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -2833,7 +2833,8 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
>  	u32 dlen = ACCESS_ONCE(name->len);
>  	char *p;
>  
> -	if (*buflen < dlen + 1)
> +	/* make sure we don't convert a negative value to unsigned int */
> +	if (*buflen < 0 || *buflen < dlen + 1)
>  		return -ENAMETOOLONG;
>  	*buflen -= dlen + 1;

It's much easier to fix, actually.  Look at the callers of prepend_name();
when it returns a negative value, they either discard the value left in
*buflen (__dentry_path()) or pass it back to their caller and return a
negative value themselves (prepend_path()).  The same is true for
prepend_path() (discared in __d_path(), d_absolute_path(), sys_getcwd();
passed to caller with negative return value in path_with_deleted()) and
path_with_deleted() (the only caller is d_path() and it always discards).

In other words, when prepend_name() returns -ENAMETOOLONG, it is free to
leave whatever it wants in *buflen.  So let's do what prepend() does -
subtract from *buflen, then check if the result has become negative.
Generates a better code than the original, actually...

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] dcache: fix dpath buffer corruption for too small buffers
  2014-03-23  4:36 ` Al Viro
@ 2014-03-23 16:07   ` Imre Deak
  0 siblings, 0 replies; 4+ messages in thread
From: Imre Deak @ 2014-03-23 16:07 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-fsdevel, linux-kernel

On Sun, 2014-03-23 at 04:36 +0000, Al Viro wrote:
> On Thu, Mar 13, 2014 at 01:29:46AM +0200, Imre Deak wrote:
> > During dentry path lookups we can end up corrupting memory if the
> > destination path buffer is too small. This is because prepend_path()
> > and prepend() adjust the passed buffer length unconditionally, allowing
> > for the buffer length to go negative. Then a later prepend_name() call
> > will receive a negative length and convert this to unsigned before
> > comparing it against the source string length leading to a possible
> > memory corruption preceeding the destination buffer.
> > 
> > diff --git a/fs/dcache.c b/fs/dcache.c
> > index 265e0ce..4015fd9 100644
> > --- a/fs/dcache.c
> > +++ b/fs/dcache.c
> > @@ -2833,7 +2833,8 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
> >  	u32 dlen = ACCESS_ONCE(name->len);
> >  	char *p;
> >  
> > -	if (*buflen < dlen + 1)
> > +	/* make sure we don't convert a negative value to unsigned int */
> > +	if (*buflen < 0 || *buflen < dlen + 1)
> >  		return -ENAMETOOLONG;
> >  	*buflen -= dlen + 1;
> 
> It's much easier to fix, actually.  Look at the callers of prepend_name();
> when it returns a negative value, they either discard the value left in
> *buflen (__dentry_path()) or pass it back to their caller and return a
> negative value themselves (prepend_path()).  The same is true for
> prepend_path() (discared in __d_path(), d_absolute_path(), sys_getcwd();
> passed to caller with negative return value in path_with_deleted()) and
> path_with_deleted() (the only caller is d_path() and it always discards).
> 
> In other words, when prepend_name() returns -ENAMETOOLONG, it is free to
> leave whatever it wants in *buflen.  So let's do what prepend() does -
> subtract from *buflen, then check if the result has become negative.
> Generates a better code than the original, actually...

Thanks for the review.

I was hesitating between the two solutions and picked the above one out
of paranoia, in case dlen > INT_MAX. prepend() accepts an int namelen,
so it doesn't have this issue. But I realize such big qstrs should be
guarded against at some higher level already and your solution makes
things more unified with the rest of the helpers.

I'll follow up with a v2 per your suggestion.

--Imre


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v2] dcache: fix dpath buffer corruption for too small buffers
  2014-03-12 23:29 [PATCH] dcache: fix dpath buffer corruption for too small buffers Imre Deak
  2014-03-23  4:36 ` Al Viro
@ 2014-03-24 12:17 ` Imre Deak
  1 sibling, 0 replies; 4+ messages in thread
From: Imre Deak @ 2014-03-24 12:17 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-fsdevel, linux-kernel

During dentry path lookups we can end up corrupting memory if the
destination path buffer is too small. This is because prepend_path()
and prepend() adjust the passed buffer length unconditionally, allowing
for the buffer length to go negative. Then a later prepend_name() call
will receive a negative length and convert this to unsigned before
comparing it against the source string length leading to a possible
memory corruption preceeding the destination buffer.

v2:
- Fix this by doing the same as prepend() and subtract the qstr length
  unconditionally from *buflen. All the callers know already how to
  properly treat a negative *buflen (Al Viro).

This fixes the following core dumping for me:

evolution[16162]: segfault at 0 ip 00007faadd0e7609 sp 00007ffffd63e780 error 4 in libevolution-mail.so.0.0.0[7faadd05a000+c7000]
BUG: unable to handle kernel paging request at ffffc900000f5000
IP: [<ffffffff812e04fa>] memmove+0x4a/0x1a0
PGD 41d80e067 PUD 41d80f067 PMD 41d820067 PTE 0
Oops: 0002 [#1] PREEMPT SMP
Modules linked in: nbd vfat fat nls_utf8 fuse rfcomm bnep bluetooth rfkill snd_hda_codec_hdmi snd_hda_codec_realtek kvm_intel kvm crc32c_intel snd_hda_intel snd_hda_codec snd_hwdep snd_pcm_oss snd_mixer_oss ftdi_sio usbserial snd_pcm cdc_acm snd_seq_dummy snd_page_alloc snd_seq_oss microcode serio_raw i915 snd_seq_midi snd_rawmidi snd_seq_midi_event snd_seq snd_seq_device snd_timer snd cfbfillrect soundcore cfbimgblt i2c_algo_bit cfbcopyarea drm_kms_helper lpc_ich drm mfd_core usb_storage usbhid
CPU: 1 PID: 16162 Comm: evolution Tainted: G        W    3.13.0-imre+ #327
Hardware name: Dell Inc. XPS 8300  /0Y2MRG, BIOS A03 02/15/2011
task: ffff8804157a8000 ti: ffff8803e0afa000 task.ti: ffff8803e0afa000
RIP: 0010:[<ffffffff812e04fa>]  [<ffffffff812e04fa>] memmove+0x4a/0x1a0
RSP: 0000:ffff8803e0afba70  EFLAGS: 00010283
RAX: ffffc900000f5000 RBX: 00000000ffffffdd RCX: ffffc900000f5000
RDX: ffffffffffffffe3 RSI: ffffc900000f4ffd RDI: ffffc900000f5000
RBP: ffff8803e0afbc30 R08: 312e302e6f732e30 R09: 2e332d6b74677469
R10: 6b62657762696c2f R11: 62696c2f7273752f R12: 0000000000000023
R13: ffff880415cb7790 R14: ffffc900000f5023 R15: ffffc900000e7370
FS:  00007faafb750a40(0000) GS:ffff88042fa40000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffc900000f5000 CR3: 0000000043388000 CR4: 00000000000407e0
Stack:
 ffffffff811e4a3d ffffffff811e4757 ffff8804157a8000 0000000000007558
 000004e5000004e5 ffff880400014000 ffff880100007558 ffff8801668bfb00
 ffffffff81c226a0 ffff8803e0afbce0 ffffc900000e8558 ffffc900000e1000
Call Trace:
 [<ffffffff811e4a3d>] ? elf_core_dump+0xb3d/0x1400
 [<ffffffff811e4757>] ? elf_core_dump+0x857/0x1400
 [<ffffffff811e9b9a>] do_coredump+0xbda/0xf90
 [<ffffffff8109890d>] ? trace_hardirqs_off+0xd/0x10
 [<ffffffff81061ec6>] get_signal_to_deliver+0x5c6/0x6b0
 [<ffffffff81002378>] do_signal+0x48/0x560
 [<ffffffff811d359f>] ? fsnotify+0x8f/0x340
 [<ffffffff816ac7d1>] ? retint_signal+0x11/0x90
 [<ffffffff810028c5>] do_notify_resume+0x35/0x80
 [<ffffffff816ac806>] retint_signal+0x46/0x90
Code: 00 00 48 81 fa a8 02 00 00 72 05 40 38 fe 74 41 48 83 ea 20 48 83 ea 20 4c 8b 1e 4c 8b 56 08 4c 8b 4e 10 4c 8b 46 18 48 8d 76 20 <4c> 89 1f 4c 89 57 08 4c 89 4f 10 4c 89 47 18 48 8d 7f 20 73 d4
RIP  [<ffffffff812e04fa>] memmove+0x4a/0x1a0
 RSP <ffff8803e0afba70>
CR2: ffffc900000f5000

Signed-off-by: Imre Deak <imre.deak@intel.com>
---
 fs/dcache.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 265e0ce..ca02c13 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -2833,9 +2833,9 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
 	u32 dlen = ACCESS_ONCE(name->len);
 	char *p;
 
-	if (*buflen < dlen + 1)
-		return -ENAMETOOLONG;
 	*buflen -= dlen + 1;
+	if (*buflen < 0)
+		return -ENAMETOOLONG;
 	p = *buffer -= dlen + 1;
 	*p++ = '/';
 	while (dlen--) {
-- 
1.8.4

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2014-03-24 12:17 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-03-12 23:29 [PATCH] dcache: fix dpath buffer corruption for too small buffers Imre Deak
2014-03-23  4:36 ` Al Viro
2014-03-23 16:07   ` Imre Deak
2014-03-24 12:17 ` [PATCH v2] " Imre Deak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).