Commit message: fix: Do not use the "/tmp" directory

https://github.com/linuxdeepin/deepin-clone/issues/16
https://bugzilla.opensuse.org/show_bug.cgi?id=1130388
CVE: CVE-2019-13227
CWE: CWE-61
Vulnerability description: In GUI mode, deepin-clone before 1.1.3 creates a log file at the fixed path /tmp/.deepin-clone.log as root, and follows symlinks there. An unprivileged user can prepare a symlink attack there to create or overwrite files in arbitrary file system locations. The content is not attacker controlled.

Project: deepin-clone
File: helper.cpp

QString Helper::temporaryMountDevice(const QString &device, const QString &name, bool readonly)
{
    QString mount_point = mountPoint(device);

    if (!mount_point.isEmpty())
        return mount_point;

    mount_point = "%1/.%2/mount/%3";
    const QStringList &tmp_paths = QStandardPaths::standardLocations(QStandardPaths::TempLocation);

    mount_point = mount_point.arg(tmp_paths.isEmpty() ? "/tmp" : tmp_paths.first()).arg(qApp->applicationName()).arg(name);

    if (!QDir::current().mkpath(mount_point)) {
        dCError("mkpath \"%s\" failed", qPrintable(mount_point));

        return QString();
    }

    if (!mountDevice(device, mount_point, readonly)) {
        dCError("Mount the device \"%s\" to \"%s\" failed", qPrintable(device), qPrintable(mount_point));

        return QString();
    }

    return mount_point;
}

Project: deepin-clone
File: bootdoctor.cpp

bool BootDoctor::fix(const QString &partDevice)
{
    m_lastErrorString.clear();

    DDevicePartInfo part_info(partDevice);
    const QString part_old_uuid = part_info.uuid();

    if (Helper::processExec("lsblk -s -d -n -o UUID") == 0) {
        if (Helper::lastProcessStandardOutput().contains(part_old_uuid.toLatin1())) {
            // reset uuid
            if (Helper::resetPartUUID(part_info)) {
                QThread::sleep(1);
                part_info.refresh();

                qDebug() << part_old_uuid << part_info.uuid();
            } else {
                dCWarning("Failed to reset uuid");
            }
        }
    }

    bool device_is_mounted = Helper::isMounted(partDevice);
    const QString &mount_root = Helper::temporaryMountDevice(partDevice, QFileInfo(partDevice).fileName());

    if (mount_root.isEmpty()) {
        m_lastErrorString = QObject::tr("Failed to mount partition \"%1\"").arg(partDevice);
        goto failed;
    }

    {
        const QStringList &tmp_paths = QStandardPaths::standardLocations(QStandardPaths::TempLocation);
        const QString tmp_dir = (tmp_paths.isEmpty() ? "/tmp" : tmp_paths.first()) + "/.deepin-clone";

        if (!QDir::current().mkpath(tmp_dir)) {
            dCError("mkpath \"%s\" failed", qPrintable(tmp_dir));
            goto failed;
        }

        const QString &repo_path = tmp_dir + "/repo.iso";

        if (!QFile::exists(repo_path)
                && !QFile::copy(QString(":/repo_%1.iso").arg(HOST_ARCH), repo_path)) {
            dCError("copy file failed, new name: %s", qPrintable(repo_path));
            goto failed;
        }

        bool ok = false;

        const QString &repo_mount_point = mount_root + "/deepin-clone";
        QFile file_boot_fix(mount_root + "/boot_fix.sh");

        do {
            if (!QDir(mount_root).exists("deepin-clone") && !QDir(mount_root).mkdir("deepin-clone")) {
                dCError("Create \"deepin-clone\" dir failed(\"%s\")", qPrintable(mount_root));
                break;
            }

            if (!Helper::mountDevice(repo_path, repo_mount_point, true)) {
                m_lastErrorString = QObject::tr("Failed to mount partition \"%1\"").arg(repo_path);
                break;
            }

            if (file_boot_fix.exists()) {
                file_boot_fix.remove();
            }

            if (!QFile::copy(QString(":/scripts/boot_fix_%1.sh").arg(
                     #if defined(HOST_ARCH_x86_64) || defined(HOST_ARCH_i386) || defined(HOST_ARCH_i686)
                                 "x86"
                     #elif defined(HOST_ARCH_mips64) || defined(HOST_ARCH_mips32)
                                 "mips"
                     #elif defined(HOST_ARCH_sw_64)
                                 "sw_64"
                     #elif defined(HOST_ARCH_aarch64)
                                 "aarch64"
                     #else
                     #pragma message "Machine: " HOST_ARCH
                                "unknow"
                     #endif
                                 ), file_boot_fix.fileName())) {
                dCError("copy file failed, new name: %s", qPrintable(file_boot_fix.fileName()));
                break;
            }

            if (!file_boot_fix.setPermissions(file_boot_fix.permissions() | QFile::ExeUser)) {
                dCError("Set \"%s\" permissions failed", qPrintable(file_boot_fix.fileName()));
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /dev %1/dev").arg(mount_root)) != 0) {
                dCError("Failed to bind /dev");
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /dev/pts %1/dev/pts").arg(mount_root)) != 0) {
                dCError("Failed to bind /dev/pts");
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /proc %1/proc").arg(mount_root)) != 0) {
                dCError("Failed to bind /proc");
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /sys %1/sys").arg(mount_root)) != 0) {
                dCError("Failed to bind /sys");
                break;
            }

            ok = true;
        } while (0);

        QProcess process;

        if (ok) {
            const QString &parent_device = Helper::parentDevice(partDevice);

            bool is_efi = false;

            if (!parent_device.isEmpty()) {
                DDeviceDiskInfo info(parent_device);

                dCDebug("Disk partition table type: %d", info.ptType());

                if (info.ptType() == DDeviceDiskInfo::GPT) {
                    for (const DPartInfo &part : info.childrenPartList()) {
                        if (part.guidType() == DPartInfo::EFI_SP_None) {
                            const QString &efi_path = mount_root + "/boot/efi";

                            QDir::current().mkpath(efi_path);

                            if (Helper::processExec(QString("mount %1 %2").arg(part.filePath()).arg(efi_path)) != 0) {
                                dCError("Failed to mount EFI partition");
                                m_lastErrorString = QObject::tr("Failed to mount partition \"%1\"").arg(part.filePath());
                                ok = false;
                                break;
                            }

                            is_efi = true;

                            break;
                        }
                    }

                    if (!is_efi && m_lastErrorString.isEmpty()) {
                        m_lastErrorString = QObject::tr("EFI partition not found");
                        ok = false;
                    }
                } else if (info.ptType() == DDeviceDiskInfo::Unknow) {
                    m_lastErrorString = QObject::tr("Unknown partition style");
                    ok = false;
                }
            }

            if (ok) {
                process.setProcessChannelMode(QProcess::MergedChannels);
                process.start(QString("chroot %1 ./boot_fix.sh %2 %3 /deepin-clone")
                              .arg(mount_root)
                              .arg(parent_device)
                              .arg(is_efi ? "true" : "false"));

                while (process.waitForReadyRead()) {
                    const QByteArray &data = process.readAll().simplified().constData();

                    dCDebug(data.constData());
                }

                process.waitForFinished(-1);

                switch (process.exitCode()) {
                case 1:
                    m_lastErrorString = QObject::tr("Boot for install system failed");
                    break;
                case 2:
                    m_lastErrorString = QObject::tr("Boot for update system failed");
                    break;
                default:
                    break;
                }
            }
        }

        // clear
        Helper::processExec("umount " + repo_mount_point);
        QDir(mount_root).rmdir("deepin-clone");
        file_boot_fix.remove();
        Helper::processExec("umount " + mount_root + "/dev/pts");
        Helper::processExec("umount " + mount_root + "/dev");
        Helper::processExec("umount " + mount_root + "/proc");
        Helper::processExec("umount " + mount_root + "/sys");
        Helper::processExec("umount " + mount_root + "/boot/efi");

        if (ok && process.exitCode() == 0) {
            if (part_old_uuid != part_info.uuid()) {
                dCDebug("Reset the uuid from \"%s\" to \"%s\"", qPrintable(part_old_uuid), qPrintable(part_info.uuid()));

                // update /etc/fstab
                QFile file(mount_root + "/etc/fstab");

                if (file.exists() && file.open(QIODevice::ReadWrite)) {
                    QByteArray data = file.readAll();

                    if (file.seek(0)) {
                        file.write(data.replace(part_old_uuid.toLatin1(), part_info.uuid().toLatin1()));
                    }

                    file.close();
                } else {
                    dCWarning("Failed to update /etc/fstab, error: %s", qPrintable(file.errorString()));
                }

                file.setFileName(mount_root + "/etc/crypttab");

                if (file.exists() && file.open(QIODevice::ReadWrite)) {
                    QByteArray data = file.readAll();

                    if (file.seek(0)) {
                        file.write(data.replace(part_old_uuid.toLatin1(), part_info.uuid().toLatin1()));
                    }

                    file.close();
                } else {
                    dCWarning("Failed to update /etc/crypttab, error: %s", qPrintable(file.errorString()));
                }
            }

            if (!device_is_mounted)
                Helper::umountDevice(partDevice);

            return true;
        }
    }

failed:
    if (!device_is_mounted)
        Helper::umountDevice(partDevice);

    if (m_lastErrorString.isEmpty())
        m_lastErrorString = QObject::tr("Boot for repair system failed");

    dCDebug("Restore partition uuid");

    if (!Helper::resetPartUUID(part_info, part_old_uuid.toLatin1())) {
        dCWarning("Failed to restore partition uuid, part: %s, uuid: %s", qPrintable(partDevice), qPrintable(part_old_uuid));
    }

    return false;
}

Project: deepin-clone
File: helper.cpp

bool Helper::getPartitionSizeInfo(const QString &partDevice, qint64 *used, qint64 *free, int *blockSize)
{
    QProcess process;
    QStringList env_list = QProcess::systemEnvironment();

    env_list.append("LANG=C");
    process.setEnvironment(env_list);

    if (Helper::isMounted(partDevice)) {
        process.start(QString("df -B1 -P %1").arg(partDevice));
        process.waitForFinished();

        if (process.exitCode() != 0) {
            dCError("Call df failed: %s", qPrintable(process.readAllStandardError()));

            return false;
        }

        QByteArray output = process.readAll();
        const QByteArrayList &lines = output.trimmed().split('\n');

        if (lines.count() != 2)
            return false;

        output = lines.last().simplified();

        const QByteArrayList &values = output.split(' ');

        if (values.count() != 6)
            return false;

        bool ok = false;

        if (used)
            *used = values.at(2).toLongLong(&ok);

        if (!ok)
            return false;

        if (free)
            *free = values.at(3).toLongLong(&ok);

        if (!ok)
            return false;

        return true;
    } else {
        process.start(QString("%1 -s %2 -c -q -C -L /tmp/partclone.log").arg(getPartcloneExecuter(DDevicePartInfo(partDevice))).arg(partDevice));
        process.setStandardOutputFile("/dev/null");
        process.setReadChannel(QProcess::StandardError);
        process.waitForStarted();

        qint64 used_block = -1;
        qint64 free_block = -1;

        while (process.waitForReadyRead(5000)) {
            const QByteArray &data = process.readAll();

            for (QByteArray line : data.split('\n')) {
                line = line.simplified();

                if (QString::fromLatin1(line).contains(QRegularExpression("\\berror\\b"))) {
                    dCError("Call \"%s %s\" failed: \"%s\"", qPrintable(process.program()), qPrintable(process.arguments().join(' ')), line.constData());
                }

                if (line.startsWith("Space in use:")) {
                    bool ok = false;
                    const QByteArray &value = line.split(' ').value(6, "-1");

                    used_block = value.toLongLong(&ok);

                    if (!ok) {
                        dCError("String to LongLong failed, String: %s", value.constData());

                        return false;
                    }
                } else if (line.startsWith("Free Space:")) {
                    bool ok = false;
                    const QByteArray &value = line.split(' ').value(5, "-1");

                    free_block = value.toLongLong(&ok);

                    if (!ok) {
                        dCError("String to LongLong failed, String: %s", value.constData());

                        return false;
                    }
                } else if (line.startsWith("Block size:")) {
                    bool ok = false;
                    const QByteArray &value = line.split(' ').value(2, "-1");

                    int block_size = value.toInt(&ok);

                    if (!ok) {
                        dCError("String to Int failed, String: %s", value.constData());

                        return false;
                    }

                    if (used_block < 0 || free_block < 0 || block_size < 0)
                        return false;

                    if (used)
                        *used = used_block * block_size;

                    if (free)
                        *free = free_block * block_size;

                    if (blockSize)
                        *blockSize = block_size;

                    process.terminate();
                    process.waitForFinished();

                    return true;
                }
            }
        }
    }

    return false;
}