Found the bug.

Docker uses seccomp security profiles to restrict e.g. system calls [1].
In docker 17.05 system call "statx" is blocked [2][3], in trunk and in
18.04 which is not released yet that system call will be allowed [4][5].
By running the docker container with "--security-opt seccomp=unconfined"
or "--privileged" the system call "statx" not blocked [5] and everything
works as excepted: QFile::exists() returns true for existing files.

Older revisions of QFile (e.g. qt 5.7 from debian stretch) do work as
excepted even when statx is blocked, because they do not use system call
"statx".

That system call is not necessary to find out if a file exists. Qt is
able to detect whether "statx" is supported. But it fails to detect that
its call is not permitted. The reason for that is in
src/corelib/io/qfilesystemengine_unix.cpp which is still present in
latest upstream revision [6]. But lets start from the beginning:

QFile::exists() returns true if ExistsFlag is set [7]. Attributes are
set in "QFSFileEnginePrivate::doStat" [10]. This function first reads
attributes with 2-args-function-overload
"QFileSystemEngine::fillMetaData" [11] and then loads the missing
attributes with 3-args-function-overload
"QFileSystemEngine::fillMetaData" [12]. Boths overloads suffer from the
same bug.

The 2-args-overload "bool QFileSystemEngine::fillMetaData(int fd,
QFileSystemMetaData &data)" [11] calls "qt_fstatx". QtCore in debian sid
was build with a kernel that supports the statx system call and thus
function "qt_fstatx" does indeed call "syscall(SYS_statx, ...)". If this
syscall is blocked, then "syscall(...)" returns -1 and errno is set to 1
(EPERM == "Operation not permitted") [13]. Back in
"QFileSystemEngine::fillMetaData" [11] look at this code snippet:

  int ret = qt_fstatx(fd, &statxBuffer);
  if (ret != -ENOSYS) {
      if (ret == 0) {
          data.fillFromStatxBuf(statxBuffer);
          return true;
      }
      return false;
  }

If statx is blocked, then ret==-EPERM (negated in [14]), which means
"return false" is executed, thus no attributes are read.

"QFSFileEnginePrivate::doStat" now executes the 3-args-overload "bool
QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry,
QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what)"
[12]. It contains this snippet:

  statResult = qt_lstatx(nativeFilePath, &statxBuffer);
  if (statResult == -ENOSYS) {
      //...
  } else if (statResult == 0) {
      //...
  }
 
  if (statResult >= 0) {
      //...
  } else {
      // it doesn't exist
      entryErrno = errno;
      data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
  }

When statx is blocked then statResult is -1 (-EPERM) and thus last
branch is executed. Thus 3-args-overload of fillMetaData says "i know
that file does not exist" (knownFlagsMask has ExistsAttribute but
entryFlags has not). The code "data.entryFlags |= flag |
QFileSystemMetaData::ExistsAttribute;" later in that function [16] is
never executed because entryErrno equals -1 (-EPERM).

Thus QFile always returns false, for all files, even existing once, as
long as statx is supported but blocked via e.g. seccomp.

Cheers,
Jakob


[1] https://github.com/Microsoft/docker/blob/master/docs/security/seccomp.md
[2] debian package "docker-engine" currently available for stretch is
17.05.0~ce-0~debian-stretch
[3] https://github.com/moby/moby/blob/17.05.x/profiles/seccomp/default.json
[4] https://github.com/moby/moby/blob/master/profiles/seccomp/default.json
[5] https://github.com/docker/for-linux/issues/208
[6]
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfilesystemengine_unix.cpp
[7] https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfile.cpp#n425
[8] -DELETED-
[9] -DELETED-
[10]
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfsfileengine_unix.cpp#n419
[11]
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfilesystemengine_unix.cpp#n439
[12]
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfilesystemengine_unix.cpp#n931
[13] /usr/include/asm-generic/errno-base.h
[14]
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfilesystemengine_unix.cpp#n350
[15]
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfilesystemengine_unix.cpp#n979
[16]
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qfilesystemengine_unix.cpp#n1053

Am 08.06.2018 um 20:54 schrieb Dmitry Shachnev:
> Hi Jakob!
>
> On Fri, Jun 08, 2018 at 04:01:54PM +0200, Wearenotalone wrote:
>> did a fresh install of debian sid within docker and stumbled upon this
>> bug in QFile:
>> QFile's method exists() returns false even if that file exists.
> Thanks for your bug report and the test case.
>
> Can you please obtain strace of your example and attach it?
> I am not able to reproduce this issue, but I would like to compare yours
> and mine strace outputs.
>
> --
> Dmitry Shachnev

Reply via email to