mbox series

[userspace,v2,0/6] Parallel setfiles/restorecon

Message ID 20211014145319.798740-1-omosnace@redhat.com (mailing list archive)
Headers show
Series Parallel setfiles/restorecon | expand

Message

Ondrej Mosnacek Oct. 14, 2021, 2:53 p.m. UTC
This series adds basic support for parallel relabeling to the libselinux
API and the setfiles/restorecon CLI tools. It turns out that doing the
relabeling in parallel can significantly reduce the time even with a
relatively simple approach.

The first patch is a small cleanup that was found along the way and can
be applied independently. Patches 2-4 are small incremental changes that
make the internal selinux_restorecon functions more thread-safe (I kept
them separate for ease of of review, but maybe they should be rather
folded into the netx patch...). Patch 5 then completes the parallel
relabeling implementation at libselinux level and adds a new function
to the API that allows to make use of it. Finally, patch 6 adds parallel
relabeling support to he setfiles/restorecon tools.

The relevant man pages are also updated to reflect the new
functionality.

The patch descriptions contain more details, namely the last patch has
also some benchmark numbers.

Changes v1->v2:
- make selinux_log() synchronized instead of introducing selinux_log_sync()
- fix -Wcomma warning
- update the swig files as well
- bump new symbol version to LIBSELINUX_3.3 (this may need further update
  depending on when this gets merged)

Ondrej Mosnacek (6):
  selinux_restorecon: simplify fl_head allocation by using calloc()
  selinux_restorecon: protect file_spec list with a mutex
  libselinux: make selinux_log() thread-safe
  selinux_restorecon: add a global mutex to synchronize progress output
  selinux_restorecon: introduce selinux_restorecon_parallel(3)
  setfiles/restorecon: support parallel relabeling

 libselinux/include/selinux/restorecon.h       |  14 +
 libselinux/man/man3/selinux_restorecon.3      |  29 ++
 .../man/man3/selinux_restorecon_parallel.3    |   1 +
 libselinux/src/callbacks.c                    |   8 +-
 libselinux/src/callbacks.h                    |  13 +-
 libselinux/src/libselinux.map                 |   5 +
 libselinux/src/selinux_internal.h             |  14 +
 libselinux/src/selinux_restorecon.c           | 466 ++++++++++++------
 libselinux/src/selinuxswig_python.i           |   6 +-
 libselinux/src/selinuxswig_python_exception.i |   8 +
 policycoreutils/setfiles/Makefile             |   2 +-
 policycoreutils/setfiles/restore.c            |   7 +-
 policycoreutils/setfiles/restore.h            |   2 +-
 policycoreutils/setfiles/restorecon.8         |   9 +
 policycoreutils/setfiles/setfiles.8           |   9 +
 policycoreutils/setfiles/setfiles.c           |  28 +-
 16 files changed, 444 insertions(+), 177 deletions(-)
 create mode 100644 libselinux/man/man3/selinux_restorecon_parallel.3

Comments

Christian Göttsche Oct. 15, 2021, 12:37 p.m. UTC | #1
On Thu, 14 Oct 2021 at 16:53, Ondrej Mosnacek <omosnace@redhat.com> wrote:
>
> This series adds basic support for parallel relabeling to the libselinux
> API and the setfiles/restorecon CLI tools. It turns out that doing the
> relabeling in parallel can significantly reduce the time even with a
> relatively simple approach.
>
> The first patch is a small cleanup that was found along the way and can
> be applied independently. Patches 2-4 are small incremental changes that
> make the internal selinux_restorecon functions more thread-safe (I kept
> them separate for ease of of review, but maybe they should be rather
> folded into the netx patch...). Patch 5 then completes the parallel
> relabeling implementation at libselinux level and adds a new function
> to the API that allows to make use of it. Finally, patch 6 adds parallel
> relabeling support to he setfiles/restorecon tools.
>
> The relevant man pages are also updated to reflect the new
> functionality.
>
> The patch descriptions contain more details, namely the last patch has
> also some benchmark numbers.
>
> Changes v1->v2:
> - make selinux_log() synchronized instead of introducing selinux_log_sync()
> - fix -Wcomma warning
> - update the swig files as well
> - bump new symbol version to LIBSELINUX_3.3 (this may need further update
>   depending on when this gets merged)
>
> Ondrej Mosnacek (6):
>   selinux_restorecon: simplify fl_head allocation by using calloc()
>   selinux_restorecon: protect file_spec list with a mutex
>   libselinux: make selinux_log() thread-safe
>   selinux_restorecon: add a global mutex to synchronize progress output
>   selinux_restorecon: introduce selinux_restorecon_parallel(3)
>   setfiles/restorecon: support parallel relabeling
>
>  libselinux/include/selinux/restorecon.h       |  14 +
>  libselinux/man/man3/selinux_restorecon.3      |  29 ++
>  .../man/man3/selinux_restorecon_parallel.3    |   1 +
>  libselinux/src/callbacks.c                    |   8 +-
>  libselinux/src/callbacks.h                    |  13 +-
>  libselinux/src/libselinux.map                 |   5 +
>  libselinux/src/selinux_internal.h             |  14 +
>  libselinux/src/selinux_restorecon.c           | 466 ++++++++++++------
>  libselinux/src/selinuxswig_python.i           |   6 +-
>  libselinux/src/selinuxswig_python_exception.i |   8 +
>  policycoreutils/setfiles/Makefile             |   2 +-
>  policycoreutils/setfiles/restore.c            |   7 +-
>  policycoreutils/setfiles/restore.h            |   2 +-
>  policycoreutils/setfiles/restorecon.8         |   9 +
>  policycoreutils/setfiles/setfiles.8           |   9 +
>  policycoreutils/setfiles/setfiles.c           |  28 +-
>  16 files changed, 444 insertions(+), 177 deletions(-)
>  create mode 100644 libselinux/man/man3/selinux_restorecon_parallel.3
>
> --
> 2.31.1
>


Running under ThreadSanitizer shows multiple instances of the following issue:

==================
WARNING: ThreadSanitizer: data race (pid=16933)
  Read of size 4 at 0x7f8790d636f4 by thread T2:
    #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d1b)
    #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
    #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
    #3 selabel_lookup_common ./libselinux/src/label.c:167
(libselinux.so.1+0x12291)
    #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
    #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
(libselinux.so.1+0x20c76)
    #6 selinux_restorecon_thread
./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)

  Previous write of size 4 at 0x7f8790d636f4 by thread T1:
    #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d29)
    #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
    #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
    #3 selabel_lookup_common ./libselinux/src/label.c:167
(libselinux.so.1+0x12291)
    #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
    #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
(libselinux.so.1+0x20c76)
    #6 selinux_restorecon_thread
./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)

  Location is heap block of size 267120 at 0x7f8790d45000 allocated by
main thread:
    #0 malloc ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:655
(libtsan.so.0+0x31799)
    #1 sort_specs ./libselinux/src/label_file.h:207 (libselinux.so.1+0x17bd9)
    #2 init ./libselinux/src/label_file.c:795 (libselinux.so.1+0x17bd9)
    #3 selabel_file_init ./libselinux/src/label_file.c:1293
(libselinux.so.1+0x1835b)
    #4 selabel_open ./libselinux/src/label.c:228 (libselinux.so.1+0x125d8)
    #5 restore_init ./policycoreutils/setfiles/restore.c:30 (setfiles+0x3886)
    #6 main ./policycoreutils/setfiles/setfiles.c:434 (setfiles+0x344e)

  Thread T2 (tid=16940, running) created by main thread at:
    #0 pthread_create
../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
(libtsan.so.0+0x5ad75)
    #1 selinux_restorecon_common
./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
    #2 selinux_restorecon_parallel
./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
    #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
    #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)

  Thread T1 (tid=16939, running) created by main thread at:
    #0 pthread_create
../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
(libtsan.so.0+0x5ad75)
    #1 selinux_restorecon_common
./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
    #2 selinux_restorecon_parallel
./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
    #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
    #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)

SUMMARY: ThreadSanitizer: data race ./libselinux/src/label_file.c:954
in lookup_all
==================

Also some thread joins seems to be missing:

==================
WARNING: ThreadSanitizer: thread leak (pid=16933)
  Thread T1 (tid=16939, finished) created by main thread at:
    #0 pthread_create
../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
(libtsan.so.0+0x5ad75)
    #1 selinux_restorecon_common
./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
    #2 selinux_restorecon_parallel
./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
    #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
    #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)

  And 2 more similar thread leaks.

SUMMARY: ThreadSanitizer: thread leak
./libselinux/src/selinux_restorecon.c:1197 in
selinux_restorecon_common
==================


Performance on 4 cores:

real    0m27.323s
user    0m20.824s
sys     0m4.029s

vs

real    0m12.462s
user    0m31.220s
sys     0m4.316s
Ondrej Mosnacek Oct. 15, 2021, 1:25 p.m. UTC | #2
On Fri, Oct 15, 2021 at 2:37 PM Christian Göttsche
<cgzones@googlemail.com> wrote:
> On Thu, 14 Oct 2021 at 16:53, Ondrej Mosnacek <omosnace@redhat.com> wrote:
> >
> > This series adds basic support for parallel relabeling to the libselinux
> > API and the setfiles/restorecon CLI tools. It turns out that doing the
> > relabeling in parallel can significantly reduce the time even with a
> > relatively simple approach.
> >
> > The first patch is a small cleanup that was found along the way and can
> > be applied independently. Patches 2-4 are small incremental changes that
> > make the internal selinux_restorecon functions more thread-safe (I kept
> > them separate for ease of of review, but maybe they should be rather
> > folded into the netx patch...). Patch 5 then completes the parallel
> > relabeling implementation at libselinux level and adds a new function
> > to the API that allows to make use of it. Finally, patch 6 adds parallel
> > relabeling support to he setfiles/restorecon tools.
> >
> > The relevant man pages are also updated to reflect the new
> > functionality.
> >
> > The patch descriptions contain more details, namely the last patch has
> > also some benchmark numbers.
> >
> > Changes v1->v2:
> > - make selinux_log() synchronized instead of introducing selinux_log_sync()
> > - fix -Wcomma warning
> > - update the swig files as well
> > - bump new symbol version to LIBSELINUX_3.3 (this may need further update
> >   depending on when this gets merged)
> >
> > Ondrej Mosnacek (6):
> >   selinux_restorecon: simplify fl_head allocation by using calloc()
> >   selinux_restorecon: protect file_spec list with a mutex
> >   libselinux: make selinux_log() thread-safe
> >   selinux_restorecon: add a global mutex to synchronize progress output
> >   selinux_restorecon: introduce selinux_restorecon_parallel(3)
> >   setfiles/restorecon: support parallel relabeling
> >
> >  libselinux/include/selinux/restorecon.h       |  14 +
> >  libselinux/man/man3/selinux_restorecon.3      |  29 ++
> >  .../man/man3/selinux_restorecon_parallel.3    |   1 +
> >  libselinux/src/callbacks.c                    |   8 +-
> >  libselinux/src/callbacks.h                    |  13 +-
> >  libselinux/src/libselinux.map                 |   5 +
> >  libselinux/src/selinux_internal.h             |  14 +
> >  libselinux/src/selinux_restorecon.c           | 466 ++++++++++++------
> >  libselinux/src/selinuxswig_python.i           |   6 +-
> >  libselinux/src/selinuxswig_python_exception.i |   8 +
> >  policycoreutils/setfiles/Makefile             |   2 +-
> >  policycoreutils/setfiles/restore.c            |   7 +-
> >  policycoreutils/setfiles/restore.h            |   2 +-
> >  policycoreutils/setfiles/restorecon.8         |   9 +
> >  policycoreutils/setfiles/setfiles.8           |   9 +
> >  policycoreutils/setfiles/setfiles.c           |  28 +-
> >  16 files changed, 444 insertions(+), 177 deletions(-)
> >  create mode 100644 libselinux/man/man3/selinux_restorecon_parallel.3
> >
> > --
> > 2.31.1
> >
>
>
> Running under ThreadSanitizer shows multiple instances of the following issue:
>
> ==================
> WARNING: ThreadSanitizer: data race (pid=16933)
>   Read of size 4 at 0x7f8790d636f4 by thread T2:
>     #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d1b)
>     #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
>     #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
>     #3 selabel_lookup_common ./libselinux/src/label.c:167
> (libselinux.so.1+0x12291)
>     #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
>     #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
> (libselinux.so.1+0x20c76)
>     #6 selinux_restorecon_thread
> ./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)
>
>   Previous write of size 4 at 0x7f8790d636f4 by thread T1:
>     #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d29)
>     #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
>     #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
>     #3 selabel_lookup_common ./libselinux/src/label.c:167
> (libselinux.so.1+0x12291)
>     #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
>     #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
> (libselinux.so.1+0x20c76)
>     #6 selinux_restorecon_thread
> ./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)
>
>   Location is heap block of size 267120 at 0x7f8790d45000 allocated by
> main thread:
>     #0 malloc ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:655
> (libtsan.so.0+0x31799)
>     #1 sort_specs ./libselinux/src/label_file.h:207 (libselinux.so.1+0x17bd9)
>     #2 init ./libselinux/src/label_file.c:795 (libselinux.so.1+0x17bd9)
>     #3 selabel_file_init ./libselinux/src/label_file.c:1293
> (libselinux.so.1+0x1835b)
>     #4 selabel_open ./libselinux/src/label.c:228 (libselinux.so.1+0x125d8)
>     #5 restore_init ./policycoreutils/setfiles/restore.c:30 (setfiles+0x3886)
>     #6 main ./policycoreutils/setfiles/setfiles.c:434 (setfiles+0x344e)
>
>   Thread T2 (tid=16940, running) created by main thread at:
>     #0 pthread_create
> ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> (libtsan.so.0+0x5ad75)
>     #1 selinux_restorecon_common
> ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
>     #2 selinux_restorecon_parallel
> ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
>     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
>     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
>
>   Thread T1 (tid=16939, running) created by main thread at:
>     #0 pthread_create
> ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> (libtsan.so.0+0x5ad75)
>     #1 selinux_restorecon_common
> ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
>     #2 selinux_restorecon_parallel
> ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
>     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
>     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
>
> SUMMARY: ThreadSanitizer: data race ./libselinux/src/label_file.c:954
> in lookup_all
> ==================

Good catch, although this one seems to be pre-existing w.r.t. thread
safety of selinux_restorecon(3) (which seems to be implied by the
existence of struct spec::regex_lock). I spotted some existing usage
of GCC atomic builtins, so I'll try to fix it using those.

> Also some thread joins seems to be missing:
>
> ==================
> WARNING: ThreadSanitizer: thread leak (pid=16933)
>   Thread T1 (tid=16939, finished) created by main thread at:
>     #0 pthread_create
> ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> (libtsan.so.0+0x5ad75)
>     #1 selinux_restorecon_common
> ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
>     #2 selinux_restorecon_parallel
> ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
>     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
>     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
>
>   And 2 more similar thread leaks.
>
> SUMMARY: ThreadSanitizer: thread leak
> ./libselinux/src/selinux_restorecon.c:1197 in
> selinux_restorecon_common
> ==================

Hm... My intention was to avoid the need for joins by synchronizing
via other means and just letting the threads exit on their own, but I
guess that doesn't fly with the sanitizer. Guess I'll have to allocate
an array for the handles and join the threads at the end after all...
Ondrej Mosnacek Oct. 18, 2021, 8:56 a.m. UTC | #3
On Fri, Oct 15, 2021 at 3:25 PM Ondrej Mosnacek <omosnace@redhat.com> wrote:
> On Fri, Oct 15, 2021 at 2:37 PM Christian Göttsche
> <cgzones@googlemail.com> wrote:
> > On Thu, 14 Oct 2021 at 16:53, Ondrej Mosnacek <omosnace@redhat.com> wrote:
> > >
> > > This series adds basic support for parallel relabeling to the libselinux
> > > API and the setfiles/restorecon CLI tools. It turns out that doing the
> > > relabeling in parallel can significantly reduce the time even with a
> > > relatively simple approach.
> > >
> > > The first patch is a small cleanup that was found along the way and can
> > > be applied independently. Patches 2-4 are small incremental changes that
> > > make the internal selinux_restorecon functions more thread-safe (I kept
> > > them separate for ease of of review, but maybe they should be rather
> > > folded into the netx patch...). Patch 5 then completes the parallel
> > > relabeling implementation at libselinux level and adds a new function
> > > to the API that allows to make use of it. Finally, patch 6 adds parallel
> > > relabeling support to he setfiles/restorecon tools.
> > >
> > > The relevant man pages are also updated to reflect the new
> > > functionality.
> > >
> > > The patch descriptions contain more details, namely the last patch has
> > > also some benchmark numbers.
> > >
> > > Changes v1->v2:
> > > - make selinux_log() synchronized instead of introducing selinux_log_sync()
> > > - fix -Wcomma warning
> > > - update the swig files as well
> > > - bump new symbol version to LIBSELINUX_3.3 (this may need further update
> > >   depending on when this gets merged)
> > >
> > > Ondrej Mosnacek (6):
> > >   selinux_restorecon: simplify fl_head allocation by using calloc()
> > >   selinux_restorecon: protect file_spec list with a mutex
> > >   libselinux: make selinux_log() thread-safe
> > >   selinux_restorecon: add a global mutex to synchronize progress output
> > >   selinux_restorecon: introduce selinux_restorecon_parallel(3)
> > >   setfiles/restorecon: support parallel relabeling
> > >
> > >  libselinux/include/selinux/restorecon.h       |  14 +
> > >  libselinux/man/man3/selinux_restorecon.3      |  29 ++
> > >  .../man/man3/selinux_restorecon_parallel.3    |   1 +
> > >  libselinux/src/callbacks.c                    |   8 +-
> > >  libselinux/src/callbacks.h                    |  13 +-
> > >  libselinux/src/libselinux.map                 |   5 +
> > >  libselinux/src/selinux_internal.h             |  14 +
> > >  libselinux/src/selinux_restorecon.c           | 466 ++++++++++++------
> > >  libselinux/src/selinuxswig_python.i           |   6 +-
> > >  libselinux/src/selinuxswig_python_exception.i |   8 +
> > >  policycoreutils/setfiles/Makefile             |   2 +-
> > >  policycoreutils/setfiles/restore.c            |   7 +-
> > >  policycoreutils/setfiles/restore.h            |   2 +-
> > >  policycoreutils/setfiles/restorecon.8         |   9 +
> > >  policycoreutils/setfiles/setfiles.8           |   9 +
> > >  policycoreutils/setfiles/setfiles.c           |  28 +-
> > >  16 files changed, 444 insertions(+), 177 deletions(-)
> > >  create mode 100644 libselinux/man/man3/selinux_restorecon_parallel.3
> > >
> > > --
> > > 2.31.1
> > >
> >
> >
> > Running under ThreadSanitizer shows multiple instances of the following issue:
> >
> > ==================
> > WARNING: ThreadSanitizer: data race (pid=16933)
> >   Read of size 4 at 0x7f8790d636f4 by thread T2:
> >     #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d1b)
> >     #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
> >     #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
> >     #3 selabel_lookup_common ./libselinux/src/label.c:167
> > (libselinux.so.1+0x12291)
> >     #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
> >     #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
> > (libselinux.so.1+0x20c76)
> >     #6 selinux_restorecon_thread
> > ./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)
> >
> >   Previous write of size 4 at 0x7f8790d636f4 by thread T1:
> >     #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d29)
> >     #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
> >     #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
> >     #3 selabel_lookup_common ./libselinux/src/label.c:167
> > (libselinux.so.1+0x12291)
> >     #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
> >     #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
> > (libselinux.so.1+0x20c76)
> >     #6 selinux_restorecon_thread
> > ./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)
> >
> >   Location is heap block of size 267120 at 0x7f8790d45000 allocated by
> > main thread:
> >     #0 malloc ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:655
> > (libtsan.so.0+0x31799)
> >     #1 sort_specs ./libselinux/src/label_file.h:207 (libselinux.so.1+0x17bd9)
> >     #2 init ./libselinux/src/label_file.c:795 (libselinux.so.1+0x17bd9)
> >     #3 selabel_file_init ./libselinux/src/label_file.c:1293
> > (libselinux.so.1+0x1835b)
> >     #4 selabel_open ./libselinux/src/label.c:228 (libselinux.so.1+0x125d8)
> >     #5 restore_init ./policycoreutils/setfiles/restore.c:30 (setfiles+0x3886)
> >     #6 main ./policycoreutils/setfiles/setfiles.c:434 (setfiles+0x344e)
> >
> >   Thread T2 (tid=16940, running) created by main thread at:
> >     #0 pthread_create
> > ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> > (libtsan.so.0+0x5ad75)
> >     #1 selinux_restorecon_common
> > ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
> >     #2 selinux_restorecon_parallel
> > ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
> >     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
> >     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
> >
> >   Thread T1 (tid=16939, running) created by main thread at:
> >     #0 pthread_create
> > ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> > (libtsan.so.0+0x5ad75)
> >     #1 selinux_restorecon_common
> > ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
> >     #2 selinux_restorecon_parallel
> > ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
> >     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
> >     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
> >
> > SUMMARY: ThreadSanitizer: data race ./libselinux/src/label_file.c:954
> > in lookup_all
> > ==================
>
> Good catch, although this one seems to be pre-existing w.r.t. thread
> safety of selinux_restorecon(3) (which seems to be implied by the
> existence of struct spec::regex_lock). I spotted some existing usage
> of GCC atomic builtins, so I'll try to fix it using those.
>
> > Also some thread joins seems to be missing:
> >
> > ==================
> > WARNING: ThreadSanitizer: thread leak (pid=16933)
> >   Thread T1 (tid=16939, finished) created by main thread at:
> >     #0 pthread_create
> > ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> > (libtsan.so.0+0x5ad75)
> >     #1 selinux_restorecon_common
> > ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
> >     #2 selinux_restorecon_parallel
> > ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
> >     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
> >     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
> >
> >   And 2 more similar thread leaks.
> >
> > SUMMARY: ThreadSanitizer: thread leak
> > ./libselinux/src/selinux_restorecon.c:1197 in
> > selinux_restorecon_common
> > ==================
>
> Hm... My intention was to avoid the need for joins by synchronizing
> via other means and just letting the threads exit on their own, but I
> guess that doesn't fly with the sanitizer. Guess I'll have to allocate
> an array for the handles and join the threads at the end after all...

So I was able to get ThreadSanitizer to detect the race (by using only
two threads and a very small number of files), but then the program
just runs forever and I never get to see the thread leaks... Any hints
on how you ran/compiled it to see the thread leaks? Or how long you
had to wait for the program to finish? BTW, I only managed to get
thread sanitizer to work with GCC. With CLang I always got some linker
error...


--
Ondrej Mosnacek
Software Engineer, Linux Security - SELinux kernel
Red Hat, Inc.
Ondrej Mosnacek Oct. 19, 2021, 12:29 p.m. UTC | #4
On Mon, Oct 18, 2021 at 10:56 AM Ondrej Mosnacek <omosnace@redhat.com> wrote:
> On Fri, Oct 15, 2021 at 3:25 PM Ondrej Mosnacek <omosnace@redhat.com> wrote:
> > On Fri, Oct 15, 2021 at 2:37 PM Christian Göttsche
> > <cgzones@googlemail.com> wrote:
> > > On Thu, 14 Oct 2021 at 16:53, Ondrej Mosnacek <omosnace@redhat.com> wrote:
> > > >
> > > > This series adds basic support for parallel relabeling to the libselinux
> > > > API and the setfiles/restorecon CLI tools. It turns out that doing the
> > > > relabeling in parallel can significantly reduce the time even with a
> > > > relatively simple approach.
> > > >
> > > > The first patch is a small cleanup that was found along the way and can
> > > > be applied independently. Patches 2-4 are small incremental changes that
> > > > make the internal selinux_restorecon functions more thread-safe (I kept
> > > > them separate for ease of of review, but maybe they should be rather
> > > > folded into the netx patch...). Patch 5 then completes the parallel
> > > > relabeling implementation at libselinux level and adds a new function
> > > > to the API that allows to make use of it. Finally, patch 6 adds parallel
> > > > relabeling support to he setfiles/restorecon tools.
> > > >
> > > > The relevant man pages are also updated to reflect the new
> > > > functionality.
> > > >
> > > > The patch descriptions contain more details, namely the last patch has
> > > > also some benchmark numbers.
> > > >
> > > > Changes v1->v2:
> > > > - make selinux_log() synchronized instead of introducing selinux_log_sync()
> > > > - fix -Wcomma warning
> > > > - update the swig files as well
> > > > - bump new symbol version to LIBSELINUX_3.3 (this may need further update
> > > >   depending on when this gets merged)
> > > >
> > > > Ondrej Mosnacek (6):
> > > >   selinux_restorecon: simplify fl_head allocation by using calloc()
> > > >   selinux_restorecon: protect file_spec list with a mutex
> > > >   libselinux: make selinux_log() thread-safe
> > > >   selinux_restorecon: add a global mutex to synchronize progress output
> > > >   selinux_restorecon: introduce selinux_restorecon_parallel(3)
> > > >   setfiles/restorecon: support parallel relabeling
> > > >
> > > >  libselinux/include/selinux/restorecon.h       |  14 +
> > > >  libselinux/man/man3/selinux_restorecon.3      |  29 ++
> > > >  .../man/man3/selinux_restorecon_parallel.3    |   1 +
> > > >  libselinux/src/callbacks.c                    |   8 +-
> > > >  libselinux/src/callbacks.h                    |  13 +-
> > > >  libselinux/src/libselinux.map                 |   5 +
> > > >  libselinux/src/selinux_internal.h             |  14 +
> > > >  libselinux/src/selinux_restorecon.c           | 466 ++++++++++++------
> > > >  libselinux/src/selinuxswig_python.i           |   6 +-
> > > >  libselinux/src/selinuxswig_python_exception.i |   8 +
> > > >  policycoreutils/setfiles/Makefile             |   2 +-
> > > >  policycoreutils/setfiles/restore.c            |   7 +-
> > > >  policycoreutils/setfiles/restore.h            |   2 +-
> > > >  policycoreutils/setfiles/restorecon.8         |   9 +
> > > >  policycoreutils/setfiles/setfiles.8           |   9 +
> > > >  policycoreutils/setfiles/setfiles.c           |  28 +-
> > > >  16 files changed, 444 insertions(+), 177 deletions(-)
> > > >  create mode 100644 libselinux/man/man3/selinux_restorecon_parallel.3
> > > >
> > > > --
> > > > 2.31.1
> > > >
> > >
> > >
> > > Running under ThreadSanitizer shows multiple instances of the following issue:
> > >
> > > ==================
> > > WARNING: ThreadSanitizer: data race (pid=16933)
> > >   Read of size 4 at 0x7f8790d636f4 by thread T2:
> > >     #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d1b)
> > >     #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
> > >     #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
> > >     #3 selabel_lookup_common ./libselinux/src/label.c:167
> > > (libselinux.so.1+0x12291)
> > >     #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
> > >     #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
> > > (libselinux.so.1+0x20c76)
> > >     #6 selinux_restorecon_thread
> > > ./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)
> > >
> > >   Previous write of size 4 at 0x7f8790d636f4 by thread T1:
> > >     #0 lookup_all ./libselinux/src/label_file.c:954 (libselinux.so.1+0x14d29)
> > >     #1 lookup_common ./libselinux/src/label_file.c:997 (libselinux.so.1+0x14f62)
> > >     #2 lookup ./libselinux/src/label_file.c:1095 (libselinux.so.1+0x14fff)
> > >     #3 selabel_lookup_common ./libselinux/src/label.c:167
> > > (libselinux.so.1+0x12291)
> > >     #4 selabel_lookup_raw ./libselinux/src/label.c:256 (libselinux.so.1+0x126ca)
> > >     #5 restorecon_sb ./libselinux/src/selinux_restorecon.c:638
> > > (libselinux.so.1+0x20c76)
> > >     #6 selinux_restorecon_thread
> > > ./libselinux/src/selinux_restorecon.c:947 (libselinux.so.1+0x21e99)
> > >
> > >   Location is heap block of size 267120 at 0x7f8790d45000 allocated by
> > > main thread:
> > >     #0 malloc ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:655
> > > (libtsan.so.0+0x31799)
> > >     #1 sort_specs ./libselinux/src/label_file.h:207 (libselinux.so.1+0x17bd9)
> > >     #2 init ./libselinux/src/label_file.c:795 (libselinux.so.1+0x17bd9)
> > >     #3 selabel_file_init ./libselinux/src/label_file.c:1293
> > > (libselinux.so.1+0x1835b)
> > >     #4 selabel_open ./libselinux/src/label.c:228 (libselinux.so.1+0x125d8)
> > >     #5 restore_init ./policycoreutils/setfiles/restore.c:30 (setfiles+0x3886)
> > >     #6 main ./policycoreutils/setfiles/setfiles.c:434 (setfiles+0x344e)
> > >
> > >   Thread T2 (tid=16940, running) created by main thread at:
> > >     #0 pthread_create
> > > ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> > > (libtsan.so.0+0x5ad75)
> > >     #1 selinux_restorecon_common
> > > ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
> > >     #2 selinux_restorecon_parallel
> > > ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
> > >     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
> > >     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
> > >
> > >   Thread T1 (tid=16939, running) created by main thread at:
> > >     #0 pthread_create
> > > ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> > > (libtsan.so.0+0x5ad75)
> > >     #1 selinux_restorecon_common
> > > ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
> > >     #2 selinux_restorecon_parallel
> > > ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
> > >     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
> > >     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
> > >
> > > SUMMARY: ThreadSanitizer: data race ./libselinux/src/label_file.c:954
> > > in lookup_all
> > > ==================
> >
> > Good catch, although this one seems to be pre-existing w.r.t. thread
> > safety of selinux_restorecon(3) (which seems to be implied by the
> > existence of struct spec::regex_lock). I spotted some existing usage
> > of GCC atomic builtins, so I'll try to fix it using those.
> >
> > > Also some thread joins seems to be missing:
> > >
> > > ==================
> > > WARNING: ThreadSanitizer: thread leak (pid=16933)
> > >   Thread T1 (tid=16939, finished) created by main thread at:
> > >     #0 pthread_create
> > > ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969
> > > (libtsan.so.0+0x5ad75)
> > >     #1 selinux_restorecon_common
> > > ./libselinux/src/selinux_restorecon.c:1197 (libselinux.so.1+0x2341c)
> > >     #2 selinux_restorecon_parallel
> > > ./libselinux/src/selinux_restorecon.c:1306 (libselinux.so.1+0x23985)
> > >     #3 process_glob ./policycoreutils/setfiles/restore.c:94 (setfiles+0x3ba4)
> > >     #4 main ./policycoreutils/setfiles/setfiles.c:463 (setfiles+0x362a)
> > >
> > >   And 2 more similar thread leaks.
> > >
> > > SUMMARY: ThreadSanitizer: thread leak
> > > ./libselinux/src/selinux_restorecon.c:1197 in
> > > selinux_restorecon_common
> > > ==================
> >
> > Hm... My intention was to avoid the need for joins by synchronizing
> > via other means and just letting the threads exit on their own, but I
> > guess that doesn't fly with the sanitizer. Guess I'll have to allocate
> > an array for the handles and join the threads at the end after all...
>
> So I was able to get ThreadSanitizer to detect the race (by using only
> two threads and a very small number of files), but then the program
> just runs forever and I never get to see the thread leaks... Any hints
> on how you ran/compiled it to see the thread leaks? Or how long you
> had to wait for the program to finish? BTW, I only managed to get
> thread sanitizer to work with GCC. With CLang I always got some linker
> error...

Seems it was getting stuck because of the lack of joins. Once I
rewrote selinux_restorecon_common() to wait for the threads via
pthread_join(), setfiles built with -fsanitize=thread exited quickly
and without warnings.