Bug 228430 - Simple program using QJson* classes segfaults
Summary: Simple program using QJson* classes segfaults
Status: Closed Not A Bug
Alias: None
Product: Ports & Packages
Classification: Unclassified
Component: Individual Port(s) (show other bugs)
Version: Latest
Hardware: Any Any
: --- Affects Some People
Assignee: freebsd-kde (group)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-05-22 19:16 UTC by Gleb Popov
Modified: 2018-08-10 10:18 UTC (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Gleb Popov freebsd_committer freebsd_triage 2018-05-22 19:16:47 UTC
Command line to compile:
c++ -I/usr/local/include/qt5/QtCore -I/usr/local/include/qt5 -fPIC -L/usr/local/lib/qt5 -lQt5Core test.cpp -O0 -g

The problematic code (extracted from KDevelop sources):

#include <QJsonObject>

QJsonObject makeObject()
{
    QJsonObject ret, kplugin;

    kplugin["EnabledByDefault"] = false;
    ret["KPlugin"] = kplugin;

    return ret;
}

int main()
{
    // works
    auto p = makeObject()["KPlugin"].toObject();
    const auto enabledByDefaultValue = p["EnabledByDefault"];

    // segfaults
//     const auto enabledByDefaultValue = makeObject()["KPlugin"].toObject()["EnabledByDefault"];


    const bool enabledByDefault = enabledByDefaultValue.isNull();
    return 0;
}

Changing clang to g++6 doesn't make segfault go away.
But compiling on FreeBSD 11 or 10 does make it go away.

I've tried using Hex-Rays decompiler to find difference between OK and FAIL versions. Here is the pseudocode for OK version:

KPluginMetaData::rawData((KPluginMetaData *)&pluginInfoThis);
QString::QString(&string_KPlugin, "KPlugin");
LODWORD(v10) = QJsonObject::operator[](&pluginInfoThis, &string_KPlugin);
v25 = v10;
v26 = v11;
QJsonValueRef::toObject((QJsonValueRef *)&v27);
QString::~QString((QString *)&string_KPlugin);
QJsonObject::~QJsonObject((QJsonObject *)&pluginInfoThis);
QString::QString(&string_EnabledByDef, "EnabledByDefault");
LODWORD(v12) = QJsonObject::operator[](&v27, &string_EnabledByDef);
v21 = v12;
v22 = v13;
QString::~QString((QString *)&string_EnabledByDef);
v16 = 1;
if ( !(QJsonValueRef::isNull((QJsonValueRef *)&v21) & 1) )

Note that before QJsonValueRef::isNull is called, 3 destructors are run:

QString::~QString((QString *)&string_KPlugin);
QJsonObject::~QJsonObject((QJsonObject *)&pluginInfoThis);
QString::~QString((QString *)&string_EnabledByDef);

And here is decompiled code of the FAIL version:

KPluginMetaData::rawData((KPluginMetaData *)&pluginInfoThis);
QString::QString(&string_KPlugin, "KPlugin");
LODWORD(v10) = QJsonObject::operator[](&pluginInfoThis, &string_KPlugin);
v23 = v10;
v24 = v11;
QJsonValueRef::toObject((QJsonValueRef *)&v25);
QString::QString(&string_EnabledByDef, "EnabledByDefault");
LODWORD(v12) = QJsonObject::operator[](&v25, &string_EnabledByDef);
v26 = v12;
v27 = v13;
QString::~QString((QString *)&string_EnabledByDef);
QJsonObject::~QJsonObject((QJsonObject *)&v25);
QString::~QString((QString *)&string_KPlugin);
QJsonObject::~QJsonObject((QJsonObject *)&pluginInfoThis);
v16 = 1;
if ( !(QJsonValueRef::isNull((QJsonValueRef *)&v26) & 1) )

There 4 destructors are run - 3 from above and additional QJsonObject::~QJsonObject((QJsonObject *)&v25); which is an object that QJsonObject::operator[](&v25, &string_EnabledByDef); operates on. IIUIC, it should be OK to call the destructor early, as Qt manages references to object's data behind the scenes. However, something goes wrong.

CFG's of both cases:
http://arrowd.name/ok.png
http://arrowd.name/fail.png
Comment 1 Tobias C. Berner freebsd_committer freebsd_triage 2018-05-22 19:41:44 UTC
I'm unfortunately unable to reproduce the issue here. Let me know if there is anything more to test.


mfg Tobias
Comment 2 Gleb Popov freebsd_committer freebsd_triage 2018-05-24 14:45:06 UTC
I was able to reproduce this in the jail created from the latest 12.0-CURRENT snapshot:

Create a new jail
# poudriere -c -j cur12 -v 12.0-CURRENT

Build pkg and leave the jail running
# poudriere testport -j cur12 -I ports-mgmt/pkg

Copy the testcase into the jail
# cp test.cpp /usr/local/poudriere/data/.m/cur12-default/ref/root/

Enter it
# sudo jexec cur12-sbreeze-n env -i TERM=$TERM /usr/bin/login -fp root

Compile the testcase
# c++ -I/usr/local/include/qt5/QtCore -I/usr/local/include/qt5 -fPIC -L/usr/local/lib/qt5 -lQt5Core test.cpp -o fail

Get a segfault
# ./fail
Segmentation fault (core dumped)


The reversing effort I did was right - when QJsonObject::~QJsonObject is called it dereferences .d field and the count becomes 0. However, returned QJsonValueRef enabledByDefaultValue also holds the same d while it gets deleted. So, when the execution gets to enabledByDefaultValue.isNull(), that .d field is already freed and contains garbage.
Comment 3 Gleb Popov freebsd_committer freebsd_triage 2018-08-10 10:18:19 UTC
The testcase itself is buggy and contains undefined behavior. This was visible for me only because I had MALLOC_PRODUCTION turned off.

More details: https://phabricator.kde.org/D12743#268639