summaryrefslogblamecommitdiffstats
path: root/applypatch/applypatch.cpp
blob: 90d8e86048748c73a4c6a61a06340f9047c6a5ab (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                                                           

                                  
                  
                  




                     
                      

                   
                    
                     

                 

                  
 
                              
                                 
                                  
                                 
                                   
                        
 
                       
                          
                               
 



                                                                                    
 
                                                                        
                                                           
                                                     
                 
   
 


                                                           
                 
   
 
                                                                    
                                                         
              

 






                                                                                

   
                                                                       
                  
                                                                           









                                                                                          
       
     

   


                                                                        
   
 
                                                                


                                                                 
 

                                                                                         

 
                                                                              

                                                                                        
                 
                                                                     
                 
   
 

                                                                                               
                 
   

                       
                                                            
                 
   

                                 
                                                            
                 
   
 
              

 



                                                                                                   

                       
                                                    
                                                                      

                                                              
                   

     
                                                               
                                                                                    
                   
     
 

                                                                                              
                   
     
 
                         
                                                              
                   
     
                                   
                                                               
                   
     
 
                                                     
                   
                                                                                 
                   
     
 

                                                                                       


                                                               
            
                                      
     

             
              
                                                           
                                                            
                   









                                                      

                                                                            
                     
       
 
                                                   
                                                              


                  

     
                       
                                                                                 

                     
     
 
                                   
                                                     
                   
     
   
 
                 
                                                        
                 
   
 
         
 
              
 
 

                                                        
















                                                         
     


                             

 




                                                                            


                    

                      

 


                                                                                         
 






                                                                                                  
   
 
                           

                                                             
   
 


                                           
 

                                                                                     
 






                                                                                                  
   
 
                           
                                                         

                                               
   
 



                                                                             
   
 





                                                                                         
   


                                                        
   

              
 





                                                                                    
   
 
                                        
                                        
                 
   
 

                                               





                                                                            
                                              
                 
   
 

                                                                                       
                                                    
                 
   
                                                                          
                                                  
                 
   
 
                                           
                       

                  
                                                                         
                                 
                                                              

               
 

                   
                                                                                                
          
            
                                                                                                   
   
 
                    
                                                          
                 
   
 


                                                                                                 
 
                                                                                                 

                                                                        

                                            

                                                                                               
 






                                                                                              
 
                 

   

                                                     
                                               


                                                                    
   
 
                                          
                                                   

             

































                                                                            
 
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "applypatch/applypatch.h"

#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <openssl/sha.h>

#include "edify/expr.h"
#include "otautil/paths.h"
#include "otautil/print_sha1.h"

using namespace std::string_literals;

static bool GenerateTarget(const Partition& target, const FileContents& source_file,
                           const Value& patch, const Value* bonus_data);

bool LoadFileContents(const std::string& filename, FileContents* file) {
  // No longer allow loading contents from eMMC partitions.
  if (android::base::StartsWith(filename, "EMMC:")) {
    return false;
  }

  std::string data;
  if (!android::base::ReadFileToString(filename, &data)) {
    PLOG(ERROR) << "Failed to read \"" << filename << "\"";
    return false;
  }

  file->data = std::vector<unsigned char>(data.begin(), data.end());
  SHA1(file->data.data(), file->data.size(), file->sha1);
  return true;
}

// Reads the contents of a Partition to the given FileContents buffer.
static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out,
                                  bool check_backup) {
  uint8_t expected_sha1[SHA_DIGEST_LENGTH];
  if (ParseSha1(partition.hash, expected_sha1) != 0) {
    LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\"";
    return false;
  }

  android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY));
  if (dev == -1) {
    PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\"";
  } else {
    std::vector<unsigned char> buffer(partition.size);
    if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) {
      PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition "
                  << partition;
    } else {
      SHA1(buffer.data(), buffer.size(), out->sha1);
      if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
        out->data = std::move(buffer);
        return true;
      }
    }
  }

  if (!check_backup) {
    LOG(ERROR) << "Partition contents don't have the expected checksum";
    return false;
  }

  if (LoadFileContents(Paths::Get().cache_temp_source(), out) &&
      memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
    return true;
  }

  LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum";
  return false;
}

bool SaveFileContents(const std::string& filename, const FileContents* file) {
  android::base::unique_fd fd(
      open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
  if (fd == -1) {
    PLOG(ERROR) << "Failed to open \"" << filename << "\" for write";
    return false;
  }

  if (!android::base::WriteFully(fd, file->data.data(), file->data.size())) {
    PLOG(ERROR) << "Failed to write " << file->data.size() << " bytes of data to " << filename;
    return false;
  }

  if (fsync(fd) != 0) {
    PLOG(ERROR) << "Failed to fsync \"" << filename << "\"";
    return false;
  }

  if (close(fd.release()) != 0) {
    PLOG(ERROR) << "Failed to close \"" << filename << "\"";
    return false;
  }

  return true;
}

// Writes a memory buffer to 'target' Partition.
static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) {
  const unsigned char* data = file_contents.data.data();
  size_t len = file_contents.data.size();
  size_t start = 0;
  bool success = false;
  for (size_t attempt = 0; attempt < 2; ++attempt) {
    android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR));
    if (fd == -1) {
      PLOG(ERROR) << "Failed to open \"" << partition << "\"";
      return false;
    }

    if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
      PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\"";
      return false;
    }

    if (!android::base::WriteFully(fd, data + start, len - start)) {
      PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\"";
      return false;
    }

    if (fsync(fd) != 0) {
      PLOG(ERROR) << "Failed to sync \"" << partition << "\"";
      return false;
    }
    if (close(fd.release()) != 0) {
      PLOG(ERROR) << "Failed to close \"" << partition << "\"";
      return false;
    }

    fd.reset(open(partition.name.c_str(), O_RDONLY));
    if (fd == -1) {
      PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification";
      return false;
    }

    // Drop caches so our subsequent verification read won't just be reading the cache.
    sync();
    std::string drop_cache = "/proc/sys/vm/drop_caches";
    if (!android::base::WriteStringToFile("3\n", drop_cache)) {
      PLOG(ERROR) << "Failed to write to " << drop_cache;
    } else {
      LOG(INFO) << "  caches dropped";
    }
    sleep(1);

    // Verify.
    if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
      PLOG(ERROR) << "Failed to seek to 0 on " << partition;
      return false;
    }

    unsigned char buffer[4096];
    start = len;
    for (size_t p = 0; p < len; p += sizeof(buffer)) {
      size_t to_read = len - p;
      if (to_read > sizeof(buffer)) {
        to_read = sizeof(buffer);
      }

      if (!android::base::ReadFully(fd, buffer, to_read)) {
        PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p;
        return false;
      }

      if (memcmp(buffer, data + p, to_read) != 0) {
        LOG(ERROR) << "Verification failed starting at " << p;
        start = p;
        break;
      }
    }

    if (start == len) {
      LOG(INFO) << "Verification read succeeded (attempt " << attempt + 1 << ")";
      success = true;
      break;
    }

    if (close(fd.release()) != 0) {
      PLOG(ERROR) << "Failed to close " << partition;
      return false;
    }
  }

  if (!success) {
    LOG(ERROR) << "Failed to verify after all attempts";
    return false;
  }

  sync();

  return true;
}

int ParseSha1(const std::string& str, uint8_t* digest) {
  const char* ps = str.c_str();
  uint8_t* pd = digest;
  for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) {
    int digit;
    if (*ps >= '0' && *ps <= '9') {
      digit = *ps - '0';
    } else if (*ps >= 'a' && *ps <= 'f') {
      digit = *ps - 'a' + 10;
    } else if (*ps >= 'A' && *ps <= 'F') {
      digit = *ps - 'A' + 10;
    } else {
      return -1;
    }
    if (i % 2 == 0) {
      *pd = digit << 4;
    } else {
      *pd |= digit;
      ++pd;
    }
  }
  if (*ps != '\0') return -1;
  return 0;
}

bool PatchPartitionCheck(const Partition& target, const Partition& source) {
  FileContents target_file;
  FileContents source_file;
  return (ReadPartitionToBuffer(target, &target_file, false) ||
          ReadPartitionToBuffer(source, &source_file, true));
}

int ShowLicenses() {
  ShowBSDiffLicense();
  return 0;
}

bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
                    const Value* bonus) {
  LOG(INFO) << "Patching " << target.name;

  // We try to load and check against the target hash first.
  FileContents target_file;
  if (ReadPartitionToBuffer(target, &target_file, false)) {
    // The early-exit case: the patch was already applied, this file has the desired hash, nothing
    // for us to do.
    LOG(INFO) << "  already " << target.hash.substr(0, 8);
    return true;
  }

  FileContents source_file;
  if (ReadPartitionToBuffer(source, &source_file, true)) {
    return GenerateTarget(target, source_file, patch, bonus);
  }

  LOG(ERROR) << "Failed to find any match";
  return false;
}

bool FlashPartition(const Partition& partition, const std::string& source_filename) {
  LOG(INFO) << "Flashing " << partition;

  // We try to load and check against the target hash first.
  FileContents target_file;
  if (ReadPartitionToBuffer(partition, &target_file, false)) {
    // The early-exit case: the patch was already applied, this file has the desired hash, nothing
    // for us to do.
    LOG(INFO) << "  already " << partition.hash.substr(0, 8);
    return true;
  }

  FileContents source_file;
  if (!LoadFileContents(source_filename, &source_file)) {
    LOG(ERROR) << "Failed to load source file";
    return false;
  }

  uint8_t expected_sha1[SHA_DIGEST_LENGTH];
  if (ParseSha1(partition.hash, expected_sha1) != 0) {
    LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\"";
    return false;
  }

  if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
    // The source doesn't have desired checksum.
    LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum";
    LOG(ERROR) << "expected: " << partition.hash.substr(0, 8)
               << ", found: " << short_sha1(source_file.sha1);
    return false;
  }
  if (!WriteBufferToPartition(source_file, partition)) {
    LOG(ERROR) << "Failed to write to " << partition;
    return false;
  }
  return true;
}

static bool GenerateTarget(const Partition& target, const FileContents& source_file,
                           const Value& patch, const Value* bonus_data) {
  uint8_t expected_sha1[SHA_DIGEST_LENGTH];
  if (ParseSha1(target.hash, expected_sha1) != 0) {
    LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\"";
    return false;
  }

  if (patch.type != Value::Type::BLOB) {
    LOG(ERROR) << "patch is not a blob";
    return false;
  }

  const char* header = patch.data.data();
  size_t header_bytes_read = patch.data.size();
  bool use_bsdiff = false;
  if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
    use_bsdiff = true;
  } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
    use_bsdiff = false;
  } else {
    LOG(ERROR) << "Unknown patch file format";
    return false;
  }

  // We write the original source to cache, in case the partition write is interrupted.
  if (!CheckAndFreeSpaceOnCache(source_file.data.size())) {
    LOG(ERROR) << "Not enough free space on /cache";
    return false;
  }
  if (!SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
    LOG(ERROR) << "Failed to back up source file";
    return false;
  }

  // We store the decoded output in memory.
  FileContents patched;
  SHA_CTX ctx;
  SHA1_Init(&ctx);
  SinkFn sink = [&patched, &ctx](const unsigned char* data, size_t len) {
    SHA1_Update(&ctx, data, len);
    patched.data.insert(patched.data.end(), data, data + len);
    return len;
  };

  int result;
  if (use_bsdiff) {
    result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch, 0, sink);
  } else {
    result =
        ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch, sink, bonus_data);
  }

  if (result != 0) {
    LOG(ERROR) << "Failed to apply the patch: " << result;
    return false;
  }

  SHA1_Final(patched.sha1, &ctx);
  if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
    LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1);

    LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1);
    LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 "
               << short_sha1(source_file.sha1);

    uint8_t patch_digest[SHA_DIGEST_LENGTH];
    SHA1(reinterpret_cast<const uint8_t*>(patch.data.data()), patch.data.size(), patch_digest);
    LOG(ERROR) << "patch size " << patch.data.size() << " SHA-1 " << short_sha1(patch_digest);

    if (bonus_data != nullptr) {
      uint8_t bonus_digest[SHA_DIGEST_LENGTH];
      SHA1(reinterpret_cast<const uint8_t*>(bonus_data->data.data()), bonus_data->data.size(),
           bonus_digest);
      LOG(ERROR) << "bonus size " << bonus_data->data.size() << " SHA-1 "
                 << short_sha1(bonus_digest);
    }

    return false;
  }

  LOG(INFO) << "  now " << short_sha1(expected_sha1);

  // Write back the temp file to the partition.
  if (!WriteBufferToPartition(patched, target)) {
    LOG(ERROR) << "Failed to write patched data to " << target.name;
    return false;
  }

  // Delete the backup copy of the source.
  unlink(Paths::Get().cache_temp_source().c_str());

  // Success!
  return true;
}

bool CheckPartition(const Partition& partition) {
  FileContents target_file;
  return ReadPartitionToBuffer(partition, &target_file, false);
}

Partition Partition::Parse(const std::string& input_str, std::string* err) {
  std::vector<std::string> pieces = android::base::Split(input_str, ":");
  if (pieces.size() != 4 || pieces[0] != "EMMC") {
    *err = "Invalid number of tokens or non-eMMC target";
    return {};
  }

  size_t size;
  if (!android::base::ParseUint(pieces[2], &size) || size == 0) {
    *err = "Failed to parse \"" + pieces[2] + "\" as byte count";
    return {};
  }

  return Partition(pieces[1], size, pieces[3]);
}

std::string Partition::ToString() const {
  if (*this) {
    return "EMMC:"s + name + ":" + std::to_string(size) + ":" + hash;
  }
  return "<invalid-partition>";
}

std::ostream& operator<<(std::ostream& os, const Partition& partition) {
  os << partition.ToString();
  return os;
}