Index | Thread | Search

From:
"Theo de Raadt" <deraadt@openbsd.org>
Subject:
Re: FD state after close(2) error?
To:
Martijn Dekker <martijn@inlv.org>
Cc:
Tech-OpenBSD <tech@openbsd.org>
Date:
Fri, 07 Aug 2026 13:03:19 -0600

Download raw body.

Thread
Martijn Dekker <martijn@inlv.org> wrote:

> Op 07-08-2026 om 16:39 schreef Theo de Raadt:
> > Martijn Dekker <martijn@inlv.org> wrote:
> > 
> >> Is there any scenario where a file descriptor passed to close(2)
> >> remains, or might remain, open if EINTR or EIO occurs? The manual page
> >> does not document this.
> > It does document it, in the first sentence:
> > DESCRIPTION
> >       The close() call deletes a descriptor d from the per-process object
> >       reference table.
> > Nothing later on disagrees with this sentence.
> 
> 
> That's ambiguous at best.

It is not ambigious.  It is accurate text.

> The ERRORS section details the conditions
> under which close(2) fails, but it does not define what it means to
> fail.

Let's try your approach by looking at the open(2) manual page.

DESCRIPTION
     The file name specified by path is opened for reading and/or writing as
     specified by the argument flags and the file descriptor returned to the
     calling process.  The flags argument may indicate the file is to be

So shall we interpret that if we pass O_CREAT|O_WRONLY and then an error
happens the file *MAY* have been created in the filesystem, but an error
is returned?

Your opinion on ambiguity here is not correct.

> A reasonable default interpretation would be that the call
> failed to do its specified job, which begins with deleting the
> descriptor d from the per-process object reference table.

Sure if you read it wrong, you could also interpret that calling
close(-7) will deliver you a free pony.

The close() system call behaviour has not significantly changed in
over 30 years.

The text in the manual page is ACCURATE and very likely older than you.
 
> I would suggest adding, to the end of the ERRORS section, this
> sentence from FreeBSD's close(2) manual page:
> 
>      In case of any error except EBADF, the supplied file descriptor is
>      deallocated and therefore is no longer valid.

And let me see, to open(2) we should add this text:

    In case of any error, the requested file is not created.

Right?

I think that's bullshit.  The first sentence was clear.

Unfortunately you cannot write code to that spec, because POSIX says:

    If close() is interrupted by a signal that is to be caught, it shall
    return -1 with errno set to [EINTR] and the state of fildes is
    unspecified. If an I/O error occurred while reading from or writing to
    the file system during close(), it may return -1 with errno set to
    [EIO]; if this error is returned, the state of fildes is unspecified.

I recommend you take up concerns with the POSIX people.

> Given the incredible mess around close(2) breakage on various systems
> over the last few decades, it would seem prudent to be quite explicit
> about this.

I've never seen any code which is broken by entirely ignoring the return
value from close(2), and assuming that it works as originally intended.

The base of userland code does commonly check for error, and it should
not be checking.

> In the meantime, I much appreciate your on-list clarification, which I
> can use as a reference.

You should not use my comments.  You should refer to code.

> > The addition of error return values to close() by POSIX has been
> > contentious since the start, because it makes this system call operation
> > non-ACID (even when returning an error, it has performed a step).  This
> > mainly arrived because of a weird behaviour in NFS.  But many believe the
> > specification was a mistake, because no program is prepared or capable of
> > taking corrective action when these errors occur, because sufficient
> > information about the issue isn't reported AND because the descriptor
> > which would be needed for resolution has been deleted (see the first sentence).
> 
> 
> I agree that avoiding a change of state if an error occurs would make
> the most sense. Unfortunately, HP-UX is the only extant system that
> acts like this.
> 
> OpenBSD, FreeBSD and Linux are at least consistent: it's always
> closed, full stop. I'm awaiting confirmation from NetBSD that they do
> the same thing, but at this point I suspect so. (Their close(2) man
> page is equally unclear on the matter.)

Why are you asking people?  You should be reading the pretty simple kernel
code, and if you look back over decades of repository, it will be fine.

> AIX and Solaris/illumos have the worst of both worlds: their manual
> pages say that the state of the file descriptor is indeterminate if
> close(2) fails. So retrying close(2) is necessary to avoid file
> descriptor leaks. Good luck safely and effectively closing a file in a
> multithreaded application.

They say that POSIX says it is indeterminate, but why have you not
checked the actual Solaris code to verify?

Meaning, the Solaris manual page is telling you there may be systems
(perhaps not AIX and Solaris/illumos) where something weird could
happen, and it is following POSIX guidance to tell people that they
may need to be wary if writing portable code.

THIS is the problem:

https://pubs.opengroup.org/onlinepubs/009604299/functions/close.html

I think there are situations where operating system manual pages for
functions should warn that there are dangerous divergences on other systems,
but there are also other cases where they should not say a word about it
because it doesn't matter.

We do not want people submitting hundreds of pointless diffs to add
return value checks for all the close() functions in our tree (with
no suitable resolution when that happens), and the best way to do that
is to not admit that there are potentially broken systems.  Those
broken operating systems will hopefully expire, and we can ignore that
useless lawyer speak in POSIX.

> the meantime, POSIX-1.2024 has added a new complication: if
> close(2) and the new posix_close(2) are interrupted but return with
> the file descriptor closed, they're now required to return -1 and set
> errno to EINPROGRESS. Since the FD is closed just fine and the kernel
> will take care of the rest, that's not actually a failure, and there's
> nothing the caller can or should do about it. 

POSIX has been turning into a joke for a while  The rule of thumb was
that they can only document existing behaviour.  That is not existing
behaviour.

> However, this change is
> likely to incorrectly trigger failure handling in existing code.
> https://pubs.opengroup.org/onlinepubs/9799919799/functions/close.html

PoC||GTFO

And now I've hit my limit on interest in this area.