From d362e9e1d3c0dc3c88474ee1d1b46613064aa6a6 Mon Sep 17 00:00:00 2001 From: Arturo Bernal Date: Tue, 20 Oct 2026 13:44:05 +0200 Subject: [PATCH] core: Refresh cached file info from the open fd in default_handler. r->finfo was left as populated by the directory walk's stat() on the pathname, even after the file is opened. A rename(2) racing with that open() leaves the fd pointing at a different file than the one that was stat()'d, so Last-Modified, ETag, Content-Length and the byte range actually sent end up describing the old file while the content comes from the new one. Refresh r->finfo from the already-open file descriptor right after open(), before any of that is computed. PR 54962. --- changes-entries/core-default-handler-stale-stat.txt | 7 +++++++ server/core.c | 11 +++++++++++ 2 files changed, 18 insertions(+) create mode 100644 changes-entries/core-default-handler-stale-stat.txt diff --git a/changes-entries/core-default-handler-stale-stat.txt b/changes-entries/core-default-handler-stale-stat.txt new file mode 100644 index 00000000000..959f2e225dc --- /dev/null +++ b/changes-entries/core-default-handler-stale-stat.txt @@ -0,0 +1,7 @@ + *) core: Refresh the cached file information from the open file + descriptor in the default handler, instead of keeping the result of + the directory walk's stat() on the pathname. A rename(2) racing + with the open() could otherwise leave Last-Modified, ETag, + Content-Length and the byte range actually sent describing a + different file than the one that was opened. PR 54962. + [Ståle Kristoffersen] diff --git a/server/core.c b/server/core.c index a6928ef916c..394524012e4 100644 --- a/server/core.c +++ b/server/core.c @@ -5248,6 +5248,17 @@ static int default_handler(request_rec *r) return HTTP_FORBIDDEN; } + /* r->finfo was populated by the directory walk's stat() on the + * pathname, which can race with a rename(2) replacing the file + * before it is opened here. Refresh it from the now-open file + * descriptor so that Last-Modified, ETag, Content-Length and the + * byte range actually sent all describe the same inode, instead + * of mixing stale directory-walk metadata with the content of + * whatever file the name happened to refer to by the time it + * was opened. + */ + apr_file_info_get(&r->finfo, APR_FINFO_MIN | APR_FINFO_IDENT, fd); + ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); ap_set_etag_fd(r, fd);