diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 2173342d..14899391 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -233,12 +233,14 @@ jobs: context menu entries work out of the box. ### Latest fix - - **Orphaned column staying on screen after deleting its backing folder in Column View.** - Each column now watches its own backing directory's `NemoFile` "changed" signal; when - that directory is deleted or moved (e.g. to Trash) the column and every column to its - right are torn down. Previously the removal relied on the parent column's directory - monitor or the `NemoView` `remove_file` virtual observing the deletion, which left the - child column visible after its backing folder was removed. + - **Crashes on file delete and rename in Column View.** When a column was closed, its + backing directory's `NemoFile` "changed" signal handler was never disconnected, so a + later directory change (triggered by deleting or renaming a file) invoked the callback + with a freed column struct and crashed (SEGV). The handler is now disconnected in + column teardown. + - **Use-after-free in `column_view_purge_dead_descendant_columns`.** It iterated the + live column list while tearing columns down, so a deleted folder with open descendant + columns could dereference freed memory. It now walks a snapshot and rebuilds once. ### Highlights - **Column View**: a new view mode (toolbar button "Columns", view menu, and the diff --git a/debian/changelog b/debian/changelog index 574bbf89..92fbe00c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +nemo (1.0.35) unstable; urgency=medium + + [ Column View ] + * Fix crashes on file delete and rename in Column View. When a column was + closed, its backing directory's NemoFile "changed" signal handler was + never disconnected, so a later directory change (triggered by deleting + or renaming a file) invoked the callback with a freed column struct and + crashed (SEGV). The handler is now disconnected in column teardown. + * Fix use-after-free in column_view_purge_dead_descendant_columns: it + iterated the live column list while tearing columns down, so a deleted + folder with open descendant columns could dereference freed memory. It + now walks a snapshot and rebuilds once. + nemo (1.0.33) unstable; urgency=medium [ Column View ] diff --git a/meson.build b/meson.build index f70f8cbb..77514fe3 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ # Meson build file # https://github.com/simpletools-nl/mint-nemo -project('nemo', 'c', version : '1.0.33', meson_version : '>=0.64.0') +project('nemo', 'c', version : '1.0.35', meson_version : '>=0.64.0') # 1. If the library code has changed at all since last release, then increment revision. # 2. If any interfaces have been added, then increment current and set revision to 0. diff --git a/src/nemo-column-view.c b/src/nemo-column-view.c index 98d49ccb..6dc3c2ad 100644 --- a/src/nemo-column-view.c +++ b/src/nemo-column-view.c @@ -958,6 +958,10 @@ column_view_column_free (NemoColumnViewColumn *col) col->directory = NULL; } + if (col->directory_file_changed_id > 0 && col->directory_file != NULL) { + g_signal_handler_disconnect (col->directory_file, col->directory_file_changed_id); + col->directory_file_changed_id = 0; + } if (col->directory_file != NULL) { nemo_file_unref (col->directory_file); col->directory_file = NULL; @@ -1135,48 +1139,64 @@ column_view_purge_dead_descendant_columns (NemoColumnView *view, return; } - for (l = view->priv->columns; l != NULL; l = l->next) { - NemoColumnViewColumn *col = l->data; - GFile *col_loc; - GFile *file_loc; - - if (col->directory_file == NULL || - !NEMO_IS_FILE (col->directory_file)) { - continue; - } - - /* Compare by location, not by NemoFile pointer identity: the - * directory may hand out a different NemoFile instance for a - * file that was just deleted. */ - col_loc = nemo_file_get_location (col->directory_file); - file_loc = nemo_file_get_location (file); + /* Iterate over a snapshot: column_view_rebuild_after_column() removes + * and frees columns from view->priv->columns while we run, so walking + * the live list would be a use-after-free. A single rebuild already + * tears down every column to the right of source_index, so once we + * find one match we rebuild and return. */ + { + GList *columns_copy = g_list_copy (view->priv->columns); - if (col_loc != NULL && file_loc != NULL && - g_file_equal (col_loc, file_loc)) { - gint index = g_list_index (view->priv->columns, l); + for (l = columns_copy; l != NULL; l = l->next) { + NemoColumnViewColumn *col = l->data; + GFile *col_loc = NULL; + GFile *file_loc = NULL; + gint index; - /* The removed file is the directory backing this column - * (and, since columns form a parent->child chain, every - * column to its right as well). Tear them all down, - * keeping the source column (which still legitimately - * exists). */ - if (index > source_index) { - column_view_rebuild_after_column (view, source_index); + if (col->directory_file == NULL || + !NEMO_IS_FILE (col->directory_file)) { + continue; + } - { - NemoColumnViewColumn *last; + /* Compare by location, not by NemoFile pointer identity: the + * directory may hand out a different NemoFile instance for a + * file that was just deleted. */ + col_loc = nemo_file_get_location (col->directory_file); + file_loc = nemo_file_get_location (file); - last = g_list_last (view->priv->columns)->data; - if (last != NULL && last->location != NULL) { - column_view_update_address_bar (view, - last->location); + if (col_loc != NULL && file_loc != NULL && + g_file_equal (col_loc, file_loc)) { + index = g_list_index (view->priv->columns, col); + + /* The removed file is the directory backing this + * column (and, since columns form a parent->child + * chain, every column to its right as well). Tear + * them all down, keeping the source column (which + * still legitimately exists). */ + if (index > source_index) { + g_list_free (columns_copy); + g_clear_object (&col_loc); + g_clear_object (&file_loc); + column_view_rebuild_after_column (view, source_index); + + { + NemoColumnViewColumn *last; + + last = g_list_last (view->priv->columns)->data; + if (last != NULL && last->location != NULL) { + column_view_update_address_bar (view, + last->location); + } } + return; } } + + g_clear_object (&col_loc); + g_clear_object (&file_loc); } - g_clear_object (&col_loc); - g_clear_object (&file_loc); + g_list_free (columns_copy); } }