archive.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // Copyright (c) 2014 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/common/asar/archive.h"
  5. #include <string>
  6. #include <string_view>
  7. #include <utility>
  8. #include <vector>
  9. #include "base/check.h"
  10. #include "base/containers/span.h"
  11. #include "base/files/file.h"
  12. #include "base/files/file_util.h"
  13. #include "base/json/json_reader.h"
  14. #include "base/logging.h"
  15. #include "base/pickle.h"
  16. #include "base/values.h"
  17. #include "electron/fuses.h"
  18. #include "shell/common/asar/asar_util.h"
  19. #include "shell/common/asar/scoped_temporary_file.h"
  20. #include "shell/common/thread_restrictions.h"
  21. #if BUILDFLAG(IS_WIN)
  22. #include <io.h>
  23. #endif
  24. namespace asar {
  25. namespace {
  26. #if BUILDFLAG(IS_WIN)
  27. const char kSeparators[] = "\\/";
  28. #else
  29. const char kSeparators[] = "/";
  30. #endif
  31. const base::Value::Dict* GetNodeFromPath(std::string path,
  32. const base::Value::Dict& root);
  33. // Gets the "files" from "dir".
  34. const base::Value::Dict* GetFilesNode(const base::Value::Dict& root,
  35. const base::Value::Dict& dir) {
  36. // Test for symbol linked directory.
  37. const std::string* link = dir.FindString("link");
  38. if (link != nullptr) {
  39. const base::Value::Dict* linked_node = GetNodeFromPath(*link, root);
  40. if (!linked_node)
  41. return nullptr;
  42. return linked_node->FindDict("files");
  43. }
  44. return dir.FindDict("files");
  45. }
  46. // Gets sub-file "name" from "dir".
  47. const base::Value::Dict* GetChildNode(const base::Value::Dict& root,
  48. const std::string& name,
  49. const base::Value::Dict& dir) {
  50. if (name.empty())
  51. return &root;
  52. const base::Value::Dict* files = GetFilesNode(root, dir);
  53. return files ? files->FindDict(name) : nullptr;
  54. }
  55. // Gets the node of "path" from "root".
  56. const base::Value::Dict* GetNodeFromPath(std::string path,
  57. const base::Value::Dict& root) {
  58. if (path.empty())
  59. return &root;
  60. const base::Value::Dict* dir = &root;
  61. for (size_t delimiter_position = path.find_first_of(kSeparators);
  62. delimiter_position != std::string::npos;
  63. delimiter_position = path.find_first_of(kSeparators)) {
  64. const base::Value::Dict* child =
  65. GetChildNode(root, path.substr(0, delimiter_position), *dir);
  66. if (!child)
  67. return nullptr;
  68. dir = child;
  69. path.erase(0, delimiter_position + 1);
  70. }
  71. return GetChildNode(root, path, *dir);
  72. }
  73. bool FillFileInfoWithNode(Archive::FileInfo* info,
  74. uint32_t header_size,
  75. bool load_integrity,
  76. const base::Value::Dict* node) {
  77. if (std::optional<int> size = node->FindInt("size")) {
  78. info->size = static_cast<uint32_t>(*size);
  79. } else {
  80. return false;
  81. }
  82. if (std::optional<bool> unpacked = node->FindBool("unpacked")) {
  83. info->unpacked = *unpacked;
  84. if (info->unpacked) {
  85. return true;
  86. }
  87. }
  88. const std::string* offset = node->FindString("offset");
  89. if (offset &&
  90. base::StringToUint64(std::string_view{*offset}, &info->offset)) {
  91. info->offset += header_size;
  92. } else {
  93. return false;
  94. }
  95. if (std::optional<bool> executable = node->FindBool("executable")) {
  96. info->executable = *executable;
  97. }
  98. #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
  99. if (load_integrity &&
  100. electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled()) {
  101. if (const base::Value::Dict* integrity = node->FindDict("integrity")) {
  102. const std::string* algorithm = integrity->FindString("algorithm");
  103. const std::string* hash = integrity->FindString("hash");
  104. std::optional<int> block_size = integrity->FindInt("blockSize");
  105. const base::Value::List* blocks = integrity->FindList("blocks");
  106. if (algorithm && hash && block_size && block_size > 0 && blocks) {
  107. IntegrityPayload integrity_payload;
  108. integrity_payload.hash = *hash;
  109. integrity_payload.block_size =
  110. static_cast<uint32_t>(block_size.value());
  111. for (auto& value : *blocks) {
  112. if (const std::string* block = value.GetIfString()) {
  113. integrity_payload.blocks.push_back(*block);
  114. } else {
  115. LOG(FATAL)
  116. << "Invalid block integrity value for file in ASAR archive";
  117. }
  118. }
  119. if (*algorithm == "SHA256") {
  120. integrity_payload.algorithm = HashAlgorithm::kSHA256;
  121. info->integrity = std::move(integrity_payload);
  122. }
  123. }
  124. }
  125. if (!info->integrity.has_value()) {
  126. LOG(FATAL) << "Failed to read integrity for file in ASAR archive";
  127. }
  128. }
  129. #endif
  130. return true;
  131. }
  132. } // namespace
  133. IntegrityPayload::IntegrityPayload()
  134. : algorithm(HashAlgorithm::kNone), block_size(0) {}
  135. IntegrityPayload::~IntegrityPayload() = default;
  136. IntegrityPayload::IntegrityPayload(const IntegrityPayload& other) = default;
  137. Archive::FileInfo::FileInfo()
  138. : unpacked(false), executable(false), size(0), offset(0) {}
  139. Archive::FileInfo::~FileInfo() = default;
  140. Archive::Archive(const base::FilePath& path)
  141. : initialized_(false), path_(path), file_(base::File::FILE_OK) {
  142. electron::ScopedAllowBlockingForElectron allow_blocking;
  143. file_.Initialize(path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
  144. #if BUILDFLAG(IS_WIN)
  145. fd_ = _open_osfhandle(reinterpret_cast<intptr_t>(file_.GetPlatformFile()), 0);
  146. #elif BUILDFLAG(IS_POSIX)
  147. fd_ = file_.GetPlatformFile();
  148. #endif
  149. }
  150. Archive::~Archive() {
  151. #if BUILDFLAG(IS_WIN)
  152. if (fd_ != -1) {
  153. _close(fd_);
  154. // Don't close the handle since we already closed the fd.
  155. file_.TakePlatformFile();
  156. }
  157. #endif
  158. electron::ScopedAllowBlockingForElectron allow_blocking;
  159. file_.Close();
  160. }
  161. bool Archive::Init() {
  162. // Should only be initialized once
  163. CHECK(!initialized_);
  164. initialized_ = true;
  165. if (!file_.IsValid()) {
  166. if (file_.error_details() != base::File::FILE_ERROR_NOT_FOUND) {
  167. LOG(WARNING) << "Opening " << path_.value() << ": "
  168. << base::File::ErrorToString(file_.error_details());
  169. }
  170. return false;
  171. }
  172. std::vector<char> buf;
  173. int len;
  174. buf.resize(8);
  175. {
  176. electron::ScopedAllowBlockingForElectron allow_blocking;
  177. len = file_.ReadAtCurrentPos(buf.data(), buf.size());
  178. }
  179. if (len != static_cast<int>(buf.size())) {
  180. PLOG(ERROR) << "Failed to read header size from " << path_.value();
  181. return false;
  182. }
  183. uint32_t size;
  184. if (!base::PickleIterator(base::Pickle::WithData(base::as_byte_span(buf)))
  185. .ReadUInt32(&size)) {
  186. LOG(ERROR) << "Failed to parse header size from " << path_.value();
  187. return false;
  188. }
  189. buf.resize(size);
  190. {
  191. electron::ScopedAllowBlockingForElectron allow_blocking;
  192. len = file_.ReadAtCurrentPos(buf.data(), buf.size());
  193. }
  194. if (len != static_cast<int>(buf.size())) {
  195. PLOG(ERROR) << "Failed to read header from " << path_.value();
  196. return false;
  197. }
  198. std::string header;
  199. if (!base::PickleIterator(base::Pickle::WithData(base::as_byte_span(buf)))
  200. .ReadString(&header)) {
  201. LOG(ERROR) << "Failed to parse header from " << path_.value();
  202. return false;
  203. }
  204. #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
  205. // Validate header signature if required and possible
  206. if (electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled() &&
  207. RelativePath().has_value()) {
  208. std::optional<IntegrityPayload> integrity = HeaderIntegrity();
  209. if (!integrity.has_value()) {
  210. LOG(FATAL) << "Failed to get integrity for validatable asar archive: "
  211. << RelativePath().value();
  212. }
  213. // Currently we only support the sha256 algorithm, we can add support for
  214. // more below ensure we read them in preference order from most secure to
  215. // least
  216. if (integrity.value().algorithm != HashAlgorithm::kNone) {
  217. ValidateIntegrityOrDie(header.c_str(), header.length(),
  218. integrity.value());
  219. } else {
  220. LOG(FATAL) << "No eligible hash for validatable asar archive: "
  221. << RelativePath().value();
  222. }
  223. header_validated_ = true;
  224. }
  225. #endif
  226. std::optional<base::Value> value = base::JSONReader::Read(header);
  227. if (!value || !value->is_dict()) {
  228. LOG(ERROR) << "Failed to parse header";
  229. return false;
  230. }
  231. header_size_ = 8 + size;
  232. header_ = std::move(*value).TakeDict();
  233. return true;
  234. }
  235. #if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_WIN)
  236. std::optional<IntegrityPayload> Archive::HeaderIntegrity() const {
  237. return std::nullopt;
  238. }
  239. std::optional<base::FilePath> Archive::RelativePath() const {
  240. return std::nullopt;
  241. }
  242. #endif
  243. bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) const {
  244. if (!header_)
  245. return false;
  246. const base::Value::Dict* node =
  247. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  248. if (!node)
  249. return false;
  250. const std::string* link = node->FindString("link");
  251. if (link)
  252. return GetFileInfo(base::FilePath::FromUTF8Unsafe(*link), info);
  253. return FillFileInfoWithNode(info, header_size_, header_validated_, node);
  254. }
  255. bool Archive::Stat(const base::FilePath& path, Stats* stats) const {
  256. if (!header_)
  257. return false;
  258. const base::Value::Dict* node =
  259. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  260. if (!node)
  261. return false;
  262. if (node->Find("link")) {
  263. stats->type = FileType::kLink;
  264. return true;
  265. }
  266. if (node->Find("files")) {
  267. stats->type = FileType::kDirectory;
  268. return true;
  269. }
  270. return FillFileInfoWithNode(stats, header_size_, header_validated_, node);
  271. }
  272. bool Archive::Readdir(const base::FilePath& path,
  273. std::vector<base::FilePath>* files) const {
  274. if (!header_)
  275. return false;
  276. const base::Value::Dict* node =
  277. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  278. if (!node)
  279. return false;
  280. const base::Value::Dict* files_node = GetFilesNode(*header_, *node);
  281. if (!files_node)
  282. return false;
  283. for (const auto iter : *files_node)
  284. files->push_back(base::FilePath::FromUTF8Unsafe(iter.first));
  285. return true;
  286. }
  287. bool Archive::Realpath(const base::FilePath& path,
  288. base::FilePath* realpath) const {
  289. if (!header_)
  290. return false;
  291. const base::Value::Dict* node =
  292. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  293. if (!node)
  294. return false;
  295. const std::string* link = node->FindString("link");
  296. if (link) {
  297. *realpath = base::FilePath::FromUTF8Unsafe(*link);
  298. return true;
  299. }
  300. *realpath = path;
  301. return true;
  302. }
  303. bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
  304. if (!header_)
  305. return false;
  306. base::AutoLock auto_lock(external_files_lock_);
  307. auto it = external_files_.find(path.value());
  308. if (it != external_files_.end()) {
  309. *out = it->second->path();
  310. return true;
  311. }
  312. FileInfo info;
  313. if (!GetFileInfo(path, &info))
  314. return false;
  315. if (info.unpacked) {
  316. *out = path_.AddExtension(FILE_PATH_LITERAL("unpacked")).Append(path);
  317. return true;
  318. }
  319. auto temp_file = std::make_unique<ScopedTemporaryFile>();
  320. base::FilePath::StringType ext = path.Extension();
  321. if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size,
  322. info.integrity))
  323. return false;
  324. #if BUILDFLAG(IS_POSIX)
  325. if (info.executable) {
  326. // chmod a+x temp_file;
  327. base::SetPosixFilePermissions(temp_file->path(), 0755);
  328. }
  329. #endif
  330. *out = temp_file->path();
  331. external_files_[path.value()] = std::move(temp_file);
  332. return true;
  333. }
  334. int Archive::GetUnsafeFD() const {
  335. return fd_;
  336. }
  337. } // namespace asar