Bug 504466

Summary: Double close causes SEGV
Product: [Developer tools] valgrind Reporter: TheValiant <facial-wired-quit>
Component: memcheckAssignee: Julian Seward <jseward>
Status: RESOLVED FIXED    
Severity: crash CC: facial-wired-quit, mark
Priority: NOR    
Version First Reported In: 3.24.0   
Target Milestone: ---   
Platform: Ubuntu   
OS: Linux   
Latest Commit: Version Fixed/Implemented In:
Sentry Crash Report:

Description TheValiant 2025-05-18 12:50:00 UTC
SUMMARY
Double closing fd 0 causes valgrind to SEGV with --track-fds=yes

STEPS TO REPRODUCE
1. Save this into a .c file:

#include <unistd.h>

int main()
{
	close(0); close(0);
}

2. cc main.c -g3 -O0 -fno-optimize-sibling-calls -fno-omit-frame-pointer #(flags dont matter)

3. valgrind -s --track-fds=yes --track-origins=yes ./a.out



OBSERVED RESULT:

==219== Memcheck, a memory error detector
==219== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==219== Using Valgrind-3.24.0 and LibVEX; rerun with -h for copyright info
==219== Command: ./a.out
==219==
==219== File descriptor 0: /dev/pts/0 is already closed
==219==    at 0x48F0687: __internal_syscall_cancel (cancellation.c:64)
==219==    by 0x48F06AC: __syscall_cancel (cancellation.c:75)
==219==    by 0x4960779: close (close.c:27)
==219==    by 0x109151: main (main.c:5)
==219==  Previously closed
==219==    at 0x48F0687: __internal_syscall_cancel (cancellation.c:64)
==219==    by 0x48F06AC: __syscall_cancel (cancellation.c:75)
==219==    by 0x4960779: close (close.c:27)
==219==    by 0x10914A: main (main.c:5)
==219==  Originally opened
--219-- VALGRIND INTERNAL ERROR: Valgrind received a signal 11 (SIGSEGV) - exiting
--219-- si_code=1;  Faulting address: 0xC;  sp: 0x10091eabf0

valgrind: the 'impossible' happened:
   Killed by fatal signal

host stacktrace:
==219==    at 0x5804128E: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==219==    by 0x5803FA0C: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==219==    by 0x58040030: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==219==    by 0x580A2E1F: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==219==    by 0x580A0F5D: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==219==    by 0x5809C97A: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==219==    by 0x5809EA46: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==219==    by 0x580ED6AD: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)

sched status:
  running_tid=1

Thread 1: status = VgTs_Runnable syscall 3 (lwpid 219)
==219==    at 0x48F0687: __internal_syscall_cancel (cancellation.c:64)
==219==    by 0x48F06AC: __syscall_cancel (cancellation.c:75)
==219==    by 0x4960779: close (close.c:27)
==219==    by 0x109151: main (main.c:5)
client stack range: [0x1FFEFFE000 0x1FFF000FFF] client SP: 0x1FFF000280
valgrind stack range: [0x10090EB000 0x10091EAFFF] top usage: 18424 of 1048576



EXPECTED RESULT: assuming a double close on a random pipefd[2]

==321== Memcheck, a memory error detector
==321== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==321== Using Valgrind-3.24.0 and LibVEX; rerun with -h for copyright info
==321== Command: ./a.out
==321==
==321== File descriptor 3: file descriptor 3 is already closed
==321==    at 0x48F0687: __internal_syscall_cancel (cancellation.c:64)
==321==    by 0x48F06AC: __syscall_cancel (cancellation.c:75)
==321==    by 0x4960779: close (close.c:27)
==321==    by 0x109170: main (main.c:8)
==321==  Previously closed
==321==    at 0x48F0687: __internal_syscall_cancel (cancellation.c:64)
==321==    by 0x48F06AC: __syscall_cancel (cancellation.c:75)
==321==    by 0x4960779: close (close.c:27)
==321==    by 0x109168: main (main.c:7)
==321==  Originally opened
==321==    at 0x4964959: pipe (pipe.c:29)
==321==    by 0x109160: main (main.c:6)
==321==
==321== FILE DESCRIPTORS: 4 open (3 std) at exit.
==321== Open file descriptor 4:
==321==    at 0x4964959: pipe (pipe.c:29)
==321==    by 0x109160: main (main.c:6)
==321==
==321==
==321== HEAP SUMMARY:
==321==     in use at exit: 0 bytes in 0 blocks
==321==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==321==
==321== All heap blocks were freed -- no leaks are possible
==321==
==321== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==321==
==321== 1 errors in context 1 of 2:
==321== File descriptor 3: file descriptor 3 is already closed
==321==    at 0x48F0687: __internal_syscall_cancel (cancellation.c:64)
==321==    by 0x48F06AC: __syscall_cancel (cancellation.c:75)
==321==    by 0x4960779: close (close.c:27)
==321==    by 0x109170: main (main.c:8)
==321==  Previously closed
==321==    at 0x48F0687: __internal_syscall_cancel (cancellation.c:64)
==321==    by 0x48F06AC: __syscall_cancel (cancellation.c:75)
==321==    by 0x4960779: close (close.c:27)
==321==    by 0x109168: main (main.c:7)
==321==  Originally opened
==321==    at 0x4964959: pipe (pipe.c:29)
==321==    by 0x109160: main (main.c:6)
==321== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

ADDITIONAL INFORMATION
Tested using gcc-14, clang-19, and clang-21. All 3 compilers used on ubuntu 24 and 25 and debian:experimental docker.
Comment 1 Mark Wielaard 2025-05-18 13:07:16 UTC
Oops. We don't handle inherited file desriptors right for double close:

diff --git a/coregrind/m_syswrap/syswrap-generic.c b/coregrind/m_syswrap/syswrap-generic.c
index 81c8fc028d88..8d382e00504b 100644
--- a/coregrind/m_syswrap/syswrap-generic.c
+++ b/coregrind/m_syswrap/syswrap-generic.c
@@ -1197,8 +1197,11 @@ void fd_pp_Error (const Error *err)
       VG_(pp_ExeContext)( where );
       VG_(emit)("%sPreviously closed%s\n", auxpre, auxpost);
       VG_(pp_ExeContext)(bce->where_closed);
-      VG_(emit)("%sOriginally opened%s\n", auxpre, auxpost);
-      VG_(pp_ExeContext)(bce->where_opened);
+      // Inherited file descriptors where never opened (by the program)
+      if (bce->where_opened) {
+         VG_(emit)("%sOriginally opened%s\n", auxpre, auxpost);
+         VG_(pp_ExeContext)(bce->where_opened);
+      }
    } else if (VG_(get_error_kind)(err) == FdNotClosed) {
       if (xml) VG_(emit)("  <kind>FdNotClosed</kind>\n");
       struct NotClosedExtra *nce = (struct NotClosedExtra *)
Comment 2 Mark Wielaard 2025-05-18 19:41:16 UTC
commit 8187386962598d1393eaf6cf4e032996f5edabb3
Author: Mark Wielaard <mark@klomp.org>
Date:   Sun May 18 15:31:36 2025 +0200

    Check whether file descriptor is inherited before printing where_opened
    
    Inherited file descriptors don't have an ExeContext where they were
    opened (by the program). So don't try to print the NULL where_opened
    when reporting double close errors for such file descriptors.
    
    Add a testcase none/tests/fdleak_doubleclose0 that crashes valgrind
    before this fix.
    
    https://bugs.kde.org/show_bug.cgi?id=504466