// SPDX-License-Identifier: MPL-2.0 // Some parts of the following driver is made using this guide: https://elm-chan.org/docs/fat_e.html #include "vybuild.hpp" #include "common/versions.h" #include "api.h" #include #include using namespace std; typedef int (*crypto_sign_func_ptr)(uint8_t*,size_t*,const uint8_t*,size_t,const uint8_t*); typedef void* (*sha3_512_func_ptr)(const void*,size_t,void*,int); struct common_crypto_struct { crypto_sign_func_ptr crypto_sign; sha3_512_func_ptr sha3_512; }; bool is_crypto_lib_available=false; struct common_crypto_struct common_crypto; uint64_t vftm_target_offset=0; vector vftm_target_content; uint8_t manifest_keypair[CRYPTO_PUBLICKEYBYTES+CRYPTO_SECRETKEYBYTES]={0}; bool vybuild::fat32::is_crypto_lib_loaded() { return is_crypto_lib_available; } bool vybuild::fat32::is_vftm_target_set() { return vftm_target_offset!=0; } uint8_t* vybuild::fat32::get_manifest_keypair_buffer() { return manifest_keypair; } void vybuild::fat32::load_crypto_lib(string path) { void *handle; handle=dlopen(path.c_str(),RTLD_LAZY | RTLD_LOCAL); if (!handle) { throw std::runtime_error("Something went wrong during the loading of libcommoncrypto.so: "+string(dlerror())); } struct common_crypto_struct crypto; crypto.crypto_sign=reinterpret_cast(dlsym(handle,"crypto_sign_signature")); crypto.sha3_512=reinterpret_cast(dlsym(handle,"sha3")); char *error=dlerror(); if (error!=NULL) { dlclose(handle); throw std::runtime_error("Something went wrong during the loading of symbols from libcommoncrypto.so: "+string(error)); } common_crypto=crypto; is_crypto_lib_available=true; return; } #pragma pack(1) struct vystem_fat_trusted_manifest { uint8_t sig[8]={'V','y','F','a','t','S','i','g'}; uint16_t manifest_version=VY_COMMON_VERSIONS_MANIFEST; uint16_t bootloader_version=VY_COMMON_VERSIONS_BOOTLOADER; uint32_t os_version=VY_COMMON_VERSIONS_OS; uint8_t fat_hash[64]; uint8_t fat_build_id[32]; uint64_t clusters_count; uint64_t bytes_per_sector; uint64_t sectors_per_cluster; uint64_t fat_size_bytes; uint64_t fat_offset_bytes; uint64_t bootloader_cluster_count; uint64_t bootloader_cluster_offset; uint64_t bootloader_build_id; uint8_t fat_header_hash[64]; uint8_t bootloader_binary_hash[64]; uint64_t signature_offset_bytes; uint64_t signature_size; uint8_t gpt_disk_guid[16]; uint8_t gpt_efi_part_unique_guid[16]; uint8_t manifest_hash[64]; }; #pragma pack() #pragma pack(1) struct fat32_header { uint8_t BS_JmpBoot[3]={0xeb,0x58,0x90}; uint8_t BS_OEMName[8]={'V','Y','B','U','I','L','D','1'}; uint16_t BPB_BytsPerSec=512; uint8_t BPB_SecPerClus=1; uint16_t BPB_RsvdSecCnt=32; // equivalent to 32 clusters uint8_t BPB_NumFATs=2; uint16_t BPB_RootEntCnt=0; // always 0 for FAT32 uint16_t BPB_TotSec16=0; uint8_t BPB_Media=0xF8; uint16_t BPB_FATSz16=0; uint16_t BPB_SecPerTrk=0; // set to 0 because guide didn't indicate anything uint16_t BPB_NumHeads=0; // also set to 0 for same reason, useless on modern hardware uint32_t BPB_HiddSec=0; // i honestly don't know what value the UEFI firmware expect here, but i pray for it to work uint32_t BPB_TotSec32; // finally a useful field, amount of SECTORS in the whole fs uint32_t BPB_FATSz32; // amount of SECTORS in each FAT uint16_t BPB_ExtFlags=0; // too complicated for now uint16_t BPB_FSVer=0; // apparentaly not only FAT32 is very old and will never be updated, but current version is 0.0 uint32_t BPB_RootClus=2; // root directory cluster uint16_t BPB_FSInfo=1; // this is an offset uint16_t BPB_BkBootSec=6; // this is also an offset uint8_t BPB_Reserved[12]={0}; // i wonder why so much useles space waster into reserved fields uint8_t BS_DrvNum=0x80; // hope this will work on UEFI firmware, i hate retrocompatibility, it lead to useles thing uint8_t BS_Reserved=0; // another useles field uint8_t BS_BootSig=0x29; // another signature uint32_t BS_VolID=0; // the guide want me to put a date and time here, but too bad, i'm lazy uint8_t BS_VolLab[11]={'V','Y','A','U','T','O','F','A','T','3','2'}; // yay, my custom volume label fit! uint8_t BS_FilSysType[8]={'F','A','T','3','2',' ',' ',' '}; // ok why not uint8_t BS_BootCode32[420]={0}; // who the fuck think this is a good idea to put a field so large into a fs header ?! uint16_t BS_Sign=0xAA55; // another signature }; // yeah, exactly 512 bytes #pragma pack() #define ENTRY_FLAG_RO 0x01 #define ENTRY_FLAG_HIDDEN 0x02 #define ENTRY_FLAG_SYSTEM 0x04 #define ENTRY_FLAG_VOL_ID 0x08 #define ENTRY_FLAG_IS_DIR 0x10 #define ENTRY_FLAG_ARCHIVE 0x20 #define ENTRY_FLAG_LFN 0x0F #pragma pack(1) struct dir_entry { char name[11]; uint8_t attributes; uint8_t sfn_flags=0; uint8_t creation_time_tenth=0; uint16_t creation_time=0; uint16_t creation_date=0; uint16_t last_access_date=0; uint16_t upper_part_first_cluster; uint16_t last_time_change=0; uint16_t last_data_change=0; uint16_t lower_part_first_cluster=0; uint32_t file_size=0; }; #pragma pack() #pragma pack(1) struct fat32_fsinfo { uint32_t FSI_LeadSig=0x41615252; uint8_t FSI_Reserved1[480]={0}; // what the actual fuck is this... i think we have a new record uint32_t FSI_StrucSig=0x61417272; uint32_t FSI_Free_Count=0xFFFFFFFF; uint32_t FSI_Nxt_Free=0xFFFFFFFF; uint8_t FSI_Reserved2[12]={0}; uint32_t FSI_TrailSig=0xAA550000; }; #pragma pack() struct fat32_container { fat32_header header; uint64_t fat_start_sector=header.BPB_RsvdSecCnt; uint64_t fat_total_sectors; // complete it with header.BPB_FATSz32 * header.BPB_NumFATs, amount of sectors in all FATs uint64_t data_start_sector; // complete with header.BPB_RsvdSecCnt + fat_total_sectors uint64_t data_total_sectors; // complete with header.BPB_TotSec32 - data_start_sector uint64_t clusters_count; // complete with data_total_sectors / header.BPB_SecPerClus uint64_t root_clusters_sector; vector fat; vector data; fat32_fsinfo fsinfo; }; map fs_map; vector get_urandom(size_t n) { vector out(n); ifstream urandom("/dev/urandom",ios::binary); if (!urandom.is_open()) { throw runtime_error("Couldn't open /dev/urandom."); } urandom.read(reinterpret_cast(out.data()),n); if (urandom.gcount()!=static_cast(n)) { throw runtime_error("Coudln't open /dev/urandom."); } return out; } void read_sector(uint8_t *buf_start_ptr,size_t sector_index,uint8_t* out_buf) { size_t byte_offset=sector_index*512; memcpy(out_buf,buf_start_ptr+byte_offset,512); return; } void write_sector(uint8_t *buf_start_ptr,size_t sector_index,uint8_t* in_buf) { size_t byte_offset=sector_index*512; memcpy(buf_start_ptr+byte_offset,in_buf,512); return; } uint32_t fat_read_entry(fat32_container& cont,uint32_t cluster) { if (cluster>=cont.clusters_count+2) throw std::runtime_error("FAT cluster out of bounds"); size_t offset=cluster*4; uint32_t value; memcpy(&value,cont.fat.data()+offset,4); return value & 0x0FFFFFFF; } void fat_write_entry(fat32_container& cont,uint32_t cluster,uint32_t value) { if (cluster>=cont.clusters_count+2) throw std::runtime_error("FAT cluster out of bounds"); size_t offset=cluster*4; value&=0x0FFFFFFF; memcpy(cont.fat.data()+offset,&value,4); return; } vystem_fat_trusted_manifest generate_manifest(fat32_container& cont,string disk_guid_var,string efi_part_unique_guid_var) { if (!is_crypto_lib_available) throw std::runtime_error("Crypto library isn't available."); if (vftm_target_offset==0) throw std::runtime_error("VFTM target isn't set."); int zero_bytes=0; for (int i=0;i=cont.clusters_count+2) throw std::runtime_error("Directory cluster out of bounds."); dir_entry* entries=reinterpret_cast(cluster_ptr(cont,dir_cluster)); size_t max_entries=(cont.header.BPB_SecPerClus*512)/sizeof(dir_entry); for (size_t i=0;i(part.size(),8)); dir_entry* entries=reinterpret_cast(cluster_ptr(cont,current_cluster)); size_t max_entries=(cont.header.BPB_SecPerClus*512)/sizeof(dir_entry); bool found=false; for (size_t i=0;i(dir_name.size(),8)); memcpy(entry.name+8," ",3); entry.attributes=ENTRY_FLAG_IS_DIR; entry.lower_part_first_cluster=new_cluster&0xFFFF; entry.upper_part_first_cluster=new_cluster>>16; entry.file_size=0; if (!dir_add_entry(cont,parent_cluster,entry)) throw std::runtime_error("Parent directory full."); dir_entry dot{}; memset(dot.name,' ',11); memcpy(dot.name,". ",11); dot.attributes=ENTRY_FLAG_IS_DIR; dot.lower_part_first_cluster=new_cluster&0xFFFF; dot.upper_part_first_cluster=new_cluster>>16; dot.file_size=0; dir_entry dotdot{}; memset(dotdot.name,' ',11); memcpy(dotdot.name,".. ",11); dotdot.attributes=ENTRY_FLAG_IS_DIR; dotdot.lower_part_first_cluster=parent_cluster&0xFFFF; dotdot.upper_part_first_cluster=parent_cluster>>16; dotdot.file_size=0; dir_add_entry(cont,new_cluster,dot); dir_add_entry(cont,new_cluster,dotdot); return new_cluster; } uint32_t create_dir(fat32_container& cont,string& full_path) { if (full_path.empty() || full_path[0]!='/') throw std::runtime_error("Path must be absolute."); size_t last_slash=full_path.find_last_of('/'); if (last_slash==string::npos) throw std::runtime_error("Invalid path."); string parent=(last_slash==0)?"/":full_path.substr(0,last_slash); string dir_name=full_path.substr(last_slash+1); if (dir_name.empty()) throw std::runtime_error("Directory name is empty."); return create_dir_path(cont, parent, dir_name); } void vybuild::fat32::add_directory_fat32(string key,string full_path) { if (fs_map.find(key)==fs_map.end()) throw std::runtime_error(key+" isn't a valid Fat32 image key."); fat32_container& fat=fs_map[key]; create_dir(fat,full_path); return; } uint32_t fat_allocate_chain(fat32_container& cont,uint32_t count) { if (count==0) throw std::runtime_error("Cluster chain size cannot be zero."); uint32_t max_cluster=cont.clusters_count+2; for (uint32_t start=2;start+count<=max_cluster;start++) { bool free=true; for (uint32_t i=0;i12) throw std::runtime_error("File name including extension is too long."); size_t dot=file_name.find_last_of('.'); if (dot==string::npos || dot==0) throw std::runtime_error("Invalid file name."); string name=file_name.substr(0,dot); string ext=file_name.substr(dot+1); if (ext.size()>3 || ext.size()==0) throw std::runtime_error("Invalid extension."); if (name.size()>8 || ext.size()==0) throw std::runtime_error("Invalid file name."); transform(name.begin(),name.end(),name.begin(),::toupper); transform(ext.begin(),ext.end(),ext.begin(),::toupper); uint32_t parent_cluster=path_find_dir_cluster(cont,parent); if (parent_cluster==0) throw std::runtime_error("Parent folder doesn't exist."); uint32_t cluster_size=cont.header.BPB_SecPerClus*512; uint32_t cluster_count=max(1,(file_size+cluster_size-1)/cluster_size); uint32_t start_cluster=fat_allocate_chain(cont,cluster_count); dir_entry entry{}; memset(entry.name,' ',11); memcpy(entry.name,name.c_str(),min(name.size(),8)); memcpy(entry.name+8,ext.data(),min(ext.size(),3)); entry.attributes=ENTRY_FLAG_ARCHIVE; entry.lower_part_first_cluster=start_cluster&0xFFFF; entry.upper_part_first_cluster=start_cluster>>16; entry.file_size=file_size; if (!dir_add_entry(cont,parent_cluster,entry)) throw std::runtime_error("Parent directory full."); memcpy(cluster_ptr(cont,start_cluster),buffer_in,file_size); if (is_vftm_target) { vftm_target_content.resize(cluster_count*cluster_size,0); memcpy(vftm_target_content.data(),buffer_in,file_size); vftm_target_offset=start_cluster; } return start_cluster; } void vybuild::fat32::add_file_fat32(string key,string full_path,uint8_t *buffer_in,size_t file_size,bool is_vftm_target) { if (fs_map.find(key)==fs_map.end()) throw std::runtime_error(key+" isn't a valid Fat32 image key."); fat32_container& fat=fs_map[key]; create_file(fat,full_path,buffer_in,file_size,is_vftm_target); return; } void generate_vftm_sectors(const uint8_t* part_guid,int* s1,int* s2) { uint32_t h=0x811c9dc5; for (int i=0;i<16;i++) h=(h^part_guid[i])*0x01000193; *s1=8+(h%24); *s2=8+(((h>>16)%23+(*s1-7))%24); } void secure_erase(void *address,size_t size) { explicit_bzero(address,size); } vector export_fat32(fat32_container& cont,bool generate_vftm,string disk_guid_var,string efi_part_unique_guid_var) { uint32_t total_sectors=cont.header.BPB_TotSec32; vector img(total_sectors*512,0); auto write_sector=[&](uint32_t sector,const void* data,size_t size) { memcpy(img.data()+sector*512,data,size); }; write_sector(0,&cont.header,sizeof(cont.header)); write_sector(cont.header.BPB_BkBootSec,&cont.header,sizeof(cont.header)); write_sector(1,&cont.fsinfo,sizeof(cont.fsinfo)); write_sector(cont.header.BPB_BkBootSec+1,&cont.fsinfo,sizeof(cont.fsinfo)); uint32_t fat_size_bytes=cont.header.BPB_FATSz32*512; uint32_t fat_sector_start=cont.header.BPB_RsvdSecCnt; memcpy(img.data()+fat_sector_start*512,cont.fat.data(),fat_size_bytes); uint32_t fat2_sector_start=fat_sector_start+cont.header.BPB_FATSz32; memcpy(img.data()+fat2_sector_start*512,cont.fat.data(),fat_size_bytes); uint32_t data_sector_start=cont.data_start_sector; memcpy(img.data()+data_sector_start*512,cont.data.data(),cont.data.size()); if (generate_vftm) { auto efi_part_unique_guid=vybuild::vars::get_global_variable("\\$"+efi_part_unique_guid_var+"\\$"); auto efi_part_unique_guid_bytes=vybuild::disk::uuid4_to_bytes(efi_part_unique_guid); auto vftm=generate_manifest(cont,disk_guid_var,efi_part_unique_guid_var); int manifest_sector,pk_manifest_sector; generate_vftm_sectors((uint8_t*)efi_part_unique_guid_bytes.data(),&manifest_sector,&pk_manifest_sector); uint8_t signature_manifest[CRYPTO_BYTES]; size_t siglen; if (common_crypto.crypto_sign(signature_manifest,&siglen,(uint8_t*)&vftm,sizeof(vftm),manifest_keypair+CRYPTO_PUBLICKEYBYTES)) { secure_erase(manifest_keypair,sizeof(manifest_keypair)); throw std::runtime_error("Couldn't sign VFTM."); } if (siglen!=CRYPTO_BYTES) { secure_erase(manifest_keypair,sizeof(manifest_keypair)); throw std::runtime_error("The returned signature isn't the expected size."); } secure_erase(manifest_keypair+CRYPTO_PUBLICKEYBYTES,CRYPTO_SECRETKEYBYTES); memcpy(img.data()+manifest_sector*cont.header.BPB_BytsPerSec,&vftm,sizeof(vftm)); memcpy(img.data()+pk_manifest_sector*cont.header.BPB_BytsPerSec,manifest_keypair,CRYPTO_PUBLICKEYBYTES); img.resize(img.size()+CRYPTO_BYTES); memcpy(img.data()+vftm.signature_offset_bytes,signature_manifest,CRYPTO_BYTES); } return img; } vector vybuild::fat32::export_fat32(string key,bool generate_vftm,string disk_guid_var,string efi_part_unique_guid_var) { if (fs_map.find(key)==fs_map.end()) throw std::runtime_error(key+" isn't a valid Fat32 image key."); fat32_container& fat=fs_map[key]; return export_fat32(fat,generate_vftm,disk_guid_var,efi_part_unique_guid_var); }