archive.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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 <utility>
  7. #include <vector>
  8. #include "base/check.h"
  9. #include "base/files/file.h"
  10. #include "base/files/file_util.h"
  11. #include "base/json/json_reader.h"
  12. #include "base/logging.h"
  13. #include "base/pickle.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "base/values.h"
  16. #include "electron/fuses.h"
  17. #include "shell/common/asar/asar_util.h"
  18. #include "shell/common/asar/scoped_temporary_file.h"
  19. #include "shell/common/thread_restrictions.h"
  20. #if BUILDFLAG(IS_WIN)
  21. #include <io.h>
  22. #endif
  23. namespace asar {
  24. namespace {
  25. #if BUILDFLAG(IS_WIN)
  26. const char kSeparators[] = "\\/";
  27. #else
  28. const char kSeparators[] = "/";
  29. #endif
  30. const base::Value::Dict* GetNodeFromPath(std::string path,
  31. const base::Value::Dict& root);
  32. // Gets the "files" from "dir".
  33. const base::Value::Dict* GetFilesNode(const base::Value::Dict& root,
  34. const base::Value::Dict& dir) {
  35. // Test for symbol linked directory.
  36. const std::string* link = dir.FindString("link");
  37. if (link != nullptr) {
  38. const base::Value::Dict* linked_node = GetNodeFromPath(*link, root);
  39. if (!linked_node)
  40. return nullptr;
  41. return linked_node->FindDict("files");
  42. }
  43. return dir.FindDict("files");
  44. }
  45. // Gets sub-file "name" from "dir".
  46. const base::Value::Dict* GetChildNode(const base::Value::Dict& root,
  47. const std::string& name,
  48. const base::Value::Dict& dir) {
  49. if (name.empty())
  50. return &root;
  51. const base::Value::Dict* files = GetFilesNode(root, dir);
  52. return files ? files->FindDict(name) : nullptr;
  53. }
  54. // Gets the node of "path" from "root".
  55. const base::Value::Dict* GetNodeFromPath(std::string path,
  56. const base::Value::Dict& root) {
  57. if (path.empty())
  58. return &root;
  59. const base::Value::Dict* dir = &root;
  60. for (size_t delimiter_position = path.find_first_of(kSeparators);
  61. delimiter_position != std::string::npos;
  62. delimiter_position = path.find_first_of(kSeparators)) {
  63. const base::Value::Dict* child =
  64. GetChildNode(root, path.substr(0, delimiter_position), *dir);
  65. if (!child)
  66. return nullptr;
  67. dir = child;
  68. path.erase(0, delimiter_position + 1);
  69. }
  70. return GetChildNode(root, path, *dir);
  71. }
  72. bool FillFileInfoWithNode(Archive::FileInfo* info,
  73. uint32_t header_size,
  74. bool load_integrity,
  75. const base::Value::Dict* node) {
  76. if (absl::optional<int> size = node->FindInt("size")) {
  77. info->size = static_cast<uint32_t>(*size);
  78. } else {
  79. return false;
  80. }
  81. if (absl::optional<bool> unpacked = node->FindBool("unpacked")) {
  82. info->unpacked = *unpacked;
  83. if (info->unpacked) {
  84. return true;
  85. }
  86. }
  87. const std::string* offset = node->FindString("offset");
  88. if (offset &&
  89. base::StringToUint64(base::StringPiece(*offset), &info->offset)) {
  90. info->offset += header_size;
  91. } else {
  92. return false;
  93. }
  94. if (absl::optional<bool> executable = node->FindBool("executable")) {
  95. info->executable = *executable;
  96. }
  97. #if BUILDFLAG(IS_MAC)
  98. if (load_integrity &&
  99. electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled()) {
  100. if (const base::Value::Dict* integrity = node->FindDict("integrity")) {
  101. const std::string* algorithm = integrity->FindString("algorithm");
  102. const std::string* hash = integrity->FindString("hash");
  103. absl::optional<int> block_size = integrity->FindInt("blockSize");
  104. const base::Value::List* blocks = integrity->FindList("blocks");
  105. if (algorithm && hash && block_size && block_size > 0 && blocks) {
  106. IntegrityPayload integrity_payload;
  107. integrity_payload.hash = *hash;
  108. integrity_payload.block_size =
  109. static_cast<uint32_t>(block_size.value());
  110. for (auto& value : *blocks) {
  111. if (const std::string* block = value.GetIfString()) {
  112. integrity_payload.blocks.push_back(*block);
  113. } else {
  114. LOG(FATAL)
  115. << "Invalid block integrity value for file in ASAR archive";
  116. }
  117. }
  118. if (*algorithm == "SHA256") {
  119. integrity_payload.algorithm = HashAlgorithm::SHA256;
  120. info->integrity = std::move(integrity_payload);
  121. }
  122. }
  123. }
  124. if (!info->integrity.has_value()) {
  125. LOG(FATAL) << "Failed to read integrity for file in ASAR archive";
  126. return false;
  127. }
  128. }
  129. #endif
  130. return true;
  131. }
  132. } // namespace
  133. IntegrityPayload::IntegrityPayload()
  134. : algorithm(HashAlgorithm::NONE), 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(buf.data(), buf.size()))
  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(buf.data(), buf.size()))
  200. .ReadString(&header)) {
  201. LOG(ERROR) << "Failed to parse header from " << path_.value();
  202. return false;
  203. }
  204. #if BUILDFLAG(IS_MAC)
  205. // Validate header signature if required and possible
  206. if (electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled() &&
  207. RelativePath().has_value()) {
  208. absl::optional<IntegrityPayload> integrity = HeaderIntegrity();
  209. if (!integrity.has_value()) {
  210. LOG(FATAL) << "Failed to get integrity for validatable asar archive: "
  211. << RelativePath().value();
  212. return false;
  213. }
  214. // Currently we only support the sha256 algorithm, we can add support for
  215. // more below ensure we read them in preference order from most secure to
  216. // least
  217. if (integrity.value().algorithm != HashAlgorithm::NONE) {
  218. ValidateIntegrityOrDie(header.c_str(), header.length(),
  219. integrity.value());
  220. } else {
  221. LOG(FATAL) << "No eligible hash for validatable asar archive: "
  222. << RelativePath().value();
  223. }
  224. header_validated_ = true;
  225. }
  226. #endif
  227. absl::optional<base::Value> value = base::JSONReader::Read(header);
  228. if (!value || !value->is_dict()) {
  229. LOG(ERROR) << "Failed to parse header";
  230. return false;
  231. }
  232. header_size_ = 8 + size;
  233. header_ = std::move(value->GetDict());
  234. return true;
  235. }
  236. #if !BUILDFLAG(IS_MAC)
  237. absl::optional<IntegrityPayload> Archive::HeaderIntegrity() const {
  238. return absl::nullopt;
  239. }
  240. absl::optional<base::FilePath> Archive::RelativePath() const {
  241. return absl::nullopt;
  242. }
  243. #endif
  244. bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) const {
  245. if (!header_)
  246. return false;
  247. const base::Value::Dict* node =
  248. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  249. if (!node)
  250. return false;
  251. const std::string* link = node->FindString("link");
  252. if (link)
  253. return GetFileInfo(base::FilePath::FromUTF8Unsafe(*link), info);
  254. return FillFileInfoWithNode(info, header_size_, header_validated_, node);
  255. }
  256. bool Archive::Stat(const base::FilePath& path, Stats* stats) const {
  257. if (!header_)
  258. return false;
  259. const base::Value::Dict* node =
  260. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  261. if (!node)
  262. return false;
  263. if (node->Find("link")) {
  264. stats->is_file = false;
  265. stats->is_link = true;
  266. return true;
  267. }
  268. if (node->Find("files")) {
  269. stats->is_file = false;
  270. stats->is_directory = true;
  271. return true;
  272. }
  273. return FillFileInfoWithNode(stats, header_size_, header_validated_, node);
  274. }
  275. bool Archive::Readdir(const base::FilePath& path,
  276. std::vector<base::FilePath>* files) const {
  277. if (!header_)
  278. return false;
  279. const base::Value::Dict* node =
  280. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  281. if (!node)
  282. return false;
  283. const base::Value::Dict* files_node = GetFilesNode(*header_, *node);
  284. if (!files_node)
  285. return false;
  286. for (const auto iter : *files_node)
  287. files->push_back(base::FilePath::FromUTF8Unsafe(iter.first));
  288. return true;
  289. }
  290. bool Archive::Realpath(const base::FilePath& path,
  291. base::FilePath* realpath) const {
  292. if (!header_)
  293. return false;
  294. const base::Value::Dict* node =
  295. GetNodeFromPath(path.AsUTF8Unsafe(), *header_);
  296. if (!node)
  297. return false;
  298. const std::string* link = node->FindString("link");
  299. if (link) {
  300. *realpath = base::FilePath::FromUTF8Unsafe(*link);
  301. return true;
  302. }
  303. *realpath = path;
  304. return true;
  305. }
  306. bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
  307. if (!header_)
  308. return false;
  309. base::AutoLock auto_lock(external_files_lock_);
  310. auto it = external_files_.find(path.value());
  311. if (it != external_files_.end()) {
  312. *out = it->second->path();
  313. return true;
  314. }
  315. FileInfo info;
  316. if (!GetFileInfo(path, &info))
  317. return false;
  318. if (info.unpacked) {
  319. *out = path_.AddExtension(FILE_PATH_LITERAL("unpacked")).Append(path);
  320. return true;
  321. }
  322. auto temp_file = std::make_unique<ScopedTemporaryFile>();
  323. base::FilePath::StringType ext = path.Extension();
  324. if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size,
  325. info.integrity))
  326. return false;
  327. #if BUILDFLAG(IS_POSIX)
  328. if (info.executable) {
  329. // chmod a+x temp_file;
  330. base::SetPosixFilePermissions(temp_file->path(), 0755);
  331. }
  332. #endif
  333. *out = temp_file->path();
  334. external_files_[path.value()] = std::move(temp_file);
  335. return true;
  336. }
  337. int Archive::GetUnsafeFD() const {
  338. return fd_;
  339. }
  340. } // namespace asar