Skip to content

Commit 024d1dd

Browse files
committed
feat: add layer signature extraction support
1. Implement extractSignData in LayerPackager to extract signature data from layer files 2. Modify installFromLayer to extract sign data and pass overlays to repo->importLayerDir 3. Add transaction rollback for importLayerDir in installFromLayer and installRef 4. Fix error handling in executePostInstallHooks: return error instead of logging 5. Add unit tests for extractSignData with signed and unsigned layers Log: Added signature extraction for layer installation, enabling signature verification and overlay application. Influence: 1. Test installation of layers with valid signatures 2. Test installation of layers without signatures (should succeed) 3. Test installation with corrupted or missing signature metadata 4. Verify that signature overlay is correctly extracted and placed 5. Test rollback on failure during signature extraction 6. Verify error when post-install hooks fail due to signature mismatch 7. Test cleanup of temporary signature files after installation
1 parent 3606ccb commit 024d1dd

4 files changed

Lines changed: 258 additions & 6 deletions

File tree

libs/linglong/src/linglong/package/layer_packager.cpp

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd.
2+
* SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd.
33
*
44
* SPDX-License-Identifier: LGPL-3.0-or-later
55
*/
@@ -8,17 +8,23 @@
88

99
#include "linglong/api/types/v1/Generators.hpp"
1010
#include "linglong/api/types/v1/LayerInfo.hpp"
11+
#include "linglong/common/error.h"
1112
#include "linglong/utils/cmd.h"
1213
#include "linglong/utils/file.h"
14+
#include "linglong/utils/finally/finally.h"
1315
#include "linglong/utils/log/log.h"
1416

17+
#include <nlohmann/json.hpp>
18+
1519
#include <QDataStream>
1620
#include <QSysInfo>
1721

22+
#include <array>
1823
#include <filesystem>
1924
#include <fstream>
2025
#include <string>
2126

27+
#include <fcntl.h>
2228
#include <unistd.h>
2329

2430
namespace linglong::package {
@@ -283,4 +289,109 @@ utils::error::Result<bool> LayerPackager::checkErofsFuseExists() const
283289
return utils::Cmd("erofsfuse").exists();
284290
}
285291

292+
utils::error::Result<void> LayerPackager::extractSignData(LayerFile &file,
293+
const std::filesystem::path &signDir)
294+
{
295+
LINGLONG_TRACE("extract sign data from layer");
296+
297+
const auto &number = magicNumber();
298+
file.seek(number.size());
299+
300+
QDataStream metaLenStream(&file);
301+
metaLenStream.setByteOrder(QDataStream::LittleEndian);
302+
quint32 metaLen = 0;
303+
metaLenStream >> metaLen;
304+
305+
auto metaRawData = file.read(metaLen);
306+
auto metaJson = nlohmann::json::parse(metaRawData.toStdString());
307+
auto erofsSize = metaJson.value("erofs_size", 0ULL);
308+
auto signSize = metaJson.value("sign_size", 0ULL);
309+
310+
if (signSize == 0) {
311+
return LINGLONG_OK;
312+
}
313+
314+
auto signOffset = static_cast<int64_t>(number.size() + sizeof(quint32) + metaLen + erofsSize);
315+
316+
std::error_code ec;
317+
auto destination = signDir / "entries" / "share" / "deepin-elf-verify" / ".elfsign";
318+
if (!std::filesystem::create_directories(destination, ec) && ec) {
319+
return LINGLONG_ERR(ec.message().c_str());
320+
}
321+
322+
auto tarFile = destination / "sign.tar";
323+
auto tarFd = ::open(tarFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
324+
if (tarFd == -1) {
325+
return LINGLONG_ERR(
326+
fmt::format("open {} failed: {}", tarFile.string(), common::error::errorString(errno)));
327+
}
328+
329+
auto removeTar = utils::finally::finally([&tarFd, &tarFile] {
330+
if (tarFd != -1) {
331+
::close(tarFd);
332+
}
333+
334+
std::error_code ec;
335+
if (!std::filesystem::remove(tarFile, ec) && ec) {
336+
LogW("failed to remove {}: {}", tarFile.string(), ec.message());
337+
}
338+
});
339+
340+
file.seek(signOffset);
341+
auto selfFd = file.handle();
342+
343+
auto totalBytes = signSize;
344+
std::array<unsigned char, 4096> buf{};
345+
while (totalBytes > 0) {
346+
auto bytesToRead = totalBytes > buf.size() ? buf.size() : totalBytes;
347+
auto readBytes = ::read(selfFd, buf.data(), bytesToRead);
348+
if (readBytes == -1) {
349+
if (errno == EINTR) {
350+
errno = 0;
351+
continue;
352+
}
353+
return LINGLONG_ERR(
354+
fmt::format("read sign data error: {}", common::error::errorString(errno)));
355+
}
356+
357+
while (true) {
358+
auto writeBytes = ::write(tarFd, buf.data(), readBytes);
359+
if (writeBytes == -1) {
360+
if (errno == EINTR) {
361+
errno = 0;
362+
continue;
363+
}
364+
return LINGLONG_ERR(
365+
fmt::format("write sign.tar error: {}", common::error::errorString(errno)));
366+
}
367+
368+
if (writeBytes != readBytes) {
369+
return LINGLONG_ERR("write sign.tar failed: byte mismatch");
370+
}
371+
372+
totalBytes -= writeBytes;
373+
break;
374+
}
375+
}
376+
377+
if (::fsync(tarFd) == -1) {
378+
return LINGLONG_ERR(
379+
fmt::format("fsync sign.tar error: {}", common::error::errorString(errno)));
380+
}
381+
382+
if (::close(tarFd) == -1) {
383+
tarFd = -1;
384+
return LINGLONG_ERR(
385+
fmt::format("failed to close tar: {}", common::error::errorString(errno)));
386+
}
387+
tarFd = -1;
388+
389+
auto ret = utils::Cmd("tar").exec({ "-xf", tarFile.string(), "-C", destination.string() });
390+
if (!ret) {
391+
return LINGLONG_ERR(ret);
392+
}
393+
394+
return LINGLONG_OK;
395+
}
396+
286397
} // namespace linglong::package

libs/linglong/src/linglong/package/layer_packager.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd.
2+
* SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd.
33
*
44
* SPDX-License-Identifier: LGPL-3.0-or-later
55
*/
@@ -38,6 +38,9 @@ class LayerPackager : public QObject
3838
void setCompressor(const QString &compressor) noexcept;
3939
const std::filesystem::path &getWorkDir() const;
4040

41+
utils::error::Result<void> extractSignData(LayerFile &file,
42+
const std::filesystem::path &signDir);
43+
4144
private:
4245
std::filesystem::path workDir;
4346
QString compressor = "lzma";

libs/linglong/src/linglong/package_manager/package_manager.cpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,20 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
689689
}
690690

691691
taskRef.updateProgress(60);
692-
auto result = this->repo->importLayerDir(*layerDir);
692+
auto signOverlayRootDir = layerPackager.getWorkDir() / "sign";
693+
auto signRet = layerPackager.extractSignData(*layerFile, signOverlayRootDir);
694+
if (!signRet) {
695+
taskRef.reportError(std::move(signRet).error());
696+
return;
697+
}
698+
std::vector<std::filesystem::path> overlays;
699+
auto signDir =
700+
signOverlayRootDir / "entries" / "share" / "deepin-elf-verify" / ".elfsign";
701+
if (std::filesystem::exists(signDir)) {
702+
overlays.emplace_back(signOverlayRootDir);
703+
}
704+
705+
auto result = this->repo->importLayerDir(*layerDir, overlays);
693706
if (!result) {
694707
taskRef.reportError(std::move(result).error());
695708
return;
@@ -715,6 +728,14 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
715728
return;
716729
}
717730

731+
utils::Transaction transaction;
732+
transaction.addRollBack([this, newRef = *newRef, module]() noexcept {
733+
auto res = repo->remove(newRef, module);
734+
if (!res) {
735+
LogW("failed to roll back importLayerDir {} {}", newRef.toString(), module);
736+
}
737+
});
738+
718739
auto ret = executePostInstallHooks(*newRef);
719740
if (!ret) {
720741
taskRef.reportError(std::move(ret).error());
@@ -725,12 +746,15 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
725746
auto res = applyApp(*newRef);
726747
if (!res) {
727748
taskRef.reportError(std::move(res).error());
749+
return;
728750
}
751+
transaction.commit();
729752
return;
730753
}
731754

732755
auto modules = this->repo->getModuleList(*localRef);
733756
if (std::find(modules.cbegin(), modules.cend(), module) == modules.cend()) {
757+
transaction.commit();
734758
return;
735759
}
736760

@@ -741,6 +765,7 @@ QVariantMap PackageManager::installFromLayer(const QDBusUnixFileDescriptor &fd,
741765
packageRef.toString(),
742766
ret.error().message());
743767
}
768+
transaction.commit();
744769
};
745770

746771
auto taskRet = tasks.addPackageTask(std::move(installer), ctx);
@@ -1195,11 +1220,21 @@ utils::error::Result<void> PackageManager::installRefModule(Task &task,
11951220
return LINGLONG_ERR(res);
11961221
}
11971222

1223+
utils::Transaction transaction;
1224+
transaction.addRollBack([this, &ref, module]() noexcept {
1225+
auto res = repo->remove(ref.reference, module);
1226+
if (!res) {
1227+
LogW("failed to roll back pull {} {}", ref.reference.toString(), module);
1228+
}
1229+
});
1230+
11981231
res = executePostInstallHooks(ref.reference);
11991232
if (!res) {
1200-
LogW(fmt::format("failed to execute postInstall hooks {}", ref.reference.toString()));
1233+
return LINGLONG_ERR("signature verification failed", res);
12011234
}
12021235

1236+
transaction.commit();
1237+
12031238
return LINGLONG_OK;
12041239
}
12051240

@@ -1242,9 +1277,16 @@ utils::error::Result<void> PackageManager::installRef(Task &task,
12421277
return LINGLONG_ERR(res);
12431278
}
12441279

1280+
transaction.addRollBack([this, &ref, module]() noexcept {
1281+
auto res = repo->remove(ref.reference, module);
1282+
if (!res) {
1283+
LogW("failed to roll back pull {} {}", ref.reference.toString(), module);
1284+
}
1285+
});
1286+
12451287
res = executePostInstallHooks(ref.reference);
12461288
if (!res) {
1247-
LogW(fmt::format("failed to execute postInstall hooks {}", ref.reference.toString()));
1289+
return LINGLONG_ERR("signature verification failed", res);
12481290
}
12491291
}
12501292

libs/linglong/tests/ll-tests/src/linglong/package/layer_packager_test.cpp

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2024-2026 UnionTech Software Technology Co., Ltd.
1+
// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
// SPDX-License-Identifier: LGPL-3.0-or-later
44

@@ -12,12 +12,16 @@
1212
#include "linglong/utils/cmd.h"
1313
#include "linglong/utils/error/error.h"
1414

15+
#include <nlohmann/json.hpp>
16+
1517
#include <QDir>
18+
#include <QFile>
1619
#include <QSharedPointer>
1720

1821
#include <filesystem>
1922
#include <fstream>
2023
#include <memory>
24+
#include <sstream>
2125
#include <string>
2226

2327
using namespace linglong;
@@ -182,4 +186,96 @@ TEST_F(LayerPackagerTest, InitWorkDir)
182186
<< "workdir should be temporary directory";
183187
}
184188

189+
TEST_F(LayerPackagerTest, ExtractSignData)
190+
{
191+
TempDir signTempDir("linglong-sign-test-");
192+
ASSERT_TRUE(signTempDir.isValid());
193+
194+
auto signContentDir = signTempDir.path() / "sign";
195+
std::filesystem::create_directories(signContentDir);
196+
std::ofstream signFile(signContentDir / "sign_test_file");
197+
signFile << "sign data content";
198+
signFile.close();
199+
200+
auto signTarPath = signTempDir.path() / "sign.tar";
201+
auto tarRet =
202+
utils::Cmd("tar").exec({ "-cvf", signTarPath.c_str(), "-C", signContentDir.c_str(), "." });
203+
ASSERT_TRUE(tarRet.has_value()) << "Failed to create sign.tar";
204+
205+
QFile originalLayer(layerFilePath.c_str());
206+
ASSERT_TRUE(originalLayer.open(QIODevice::ReadOnly));
207+
208+
auto magic = originalLayer.read(40);
209+
ASSERT_EQ(magic, magicNumber());
210+
211+
QDataStream metaLenStream(&originalLayer);
212+
metaLenStream.setByteOrder(QDataStream::LittleEndian);
213+
quint32 metaLen = 0;
214+
metaLenStream >> metaLen;
215+
216+
auto metaData = originalLayer.read(metaLen);
217+
auto metaJson = nlohmann::json::parse(metaData.toStdString());
218+
219+
auto erofsData = originalLayer.readAll();
220+
auto erofsSize = static_cast<uint64_t>(erofsData.size());
221+
originalLayer.close();
222+
223+
QFile signTarFile(signTarPath.c_str());
224+
ASSERT_TRUE(signTarFile.open(QIODevice::ReadOnly));
225+
auto signData = signTarFile.readAll();
226+
auto signSize = static_cast<uint64_t>(signData.size());
227+
signTarFile.close();
228+
229+
metaJson["erofs_size"] = erofsSize;
230+
metaJson["sign_size"] = signSize;
231+
auto newMetaData = QByteArray::fromStdString(metaJson.dump());
232+
quint32 newMetaLen = static_cast<quint32>(newMetaData.size());
233+
234+
auto signedLayerPath = signTempDir.path() / "signed.layer";
235+
QFile signedLayer(signedLayerPath.c_str());
236+
ASSERT_TRUE(signedLayer.open(QIODevice::WriteOnly));
237+
238+
signedLayer.write(magicNumber());
239+
240+
QByteArray lenBytes;
241+
QDataStream lenStream(&lenBytes, QIODevice::WriteOnly);
242+
lenStream.setByteOrder(QDataStream::LittleEndian);
243+
lenStream << newMetaLen;
244+
signedLayer.write(lenBytes);
245+
signedLayer.write(newMetaData);
246+
signedLayer.write(erofsData);
247+
signedLayer.write(signData);
248+
signedLayer.close();
249+
250+
auto signedLayerFileRet = package::LayerFile::New(signedLayerPath.c_str());
251+
ASSERT_TRUE(signedLayerFileRet.has_value())
252+
<< "Failed to create layer file: " << signedLayerFileRet.error().message();
253+
auto signedLayerFile = *signedLayerFileRet;
254+
255+
package::LayerPackager packager;
256+
auto extractRet = packager.extractSignData(*signedLayerFile, signTempDir.path());
257+
ASSERT_TRUE(extractRet.has_value()) << "Failed to extract sign data";
258+
259+
auto signDataDir = signTempDir.path() / "entries" / "share" / "deepin-elf-verify" / ".elfsign";
260+
ASSERT_TRUE(std::filesystem::exists(signDataDir / "sign_test_file"))
261+
<< "sign_test_file not found in " << signDataDir;
262+
263+
std::ifstream testFile(signDataDir / "sign_test_file");
264+
std::stringstream buffer;
265+
buffer << testFile.rdbuf();
266+
ASSERT_EQ(buffer.str(), "sign data content");
267+
}
268+
269+
TEST_F(LayerPackagerTest, ExtractSignDataNoSign)
270+
{
271+
auto layerFileRet = package::LayerFile::New(layerFilePath.c_str());
272+
ASSERT_TRUE(layerFileRet.has_value())
273+
<< "Failed to create layer file: " << layerFileRet.error().message();
274+
auto layerFile = *layerFileRet;
275+
276+
package::LayerPackager packager;
277+
auto extractRet = packager.extractSignData(*layerFile, packager.getWorkDir());
278+
ASSERT_TRUE(extractRet.has_value()) << "extractSignData should succeed";
279+
}
280+
185281
} // namespace linglong::package

0 commit comments

Comments
 (0)