Merge lp:~psusi/ubuntu/utopic/gparted/fix-crashes into lp:ubuntu/utopic/gparted

Proposed by Phillip Susi
Status: Merged
Merge reported by: Brian Murray
Merged at revision: not available
Proposed branch: lp:~psusi/ubuntu/utopic/gparted/fix-crashes
Merge into: lp:ubuntu/utopic/gparted
Diff against target: 485 lines (+390/-11)
8 files modified
.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/include/PipeCapture.h (+49/-0)
.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/src/PipeCapture.cc (+111/-0)
.pc/applied-patches (+1/-0)
debian/changelog (+8/-0)
debian/patches/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch (+215/-0)
debian/patches/series (+1/-0)
include/PipeCapture.h (+0/-1)
src/PipeCapture.cc (+5/-10)
To merge this branch: bzr merge lp:~psusi/ubuntu/utopic/gparted/fix-crashes
Reviewer Review Type Date Requested Status
Brian Murray Approve
Review via email: mp+240273@code.launchpad.net

Description of the change

Backport of an upstream fix of a cross thread write after free that causes erratic behavior and crashes.

To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote :

I've uploaded this to the utopic proposed queue. Thanks for working on this.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch'
2=== added file '.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/.timestamp'
3=== added directory '.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/include'
4=== added file '.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/include/PipeCapture.h'
5--- .pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/include/PipeCapture.h 1970-01-01 00:00:00 +0000
6+++ .pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/include/PipeCapture.h 2014-10-31 14:25:19 +0000
7@@ -0,0 +1,49 @@
8+/* Copyright (C) 2013 Phillip Susi
9+ *
10+ * This program is free software; you can redistribute it and/or modify
11+ * it under the terms of the GNU General Public License as published by
12+ * the Free Software Foundation; either version 2 of the License, or
13+ * (at your option) any later version.
14+ *
15+ * This program is distributed in the hope that it will be useful,
16+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
17+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+ * GNU General Public License for more details.
19+ *
20+ * You should have received a copy of the GNU General Public License
21+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
22+ */
23+
24+#ifndef GPARTED_PIPECAPTURE_H
25+#define GPARTED_PIPECAPTURE_H
26+
27+#include <glibmm/ustring.h>
28+#include <glibmm/main.h>
29+#include <glibmm/iochannel.h>
30+
31+namespace GParted {
32+
33+// captures output pipe of subprocess into a ustring and emits a signal on eof
34+class PipeCapture
35+{
36+ Glib::ustring &buff;
37+ Glib::ustring::size_type linestart ;
38+ Glib::ustring::size_type cursor ;
39+ Glib::ustring::size_type lineend ;
40+ Glib::RefPtr<Glib::IOChannel> channel;
41+ guint sourceid;
42+ bool OnReadable( Glib::IOCondition condition );
43+ static gboolean _OnReadable( GIOChannel *source,
44+ GIOCondition condition,
45+ gpointer data );
46+public:
47+ PipeCapture( int fd, Glib::ustring &buffer );
48+ void connect_signal();
49+ ~PipeCapture();
50+ sigc::signal<void> signal_eof;
51+ sigc::signal<void> signal_update;
52+};
53+
54+} // namepace GParted
55+
56+#endif /* GPARTED_PIPECAPTURE_H */
57
58=== added directory '.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/src'
59=== added file '.pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/src/PipeCapture.cc'
60--- .pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/src/PipeCapture.cc 1970-01-01 00:00:00 +0000
61+++ .pc/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch/src/PipeCapture.cc 2014-10-31 14:25:19 +0000
62@@ -0,0 +1,111 @@
63+/* Copyright (C) 2013 Phillip Susi
64+ *
65+ * This program is free software; you can redistribute it and/or modify
66+ * it under the terms of the GNU General Public License as published by
67+ * the Free Software Foundation; either version 2 of the License, or
68+ * (at your option) any later version.
69+ *
70+ * This program is distributed in the hope that it will be useful,
71+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
72+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
73+ * GNU General Public License for more details.
74+ *
75+ * You should have received a copy of the GNU General Public License
76+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
77+ */
78+
79+#include "../include/PipeCapture.h"
80+#include <iostream>
81+
82+namespace GParted {
83+
84+PipeCapture::PipeCapture( int fd, Glib::ustring &string ) : buff( string ),
85+ linestart( 0 ), cursor( 0 ), lineend( 0 ),
86+ sourceid( 0 )
87+{
88+ // tie fd to string
89+ // make channel
90+ channel = Glib::IOChannel::create_from_fd( fd );
91+}
92+
93+void PipeCapture::connect_signal()
94+{
95+ // connect handler to signal input/output
96+ sourceid = g_io_add_watch( channel->gobj(),
97+ GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
98+ _OnReadable,
99+ this );
100+}
101+
102+gboolean PipeCapture::_OnReadable( GIOChannel *source,
103+ GIOCondition condition,
104+ gpointer data )
105+{
106+ PipeCapture *pc = static_cast<PipeCapture *>(data);
107+ gboolean rc = pc->OnReadable( Glib::IOCondition(condition) );
108+ if (!rc)
109+ pc->sourceid = 0;
110+ return rc;
111+}
112+
113+
114+bool PipeCapture::OnReadable( Glib::IOCondition condition )
115+{
116+ //Read from pipe and store in buff. Provide minimal interpretation so
117+ // programs which use text progress bars are displayed correctly.
118+ // Linestart, cursor and lineend are offsets into buff like this:
119+ // "Previous line\n
120+ // Current line. Text progress bar: XXXXXXXXXX----------"
121+ // ^ ^ ^
122+ // linestart cursor lineend
123+ Glib::ustring str;
124+ Glib::IOStatus status = channel->read( str, 512 );
125+ if (status == Glib::IO_STATUS_NORMAL)
126+ {
127+ for( Glib::ustring::iterator s = str.begin(); s != str.end(); s++ )
128+ {
129+ if( *s == '\b' ) {
130+ if ( cursor > linestart )
131+ cursor -- ;
132+ }
133+ else if( *s == '\r' )
134+ cursor = linestart ;
135+ else if( *s == '\n' ) {
136+ cursor = lineend ;
137+ buff .append( 1, '\n' ) ;
138+ cursor ++ ;
139+ linestart = cursor ;
140+ lineend = cursor ;
141+ }
142+ else if (*s == '\x01' || *s == '\x02' )
143+ //Skip Ctrl-A and Ctrl-B chars e2fsck uses to bracket the progress bar
144+ continue;
145+ else {
146+ if ( cursor < lineend ) {
147+ buff .replace( cursor, 1, 1, *s ) ;
148+ cursor ++ ;
149+ }
150+ else {
151+ buff .append( 1, *s ) ;
152+ cursor ++ ;
153+ lineend = cursor ;
154+ }
155+ }
156+ }
157+ signal_update.emit();
158+ return true;
159+ }
160+ if (status != Glib::IO_STATUS_EOF)
161+ std::cerr << "Pipe IOChannel read failed" << std::endl;
162+ // signal completion
163+ signal_eof.emit();
164+ return false;
165+}
166+
167+PipeCapture::~PipeCapture()
168+{
169+ if( sourceid > 0 )
170+ g_source_remove( sourceid );
171+}
172+
173+} // namespace GParted
174
175=== modified file '.pc/applied-patches'
176--- .pc/applied-patches 2014-02-25 14:38:33 +0000
177+++ .pc/applied-patches 2014-10-31 14:25:19 +0000
178@@ -1,2 +1,3 @@
179 01_fix-desktop.patch
180 02_use-pkexec.patch
181+0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch
182
183=== modified file 'debian/changelog'
184--- debian/changelog 2014-07-21 21:20:18 +0000
185+++ debian/changelog 2014-10-31 14:25:19 +0000
186@@ -1,3 +1,11 @@
187+gparted (0.19.0-1ubuntu1) utopic; urgency=medium
188+
189+ * 0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch:
190+ cherry pick upstream commit fixing a memory corruption error that
191+ causes various crashes (LP: #1376051).
192+
193+ -- Phillip Susi <psusi@ubuntu.com> Fri, 24 Oct 2014 15:05:27 -0400
194+
195 gparted (0.19.0-1build1) utopic; urgency=medium
196
197 * Rebuild against libparted2.
198
199=== added file 'debian/patches/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch'
200--- debian/patches/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch 1970-01-01 00:00:00 +0000
201+++ debian/patches/0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch 2014-10-31 14:25:19 +0000
202@@ -0,0 +1,215 @@
203+From 0fcfd18061a45f208415971f190818a41cc59f47 Mon Sep 17 00:00:00 2001
204+From: Mike Fleetwood <mike.fleetwood@googlemail.com>
205+Date: Sat, 5 Jul 2014 09:35:45 +0100
206+Subject: [PATCH] Prevent cross thread write after free in _OnReadable()
207+ (#731752)
208+
209+Fragment of debugging and valgrind output:
210+D: tid=2193 main()
211+...
212+D: tid=2202 GParted_Core::set_devices_thread()
213+...
214+D: tid=2202 Utils::execute_command(command="dumpe2fs -h /dev/sda1", output, error, use_C_locale=1)
215+D: tid=2202 this=0x13fef4a0 PipeCapture::PipeCapture()
216+D: tid=2202 this=0x13fef4f0 PipeCapture::PipeCapture()
217+D: tid=2202 this=0x13fef4a0 PipeCapture::connect_signal()
218+D: sourceid=77
219+D: tid=2202 this=0x13fef4f0 PipeCapture::connect_signal()
220+D: sourceid=78
221+D: tid=2193 data=0x13fef4a0 PipeCapture::_OnReadable()
222+D: tid=2193 this=0x13fef4a0 PipeCapture::OnReadable()
223+D: signal_update.emit()
224+D: return true
225+D: tid=2193 data=0x13fef4f0 PipeCapture::_OnReadable()
226+D: tid=2193 this=0x13fef4f0 PipeCapture::OnReadable()
227+D: signal_update.emit()
228+D: return true
229+D: tid=2193 data=0x13fef4a0 PipeCapture::_OnReadable()
230+D: tid=2193 this=0x13fef4a0 PipeCapture::OnReadable()
231+D: signal_update.emit()
232+D: return true
233+D: tid=2193 data=0x13fef4f0 PipeCapture::_OnReadable()
234+D: tid=2193 this=0x13fef4f0 PipeCapture::OnReadable()
235+D: signal_eof.emit()
236+D: return false
237+D: (!rc) &(pc->sourceid)=0x13fef518
238+D: tid=2193 data=0x13fef4a0 PipeCapture::_OnReadable()
239+D: tid=2193 this=0x13fef4a0 PipeCapture::OnReadable()
240+D: signal_update.emit()
241+D: return true
242+D: tid=2193 data=0x13fef4a0 PipeCapture::_OnReadable()
243+D: tid=2193 this=0x13fef4a0 PipeCapture::OnReadable()
244+D: signal_update.emit()
245+D: return true
246+D: tid=2193 data=0x13fef4a0 PipeCapture::_OnReadable()
247+D: tid=2193 this=0x13fef4a0 PipeCapture::OnReadable()
248+D: signal_eof.emit()
249+D: tid=2202 this=0x13fef4f0 PipeCapture::~PipeCapture()
250+D: sourceid=0
251+D: tid=2202 this=0x13fef4a0 PipeCapture::~PipeCapture()
252+D: sourceid=77
253+D: return false
254+D: (!rc) &(pc->sourceid)=0x13fef4c8
255+==2193== Thread 1:
256+==2193== Invalid write of size 4
257+==2193== at 0x490580: GParted::PipeCapture::_OnReadable(_GIOChannel*, GIOCondition, void*) (PipeCapture.cc:56)
258+==2193== by 0x38662492A5: g_main_context_dispatch (gmain.c:3066)
259+==2193== by 0x3866249627: g_main_context_iterate.isra.24 (gmain.c:3713)
260+==2193== by 0x3866249A39: g_main_loop_run (gmain.c:3907)
261+==2193== by 0x3D7FD45C26: gtk_main (gtkmain.c:1257)
262+==2193== by 0x469743: GParted::GParted_Core::set_devices(std::vector<GParted::Device, std::allocator<GParted::Device> >&) (GParted_Core.cc:155)
263+==2193== by 0x4A78F1: GParted::Win_GParted::menu_gparted_refresh_devices() (Win_GParted.cc:1259)
264+==2193== by 0x4A7886: GParted::Win_GParted::on_show() (Win_GParted.cc:1253)
265+==2193== by 0x3D82B2009C: Gtk::Widget_Class::show_callback(_GtkWidget*) (widget.cc:3855)
266+==2193== by 0x3867210297: g_closure_invoke (gclosure.c:777)
267+==2193== by 0x3867221B86: signal_emit_unlocked_R (gsignal.c:3516)
268+==2193== by 0x386722A0F1: g_signal_emit_valist (gsignal.c:3330)
269+==2193== Address 0x13fef4c8 is not stack'd, malloc'd or (recently) free'd
270+==2193==
271+
272+PipeCapture.cc (with debugging):
273+ 46 gboolean PipeCapture::_OnReadable( GIOChannel *source,
274+ 47 GIOCondition condition,
275+ 48 gpointer data )
276+ 49 {
277+ 50 std::cout << "D: tid=" << (long int)syscall(SYS_gettid) << " data=" << data << " PipeCapture::_OnReadable()" << std::endl;
278+ 51 PipeCapture *pc = static_cast<PipeCapture *>(data);
279+ 52 gboolean rc = pc->OnReadable( Glib::IOCondition(condition) );
280+ 53 if (!rc)
281+ 54 {
282+ 55 std::cout << "D: (!rc) &(pc->sourceid)=" << &(pc->sourceid) << std::endl;
283+ 56 pc->sourceid = 0;
284+ 57 }
285+ 58 return rc;
286+ 59 }
287+
288+The use after free across threads only happens when an external program
289+is being executed from a thread other than the main() thread. This is
290+because by default glib registered callbacks are run by the glib main
291+loop, which is only called from the main() thread with Gtk::Main::run().
292+
293+Event sequence:
294+tid=2193 tid=2202
295+
296+main()
297+...
298+ GParted_Core::set_devices()
299+ Glib::Thread::create(... set_devices_thread ...)
300+ Gtk::Main::run() GParted_Core::set_devices_thread()
301+ ...
302+ Utils::execute_command("dumpe2fs ... /dev/sda1" ...)
303+ Glib::spawn_async_with_pipes()
304+ PipeCapture outputcapture(out, output)
305+ outputcapture.connect_signal()
306+ //Glib main loop runs callback
307+ PipeCapture::_OnReadable()
308+ pc->OnReadable()
309+ //output read
310+ signal_update.emit()
311+ return true
312+ ...
313+ //Glib main loop runs callback
314+ PipeCapture::_OnReadable()
315+ pc->OnReadable()
316+ //eof reached
317+[1] signal_eof.emit()
318+ return status.exit_status
319+[2] PipeCapture::~PipeCapture()
320+[3] return false
321+[4] pc->sourceid = 0
322+
323+What is happening is that the PipeCapture destructor [2] is running in
324+the set_devices_thread() thread and freeing the object's memory as soon
325+as signal_eof.emit() [1] has been called. Then signal_eof.emit()
326+returns back to OnReadable() which then returns false [3] back to the
327+_OnReadable() callback function which then assigns 0 to sourceid member
328+variable [4] in the already freed object, detected by valgrind as:
329+ Invalid write of size 4
330+ at ... GParted::PipeCapture::_OnReadable(...) (PipeCapture.cc:56)
331+
332+This is happening because PipeCapture member variable sourceid is being
333+saved, in a different thread, just so the _OnReadable() callback can be
334+removed. However a glib IOChannel callback, type GIOFunc(), returning
335+false will be automatically removed.
336+
337+ GLib Reference Manual 2.26 / IO Channels
338+ https://developer.gnome.org/glib/2.26/glib-IO-Channels.html#GIOFunc
339+
340+ GIOFunc()
341+
342+ Returns : the function should return FALSE if the event source
343+ should be removed
344+
345+Therefore fix by just not saving the event sourceid at all, and not
346+calling g_source_remove() to manually remove the callback, but instead
347+letting glib automatically remove the callback when it returns false.
348+
349+Bug #731752 - Write after free cross thread race in
350+ PipeCapture::_OnReadable()
351+---
352+ include/PipeCapture.h | 1 -
353+ src/PipeCapture.cc | 15 +++++----------
354+ 2 files changed, 5 insertions(+), 11 deletions(-)
355+
356+diff --git a/include/PipeCapture.h b/include/PipeCapture.h
357+index e37eba8..d3e2152 100644
358+--- a/include/PipeCapture.h
359++++ b/include/PipeCapture.h
360+@@ -31,7 +31,6 @@ class PipeCapture
361+ Glib::ustring::size_type cursor ;
362+ Glib::ustring::size_type lineend ;
363+ Glib::RefPtr<Glib::IOChannel> channel;
364+- guint sourceid;
365+ bool OnReadable( Glib::IOCondition condition );
366+ static gboolean _OnReadable( GIOChannel *source,
367+ GIOCondition condition,
368+diff --git a/src/PipeCapture.cc b/src/PipeCapture.cc
369+index bbb400e..2b5d5f8 100644
370+--- a/src/PipeCapture.cc
371++++ b/src/PipeCapture.cc
372+@@ -20,8 +20,7 @@
373+ namespace GParted {
374+
375+ PipeCapture::PipeCapture( int fd, Glib::ustring &string ) : buff( string ),
376+- linestart( 0 ), cursor( 0 ), lineend( 0 ),
377+- sourceid( 0 )
378++ linestart( 0 ), cursor( 0 ), lineend( 0 )
379+ {
380+ // tie fd to string
381+ // make channel
382+@@ -31,10 +30,10 @@ PipeCapture::PipeCapture( int fd, Glib::ustring &string ) : buff( string ),
383+ void PipeCapture::connect_signal()
384+ {
385+ // connect handler to signal input/output
386+- sourceid = g_io_add_watch( channel->gobj(),
387+- GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
388+- _OnReadable,
389+- this );
390++ g_io_add_watch( channel->gobj(),
391++ GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
392++ _OnReadable,
393++ this );
394+ }
395+
396+ gboolean PipeCapture::_OnReadable( GIOChannel *source,
397+@@ -43,8 +42,6 @@ gboolean PipeCapture::_OnReadable( GIOChannel *source,
398+ {
399+ PipeCapture *pc = static_cast<PipeCapture *>(data);
400+ gboolean rc = pc->OnReadable( Glib::IOCondition(condition) );
401+- if (!rc)
402+- pc->sourceid = 0;
403+ return rc;
404+ }
405+
406+@@ -104,8 +101,6 @@ bool PipeCapture::OnReadable( Glib::IOCondition condition )
407+
408+ PipeCapture::~PipeCapture()
409+ {
410+- if( sourceid > 0 )
411+- g_source_remove( sourceid );
412+ }
413+
414+ } // namespace GParted
415+--
416+1.9.1
417+
418
419=== modified file 'debian/patches/series'
420--- debian/patches/series 2014-02-25 14:38:33 +0000
421+++ debian/patches/series 2014-10-31 14:25:19 +0000
422@@ -1,3 +1,4 @@
423 01_fix-desktop.patch
424 02_use-pkexec.patch
425
426+0001-Prevent-cross-thread-write-after-free-in-_OnReadable.patch
427
428=== modified file 'include/PipeCapture.h'
429--- include/PipeCapture.h 2014-02-25 14:38:33 +0000
430+++ include/PipeCapture.h 2014-10-31 14:25:19 +0000
431@@ -31,7 +31,6 @@
432 Glib::ustring::size_type cursor ;
433 Glib::ustring::size_type lineend ;
434 Glib::RefPtr<Glib::IOChannel> channel;
435- guint sourceid;
436 bool OnReadable( Glib::IOCondition condition );
437 static gboolean _OnReadable( GIOChannel *source,
438 GIOCondition condition,
439
440=== modified file 'src/PipeCapture.cc'
441--- src/PipeCapture.cc 2014-06-11 10:50:11 +0000
442+++ src/PipeCapture.cc 2014-10-31 14:25:19 +0000
443@@ -20,8 +20,7 @@
444 namespace GParted {
445
446 PipeCapture::PipeCapture( int fd, Glib::ustring &string ) : buff( string ),
447- linestart( 0 ), cursor( 0 ), lineend( 0 ),
448- sourceid( 0 )
449+ linestart( 0 ), cursor( 0 ), lineend( 0 )
450 {
451 // tie fd to string
452 // make channel
453@@ -31,10 +30,10 @@
454 void PipeCapture::connect_signal()
455 {
456 // connect handler to signal input/output
457- sourceid = g_io_add_watch( channel->gobj(),
458- GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
459- _OnReadable,
460- this );
461+ g_io_add_watch( channel->gobj(),
462+ GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
463+ _OnReadable,
464+ this );
465 }
466
467 gboolean PipeCapture::_OnReadable( GIOChannel *source,
468@@ -43,8 +42,6 @@
469 {
470 PipeCapture *pc = static_cast<PipeCapture *>(data);
471 gboolean rc = pc->OnReadable( Glib::IOCondition(condition) );
472- if (!rc)
473- pc->sourceid = 0;
474 return rc;
475 }
476
477@@ -104,8 +101,6 @@
478
479 PipeCapture::~PipeCapture()
480 {
481- if( sourceid > 0 )
482- g_source_remove( sourceid );
483 }
484
485 } // namespace GParted

Subscribers

People subscribed via source and target branches

to all changes: