archive.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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() = default;
  134. IntegrityPayload::~IntegrityPayload() = default;
  135. IntegrityPayload::IntegrityPayload(const IntegrityPayload& other) = default;
  136. Archive::FileInfo::FileInfo() = default;
  137. Archive::FileInfo::~FileInfo() = default;
  138. Archive::Archive(const base::FilePath& path) : path_{path} {
  139. electron::ScopedAllowBlockingForElectron allow_blocking;
  140. file_.Initialize(path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
  141. #if BUILDFLAG(IS_WIN)
  142. fd_ = _open_osfhandle(reinterpret_cast<intptr_t>(file_.GetPlatformFile()), 0);
  143. #elif BUILDFLAG(IS_POSIX)
  144. fd_ = file_.GetPlatformFile();
  145. #endif
  146. }
  147. Archive::~Archive() {
  148. #if BUILDFLAG(IS_WIN)
  149. if (fd_ != -1) {
  150. _close(fd_);
  151. // Don't close the handle since we already closed the fd.
  152. file_.TakePlatformFile();
  153. }
  154. #endif
  155. electron::ScopedAllowBlockingForElectron allow_blocking;
  156. file_.Close();
  157. }
  158. bool Archive::Init() {
  159. // Should only be initialized once
  160. CHECK(!initialized_);
  161. initialized_ = true;
  162. if (!file_.IsValid()) {
  163. if (file_.error_details() != base::File::FILE_ERROR_NOT_FOUND) {
  164. LOG(WARNING) << "Opening " << path_.value() << ": "
  165. << base::File::ErrorToString(file_.error_details());
  166. }
  167. return false;
  168. }
  169. std::vector<uint8_t> buf;
  170. buf.resize(8);
  171. {
  172. electron::ScopedAllowBlockingForElectron allow_blocking;
  173. if (!file_.ReadAtCurrentPosAndCheck(buf)) {
  174. PLOG(ERROR) << "Failed to read header size from " << path_.value();
  175. return false;
  176. }
  177. }
  178. uint32_t size;
  179. if (!base::PickleIterator(base::Pickle::WithData(buf)).ReadUInt32(&size)) {
  180. LOG(ERROR) << "Failed to parse header size from " << path_.value();
  181. return false;
  182. }
  183. buf.resize(size);
  184. {
  185. electron::ScopedAllowBlockingForElectron allow_blocking;
  186. if (!file_.ReadAtCurrentPosAndCheck(buf)) {
  187. PLOG(ERROR) << "Failed to read header from " << path_.value();
  188. return false;
  189. }
  190. }
  191. std::string header;
  192. if (!base::PickleIterator(base::Pickle::WithData(buf)).ReadString(&header)) {
  193. LOG(ERROR) << "Failed to parse header from " << path_.value();
  194. return false;
  195. }
  196. #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
  197. // Validate header signature if required and possible
  198. if (electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled() &&
  199. RelativePath().has_value()) {
  200. std::optional<IntegrityPayload> integrity = HeaderIntegrity();
  201. if (!integrity.has_value()) {
  202. LOG(FATAL) << "Failed to get integrity for validatable asar archive: "
  203. << RelativePath().value();
  204. }
  205. // Currently we only support the sha256 algorithm, we can add support for
  206. // more below ensure we read them in preference order from most secure to
  207. // least
  208. if (integrity->algorithm != HashAlgorithm::kNone) {
  209. ValidateIntegrityOrDie(base::as_byte_span(header), *integrity);
  210. } else {
  211. LOG(FATAL) << "No eligible hash for validatable asar archive: "
  212. << RelativePath().value();
  213. }
  214. header_validated_ = true;
  215. }
  216. #endif
  217. std::optional<base::Value> value = base::JSONReader::Read(header);
  218. if (!value || !value->is_dict()) {
  219. LOG(ERROR) << "Failed to parse header";
  220. return false;
  221. }
  222. header_size_ = 8 + size;
  223. header_ = std::move(*value).TakeDict();
  224. return true;
  225. }
  226. #if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_WIN)
  227. std::optional<IntegrityPayload> Archive::HeaderIntegrity() const {
  228. return std::nullopt;
  229. }
  230. std::optional<base::FilePath> Archive::RelativePath() const {
  231. return std::nullopt;
  232. }
  233. #endif
  234. bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) const {
  235. if (!header_)
  236. return false;
  237. const base::Value::Dict* node =
  238. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  239. if (!node)
  240. return false;
  241. const std::string* link = node->FindString("link");
  242. if (link)
  243. return GetFileInfo(base::FilePath::FromUTF8Unsafe(*link), info);
  244. return FillFileInfoWithNode(info, header_size_, header_validated_, node);
  245. }
  246. bool Archive::Stat(const base::FilePath& path, Stats* stats) const {
  247. if (!header_)
  248. return false;
  249. const base::Value::Dict* node =
  250. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  251. if (!node)
  252. return false;
  253. if (node->Find("link")) {
  254. stats->type = FileType::kLink;
  255. return true;
  256. }
  257. if (node->Find("files")) {
  258. stats->type = FileType::kDirectory;
  259. return true;
  260. }
  261. return FillFileInfoWithNode(stats, header_size_, header_validated_, node);
  262. }
  263. bool Archive::Readdir(const base::FilePath& path,
  264. std::vector<base::FilePath>* files) const {
  265. if (!header_)
  266. return false;
  267. const base::Value::Dict* node =
  268. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  269. if (!node)
  270. return false;
  271. const base::Value::Dict* files_node = GetFilesNode(*header_, *node);
  272. if (!files_node)
  273. return false;
  274. for (const auto iter : *files_node)
  275. files->push_back(base::FilePath::FromUTF8Unsafe(iter.first));
  276. return true;
  277. }
  278. bool Archive::Realpath(const base::FilePath& path,
  279. base::FilePath* realpath) const {
  280. if (!header_)
  281. return false;
  282. const base::Value::Dict* node =
  283. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  284. if (!node)
  285. return false;
  286. const std::string* link = node->FindString("link");
  287. if (link) {
  288. *realpath = base::FilePath::FromUTF8Unsafe(*link);
  289. return true;
  290. }
  291. *realpath = path;
  292. return true;
  293. }
  294. bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
  295. if (!header_)
  296. return false;
  297. base::AutoLock auto_lock(external_files_lock_);
  298. auto it = external_files_.find(path.value());
  299. if (it != external_files_.end()) {
  300. *out = it->second->path();
  301. return true;
  302. }
  303. FileInfo info;
  304. if (!GetFileInfo(path, &info))
  305. return false;
  306. if (info.unpacked) {
  307. *out = path_.AddExtension(FILE_PATH_LITERAL("unpacked")).Append(path);
  308. return true;
  309. }
  310. auto temp_file = std::make_unique<ScopedTemporaryFile>();
  311. base::FilePath::StringType ext = path.Extension();
  312. if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size,
  313. info.integrity))
  314. return false;
  315. #if BUILDFLAG(IS_POSIX)
  316. if (info.executable) {
  317. // chmod a+x temp_file;
  318. base::SetPosixFilePermissions(temp_file->path(), 0755);
  319. }
  320. #endif
  321. *out = temp_file->path();
  322. external_files_[path.value()] = std::move(temp_file);
  323. return true;
  324. }
  325. int Archive::GetUnsafeFD() const {
  326. return fd_;
  327. }
  328. } // namespace asar