Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/crutchcorn/binarrow. An experimental browser runtime for AArch64 Linux ELF executables
Something went wrong. Try again.
246 kB · 6511 lines
Rust
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512//! Bounded Linux syscall dispatch for a loaded `AArch64` process.
use core::fmt;use std::collections::{BTreeMap, VecDeque};
use binarrow_aarch64::{ Aarch64State, BlockExecutor, BlockProfile, Interpreter, InterpreterInitializationError, TieredExecutionMetrics,};use binarrow_guest_memory::{AddressSpace, Permissions, RegionKind};use binarrow_host_api::{ ClosedInput, DeterministicSystem, FileAccess, FileMetadata, FileOpenFlags, FileOpenOptions, FileSeekFrom, FileSystemError, FileType, HostClock, HostFileSystem, HostInput, HostSystem, HostTerminal, HostTime, NullFileSystem, TerminalInputRead, TerminalStream,};use binarrow_linux_abi::{Errno, Syscall};use binarrow_loader::{ Credentials, LoaderError, ProcessConfig, ProcessImage, ProcessParameters, load_process, load_process_with_interpreter,};use binarrow_runtime_core::{GuestAddress, ResourceLimit, ResourceLimits, Trap};
const STANDARD_INPUT: u64 = 0;const STANDARD_OUTPUT: u64 = 1;const STANDARD_ERROR: u64 = 2;const INITIAL_PROCESS_ID: u64 = 1;#[cfg(test)]const CHILD_PROCESS_ID: u64 = 2;const DORMANT_THREAD_ID: u64 = 10_001;const ACTIVE_THREAD_ID: u64 = 10_002;const NESTED_HELPER_THREAD_ID: u64 = 10_003;const MAX_NESTED_HELPERS: usize = 4;const MAX_PROCESS_DEPTH: usize = 8;const SIGNAL_ABORT: u64 = 6;const SIGNAL_USER_1: u64 = 10;const FIRST_FILE_DESCRIPTOR: u32 = 3;const AT_FDCWD: u64 = (-100_i64).cast_unsigned();const MAX_PATH_BYTES: usize = 4096;const MAX_EXEC_VECTOR_ENTRIES: usize = 4096;const MAX_EXEC_VECTOR_BYTES: usize = 1024 * 1024;const OPEN_ACCESS_MASK: u64 = 3;const OPEN_CREATE: u64 = 0x40;const OPEN_EXCLUSIVE: u64 = 0x80;const OPEN_NOCTTY: u64 = 0x100;const OPEN_TRUNCATE: u64 = 0x200;const OPEN_APPEND: u64 = 0x400;const OPEN_DIRECTORY: u64 = 0x4000;const OPEN_LARGEFILE: u64 = 0x8000;const OPEN_DIRECTORY_FIXTURE_COMPAT: u64 = 0x1_0000;const OPEN_NOFOLLOW: u64 = 0x2_0000;const OPEN_CLOEXEC: u64 = 0x8_0000;const OPEN_NONBLOCK: u64 = 0x800;const OPEN_PATH: u64 = 0x20_0000;const FCNTL_GET_DESCRIPTOR_FLAGS: u64 = 1;const FCNTL_SET_DESCRIPTOR_FLAGS: u64 = 2;const FCNTL_GET_STATUS_FLAGS: u64 = 3;const FCNTL_SET_STATUS_FLAGS: u64 = 4;const FCNTL_DUPLICATE: u64 = 0;const FCNTL_DUPLICATE_CLOEXEC: u64 = 1030;const DESCRIPTOR_CLOEXEC: u64 = 1;const FILE_LOCK_SHARED: u64 = 1;const FILE_LOCK_EXCLUSIVE: u64 = 2;const FILE_LOCK_NONBLOCKING: u64 = 4;const FILE_LOCK_UNLOCK: u64 = 8;const IOCTL_CLEAR_CLOSE_ON_EXEC: u64 = 0x5450;const IOCTL_SET_CLOSE_ON_EXEC: u64 = 0x5451;const IOCTL_SET_NONBLOCK: u64 = 0x5421;const SUPPORTED_OPEN_FLAGS: u64 = OPEN_ACCESS_MASK | OPEN_CREATE | OPEN_EXCLUSIVE | OPEN_NOCTTY | OPEN_TRUNCATE | OPEN_APPEND | OPEN_DIRECTORY | OPEN_LARGEFILE | OPEN_DIRECTORY_FIXTURE_COMPAT | OPEN_NOFOLLOW | OPEN_CLOEXEC | OPEN_PATH;const AT_REMOVE_DIRECTORY: u64 = 0x200;const AT_SYMLINK_NOFOLLOW: u64 = 0x100;const UTIME_NOW: i64 = 0x3fff_ffff;const UTIME_OMIT: i64 = 0x3fff_fffe;const STAT_SIZE: usize = 128;const STAT_MODE_OFFSET: usize = 16;const STAT_LINK_COUNT_OFFSET: usize = 20;const STAT_FILE_SIZE_OFFSET: usize = 48;const STAT_BLOCK_SIZE_OFFSET: usize = 56;const STAT_BLOCK_COUNT_OFFSET: usize = 64;const STAT_ACCESS_SECONDS_OFFSET: usize = 72;const STAT_ACCESS_NANOSECONDS_OFFSET: usize = 80;const STAT_MODIFIED_SECONDS_OFFSET: usize = 88;const STAT_MODIFIED_NANOSECONDS_OFFSET: usize = 96;const STAT_CHANGED_SECONDS_OFFSET: usize = 104;const STAT_CHANGED_NANOSECONDS_OFFSET: usize = 112;const STAT_REGULAR_MODE: u32 = 0o100_644;const STAT_DIRECTORY_MODE: u32 = 0o040_755;const STAT_CHARACTER_MODE: u32 = 0o020_620;const STAT_FIFO_MODE: u32 = 0o010_600;const DIRECTORY_ENTRY_HEADER_SIZE: usize = 19;const DIRECTORY_ENTRY_ALIGNMENT: usize = 8;const DIRECTORY_TYPE: u8 = 4;const REGULAR_FILE_TYPE: u8 = 8;const CLOCK_REALTIME: u64 = 0;const CLOCK_MONOTONIC: u64 = 1;const CLOCK_MONOTONIC_RAW: u64 = 4;const GETRANDOM_ALLOWED_FLAGS: u64 = 3;const LINUX_SIGNAL_COUNT: usize = 64;const KERNEL_SIGACTION_SIZE: usize = 32;const KERNEL_SIGNAL_SET_SIZE: u64 = 8;const SIGNAL_STACK_SIZE: usize = 24;const SIGNAL_STACK_DISABLED: u32 = 2;const PAGE_SIZE: u64 = 4096;const MMAP_ARENA_START: u64 = 0x4000_0000;const PROT_READ: u64 = 1;const PROT_WRITE: u64 = 2;const PROT_EXECUTE: u64 = 4;const MAP_PRIVATE: u64 = 2;const MAP_FIXED: u64 = 0x10;const MAP_ANONYMOUS: u64 = 0x20;const MAP_STACK: u64 = 0x2_0000;const SIG_BLOCK: u64 = 0;const SIG_UNBLOCK: u64 = 1;const SIG_SETMASK: u64 = 2;const SIGILL: u64 = 4;const SIGNAL_DEFAULT: u64 = 0;const SIGNAL_IGNORE: u64 = 1;const SIGNAL_NODEFER: u64 = 0x4000_0000;const SYNCHRONOUS_SIGNAL_FRAME_SIZE: u64 = 4096;const SIGNAL_INFO_SIZE: u64 = 128;const SIGKILL_BIT: u64 = 1 << (9 - 1);const SIGSTOP_BIT: u64 = 1 << (19 - 1);const CPU_AFFINITY_BYTES: u64 = 8;const INITIAL_CURRENT_DIRECTORY: &[u8] = b"/project";const PIPE_CAPACITY_BYTES: usize = 64 * 1024;const MAX_POLL_DESCRIPTORS: u64 = 1024;const POLL_INPUT: u16 = 0x0001;const POLL_OUTPUT: u16 = 0x0004;const POLL_ERROR: u16 = 0x0008;const POLL_HANGUP: u16 = 0x0010;const POLL_INVALID: u16 = 0x0020;const FILE_COPY_CHUNK_BYTES: usize = 1024 * 1024;const SUPPORTED_PIPE_FLAGS: u64 = OPEN_CLOEXEC | OPEN_NONBLOCK;const ADDRESS_FAMILY_UNIX: u64 = 1;const SOCKET_TYPE_MASK: u64 = 0xf;const SOCKET_STREAM: u64 = 1;const SOCKET_SEQPACKET: u64 = 5;const MESSAGE_PEEK: u64 = 2;const MESSAGE_DONTWAIT: u64 = 0x40;const CLONE_VM: u64 = 0x100;const CLONE_VFORK: u64 = 0x4000;const SIGCHLD: u64 = 17;const SUPPORTED_CLONE_FLAGS: u64 = CLONE_VM | CLONE_VFORK | SIGCHLD;const SUPPORTED_FORK_FLAGS: u64 = SIGCHLD;const DORMANT_THREAD_CLONE_FLAGS: u64 = 0x7d_0f00;const FUTEX_COMMAND_MASK: u64 = 0x7f;const FUTEX_PRIVATE_FLAG: u64 = 0x80;const FUTEX_WAIT: u64 = 0;const FUTEX_WAKE: u64 = 1;const FUTEX_WAIT_BITSET: u64 = 9;const FUTEX_WAKE_BITSET: u64 = 10;const RUSAGE_SIZE: usize = 144;
/// Failure while resolving an executable's optional guest-side interpreter.#[derive(Clone, Debug, Eq, PartialEq)]pub enum ProcessLoadError { Loader(LoaderError), InterpreterFile { path: Vec<u8>, error: FileSystemError, }, InterpreterNotRegular(Vec<u8>), InterpreterTooLarge { path: Vec<u8>, size: u64, limit: u64, }, InterpreterShortRead(Vec<u8>),}
impl fmt::Display for ProcessLoadError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Loader(error) => write!(formatter, "{error}"), Self::InterpreterFile { path, error } => write!( formatter, "could not read guest interpreter {}: {error:?}", String::from_utf8_lossy(path) ), Self::InterpreterNotRegular(path) => write!( formatter, "guest interpreter {} is not a regular file", String::from_utf8_lossy(path) ), Self::InterpreterTooLarge { path, size, limit } => write!( formatter, "guest interpreter {} is {size} bytes, exceeding limit {limit}", String::from_utf8_lossy(path) ), Self::InterpreterShortRead(path) => write!( formatter, "guest interpreter {} ended before its declared size", String::from_utf8_lossy(path) ), } }}
impl std::error::Error for ProcessLoadError {}
impl From<LoaderError> for ProcessLoadError { fn from(error: LoaderError) -> Self { Self::Loader(error) }}
/// Load an executable, resolving `PT_INTERP` from the supplied guest filesystem.////// # Errors////// Returns [`ProcessLoadError`] for invalid ELF/process metadata or when the/// requested guest interpreter cannot be read within the filesystem limit.pub fn load_process_with_guest_filesystem<F: HostFileSystem>( elf_bytes: &[u8], parameters: &ProcessParameters, config: ProcessConfig, filesystem: &mut F,) -> Result<ProcessImage, ProcessLoadError> { match load_process(elf_bytes, parameters, config) { Ok(image) => Ok(image), Err(LoaderError::InterpreterRequired(path)) => { let interpreter = read_guest_interpreter(filesystem, &path, config.limits.max_filesystem_bytes)?; load_process_with_interpreter(elf_bytes, &interpreter, parameters, config) .map_err(ProcessLoadError::Loader) } Err(error) => Err(ProcessLoadError::Loader(error)), }}
fn read_guest_interpreter<F: HostFileSystem>( filesystem: &mut F, path: &[u8], limit: u64,) -> Result<Vec<u8>, ProcessLoadError> { let metadata = filesystem .metadata(path) .map_err(|error| ProcessLoadError::InterpreterFile { path: path.to_vec(), error, })?; if metadata.file_type != FileType::Regular { return Err(ProcessLoadError::InterpreterNotRegular(path.to_vec())); } if metadata.size > limit { return Err(ProcessLoadError::InterpreterTooLarge { path: path.to_vec(), size: metadata.size, limit, }); } let size = usize::try_from(metadata.size).map_err(|_| ProcessLoadError::InterpreterTooLarge { path: path.to_vec(), size: metadata.size, limit, })?; let handle = filesystem .open( path, FileOpenOptions { access: FileAccess::ReadOnly, flags: FileOpenFlags::NONE, }, ) .map_err(|error| ProcessLoadError::InterpreterFile { path: path.to_vec(), error, })?; let result = (|| { let mut bytes = vec![0; size]; let mut filled = 0; while filled < bytes.len() { let read = filesystem .read(handle, &mut bytes[filled..]) .map_err(|error| ProcessLoadError::InterpreterFile { path: path.to_vec(), error, })?; if read == 0 { return Err(ProcessLoadError::InterpreterShortRead(path.to_vec())); } filled += read; } Ok(bytes) })(); let _ = filesystem.close(handle); result}
#[derive(Clone, Copy, Debug, Default)]struct SignalAction { bytes: [u8; KERNEL_SIGACTION_SIZE],}
impl SignalAction { fn handler(self) -> u64 { u64::from_le_bytes( self.bytes[..8] .try_into() .expect("handler field has eight bytes"), ) }
fn flags(self) -> u64 { u64::from_le_bytes( self.bytes[8..16] .try_into() .expect("flags field has eight bytes"), ) }
fn restorer(self) -> u64 { u64::from_le_bytes( self.bytes[16..24] .try_into() .expect("restorer field has eight bytes"), ) }
fn mask(self) -> u64 { u64::from_le_bytes( self.bytes[24..] .try_into() .expect("signal-mask field has eight bytes"), ) }}
#[derive(Clone, Copy, Debug)]struct SignalStack { bytes: [u8; SIGNAL_STACK_SIZE],}
impl Default for SignalStack { fn default() -> Self { let mut bytes = [0; SIGNAL_STACK_SIZE]; bytes[8..12].copy_from_slice(&SIGNAL_STACK_DISABLED.to_le_bytes()); Self { bytes } }}
/// Successful termination of one guest process.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct ExecutionResult { pub exit_code: u8, pub executed_instructions: u64, pub dispatched_syscalls: u64, pub output_bytes: u64,}
/// Terminal input requested by a cooperatively suspended guest process.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct InputRequest { pub max_bytes: u64,}
/// A process either exits or suspends while waiting for terminal input.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum ExecutionEvent { Exited(ExecutionResult), Input(InputRequest),}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]struct PendingInput { address: GuestAddress, count: u64, arguments: [u64; 6],}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]enum ModifiedTimeUpdate { Preserve, Now, Explicit(HostTime),}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]enum PipeEnd { Read(u64), Write(u64), Socket { read: u64, write: u64 },}
#[derive(Debug, Default)]struct AnonymousPipe { bytes: VecDeque<u8>, reader_open: bool, writer_open: bool,}
struct SuspendedParent { state: Aarch64State, memory: AddressSpace, interpreter: Interpreter, limits: ResourceLimits, clear_child_tid: Option<GuestAddress>, signal_actions: [SignalAction; LINUX_SIGNAL_COUNT], signal_stack: SignalStack, signal_mask: u64, next_mmap_address: GuestAddress, file_descriptors: BTreeMap<u32, u64>, pipe_descriptors: BTreeMap<u32, PipeEnd>, descriptor_paths: BTreeMap<u32, Vec<u8>>, descriptor_flags: BTreeMap<u32, u64>, descriptor_status_flags: BTreeMap<u32, u64>, current_directory: Vec<u8>, executable_path: Vec<u8>, config: ProcessConfig, credentials: Credentials, pending_input: Option<PendingInput>, suspended_thread: Option<SuspendedThread>, dormant_thread: Option<DormantThread>, parked_helpers: Vec<SuspendedThread>, compiler_thread: Option<SuspendedThread>, worker_parent: Option<SuspendedThread>, worker_thread: Option<SuspendedThread>, next_helper_thread_id: u64, current_process_id: u64, current_thread_id: u64, parent_process_id: u64, exited_child: Option<ExitedChild>, pending_signal_sync: bool,}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]struct ExitedChild { process_id: u64, exit_code: u8,}
struct DormantThread { clear_child_tid: GuestAddress,}
struct SuspendedThread { state: Aarch64State, interpreter: Interpreter, clear_child_tid: Option<GuestAddress>, current_thread_id: u64, peer: Option<Box<SuspendedThread>>,}
struct DisabledBlockExecutor;
impl BlockExecutor for DisabledBlockExecutor { fn execute( &mut self, _block: &binarrow_aarch64::BasicBlock, _state: &Aarch64State, ) -> Option<Aarch64State> { None }}
/// Observable result of one completed Linux syscall dispatch.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum SyscallOutcome { Returned(u64), Exited(u8),}
/// Stable trace record captured at the architecture/Linux boundary.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct SyscallEvent { pub number: u64, pub arguments: [u64; 6], pub outcome: SyscallOutcome,}
/// Compact scheduler state exposed for deterministic trap diagnostics.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct ThreadExecutionState { pub process_id: u64, pub thread_id: u64, pub process_depth: usize, pub suspended_threads: usize, pub dormant_threads: usize, pub parked_helpers: usize, pub compiler_threads: usize, pub worker_parents: usize, pub worker_threads: usize, pub pending_signal_syncs: usize,}
impl fmt::Display for SyscallEvent { #[allow(clippy::too_many_lines)] fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match Syscall::from_number(self.number) { Some( syscall @ (Syscall::Mkdirat | Syscall::Unlinkat | Syscall::Linkat | Syscall::Renameat | Syscall::Faccessat | Syscall::Openat | Syscall::Getdents64 | Syscall::Readlinkat | Syscall::Newfstatat | Syscall::Utimensat), ) => format_vfs_syscall(formatter, syscall, self.arguments)?, Some(Syscall::Close) => write!(formatter, "close(fd={})", self.arguments[0])?, Some(Syscall::Dup3) => write!( formatter, "dup3(oldfd={}, newfd={}, flags={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], )?, Some(Syscall::Pipe2) => write!( formatter, "pipe2(pipefd={:#x}, flags={:#x})", self.arguments[0], self.arguments[1], )?, Some(Syscall::Socketpair) => write!( formatter, "socketpair(domain={}, type={:#x}, protocol={}, sockets={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], )?, Some(Syscall::Recvfrom) => write!( formatter, "recvfrom(fd={}, buffer={:#x}, length={}, flags={:#x}, address={:#x}, address_length={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], self.arguments[4], self.arguments[5], )?, Some(Syscall::Fcntl) => write!( formatter, "fcntl(fd={}, command={}, argument={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], )?, Some(Syscall::Ioctl) => write!( formatter, "ioctl(fd={}, request={:#x}, argument={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], )?, Some(Syscall::Flock) => write!( formatter, "flock(fd={}, operation={:#x})", self.arguments[0], self.arguments[1], )?, Some(Syscall::Getcwd) => write!( formatter, "getcwd(buf={:#x}, size={})", self.arguments[0], self.arguments[1], )?, Some(Syscall::Chdir) => { write!(formatter, "chdir(path={:#x})", self.arguments[0])?; } Some(Syscall::Lseek) => write!( formatter, "lseek(fd={}, offset={}, whence={})", self.arguments[0], self.arguments[1].cast_signed(), self.arguments[2], )?, Some(Syscall::Read) => write!( formatter, "read(fd={}, buf={:#x}, count={})", self.arguments[0], self.arguments[1], self.arguments[2], )?, Some(Syscall::Pread64) => write!( formatter, "pread64(fd={}, buf={:#x}, count={}, offset={})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3].cast_signed(), )?, Some(Syscall::Sendfile) => write!( formatter, "sendfile(out_fd={}, in_fd={}, offset={:#x}, count={})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], )?, Some(Syscall::CopyFileRange) => write!( formatter, "copy_file_range(in_fd={}, in_offset={:#x}, out_fd={}, out_offset={:#x}, count={}, flags={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], self.arguments[4], self.arguments[5], )?, Some(Syscall::Fstat) => write!( formatter, "fstat(fd={}, statbuf={:#x})", self.arguments[0], self.arguments[1], )?, Some(Syscall::Ftruncate) => write!( formatter, "ftruncate(fd={}, length={})", self.arguments[0], self.arguments[1], )?, Some(Syscall::Fchmod) => write!( formatter, "fchmod(fd={}, mode={:#o})", self.arguments[0], self.arguments[1], )?, Some(Syscall::Ppoll) => write!( formatter, "ppoll(fds={:#x}, nfds={}, timeout={:#x}, sigmask={:#x}, sigsetsize={})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], self.arguments[4], )?, Some(syscall @ (Syscall::Write | Syscall::Writev)) => { format_write_syscall(formatter, syscall, self.arguments)?; } Some(Syscall::Exit) => write!(formatter, "exit(status={})", self.arguments[0])?, Some(Syscall::ExitGroup) => { write!(formatter, "exit_group(status={})", self.arguments[0])?; } Some(Syscall::SetTidAddress) => { write!( formatter, "set_tid_address(tidptr={:#x})", self.arguments[0] )?; } Some(Syscall::Futex) => write!( formatter, "futex(address={:#x}, operation={:#x}, value={}, timeout={:#x}, address2={:#x}, value3={})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], self.arguments[4], self.arguments[5], )?, Some(Syscall::Getpid) => write!(formatter, "getpid()")?, Some(Syscall::Getppid) => write!(formatter, "getppid()")?, Some(Syscall::Gettid) => write!(formatter, "gettid()")?, Some(Syscall::Clone) => write!( formatter, "clone(flags={:#x}, stack={:#x}, parent_tid={:#x}, tls={:#x}, child_tid={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], self.arguments[4], )?, Some(Syscall::Execve) => write!( formatter, "execve(path={:#x}, argv={:#x}, envp={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], )?, Some(syscall @ (Syscall::ClockGettime | Syscall::Getrandom)) => { format_system_syscall(formatter, syscall, self.arguments)?; } Some(Syscall::SchedGetaffinity) => write!( formatter, "sched_getaffinity(pid={}, cpusetsize={}, mask={:#x})", self.arguments[0].cast_signed(), self.arguments[1], self.arguments[2], )?, Some(Syscall::SchedYield) => write!(formatter, "sched_yield()")?, Some(Syscall::Tkill) => write!( formatter, "tkill(tid={}, signal={})", self.arguments[0], self.arguments[1], )?, Some(Syscall::Sigaltstack) => write!( formatter, "sigaltstack(stack={:#x}, old_stack={:#x})", self.arguments[0], self.arguments[1], )?, Some(Syscall::RtSigaction) => write!( formatter, "rt_sigaction(signal={}, action={:#x}, old_action={:#x}, sigsetsize={})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], )?, Some(Syscall::RtSigprocmask) => write!( formatter, "rt_sigprocmask(how={}, set={:#x}, old_set={:#x}, sigsetsize={})", self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], )?, Some(syscall @ (Syscall::Munmap | Syscall::Mmap | Syscall::Mprotect)) => { format_memory_syscall(formatter, syscall, self.arguments)?; } Some(Syscall::Wait4) => write!( formatter, "wait4(pid={}, status={:#x}, options={:#x}, rusage={:#x})", self.arguments[0].cast_signed(), self.arguments[1], self.arguments[2], self.arguments[3], )?, None => write!(formatter, "syscall({})", self.number)?, } match self.outcome { SyscallOutcome::Returned(value) => write!(formatter, " = {value}"), SyscallOutcome::Exited(code) => write!(formatter, " -> exit {code}"), } }}
fn format_write_syscall( formatter: &mut fmt::Formatter<'_>, syscall: Syscall, arguments: [u64; 6],) -> fmt::Result { match syscall { Syscall::Write => write!( formatter, "write(fd={}, buf={:#x}, count={})", arguments[0], arguments[1], arguments[2], ), Syscall::Writev => write!( formatter, "writev(fd={}, iov={:#x}, iovcnt={})", arguments[0], arguments[1], arguments[2], ), _ => unreachable!("only write syscalls are delegated to the write formatter"), }}
fn format_memory_syscall( formatter: &mut fmt::Formatter<'_>, syscall: Syscall, arguments: [u64; 6],) -> fmt::Result { match syscall { Syscall::Munmap => write!( formatter, "munmap(address={:#x}, length={})", arguments[0], arguments[1], ), Syscall::Mmap => write!( formatter, "mmap(address={:#x}, length={}, protection={:#x}, flags={:#x}, fd={}, offset={:#x})", arguments[0], arguments[1], arguments[2], arguments[3], arguments[4].cast_signed(), arguments[5], ), Syscall::Mprotect => write!( formatter, "mprotect(address={:#x}, length={}, protection={:#x})", arguments[0], arguments[1], arguments[2], ), _ => unreachable!("non-memory syscall passed to memory formatter"), }}
fn format_vfs_syscall( formatter: &mut fmt::Formatter<'_>, syscall: Syscall, arguments: [u64; 6],) -> fmt::Result { match syscall { Syscall::Mkdirat => write!( formatter, "mkdirat(dirfd={}, path={:#x}, mode={:#o})", arguments[0].cast_signed(), arguments[1], arguments[2], ), Syscall::Unlinkat => write!( formatter, "unlinkat(dirfd={}, path={:#x}, flags={:#x})", arguments[0].cast_signed(), arguments[1], arguments[2], ), Syscall::Linkat => write!( formatter, "linkat(olddirfd={}, oldpath={:#x}, newdirfd={}, newpath={:#x}, flags={:#x})", arguments[0].cast_signed(), arguments[1], arguments[2].cast_signed(), arguments[3], arguments[4], ), Syscall::Renameat => write!( formatter, "renameat(olddirfd={}, oldpath={:#x}, newdirfd={}, newpath={:#x})", arguments[0].cast_signed(), arguments[1], arguments[2].cast_signed(), arguments[3], ), Syscall::Faccessat => write!( formatter, "faccessat(dirfd={}, path={:#x}, mode={:#x})", arguments[0].cast_signed(), arguments[1], arguments[2], ), Syscall::Openat => write!( formatter, "openat(dirfd={}, path={:#x}, flags={:#x}, mode={:#o})", arguments[0].cast_signed(), arguments[1], arguments[2], arguments[3], ), Syscall::Getdents64 => write!( formatter, "getdents64(fd={}, dirp={:#x}, count={})", arguments[0], arguments[1], arguments[2], ), Syscall::Readlinkat => write!( formatter, "readlinkat(dirfd={}, path={:#x}, buf={:#x}, size={})", arguments[0].cast_signed(), arguments[1], arguments[2], arguments[3], ), Syscall::Newfstatat => write!( formatter, "newfstatat(dirfd={}, path={:#x}, stat={:#x}, flags={:#x})", arguments[0].cast_signed(), arguments[1], arguments[2], arguments[3], ), Syscall::Utimensat => write!( formatter, "utimensat(dirfd={}, path={:#x}, times={:#x}, flags={:#x})", arguments[0].cast_signed(), arguments[1], arguments[2], arguments[3], ), _ => unreachable!("only VFS syscalls are delegated to the VFS formatter"), }}
fn format_system_syscall( formatter: &mut fmt::Formatter<'_>, syscall: Syscall, arguments: [u64; 6],) -> fmt::Result { match syscall { Syscall::ClockGettime => write!( formatter, "clock_gettime(clock={}, timespec={:#x})", arguments[0], arguments[1], ), Syscall::Getrandom => write!( formatter, "getrandom(buffer={:#x}, count={}, flags={:#x})", arguments[0], arguments[1], arguments[2], ), _ => unreachable!("only host-system calls are delegated to this formatter"), }}
/// A failure outside normal Linux syscall return handling.#[derive(Debug)]pub enum ExecutionError<HostError> { Trap(Trap), Host(HostError), UnsupportedSupervisorImmediate(u16),}
impl<HostError: fmt::Display> fmt::Display for ExecutionError<HostError> { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Trap(trap) => write!(formatter, "guest trapped: {trap:?}"), Self::Host(error) => write!(formatter, "terminal host failed: {error}"), Self::UnsupportedSupervisorImmediate(immediate) => write!( formatter, "unsupported AArch64 Linux supervisor immediate {immediate:#x}", ), } }}
impl<HostError: fmt::Debug + fmt::Display> std::error::Error for ExecutionError<HostError> {}
/// One loaded process with architectural state and enforced resource counters.pub struct Process { state: Aarch64State, memory: AddressSpace, interpreter: Interpreter, limits: ResourceLimits, executed_instructions: u64, dispatched_syscalls: u64, output_bytes: u64, clear_child_tid: Option<GuestAddress>, signal_actions: [SignalAction; LINUX_SIGNAL_COUNT], signal_stack: SignalStack, signal_mask: u64, next_mmap_address: GuestAddress, file_descriptors: BTreeMap<u32, u64>, pipe_descriptors: BTreeMap<u32, PipeEnd>, pipes: BTreeMap<u64, AnonymousPipe>, next_pipe_id: u64, descriptor_paths: BTreeMap<u32, Vec<u8>>, descriptor_flags: BTreeMap<u32, u64>, descriptor_status_flags: BTreeMap<u32, u64>, current_directory: Vec<u8>, executable_path: Vec<u8>, config: ProcessConfig, credentials: Credentials, trace: Vec<SyscallEvent>, pending_input: Option<PendingInput>, current_process_id: u64, current_thread_id: u64, parent_process_id: u64, suspended_parents: Vec<SuspendedParent>, suspended_thread: Option<SuspendedThread>, exited_child: Option<ExitedChild>, dormant_thread: Option<DormantThread>, parked_helpers: Vec<SuspendedThread>, compiler_thread: Option<SuspendedThread>, worker_parent: Option<SuspendedThread>, worker_thread: Option<SuspendedThread>, next_helper_thread_id: u64, pending_signal_sync: bool,}
impl Process { /// Consume a loader image and initialize its `AArch64` interpreter. /// /// # Errors /// /// Returns an initialization error if the pinned SLEIGH language cannot be /// compiled. pub fn new(image: ProcessImage) -> Result<Self, InterpreterInitializationError> { Ok(Self { state: Aarch64State::new(image.initial_state.pc, image.initial_state.sp), memory: image.memory, interpreter: Interpreter::new()?, limits: image.limits, executed_instructions: 0, dispatched_syscalls: 0, output_bytes: 0, clear_child_tid: None, signal_actions: [SignalAction::default(); LINUX_SIGNAL_COUNT], signal_stack: SignalStack::default(), signal_mask: 0, next_mmap_address: GuestAddress::new(MMAP_ARENA_START), file_descriptors: BTreeMap::new(), pipe_descriptors: BTreeMap::new(), pipes: BTreeMap::new(), next_pipe_id: 1, descriptor_paths: BTreeMap::new(), descriptor_flags: BTreeMap::new(), descriptor_status_flags: BTreeMap::new(), current_directory: INITIAL_CURRENT_DIRECTORY.to_vec(), executable_path: image.executable_path, config: image.config, credentials: image.credentials, trace: Vec::new(), pending_input: None, current_process_id: INITIAL_PROCESS_ID, current_thread_id: INITIAL_PROCESS_ID, parent_process_id: 0, suspended_parents: Vec::new(), suspended_thread: None, exited_child: None, dormant_thread: None, parked_helpers: Vec::new(), compiler_thread: None, worker_parent: None, worker_thread: None, next_helper_thread_id: NESTED_HELPER_THREAD_ID, pending_signal_sync: false, }) }
#[must_use] pub const fn state(&self) -> &Aarch64State { &self.state }
#[must_use] pub const fn memory(&self) -> &AddressSpace { &self.memory }
#[must_use] pub fn trace(&self) -> &[SyscallEvent] { &self.trace }
#[must_use] pub const fn executed_instructions(&self) -> u64 { self.executed_instructions }
#[must_use] pub const fn dispatched_syscalls(&self) -> u64 { self.dispatched_syscalls }
#[must_use] pub const fn output_bytes(&self) -> u64 { self.output_bytes }
/// Return the most recently attempted guest instruction addresses. #[must_use] pub fn recent_instruction_pcs(&self) -> Vec<GuestAddress> { self.interpreter.recent_pcs() }
/// Return interpreted basic-block profiles in deterministic hotness order. #[must_use] pub fn block_profiles(&self) -> Vec<BlockProfile> { self.interpreter.block_profiles() }
/// Return block-entry observations dropped by the bounded profiler. #[must_use] pub const fn dropped_block_entries(&self) -> u64 { self.interpreter.dropped_block_entries() }
/// Current interpreter/translated-tier counters. #[must_use] pub const fn tiered_execution_metrics(&self) -> TieredExecutionMetrics { self.interpreter.tiered_metrics() }
/// Return compact process/thread scheduler state for host diagnostics. #[must_use] pub const fn thread_execution_state(&self) -> ThreadExecutionState { ThreadExecutionState { process_id: self.current_process_id, thread_id: self.current_thread_id, process_depth: self.suspended_parents.len(), suspended_threads: self.suspended_thread.is_some() as usize, dormant_threads: self.dormant_thread.is_some() as usize, parked_helpers: self.parked_helpers.len(), compiler_threads: self.compiler_thread.is_some() as usize, worker_parents: self.worker_parent.is_some() as usize, worker_threads: self.worker_thread.is_some() as usize, pending_signal_syncs: self.pending_signal_sync as usize, } }
/// Run until `exit`/`exit_group`, a host failure, or a project resource trap. /// /// Unsupported syscall numbers return `-ENOSYS` in `x0` and execution /// continues. Invalid guest write buffers return `-EFAULT` without calling /// the host. /// /// # Errors /// /// Returns [`ExecutionError`] for CPU traps, unsupported nonzero `svc` /// immediates, output exhaustion, or terminal-host failures. pub fn run<T: HostTerminal>( &mut self, terminal: &mut T, ) -> Result<ExecutionResult, ExecutionError<T::Error>> { self.run_with_services( terminal, &mut NullFileSystem, &mut DeterministicSystem::default(), ) }
/// Run with an explicit synchronous filesystem service. /// /// # Errors /// /// Returns the same structured execution failures as [`Self::run`]. pub fn run_with_filesystem<T: HostTerminal, F: HostFileSystem>( &mut self, terminal: &mut T, filesystem: &mut F, ) -> Result<ExecutionResult, ExecutionError<T::Error>> { self.run_with_services(terminal, filesystem, &mut DeterministicSystem::default()) }
/// Run with explicit synchronous filesystem, clock, and random services. /// /// # Errors /// /// Returns the same structured execution failures as [`Self::run`]. pub fn run_with_services<T: HostTerminal, F: HostFileSystem, S: HostSystem>( &mut self, terminal: &mut T, filesystem: &mut F, system: &mut S, ) -> Result<ExecutionResult, ExecutionError<T::Error>> { match self.run_until_event(terminal, filesystem, system, &mut ClosedInput)? { ExecutionEvent::Exited(result) => Ok(result), ExecutionEvent::Input(_) => { unreachable!("closed non-interactive input cannot block") } } }
/// Run until exit, failure, or a blocking terminal-input request. /// /// Calling this method again with available input resumes the pending /// `read` without re-executing or recounting its supervisor call. /// /// # Errors /// /// Returns the same structured execution failures as [`Self::run`]. pub fn run_until_event<T: HostTerminal, F: HostFileSystem, S: HostSystem, I: HostInput>( &mut self, terminal: &mut T, filesystem: &mut F, system: &mut S, input: &mut I, ) -> Result<ExecutionEvent, ExecutionError<T::Error>> { self.run_until_event_tiered( terminal, filesystem, system, input, &mut DisabledBlockExecutor, 0, 0, ) }
/// Run with hot basic blocks offered to a translated executor. /// /// # Errors /// /// Returns the same structured execution failures as [`Self::run_until_event`]. #[allow(clippy::too_many_arguments)] pub fn run_until_event_tiered< T: HostTerminal, F: HostFileSystem, S: HostSystem, I: HostInput, E: BlockExecutor, >( &mut self, terminal: &mut T, filesystem: &mut F, system: &mut S, input: &mut I, executor: &mut E, hot_threshold: u64, max_block_instructions: u32, ) -> Result<ExecutionEvent, ExecutionError<T::Error>> { if let Some(request) = self.resume_pending_input(input) { return Ok(ExecutionEvent::Input(request)); } loop { let remaining = self .limits .instruction_budget .saturating_sub(self.executed_instructions); let stop = match self.interpreter.run_until_supervisor_call_tiered( &mut self.state, &mut self.memory, remaining, hot_threshold, max_block_instructions, executor, ) { Ok(stop) => stop, Err(Trap::ResourceLimit(ResourceLimit::Instructions)) => { self.executed_instructions += remaining; return Err(ExecutionError::Trap(Trap::ResourceLimit( ResourceLimit::Instructions, ))); } Err(trap @ Trap::UnsupportedInstruction { .. }) => { if self.deliver_synchronous_signal(SIGILL).is_some() { continue; } return Err(ExecutionError::Trap(trap)); } Err(trap) => return Err(ExecutionError::Trap(trap)), }; self.executed_instructions += stop.executed_instructions; if self.dispatched_syscalls >= self.limits.syscall_budget { return Err(ExecutionError::Trap(Trap::ResourceLimit( ResourceLimit::Syscalls, ))); } self.dispatched_syscalls += 1;
if stop.supervisor_call.immediate != 0 { return Err(ExecutionError::UnsupportedSupervisorImmediate( stop.supervisor_call.immediate, )); }
let number = stop.supervisor_call.syscall_number; let arguments = self.syscall_arguments(); if let Some(result) = self.dispatch_syscall(terminal, filesystem, system, input, number, arguments)? { return Ok(result); } } }
fn dispatch_syscall<T: HostTerminal, F: HostFileSystem, S: HostSystem, I: HostInput>( &mut self, terminal: &mut T, filesystem: &mut F, system: &mut S, input: &mut I, number: u64, arguments: [u64; 6], ) -> Result<Option<ExecutionEvent>, ExecutionError<T::Error>> { match Syscall::from_number(number) { Some(syscall @ (Syscall::Exit | Syscall::ExitGroup)) => { return Ok(self.dispatch_exit(filesystem, syscall, number, arguments)); } Some(Syscall::Tkill) => { if self.dispatch_tkill() { return Ok(self.dispatch_exit( filesystem, Syscall::ExitGroup, number, arguments, )); } } Some(Syscall::Getcwd) => self.dispatch_getcwd(), Some(Syscall::Chdir) => self.dispatch_chdir(filesystem), Some(Syscall::Dup3) => self.dispatch_dup3(filesystem), Some(Syscall::Fcntl) => self.dispatch_fcntl(), Some(Syscall::Ioctl) => self.dispatch_ioctl(), Some(Syscall::Flock) => self.dispatch_flock(), Some(Syscall::Mkdirat) => self.dispatch_mkdirat(filesystem), Some(Syscall::Unlinkat) => self.dispatch_unlinkat(filesystem), Some(Syscall::Linkat) => self.dispatch_linkat(filesystem), Some(Syscall::Renameat) => self.dispatch_renameat(filesystem), Some(Syscall::Faccessat) => self.dispatch_faccessat(filesystem), Some(Syscall::Openat) => self.dispatch_openat(filesystem), Some(Syscall::Close) => self.dispatch_close(filesystem), Some(Syscall::Pipe2) => self.dispatch_pipe2(), Some(Syscall::Socketpair) => self.dispatch_socketpair(), Some(Syscall::Recvfrom) => self.dispatch_recvfrom(), Some(Syscall::Getdents64) => self.dispatch_getdents64(filesystem), Some(Syscall::Lseek) => self.dispatch_lseek(filesystem), Some(Syscall::Readlinkat) => self.dispatch_readlinkat(filesystem), Some(Syscall::Newfstatat) => self.dispatch_newfstatat(filesystem), Some(Syscall::Fstat) => self.dispatch_fstat(filesystem), Some(Syscall::Utimensat) => self.dispatch_utimensat(filesystem), Some(Syscall::Ftruncate) => self.dispatch_ftruncate(filesystem), Some(Syscall::Fchmod) => { self.set_return(if self.file_handle(self.register(0)).is_some() { 0 } else { Errno::BadFileDescriptor.return_value() }); } Some(Syscall::Read) => { if self.register(0) == STANDARD_INPUT && !self.descriptor_exists(0) { if let Some(request) = self.dispatch_terminal_read(input, arguments) { return Ok(Some(ExecutionEvent::Input(request))); } } else { self.dispatch_read(filesystem); } } Some(Syscall::Pread64) => self.dispatch_pread64(filesystem), Some(Syscall::Sendfile) => self.dispatch_sendfile(filesystem), Some(Syscall::CopyFileRange) => self.dispatch_copy_file_range(filesystem), Some(Syscall::Ppoll) => self.dispatch_ppoll(), Some(Syscall::Write) => self.dispatch_write(terminal, filesystem)?, Some(Syscall::Writev) => self.dispatch_writev(terminal, filesystem)?, Some(Syscall::SetTidAddress) => { self.clear_child_tid = Some(GuestAddress::new(self.register(0))); self.set_return(self.current_thread_id); } Some(Syscall::Futex) => self.dispatch_futex(), Some(Syscall::Getpid) => { self.set_return(self.current_process_id); } Some(Syscall::Gettid) => self.set_return(self.current_thread_id), Some(Syscall::Getppid) => self.set_return(self.parent_process_id), Some(Syscall::Clone) => self.dispatch_clone(), Some(Syscall::Execve) => self.dispatch_execve(filesystem, system), Some(Syscall::ClockGettime) => self.dispatch_clock_gettime(system), Some(Syscall::SchedGetaffinity) => self.dispatch_sched_getaffinity(), Some(Syscall::SchedYield) => self.set_return(0), Some(Syscall::Sigaltstack) => self.dispatch_sigaltstack(), Some(Syscall::RtSigaction) => self.dispatch_rt_sigaction(), Some(Syscall::RtSigprocmask) => self.dispatch_rt_sigprocmask(), Some(Syscall::Munmap) => self.dispatch_munmap(), Some(Syscall::Mmap) => self.dispatch_mmap(filesystem), Some(Syscall::Mprotect) => self.dispatch_mprotect(), Some(Syscall::Getrandom) => self.dispatch_getrandom(system), Some(Syscall::Wait4) => self.dispatch_wait4(), None => self.set_return(Errno::NoSystemCall.return_value()), } self.trace.push(SyscallEvent { number, arguments, outcome: SyscallOutcome::Returned(self.register(0)), }); Ok(None) }
fn dispatch_exit<F: HostFileSystem>( &mut self, filesystem: &mut F, syscall: Syscall, number: u64, arguments: [u64; 6], ) -> Option<ExecutionEvent> { let exit_code = self.register(0).to_le_bytes()[0]; self.clear_child_tid(); self.trace.push(SyscallEvent { number, arguments, outcome: SyscallOutcome::Exited(exit_code), }); if syscall == Syscall::Exit && self.worker_parent.is_some() { self.finish_worker_thread(); return None; } if syscall == Syscall::Exit && self.current_thread_id >= NESTED_HELPER_THREAD_ID { if self.compiler_thread.is_some() { self.finish_running_helper(); } else { self.finish_thread(); } return None; } if syscall == Syscall::Exit && self.suspended_thread.is_some() { self.clear_parked_helper_tids(); self.parked_helpers.clear(); self.compiler_thread = None; self.worker_parent = None; self.worker_thread = None; self.finish_thread(); return None; } if !self.suspended_parents.is_empty() { self.finish_child(filesystem, exit_code); return None; } self.clear_dormant_thread_tid(); self.clear_parked_helper_tids(); Some(ExecutionEvent::Exited(ExecutionResult { exit_code, executed_instructions: self.executed_instructions, dispatched_syscalls: self.dispatched_syscalls, output_bytes: self.output_bytes, })) }
fn dispatch_clone(&mut self) { if self.register(0) == DORMANT_THREAD_CLONE_FLAGS { self.dispatch_dormant_thread_clone(); return; } if !matches!( self.register(0), SUPPORTED_CLONE_FLAGS | SUPPORTED_FORK_FLAGS ) || self.suspended_parents.len() >= MAX_PROCESS_DEPTH || self.exited_child.is_some() { self.set_return(Errno::InvalidArgument.return_value()); return; } let child_stack = self.register(1); let Ok(child_interpreter) = Interpreter::new() else { self.set_return(Errno::OutOfMemory.return_value()); return; }; let parent = SuspendedParent { state: self.state.clone(), memory: self.memory.clone(), interpreter: core::mem::replace(&mut self.interpreter, child_interpreter), limits: self.limits, clear_child_tid: self.clear_child_tid, signal_actions: self.signal_actions, signal_stack: self.signal_stack, signal_mask: self.signal_mask, next_mmap_address: self.next_mmap_address, file_descriptors: self.file_descriptors.clone(), pipe_descriptors: self.pipe_descriptors.clone(), descriptor_paths: self.descriptor_paths.clone(), descriptor_flags: self.descriptor_flags.clone(), descriptor_status_flags: self.descriptor_status_flags.clone(), current_directory: self.current_directory.clone(), executable_path: self.executable_path.clone(), config: self.config, credentials: self.credentials, pending_input: self.pending_input, suspended_thread: self.suspended_thread.take(), dormant_thread: self.dormant_thread.take(), parked_helpers: core::mem::take(&mut self.parked_helpers), compiler_thread: self.compiler_thread.take(), worker_parent: self.worker_parent.take(), worker_thread: self.worker_thread.take(), next_helper_thread_id: self.next_helper_thread_id, current_process_id: self.current_process_id, current_thread_id: self.current_thread_id, parent_process_id: self.parent_process_id, exited_child: self.exited_child.take(), pending_signal_sync: self.pending_signal_sync, }; let child_process_id = self.current_process_id + 1; self.suspended_parents.push(parent); self.parent_process_id = self.current_process_id; self.current_process_id = child_process_id; self.current_thread_id = child_process_id; self.clear_child_tid = None; self.pending_input = None; self.suspended_thread = None; self.dormant_thread = None; self.parked_helpers.clear(); self.compiler_thread = None; self.worker_parent = None; self.worker_thread = None; self.next_helper_thread_id = NESTED_HELPER_THREAD_ID; self.exited_child = None; self.pending_signal_sync = false; if child_stack != 0 { self.state.set_sp(GuestAddress::new(child_stack)); } self.set_return(0); }
fn dispatch_tkill(&mut self) -> bool { let thread_id = self.register(0); let signal = self.register(1); if (DORMANT_THREAD_ID..self.next_helper_thread_id).contains(&thread_id) && signal == SIGNAL_USER_1 { self.clear_dormant_thread_tid(); self.pending_signal_sync = true; self.set_return(0); return false; } if thread_id == self.current_thread_id && signal == SIGNAL_ABORT { self.set_return(128 + signal); return true; } self.set_return(Errno::InvalidArgument.return_value()); false }
fn dispatch_dormant_thread_clone(&mut self) { let nested_helper = self.current_thread_id == ACTIVE_THREAD_ID || self.current_thread_id >= NESTED_HELPER_THREAD_ID; let worker_clone = self.current_thread_id >= NESTED_HELPER_THREAD_ID; let invalid_thread_state = if worker_clone { self.compiler_thread.is_none() || self.worker_parent.is_some() || self.worker_thread.is_some() } else if nested_helper { self.compiler_thread.is_some() || self.parked_helpers.len() >= MAX_NESTED_HELPERS } else { self.suspended_thread.is_some() }; if self.exited_child.is_some() || invalid_thread_state { self.set_return(Errno::TryAgain.return_value()); return; } let stack = self.register(1); let parent_tid = GuestAddress::new(self.register(2)); let tls = self.register(3); let clear_child_tid = GuestAddress::new(self.register(4)); if stack == 0 || parent_tid == GuestAddress::NULL || tls == 0 || clear_child_tid == GuestAddress::NULL { self.set_return(Errno::InvalidArgument.return_value()); return; } let thread_id = if nested_helper { let thread_id = self.next_helper_thread_id; self.next_helper_thread_id = self .next_helper_thread_id .checked_add(1) .expect("the bounded helper thread ID does not overflow"); thread_id } else if self.dormant_thread.is_some() { ACTIVE_THREAD_ID } else { DORMANT_THREAD_ID }; let thread_id_bytes = u32::try_from(thread_id) .expect("the bounded helper thread ID fits u32") .to_le_bytes(); if self.memory.write(parent_tid, &thread_id_bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } let Ok(child_interpreter) = Interpreter::new() else { self.set_return(Errno::OutOfMemory.return_value()); return; }; self.set_return(thread_id); let peer = if worker_clone { None } else { self.suspended_thread.take().map(Box::new) }; let parent = SuspendedThread { state: self.state.clone(), interpreter: core::mem::replace(&mut self.interpreter, child_interpreter), clear_child_tid: self.clear_child_tid, current_thread_id: self.current_thread_id, peer, }; if worker_clone { self.worker_parent = Some(parent); } else { self.suspended_thread = Some(parent); } self.clear_child_tid = Some(clear_child_tid); self.current_thread_id = thread_id; self.state.set_sp(GuestAddress::new(stack)); self.state.set_tpidr_el0(tls); self.set_return(0); }
fn finish_thread(&mut self) { let parent = self .suspended_thread .take() .expect("a thread exit has a suspended parent thread"); self.state = parent.state; self.interpreter = parent.interpreter; self.clear_child_tid = parent.clear_child_tid; self.current_thread_id = parent.current_thread_id; self.suspended_thread = parent.peer.map(|peer| *peer); }
fn switch_thread(&mut self) { let mut thread = self .suspended_thread .take() .expect("a cooperative thread switch has a suspended peer"); self.swap_thread_context(&mut thread); self.suspended_thread = Some(thread); }
fn swap_thread_context(&mut self, thread: &mut SuspendedThread) { core::mem::swap(&mut self.state, &mut thread.state); core::mem::swap(&mut self.interpreter, &mut thread.interpreter); core::mem::swap(&mut self.clear_child_tid, &mut thread.clear_child_tid); core::mem::swap(&mut self.current_thread_id, &mut thread.current_thread_id); }
fn park_new_helper(&mut self) { let mut parent = self .suspended_thread .take() .expect("the nested helper has a suspended compiler thread"); let peer = parent.peer.take(); self.swap_thread_context(&mut parent); self.suspended_thread = peer.map(|peer| *peer); self.parked_helpers.push(parent); }
fn schedule_parked_helper(&mut self) { let mut thread = self.parked_helpers.remove(0); self.swap_thread_context(&mut thread); self.compiler_thread = Some(thread); }
fn park_running_helper(&mut self) { let mut thread = self .compiler_thread .take() .expect("a running helper has a suspended compiler thread"); self.swap_thread_context(&mut thread); self.parked_helpers.push(thread); }
fn finish_running_helper(&mut self) { let mut thread = self .compiler_thread .take() .expect("a helper exit has a suspended compiler thread"); self.swap_thread_context(&mut thread); }
fn park_worker_thread(&mut self) { let mut parent = self .worker_parent .take() .expect("a worker thread has a suspended coordinator"); self.swap_thread_context(&mut parent); self.worker_thread = Some(parent); }
fn schedule_worker_thread(&mut self) { let mut worker = self .worker_thread .take() .expect("a coordinator has a parked worker thread"); self.swap_thread_context(&mut worker); self.worker_parent = Some(worker); }
fn finish_worker_thread(&mut self) { let mut parent = self .worker_parent .take() .expect("a worker exit has a suspended coordinator"); self.swap_thread_context(&mut parent); }
fn park_dormant_thread(&mut self) { let clear_child_tid = self .clear_child_tid .expect("the musl helper clone requested child TID clearing"); let parent = self .suspended_thread .take() .expect("the dormant helper has a suspended parent thread"); self.state = parent.state; self.interpreter = parent.interpreter; self.clear_child_tid = parent.clear_child_tid; self.current_thread_id = parent.current_thread_id; let _ = self.memory.write(clear_child_tid, &0_u32.to_le_bytes()); self.dormant_thread = Some(DormantThread { clear_child_tid }); }
fn dispatch_futex(&mut self) { let operation = self.register(1) & FUTEX_COMMAND_MASK; let wait = operation == FUTEX_WAIT || operation == FUTEX_WAIT_BITSET; let wake = operation == FUTEX_WAKE || operation == FUTEX_WAKE_BITSET; if self.current_thread_id == DORMANT_THREAD_ID && wait { self.park_dormant_thread(); return; } if wake { self.set_return(0); return; } if !wait { self.set_return(Errno::NoSystemCall.return_value()); return; } let address = GuestAddress::new(self.register(0)); if address.get() & 3 != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let mut bytes = [0; 4]; if self.memory.read_exact(address, &mut bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } let expected = self.register(2).to_le_bytes(); if bytes != expected[..4] { self.set_return(Errno::TryAgain.return_value()); return; } if self.pending_signal_sync { let value = u32::from_le_bytes(bytes).wrapping_add(1); let _ = self.memory.write(address, &value.to_le_bytes()); self.pending_signal_sync = false; self.set_return(0); return; } let has_runnable_peer = self.suspended_thread.is_some() || !self.parked_helpers.is_empty() || self.compiler_thread.is_some() || self.worker_parent.is_some() || self.worker_thread.is_some(); if self.register(1) == FUTEX_PRIVATE_FLAG | FUTEX_WAIT && bytes == 2_u32.to_le_bytes() && !has_runnable_peer && self.dormant_thread.is_some() { let _ = self.memory.write(address, &3_u32.to_le_bytes()); self.set_return(0); return; } self.set_return(0); if self.worker_parent.is_some() { self.park_worker_thread(); } else if self.worker_thread.is_some() { self.schedule_worker_thread(); } else if self.current_thread_id >= NESTED_HELPER_THREAD_ID { if self.compiler_thread.is_some() { self.park_running_helper(); } else { self.park_new_helper(); } } else if self.current_thread_id == ACTIVE_THREAD_ID && !self.parked_helpers.is_empty() { self.schedule_parked_helper(); } else if self.suspended_thread.is_some() { self.switch_thread(); } }
fn finish_child<F: HostFileSystem>(&mut self, filesystem: &mut F, exit_code: u8) { self.close_all_descriptors(filesystem); let child_process_id = self.current_process_id; let parent = self .suspended_parents .pop() .expect("a child exit has a suspended parent"); self.state = parent.state; self.memory = parent.memory; self.interpreter = parent.interpreter; self.limits = parent.limits; self.clear_child_tid = parent.clear_child_tid; self.signal_actions = parent.signal_actions; self.signal_stack = parent.signal_stack; self.signal_mask = parent.signal_mask; self.next_mmap_address = parent.next_mmap_address; self.file_descriptors = parent.file_descriptors; self.pipe_descriptors = parent.pipe_descriptors; self.descriptor_paths = parent.descriptor_paths; self.descriptor_flags = parent.descriptor_flags; self.descriptor_status_flags = parent.descriptor_status_flags; self.current_directory = parent.current_directory; self.executable_path = parent.executable_path; self.config = parent.config; self.credentials = parent.credentials; self.pending_input = parent.pending_input; self.suspended_thread = parent.suspended_thread; self.dormant_thread = parent.dormant_thread; self.parked_helpers = parent.parked_helpers; self.compiler_thread = parent.compiler_thread; self.worker_parent = parent.worker_parent; self.worker_thread = parent.worker_thread; self.next_helper_thread_id = parent.next_helper_thread_id; self.pending_signal_sync = parent.pending_signal_sync; self.current_process_id = parent.current_process_id; self.current_thread_id = parent.current_thread_id; self.parent_process_id = parent.parent_process_id; self.exited_child = parent.exited_child; self.exited_child = Some(ExitedChild { process_id: child_process_id, exit_code, }); self.set_return(child_process_id); }
fn dispatch_wait4(&mut self) { let Some(child) = self.exited_child else { self.set_return(Errno::NoChild.return_value()); return; }; let requested_process = self.register(0).cast_signed(); if !matches!(requested_process, -1) && requested_process != child.process_id.cast_signed() { self.set_return(Errno::NoChild.return_value()); return; } if self.register(2) != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let status = GuestAddress::new(self.register(1)); if status != GuestAddress::NULL { let wait_status = u32::from(child.exit_code) << 8; if self .memory .write(status, &wait_status.to_le_bytes()) .is_err() { self.set_return(Errno::Fault.return_value()); return; } } let rusage = GuestAddress::new(self.register(3)); if rusage != GuestAddress::NULL && self.memory.write(rusage, &[0; RUSAGE_SIZE]).is_err() { self.set_return(Errno::Fault.return_value()); return; } self.exited_child = None; self.set_return(child.process_id); }
fn dispatch_execve<F: HostFileSystem, S: HostSystem>( &mut self, filesystem: &mut F, system: &mut S, ) { let path = match self.read_guest_path(GuestAddress::new(self.register(0))) { Ok(path) if !path.is_empty() => self.absolute_guest_path(path), Ok(_) => { self.set_return(Errno::NoEntry.return_value()); return; } Err(error) => { self.set_return(error.return_value()); return; } }; let argv = match self.read_exec_vector(GuestAddress::new(self.register(1))) { Ok(argv) if !argv.is_empty() => argv, Ok(_) => { self.set_return(Errno::InvalidArgument.return_value()); return; } Err(error) => { self.set_return(error.return_value()); return; } }; let envp = match self.read_exec_vector(GuestAddress::new(self.register(2))) { Ok(envp) => envp, Err(error) => { self.set_return(error.return_value()); return; } }; let executable = match self.read_executable(filesystem, &path) { Ok(executable) => executable, Err(error) => { self.set_return(error.return_value()); return; } }; let mut random_bytes = [0; 16]; system.fill_random(&mut random_bytes); let image = match load_process_with_guest_filesystem( &executable, &ProcessParameters { argv, envp, random_bytes, credentials: self.credentials, }, self.config, filesystem, ) { Ok(image) => image, Err(error) => { self.set_return(process_load_errno(&error).return_value()); return; } }; let interpreter = Interpreter::new().expect("the AArch64 language was initialized for this process");
self.clear_dormant_thread_tid(); self.close_on_exec_descriptors(filesystem); self.state = Aarch64State::new(image.initial_state.pc, image.initial_state.sp); self.memory = image.memory; self.interpreter = interpreter; self.limits = image.limits; self.clear_child_tid = None; self.signal_actions = [SignalAction::default(); LINUX_SIGNAL_COUNT]; self.signal_stack = SignalStack::default(); self.next_mmap_address = GuestAddress::new(MMAP_ARENA_START); self.executable_path = image.executable_path; self.config = image.config; self.credentials = image.credentials; self.pending_input = None; self.suspended_thread = None; self.dormant_thread = None; self.parked_helpers.clear(); self.compiler_thread = None; self.worker_parent = None; self.worker_thread = None; self.next_helper_thread_id = NESTED_HELPER_THREAD_ID; self.pending_signal_sync = false; }
fn read_exec_vector(&self, address: GuestAddress) -> Result<Vec<Vec<u8>>, Errno> { if address == GuestAddress::NULL { return Ok(Vec::new()); } let mut vector = Vec::new(); let mut total_bytes = 0_usize; for index in 0..MAX_EXEC_VECTOR_ENTRIES { let offset = u64::try_from(index) .expect("exec vector index fits u64") .checked_mul(8) .ok_or(Errno::Fault)?; let pointer_address = address.checked_add(offset).ok_or(Errno::Fault)?; let mut pointer_bytes = [0; 8]; self.memory .read_exact(pointer_address, &mut pointer_bytes) .map_err(|_| Errno::Fault)?; let pointer = u64::from_le_bytes(pointer_bytes); if pointer == 0 { return Ok(vector); } let value = self .read_guest_path(GuestAddress::new(pointer)) .map_err(|error| { if error == Errno::InvalidArgument { Errno::ArgumentListTooLong } else { error } })?; total_bytes = total_bytes .checked_add(value.len() + 1) .filter(|total| *total <= MAX_EXEC_VECTOR_BYTES) .ok_or(Errno::ArgumentListTooLong)?; vector.push(value); } Err(Errno::ArgumentListTooLong) }
fn read_executable<F: HostFileSystem>( &self, filesystem: &mut F, path: &[u8], ) -> Result<Vec<u8>, Errno> { let metadata = filesystem.metadata(path).map_err(filesystem_error_errno)?; if metadata.file_type != FileType::Regular { return Err(Errno::PermissionDenied); } if metadata.size > self.limits.max_filesystem_bytes { return Err(Errno::OutOfMemory); } let size = usize::try_from(metadata.size).map_err(|_| Errno::OutOfMemory)?; let handle = filesystem .open( path, FileOpenOptions { access: FileAccess::ReadOnly, flags: FileOpenFlags::NONE, }, ) .map_err(filesystem_error_errno)?; let result = (|| { let mut executable = vec![0; size]; let mut filled = 0; while filled < executable.len() { let read = filesystem .read(handle, &mut executable[filled..]) .map_err(filesystem_error_errno)?; if read == 0 { return Err(Errno::ExecutableFormat); } filled += read; } Ok(executable) })(); let _ = filesystem.close(handle); result }
fn close_on_exec_descriptors<F: HostFileSystem>(&mut self, filesystem: &mut F) { let descriptors = self .descriptor_flags .iter() .filter_map(|(descriptor, flags)| { (flags & DESCRIPTOR_CLOEXEC != 0).then_some(*descriptor) }) .collect::<Vec<_>>(); for descriptor in descriptors { let _ = self.close_descriptor(filesystem, descriptor); } }
fn dispatch_getcwd(&mut self) { let buffer = GuestAddress::new(self.register(0)); let size = self.register(1); let required = u64::try_from(self.current_directory.len()) .expect("guest current-directory length fits u64") + 1; if size < required { self.set_return(Errno::Range.return_value()); return; } let mut bytes = self.current_directory.clone(); bytes.push(0); if self.memory.write(buffer, &bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(required); }
fn dispatch_chdir<F: HostFileSystem>(&mut self, filesystem: &mut F) { let path = match self.read_guest_path(GuestAddress::new(self.register(0))) { Ok(path) if !path.is_empty() => self.absolute_guest_path(path), Ok(_) => { self.set_return(Errno::NoEntry.return_value()); return; } Err(error) => { self.set_return(error.return_value()); return; } }; match filesystem.metadata(&path) { Ok(metadata) if metadata.file_type == FileType::Directory => { self.current_directory = path; self.set_return(0); } Ok(_) => self.set_return(Errno::NotDirectory.return_value()), Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_faccessat<F: HostFileSystem>(&mut self, filesystem: &mut F) { let mode = self.register(2); if mode & !7 != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; if let Err(error) = filesystem.metadata(&path) { self.set_return(filesystem_error_return(error)); return; } let write_requested = mode & 2 != 0; let mutable_path = path == b"/project" || path.starts_with(b"/project/") || path == b"/tmp" || path.starts_with(b"/tmp/"); self.set_return(if write_requested && !mutable_path { Errno::PermissionDenied.return_value() } else { 0 }); }
fn dispatch_readlinkat<F: HostFileSystem>(&mut self, filesystem: &mut F) { if self.register(3) == 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; let target = if path == b"/proc/self/exe" { self.executable_path.as_slice() } else { self.set_return(match filesystem.metadata(&path) { Ok(_) => Errno::InvalidArgument.return_value(), Err(error) => filesystem_error_return(error), }); return; }; let count = usize::try_from(self.register(3)) .unwrap_or(usize::MAX) .min(target.len()); if self .memory .write(GuestAddress::new(self.register(2)), &target[..count]) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(count as u64); }
fn dispatch_newfstatat<F: HostFileSystem>(&mut self, filesystem: &mut F) { if !matches!(self.register(3), 0 | AT_SYMLINK_NOFOLLOW) { self.set_return(Errno::InvalidArgument.return_value()); return; } let path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; let metadata = match filesystem.metadata(&path) { Ok(metadata) => metadata, Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; if self .memory .write( GuestAddress::new(self.register(2)), &linux_stat_bytes(metadata, &path), ) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(0); }
fn dispatch_utimensat<F: HostFileSystem>(&mut self, filesystem: &mut F) { if !matches!(self.register(3), 0 | AT_SYMLINK_NOFOLLOW) { self.set_return(Errno::InvalidArgument.return_value()); return; } let path = if self.register(1) == 0 { let Ok(descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let Some(path) = self.descriptor_paths.get(&descriptor).cloned() else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; path } else { match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } } }; let modified = match self.read_utimensat_modified_time() { Ok(modified) => modified, Err(error) => { self.set_return(error.return_value()); return; } }; let result = match modified { ModifiedTimeUpdate::Explicit(modified) => { filesystem.set_modified_time(&path, Some(modified)) } ModifiedTimeUpdate::Now => filesystem.set_modified_time(&path, None), ModifiedTimeUpdate::Preserve => filesystem.metadata(&path).map(|_| ()), }; self.set_return(match result { Ok(()) => 0, Err(error) => filesystem_error_return(error), }); }
fn read_utimensat_modified_time(&self) -> Result<ModifiedTimeUpdate, Errno> { let times = GuestAddress::new(self.register(2)); if times == GuestAddress::NULL { return Ok(ModifiedTimeUpdate::Now); } let mut bytes = [0; 32]; self.memory .read_exact(times, &mut bytes) .map_err(|_| Errno::Fault)?; for offset in [8, 24] { let nanoseconds = i64::from_le_bytes( bytes[offset..offset + 8] .try_into() .expect("timespec nanoseconds occupy eight bytes"), ); if !matches!(nanoseconds, 0..1_000_000_000 | UTIME_NOW | UTIME_OMIT) { return Err(Errno::InvalidArgument); } } let nanoseconds = i64::from_le_bytes( bytes[24..32] .try_into() .expect("mtime nanoseconds occupy eight bytes"), ); if nanoseconds == UTIME_OMIT { return Ok(ModifiedTimeUpdate::Preserve); } if nanoseconds == UTIME_NOW { return Ok(ModifiedTimeUpdate::Now); } let seconds = i64::from_le_bytes( bytes[16..24] .try_into() .expect("mtime seconds occupy eight bytes"), ); Ok(ModifiedTimeUpdate::Explicit(HostTime { seconds, nanoseconds: u32::try_from(nanoseconds) .expect("validated nonnegative nanoseconds fit u32"), })) }
fn dispatch_fstat<F: HostFileSystem>(&mut self, filesystem: &mut F) { let file_descriptor = self.register(0); let standard_descriptor = matches!( file_descriptor, STANDARD_INPUT | STANDARD_OUTPUT | STANDARD_ERROR ) && u32::try_from(file_descriptor) .is_ok_and(|descriptor| !self.descriptor_exists(descriptor)); let stat = if standard_descriptor { linux_terminal_stat_bytes() } else { let Ok(file_descriptor) = u32::try_from(file_descriptor) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; if let Some(end) = self.pipe_descriptors.get(&file_descriptor) { linux_pipe_stat_bytes(match end { PipeEnd::Read(pipe_id) | PipeEnd::Write(pipe_id) => *pipe_id, PipeEnd::Socket { read, .. } => *read, }) } else { let Some(path) = self.descriptor_paths.get(&file_descriptor) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; match filesystem.metadata(path) { Ok(metadata) => linux_stat_bytes(metadata, path), Err(error) => { self.set_return(filesystem_error_return(error)); return; } } } }; if self .memory .write(GuestAddress::new(self.register(1)), &stat) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(0); }
fn dispatch_write<T: HostTerminal, F: HostFileSystem>( &mut self, terminal: &mut T, filesystem: &mut F, ) -> Result<(), ExecutionError<T::Error>> { let file_descriptor = self.register(0); if !matches!(file_descriptor, STANDARD_OUTPUT | STANDARD_ERROR) || u32::try_from(file_descriptor) .is_ok_and(|descriptor| self.descriptor_exists(descriptor)) { self.dispatch_file_write(filesystem); return Ok(()); } let stream = if file_descriptor == STANDARD_OUTPUT { TerminalStream::StandardOutput } else { TerminalStream::StandardError }; let address = GuestAddress::new(self.register(1)); let count = self.register(2); let next_total = self .output_bytes .checked_add(count) .filter(|total| *total <= self.limits.max_output_bytes) .ok_or(ExecutionError::Trap(Trap::ResourceLimit( ResourceLimit::Output, )))?; let Ok(host_count) = usize::try_from(count) else { self.set_return(Errno::InvalidArgument.return_value()); return Ok(()); }; let mut bytes = vec![0; host_count]; if self.memory.read_exact(address, &mut bytes).is_err() { self.set_return(Errno::Fault.return_value()); return Ok(()); } terminal .write(stream, &bytes) .map_err(ExecutionError::Host)?; self.output_bytes = next_total; self.set_return(count); Ok(()) }
fn dispatch_writev<T: HostTerminal, F: HostFileSystem>( &mut self, terminal: &mut T, filesystem: &mut F, ) -> Result<(), ExecutionError<T::Error>> { const IOVEC_SIZE: u64 = 16; const MAX_IOVEC_COUNT: u64 = 1_024;
let file_descriptor = self.register(0); let iovecs = GuestAddress::new(self.register(1)); let iovec_count = self.register(2); if iovec_count > MAX_IOVEC_COUNT { self.set_return(Errno::InvalidArgument.return_value()); return Ok(()); } let mut bytes = Vec::new(); for index in 0..iovec_count { let Some(offset) = index.checked_mul(IOVEC_SIZE) else { self.set_return(Errno::Fault.return_value()); return Ok(()); }; let Some(address) = iovecs.checked_add(offset) else { self.set_return(Errno::Fault.return_value()); return Ok(()); }; let mut iovec = [0; 16]; if self.memory.read_exact(address, &mut iovec).is_err() { self.set_return(Errno::Fault.return_value()); return Ok(()); } let base = u64::from_le_bytes(iovec[..8].try_into().expect("fixed iovec base")); let length = u64::from_le_bytes(iovec[8..].try_into().expect("fixed iovec length")); let Some(total) = (bytes.len() as u64).checked_add(length) else { self.set_return(Errno::InvalidArgument.return_value()); return Ok(()); }; if total > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return Ok(()); } let host_length = usize::try_from(length).expect("bounded iovec length fits usize"); let previous_length = bytes.len(); bytes.resize(previous_length + host_length, 0); if length != 0 && self .memory .read_exact(GuestAddress::new(base), &mut bytes[previous_length..]) .is_err() { self.set_return(Errno::Fault.return_value()); return Ok(()); } }
if matches!(file_descriptor, STANDARD_OUTPUT | STANDARD_ERROR) && u32::try_from(file_descriptor) .is_ok_and(|descriptor| !self.descriptor_exists(descriptor)) { let total = bytes.len() as u64; let next_total = self .output_bytes .checked_add(total) .filter(|output| *output <= self.limits.max_output_bytes) .ok_or(ExecutionError::Trap(Trap::ResourceLimit( ResourceLimit::Output, )))?; let stream = if file_descriptor == STANDARD_OUTPUT { TerminalStream::StandardOutput } else { TerminalStream::StandardError }; terminal .write(stream, &bytes) .map_err(ExecutionError::Host)?; self.output_bytes = next_total; self.set_return(total); return Ok(()); }
self.dispatch_descriptor_write(filesystem, file_descriptor, &bytes); Ok(()) }
fn dispatch_mkdirat<F: HostFileSystem>(&mut self, filesystem: &mut F) { let path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; match filesystem.create_directory(&path) { Ok(()) => self.set_return(0), Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_unlinkat<F: HostFileSystem>(&mut self, filesystem: &mut F) { let flags = self.register(2); if !matches!(flags, 0 | AT_REMOVE_DIRECTORY) { self.set_return(Errno::InvalidArgument.return_value()); return; } let path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; match filesystem.remove(&path, flags == AT_REMOVE_DIRECTORY) { Ok(()) => self.set_return(0), Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_linkat<F: HostFileSystem>(&mut self, filesystem: &mut F) { if self.register(4) != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let old_path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; let new_path = match self .read_guest_path(GuestAddress::new(self.register(3))) .and_then(|path| self.resolve_at_path(filesystem, self.register(2), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; let metadata = match filesystem.metadata(&old_path) { Ok(metadata) if metadata.file_type == FileType::Regular => metadata, Ok(_) => { self.set_return(Errno::PermissionDenied.return_value()); return; } Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; match filesystem.metadata(&new_path) { Ok(_) => { self.set_return(Errno::AlreadyExists.return_value()); return; } Err(FileSystemError::NotFound) => {} Err(error) => { self.set_return(filesystem_error_return(error)); return; } } if metadata.size > self.limits.max_filesystem_bytes { self.set_return(Errno::NoSpace.return_value()); return; } let result = copy_file_for_link(filesystem, &old_path, &new_path); self.set_return(match result { Ok(()) => 0, Err(error) => filesystem_error_return(error), }); }
fn dispatch_renameat<F: HostFileSystem>(&mut self, filesystem: &mut F) { let old_path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; let new_path = match self .read_guest_path(GuestAddress::new(self.register(3))) .and_then(|path| self.resolve_at_path(filesystem, self.register(2), path)) { Ok(path) => path, Err(error) => { self.set_return(error.return_value()); return; } }; match filesystem.rename(&old_path, &new_path) { Ok(()) => self.set_return(0), Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_getdents64<F: HostFileSystem>(&mut self, filesystem: &mut F) { let Some(handle) = self.file_handle(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let Ok(capacity) = usize::try_from(self.register(2)) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; if self.register(2) > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return; } let mut bytes = Vec::new(); let mut consumed_entries = 0_i64; loop { let entry = match filesystem.read_directory(handle, 1) { Ok(entries) => entries.into_iter().next(), Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; let Some(entry) = entry else { break; }; consumed_entries += 1; let unaligned = DIRECTORY_ENTRY_HEADER_SIZE .checked_add(entry.name.len()) .and_then(|length| length.checked_add(1)); let Some(record_length) = unaligned.and_then(|length| { length .checked_add(DIRECTORY_ENTRY_ALIGNMENT - 1) .map(|length| length & !(DIRECTORY_ENTRY_ALIGNMENT - 1)) }) else { let _ = filesystem.seek(handle, -1, FileSeekFrom::Current); self.set_return(Errno::InvalidArgument.return_value()); return; }; if bytes.len().saturating_add(record_length) > capacity { let _ = filesystem.seek(handle, -1, FileSeekFrom::Current); consumed_entries -= 1; if bytes.is_empty() { self.set_return(Errno::InvalidArgument.return_value()); return; } break; } let Ok(record_length_u16) = u16::try_from(record_length) else { let _ = filesystem.seek(handle, -1, FileSeekFrom::Current); self.set_return(Errno::InvalidArgument.return_value()); return; }; let record_start = bytes.len(); bytes.resize(record_start + record_length, 0); let inode = directory_inode(&entry.name); bytes[record_start..record_start + 8].copy_from_slice(&inode.to_le_bytes()); bytes[record_start + 8..record_start + 16] .copy_from_slice(&consumed_entries.to_le_bytes()); bytes[record_start + 16..record_start + 18] .copy_from_slice(&record_length_u16.to_le_bytes()); bytes[record_start + 18] = match entry.file_type { FileType::Directory => DIRECTORY_TYPE, FileType::Regular => REGULAR_FILE_TYPE, }; bytes[record_start + DIRECTORY_ENTRY_HEADER_SIZE ..record_start + DIRECTORY_ENTRY_HEADER_SIZE + entry.name.len()] .copy_from_slice(&entry.name); } if self .memory .write(GuestAddress::new(self.register(1)), &bytes) .is_err() { if consumed_entries != 0 { let _ = filesystem.seek(handle, -consumed_entries, FileSeekFrom::Current); } self.set_return(Errno::Fault.return_value()); return; } self.set_return(bytes.len() as u64); }
fn dispatch_openat<F: HostFileSystem>(&mut self, filesystem: &mut F) { let flags = self.register(2); let access = match flags & OPEN_ACCESS_MASK { 0 => FileAccess::ReadOnly, 1 => FileAccess::WriteOnly, 2 => FileAccess::ReadWrite, _ => { self.set_return(Errno::InvalidArgument.return_value()); return; } }; if flags & !SUPPORTED_OPEN_FLAGS != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let path = match self .read_guest_path(GuestAddress::new(self.register(1))) .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) { Ok(path) => path, Err(errno) => { self.set_return(errno.return_value()); return; } }; let Some(file_descriptor) = self.allocate_file_descriptor() else { self.set_return(Errno::TooManyOpenFiles.return_value()); return; }; let mut open_flags = FileOpenFlags::NONE; for (linux_flag, host_flag) in [ (OPEN_CREATE, FileOpenFlags::CREATE), (OPEN_EXCLUSIVE, FileOpenFlags::EXCLUSIVE), (OPEN_TRUNCATE, FileOpenFlags::TRUNCATE), (OPEN_APPEND, FileOpenFlags::APPEND), (OPEN_DIRECTORY, FileOpenFlags::DIRECTORY), (OPEN_DIRECTORY_FIXTURE_COMPAT, FileOpenFlags::DIRECTORY), ] { if flags & linux_flag != 0 { open_flags = open_flags.union(host_flag); } } match filesystem.open( &path, FileOpenOptions { access, flags: open_flags, }, ) { Ok(handle) => { self.file_descriptors.insert(file_descriptor, handle); self.descriptor_paths.insert(file_descriptor, path); self.descriptor_flags.insert( file_descriptor, if flags & OPEN_CLOEXEC == 0 { 0 } else { DESCRIPTOR_CLOEXEC }, ); self.set_return(u64::from(file_descriptor)); } Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_close<F: HostFileSystem>(&mut self, filesystem: &mut F) { let Ok(file_descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; match self.close_descriptor(filesystem, file_descriptor) { Ok(()) => self.set_return(0), Err(error) => self.set_return(error.return_value()), } }
fn dispatch_dup3<F: HostFileSystem>(&mut self, filesystem: &mut F) { let (Ok(source), Ok(destination)) = ( u32::try_from(self.register(0)), u32::try_from(self.register(1)), ) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let flags = self.register(2); if source == destination || flags & !OPEN_CLOEXEC != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } if destination >= self.limits.max_open_files || !self.descriptor_exists(source) { self.set_return(Errno::BadFileDescriptor.return_value()); return; } if self.descriptor_exists(destination) && let Err(error) = self.close_descriptor(filesystem, destination) { self.set_return(error.return_value()); return; }
self.duplicate_descriptor(source, destination, flags & OPEN_CLOEXEC != 0); self.set_return(u64::from(destination)); }
fn duplicate_descriptor(&mut self, source: u32, destination: u32, close_on_exec: bool) { if let Some(handle) = self.file_descriptors.get(&source).copied() { self.file_descriptors.insert(destination, handle); if let Some(path) = self.descriptor_paths.get(&source).cloned() { self.descriptor_paths.insert(destination, path); } } else if let Some(end) = self.pipe_descriptors.get(&source).copied() { self.pipe_descriptors.insert(destination, end); } let status = self .descriptor_status_flags .get(&source) .copied() .unwrap_or(0); self.descriptor_status_flags.insert(destination, status); self.descriptor_flags.insert( destination, if close_on_exec { DESCRIPTOR_CLOEXEC } else { 0 }, ); }
fn dispatch_pipe2(&mut self) { let pipe_descriptors = GuestAddress::new(self.register(0)); let flags = self.register(1); if flags & !SUPPORTED_PIPE_FLAGS != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let Some([reader, writer]) = self.allocate_file_descriptors() else { self.set_return(Errno::TooManyOpenFiles.return_value()); return; }; let pipe_id = self.next_pipe_id; let Some(next_pipe_id) = pipe_id.checked_add(1) else { self.set_return(Errno::TooManyOpenFiles.return_value()); return; }; let mut descriptor_bytes = [0; 8]; descriptor_bytes[..4].copy_from_slice(&reader.to_le_bytes()); descriptor_bytes[4..].copy_from_slice(&writer.to_le_bytes()); if self .memory .write(pipe_descriptors, &descriptor_bytes) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.next_pipe_id = next_pipe_id; self.pipes.insert( pipe_id, AnonymousPipe { bytes: VecDeque::new(), reader_open: true, writer_open: true, }, ); self.pipe_descriptors.insert(reader, PipeEnd::Read(pipe_id)); self.pipe_descriptors .insert(writer, PipeEnd::Write(pipe_id)); let descriptor_flags = if flags & OPEN_CLOEXEC == 0 { 0 } else { DESCRIPTOR_CLOEXEC }; self.descriptor_flags.insert(reader, descriptor_flags); self.descriptor_flags.insert(writer, descriptor_flags); self.descriptor_status_flags .insert(reader, flags & OPEN_NONBLOCK); self.descriptor_status_flags .insert(writer, (flags & OPEN_NONBLOCK) | 1); self.set_return(0); }
fn dispatch_socketpair(&mut self) { let domain = self.register(0); let socket_type = self.register(1); let protocol = self.register(2); let sockets = GuestAddress::new(self.register(3)); let kind = socket_type & SOCKET_TYPE_MASK; let flags = socket_type & !SOCKET_TYPE_MASK; if domain != ADDRESS_FAMILY_UNIX || !matches!(kind, SOCKET_STREAM | SOCKET_SEQPACKET) || protocol != 0 || flags & !SUPPORTED_PIPE_FLAGS != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let Some([first, second]) = self.allocate_file_descriptors() else { self.set_return(Errno::TooManyOpenFiles.return_value()); return; }; let first_pipe = self.next_pipe_id; let Some(second_pipe) = first_pipe.checked_add(1) else { self.set_return(Errno::TooManyOpenFiles.return_value()); return; }; let Some(next_pipe_id) = second_pipe.checked_add(1) else { self.set_return(Errno::TooManyOpenFiles.return_value()); return; }; let mut descriptor_bytes = [0; 8]; descriptor_bytes[..4].copy_from_slice(&first.to_le_bytes()); descriptor_bytes[4..].copy_from_slice(&second.to_le_bytes()); if self.memory.write(sockets, &descriptor_bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } self.next_pipe_id = next_pipe_id; for pipe_id in [first_pipe, second_pipe] { self.pipes.insert( pipe_id, AnonymousPipe { bytes: VecDeque::new(), reader_open: true, writer_open: true, }, ); } self.pipe_descriptors.insert( first, PipeEnd::Socket { read: second_pipe, write: first_pipe, }, ); self.pipe_descriptors.insert( second, PipeEnd::Socket { read: first_pipe, write: second_pipe, }, ); let descriptor_flags = if flags & OPEN_CLOEXEC == 0 { 0 } else { DESCRIPTOR_CLOEXEC }; self.descriptor_flags.insert(first, descriptor_flags); self.descriptor_flags.insert(second, descriptor_flags); let status_flags = flags & OPEN_NONBLOCK; self.descriptor_status_flags.insert(first, status_flags | 2); self.descriptor_status_flags .insert(second, status_flags | 2); self.set_return(0); }
fn dispatch_recvfrom(&mut self) { let Ok(descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let flags = self.register(3); if flags & !(MESSAGE_PEEK | MESSAGE_DONTWAIT) != 0 || self.register(4) != 0 || self.register(5) != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let Some(PipeEnd::Socket { read, .. }) = self.pipe_descriptors.get(&descriptor).copied() else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; self.dispatch_pipe_read_with_options(read, flags & MESSAGE_PEEK != 0); }
fn dispatch_ppoll(&mut self) { const POLL_DESCRIPTOR_SIZE: u64 = 8;
let descriptors = GuestAddress::new(self.register(0)); let count = self.register(1); if count > MAX_POLL_DESCRIPTORS { self.set_return(Errno::InvalidArgument.return_value()); return; } let mut ready = 0_u64; for index in 0..count { let Some(address) = index .checked_mul(POLL_DESCRIPTOR_SIZE) .and_then(|offset| descriptors.checked_add(offset)) else { self.set_return(Errno::Fault.return_value()); return; }; let mut bytes = [0_u8; 8]; if self.memory.read_exact(address, &mut bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } let descriptor = i32::from_le_bytes(bytes[..4].try_into().expect("poll fd field")); let requested = u16::from_le_bytes(bytes[4..6].try_into().expect("poll events field")); let returned = self.poll_descriptor(descriptor, requested); if returned != 0 { ready += 1; } if self .memory .write( address.checked_add(6).expect("poll revents address"), &returned.to_le_bytes(), ) .is_err() { self.set_return(Errno::Fault.return_value()); return; } } self.set_return(ready); }
fn poll_descriptor(&self, descriptor: i32, requested: u16) -> u16 { let Ok(descriptor) = u32::try_from(descriptor) else { return 0; }; match self.pipe_descriptors.get(&descriptor).copied() { Some(PipeEnd::Read(pipe_id)) => self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { let mut returned = 0; if requested & POLL_INPUT != 0 && !pipe.bytes.is_empty() { returned |= POLL_INPUT; } if !pipe.writer_open { returned |= POLL_HANGUP; } returned }), Some(PipeEnd::Write(pipe_id)) => { self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { if !pipe.reader_open { POLL_ERROR } else if requested & POLL_OUTPUT != 0 && pipe.bytes.len() < PIPE_CAPACITY_BYTES { POLL_OUTPUT } else { 0 } }) } Some(PipeEnd::Socket { read, write }) => { self.poll_pipe_read(read, requested) | self.poll_pipe_write(write, requested) } None if self.file_descriptors.contains_key(&descriptor) => requested, None if descriptor <= 2 => requested, None => POLL_INVALID, } }
fn poll_pipe_read(&self, pipe_id: u64, requested: u16) -> u16 { self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { let mut returned = 0; if requested & POLL_INPUT != 0 && !pipe.bytes.is_empty() { returned |= POLL_INPUT; } if !pipe.writer_open { returned |= POLL_HANGUP; } returned }) }
fn poll_pipe_write(&self, pipe_id: u64, requested: u16) -> u16 { self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { if !pipe.reader_open { POLL_ERROR } else if requested & POLL_OUTPUT != 0 && pipe.bytes.len() < PIPE_CAPACITY_BYTES { POLL_OUTPUT } else { 0 } }) }
fn dispatch_fcntl(&mut self) { let Ok(file_descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let standard_descriptor = file_descriptor <= 2 && !self.descriptor_exists(file_descriptor); if !standard_descriptor && !self.descriptor_exists(file_descriptor) { self.set_return(Errno::BadFileDescriptor.return_value()); return; } match self.register(1) { command @ (FCNTL_DUPLICATE | FCNTL_DUPLICATE_CLOEXEC) if !standard_descriptor => { let Ok(minimum) = u32::try_from(self.register(2)) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; let Some(destination) = (minimum.max(FIRST_FILE_DESCRIPTOR) ..self.limits.max_open_files) .find(|descriptor| !self.descriptor_exists(*descriptor)) else { self.set_return(Errno::TooManyOpenFiles.return_value()); return; }; self.duplicate_descriptor( file_descriptor, destination, command == FCNTL_DUPLICATE_CLOEXEC, ); self.set_return(u64::from(destination)); } FCNTL_GET_DESCRIPTOR_FLAGS => { self.set_return(*self.descriptor_flags.get(&file_descriptor).unwrap_or(&0)); } FCNTL_SET_DESCRIPTOR_FLAGS if self.register(2) & !DESCRIPTOR_CLOEXEC == 0 => { self.descriptor_flags .insert(file_descriptor, self.register(2)); self.set_return(0); } FCNTL_GET_STATUS_FLAGS => self.set_return(if standard_descriptor { u64::from(file_descriptor != 0) } else { *self .descriptor_status_flags .get(&file_descriptor) .unwrap_or(&0) }), FCNTL_SET_STATUS_FLAGS if self.pipe_descriptors.contains_key(&file_descriptor) => { let access = self .descriptor_status_flags .get(&file_descriptor) .copied() .unwrap_or(0) & OPEN_ACCESS_MASK; self.descriptor_status_flags .insert(file_descriptor, access | (self.register(2) & OPEN_NONBLOCK)); self.set_return(0); } FCNTL_SET_STATUS_FLAGS if !self.pipe_descriptors.contains_key(&file_descriptor) && self.register(2) == 0 => { self.set_return(0); } _ => self.set_return(Errno::InvalidArgument.return_value()), } }
fn dispatch_flock(&mut self) { let Ok(file_descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let operation = self.register(1); let lock = operation & !FILE_LOCK_NONBLOCKING; if !self.file_descriptors.contains_key(&file_descriptor) { self.set_return(Errno::BadFileDescriptor.return_value()); } else if !matches!( lock, FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE | FILE_LOCK_UNLOCK ) || operation & !(FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE | FILE_LOCK_NONBLOCKING | FILE_LOCK_UNLOCK) != 0 { self.set_return(Errno::InvalidArgument.return_value()); } else { self.set_return(0); } }
fn dispatch_ioctl(&mut self) { let Ok(file_descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let standard_descriptor = file_descriptor <= 2 && !self.descriptor_exists(file_descriptor); if !standard_descriptor && !self.descriptor_exists(file_descriptor) { self.set_return(Errno::BadFileDescriptor.return_value()); return; } match self.register(1) { IOCTL_SET_CLOSE_ON_EXEC => { self.descriptor_flags .insert(file_descriptor, DESCRIPTOR_CLOEXEC); self.set_return(0); } IOCTL_CLEAR_CLOSE_ON_EXEC => { self.descriptor_flags.insert(file_descriptor, 0); self.set_return(0); } IOCTL_SET_NONBLOCK if self.pipe_descriptors.contains_key(&file_descriptor) => { let mut bytes = [0_u8; 4]; if self .memory .read_exact(GuestAddress::new(self.register(2)), &mut bytes) .is_err() { self.set_return(Errno::Fault.return_value()); return; } let enabled = u32::from_le_bytes(bytes) != 0; let status = self .descriptor_status_flags .get(&file_descriptor) .copied() .unwrap_or(0); self.descriptor_status_flags.insert( file_descriptor, if enabled { status | OPEN_NONBLOCK } else { status & !OPEN_NONBLOCK }, ); self.set_return(0); } _ => self.set_return(Errno::NotTty.return_value()), } }
fn dispatch_lseek<F: HostFileSystem>(&mut self, filesystem: &mut F) { if u32::try_from(self.register(0)) .ok() .is_some_and(|descriptor| self.pipe_descriptors.contains_key(&descriptor)) { self.set_return(Errno::IllegalSeek.return_value()); return; } let Some(handle) = self.file_handle(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let from = match self.register(2) { 0 => FileSeekFrom::Start, 1 => FileSeekFrom::Current, 2 => FileSeekFrom::End, _ => { self.set_return(Errno::InvalidArgument.return_value()); return; } }; match filesystem.seek(handle, self.register(1).cast_signed(), from) { Ok(position) => self.set_return(position), Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_ftruncate<F: HostFileSystem>(&mut self, filesystem: &mut F) { let Some(handle) = self.file_handle(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; match filesystem.set_length(handle, self.register(1)) { Ok(()) => self.set_return(0), Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_terminal_read<I: HostInput>( &mut self, input: &mut I, arguments: [u64; 6], ) -> Option<InputRequest> { let count = self.register(2); let Ok(host_count) = usize::try_from(count) else { self.set_return(Errno::InvalidArgument.return_value()); return None; }; if count > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return None; } if host_count == 0 { self.set_return(0); return None; } let pending = PendingInput { address: GuestAddress::new(self.register(1)), count, arguments, }; let mut bytes = vec![0; host_count]; match input.read(&mut bytes) { TerminalInputRead::WouldBlock => { self.pending_input = Some(pending); Some(InputRequest { max_bytes: count }) } TerminalInputRead::Available(read) => { self.finish_terminal_read(pending, &bytes[..read.min(host_count)]); None } } }
fn resume_pending_input<I: HostInput>(&mut self, input: &mut I) -> Option<InputRequest> { let pending = self.pending_input.take()?; let host_count = usize::try_from(pending.count).expect("validated pending terminal count fits the host"); let mut bytes = vec![0; host_count]; match input.read(&mut bytes) { TerminalInputRead::WouldBlock => { self.pending_input = Some(pending); Some(InputRequest { max_bytes: pending.count, }) } TerminalInputRead::Available(read) => { let returned = self.finish_terminal_read(pending, &bytes[..read.min(host_count)]); self.trace.push(SyscallEvent { number: Syscall::Read.number(), arguments: pending.arguments, outcome: SyscallOutcome::Returned(returned), }); None } } }
fn finish_terminal_read(&mut self, pending: PendingInput, bytes: &[u8]) -> u64 { let returned = if self.memory.write(pending.address, bytes).is_ok() { bytes.len() as u64 } else { Errno::Fault.return_value() }; self.set_return(returned); returned }
fn dispatch_read<F: HostFileSystem>(&mut self, filesystem: &mut F) { if let Ok(file_descriptor) = u32::try_from(self.register(0)) && let Some(end) = self.pipe_descriptors.get(&file_descriptor).copied() { match end { PipeEnd::Read(pipe_id) => self.dispatch_pipe_read(pipe_id), PipeEnd::Write(_) => self.set_return(Errno::BadFileDescriptor.return_value()), PipeEnd::Socket { read, .. } => self.dispatch_pipe_read(read), } return; } let Some(handle) = self.file_handle(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let count = self.register(2); let Ok(host_count) = usize::try_from(count) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; if count > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return; } let position = match filesystem.seek(handle, 0, FileSeekFrom::Current) { Ok(position) => position, Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; let mut bytes = vec![0; host_count]; let read = match filesystem.read(handle, &mut bytes) { Ok(read) => read, Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; if self .memory .write(GuestAddress::new(self.register(1)), &bytes[..read]) .is_err() { if let Ok(position) = i64::try_from(position) { let _ = filesystem.seek(handle, position, FileSeekFrom::Start); } self.set_return(Errno::Fault.return_value()); return; } self.set_return(read as u64); }
fn dispatch_pipe_read(&mut self, pipe_id: u64) { self.dispatch_pipe_read_with_options(pipe_id, false); }
fn dispatch_pipe_read_with_options(&mut self, pipe_id: u64, peek: bool) { let count = self.register(2); let Ok(host_count) = usize::try_from(count) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; if count > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return; } if host_count == 0 { self.set_return(0); return; } let Some(pipe) = self.pipes.get(&pipe_id) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; if pipe.bytes.is_empty() { self.set_return(if pipe.writer_open { Errno::TryAgain.return_value() } else { 0 }); return; } let read = host_count.min(pipe.bytes.len()); let bytes = pipe.bytes.iter().take(read).copied().collect::<Vec<_>>(); if self .memory .write(GuestAddress::new(self.register(1)), &bytes) .is_err() { self.set_return(Errno::Fault.return_value()); return; } if !peek { self.pipes .get_mut(&pipe_id) .expect("validated pipe remains present") .bytes .drain(..read); } self.set_return(read as u64); }
fn dispatch_pread64<F: HostFileSystem>(&mut self, filesystem: &mut F) { let Some(handle) = self.file_handle(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let count = self.register(2); let (Ok(host_count), Ok(offset)) = (usize::try_from(count), i64::try_from(self.register(3))) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; if count > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return; } let original_position = match filesystem.seek(handle, 0, FileSeekFrom::Current) { Ok(position) => position, Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; if let Err(error) = filesystem.seek(handle, offset, FileSeekFrom::Start) { self.set_return(filesystem_error_return(error)); return; } let mut bytes = vec![0; host_count]; let read = filesystem.read(handle, &mut bytes); let restored = i64::try_from(original_position) .ok() .and_then(|position| filesystem.seek(handle, position, FileSeekFrom::Start).ok()); if restored.is_none() { self.set_return(Errno::InvalidArgument.return_value()); return; } let read = match read { Ok(read) => read, Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; if self .memory .write(GuestAddress::new(self.register(1)), &bytes[..read]) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(read as u64); }
fn dispatch_sendfile<F: HostFileSystem>(&mut self, filesystem: &mut F) { if self.register(2) != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } self.copy_file_bytes( filesystem, self.register(1), self.register(0), self.register(3), ); }
fn dispatch_copy_file_range<F: HostFileSystem>(&mut self, filesystem: &mut F) { if self.register(1) != 0 || self.register(3) != 0 || self.register(5) != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } self.copy_file_bytes( filesystem, self.register(0), self.register(2), self.register(4), ); }
fn copy_file_bytes<F: HostFileSystem>( &mut self, filesystem: &mut F, input_descriptor: u64, output_descriptor: u64, count: u64, ) { let (Some(input), Some(output)) = ( self.file_handle(input_descriptor), self.file_handle(output_descriptor), ) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; if input == output { self.set_return(Errno::InvalidArgument.return_value()); return; } let requested = usize::try_from(count) .unwrap_or(usize::MAX) .min(FILE_COPY_CHUNK_BYTES); let mut bytes = vec![0; requested]; let read = match filesystem.read(input, &mut bytes) { Ok(read) => read, Err(error) => { self.set_return(filesystem_error_return(error)); return; } }; let written = match filesystem.write(output, &bytes[..read]) { Ok(written) => written, Err(error) => { if let Ok(rewind) = i64::try_from(read) { let _ = filesystem.seek(input, -rewind, FileSeekFrom::Current); } self.set_return(filesystem_error_return(error)); return; } }; if written < read && let Ok(rewind) = i64::try_from(read - written) { let _ = filesystem.seek(input, -rewind, FileSeekFrom::Current); } self.set_return(written as u64); }
fn dispatch_file_write<F: HostFileSystem>(&mut self, filesystem: &mut F) { let file_descriptor = self.register(0); let count = self.register(2); let Ok(host_count) = usize::try_from(count) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; if count > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return; } let mut bytes = vec![0; host_count]; if self .memory .read_exact(GuestAddress::new(self.register(1)), &mut bytes) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.dispatch_descriptor_write(filesystem, file_descriptor, &bytes); }
fn dispatch_descriptor_write<F: HostFileSystem>( &mut self, filesystem: &mut F, file_descriptor: u64, bytes: &[u8], ) { if let Ok(file_descriptor) = u32::try_from(file_descriptor) && let Some(end) = self.pipe_descriptors.get(&file_descriptor).copied() { match end { PipeEnd::Read(_) => self.set_return(Errno::BadFileDescriptor.return_value()), PipeEnd::Write(pipe_id) => self.dispatch_pipe_write(pipe_id, bytes), PipeEnd::Socket { write, .. } => self.dispatch_pipe_write(write, bytes), } return; } let Some(handle) = self.file_handle(file_descriptor) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; match filesystem.write(handle, bytes) { Ok(written) => self.set_return(written as u64), Err(error) => self.set_return(filesystem_error_return(error)), } }
fn dispatch_pipe_write(&mut self, pipe_id: u64, bytes: &[u8]) { let Some(pipe) = self.pipes.get_mut(&pipe_id) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; if bytes.is_empty() { self.set_return(0); return; } if !pipe.reader_open { self.set_return(Errno::BrokenPipe.return_value()); return; } let available = PIPE_CAPACITY_BYTES.saturating_sub(pipe.bytes.len()); if available == 0 && !bytes.is_empty() { self.set_return(Errno::TryAgain.return_value()); return; } let written = available.min(bytes.len()); pipe.bytes.extend(&bytes[..written]); self.set_return(written as u64); }
fn read_guest_path(&self, address: GuestAddress) -> Result<Vec<u8>, Errno> { let mut path = Vec::new(); for offset in 0..MAX_PATH_BYTES { let Some(address) = address.checked_add(offset as u64) else { return Err(Errno::Fault); }; let mut byte = [0]; self.memory .read_exact(address, &mut byte) .map_err(|_| Errno::Fault)?; if byte[0] == 0 { return Ok(path); } path.push(byte[0]); } Err(Errno::InvalidArgument) }
fn absolute_guest_path(&self, path: Vec<u8>) -> Vec<u8> { if path.starts_with(b"/") { return path; } let mut absolute = self.current_directory.clone(); absolute.push(b'/'); absolute.extend(path); absolute }
fn resolve_at_path<F: HostFileSystem>( &self, filesystem: &mut F, directory_descriptor: u64, path: Vec<u8>, ) -> Result<Vec<u8>, Errno> { if path.is_empty() { return Err(Errno::NoEntry); } if path.starts_with(b"/") { return Ok(path); } let mut absolute = if directory_descriptor == AT_FDCWD { self.current_directory.clone() } else { let descriptor = u32::try_from(directory_descriptor).map_err(|_| Errno::BadFileDescriptor)?; let path = self .descriptor_paths .get(&descriptor) .cloned() .ok_or(Errno::BadFileDescriptor)?; let metadata = filesystem.metadata(&path).map_err(filesystem_error_errno)?; if metadata.file_type != FileType::Directory { return Err(Errno::NotDirectory); } path }; absolute.push(b'/'); absolute.extend(path); Ok(absolute) }
fn allocate_file_descriptor(&self) -> Option<u32> { let open_count = u32::try_from(self.file_descriptors.len() + self.pipe_descriptors.len()) .ok()? .checked_add(3)?; if open_count >= self.limits.max_open_files { return None; } (FIRST_FILE_DESCRIPTOR..self.limits.max_open_files) .find(|descriptor| !self.descriptor_exists(*descriptor)) }
fn allocate_file_descriptors(&self) -> Option<[u32; 2]> { let mut available = (FIRST_FILE_DESCRIPTOR..self.limits.max_open_files) .filter(|descriptor| !self.descriptor_exists(*descriptor)); Some([available.next()?, available.next()?]) }
fn descriptor_exists(&self, file_descriptor: u32) -> bool { self.file_descriptors.contains_key(&file_descriptor) || self.pipe_descriptors.contains_key(&file_descriptor) }
fn close_descriptor<F: HostFileSystem>( &mut self, filesystem: &mut F, file_descriptor: u32, ) -> Result<(), Errno> { if let Some(handle) = self.file_descriptors.get(&file_descriptor).copied() { if !self.other_file_descriptor_references(file_descriptor, handle) { filesystem.close(handle).map_err(filesystem_error_errno)?; } self.file_descriptors.remove(&file_descriptor); self.descriptor_paths.remove(&file_descriptor); } else if let Some(end) = self.pipe_descriptors.remove(&file_descriptor) { let pipe_id = match end { PipeEnd::Read(pipe_id) => { if !self.pipe_reader_is_referenced(pipe_id) && let Some(pipe) = self.pipes.get_mut(&pipe_id) { pipe.reader_open = false; } pipe_id } PipeEnd::Write(pipe_id) => { if !self.pipe_writer_is_referenced(pipe_id) && let Some(pipe) = self.pipes.get_mut(&pipe_id) { pipe.writer_open = false; } pipe_id } PipeEnd::Socket { read, write } => { if !self.pipe_reader_is_referenced(read) && let Some(pipe) = self.pipes.get_mut(&read) { pipe.reader_open = false; } if !self.pipe_writer_is_referenced(write) && let Some(pipe) = self.pipes.get_mut(&write) { pipe.writer_open = false; } if self .pipes .get(&read) .is_some_and(|pipe| !pipe.reader_open && !pipe.writer_open) { self.pipes.remove(&read); } write } }; if self .pipes .get(&pipe_id) .is_some_and(|pipe| !pipe.reader_open && !pipe.writer_open) { self.pipes.remove(&pipe_id); } } else { return Err(Errno::BadFileDescriptor); } self.descriptor_flags.remove(&file_descriptor); self.descriptor_status_flags.remove(&file_descriptor); Ok(()) }
fn other_file_descriptor_references(&self, descriptor: u32, handle: u64) -> bool { self.file_descriptors .iter() .any(|(candidate, value)| *candidate != descriptor && *value == handle) || self.suspended_parents.iter().any(|parent| { parent .file_descriptors .values() .any(|candidate| *candidate == handle) }) }
fn pipe_reader_is_referenced(&self, pipe_id: u64) -> bool { self.pipe_end_is_referenced(|end| { matches!(end, PipeEnd::Read(candidate) if candidate == pipe_id) || matches!(end, PipeEnd::Socket { read, .. } if read == pipe_id) }) }
fn pipe_writer_is_referenced(&self, pipe_id: u64) -> bool { self.pipe_end_is_referenced(|end| { matches!(end, PipeEnd::Write(candidate) if candidate == pipe_id) || matches!(end, PipeEnd::Socket { write, .. } if write == pipe_id) }) }
fn pipe_end_is_referenced(&self, predicate: impl Fn(PipeEnd) -> bool) -> bool { self.pipe_descriptors.values().copied().any(&predicate) || self .suspended_parents .iter() .any(|parent| parent.pipe_descriptors.values().copied().any(&predicate)) }
fn close_all_descriptors<F: HostFileSystem>(&mut self, filesystem: &mut F) { let descriptors = self .file_descriptors .keys() .chain(self.pipe_descriptors.keys()) .copied() .collect::<Vec<_>>(); for descriptor in descriptors { let _ = self.close_descriptor(filesystem, descriptor); } self.file_descriptors.clear(); self.pipe_descriptors.clear(); self.descriptor_paths.clear(); self.descriptor_flags .retain(|descriptor, _| *descriptor < 3); self.descriptor_status_flags .retain(|descriptor, _| *descriptor < 3); }
fn file_handle(&self, file_descriptor: u64) -> Option<u64> { self.file_descriptors .get(&u32::try_from(file_descriptor).ok()?) .copied() }
fn dispatch_rt_sigaction(&mut self) { let signal = self.register(0); let Ok(signal_index) = usize::try_from(signal.saturating_sub(1)) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; if signal == 0 || signal_index >= LINUX_SIGNAL_COUNT || self.register(3) != KERNEL_SIGNAL_SET_SIZE { self.set_return(Errno::InvalidArgument.return_value()); return; }
let action_address = GuestAddress::new(self.register(1)); let old_action_address = GuestAddress::new(self.register(2)); let mut replacement = None; if action_address.get() != 0 { let mut bytes = [0; KERNEL_SIGACTION_SIZE]; if self.memory.read_exact(action_address, &mut bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } replacement = Some(SignalAction { bytes }); } if old_action_address.get() != 0 && self .memory .write(old_action_address, &self.signal_actions[signal_index].bytes) .is_err() { self.set_return(Errno::Fault.return_value()); return; } if let Some(replacement) = replacement { self.signal_actions[signal_index] = replacement; } self.set_return(0); }
fn deliver_synchronous_signal(&mut self, signal: u64) -> Option<()> { let signal_index = signal .checked_sub(1) .and_then(|index| usize::try_from(index).ok()) .filter(|index| *index < LINUX_SIGNAL_COUNT)?; let signal_bit = 1_u64 << signal_index; let action = self.signal_actions[signal_index]; let handler = action.handler(); if self.signal_mask & signal_bit != 0 || matches!(handler, SIGNAL_DEFAULT | SIGNAL_IGNORE) { return None; }
let frame_address = self .state .sp() .get() .checked_sub(SYNCHRONOUS_SIGNAL_FRAME_SIZE) .map(|address| GuestAddress::new(address & !15))?; let mut signal_info = [0_u8; 16]; signal_info[..4].copy_from_slice(&u32::try_from(signal).ok()?.to_le_bytes()); signal_info[8..12].copy_from_slice(&1_u32.to_le_bytes()); if self.memory.write(frame_address, &signal_info).is_err() { return None; }
let context_address = frame_address.checked_add(SIGNAL_INFO_SIZE)?; self.state.set_sp(frame_address); self.state.set_pc(GuestAddress::new(handler)); self.state.set_x(0, signal).ok()?; self.state.set_x(1, frame_address.get()).ok()?; self.state.set_x(2, context_address.get()).ok()?; self.state.set_x(30, action.restorer()).ok()?; self.signal_mask |= action.mask(); if action.flags() & SIGNAL_NODEFER == 0 { self.signal_mask |= signal_bit; } Some(()) }
fn dispatch_sched_getaffinity(&mut self) { let pid = self.register(0); let size = self.register(1); let mask = GuestAddress::new(self.register(2)); if (pid != 0 && pid != self.current_process_id) || size < CPU_AFFINITY_BYTES { self.set_return(Errno::InvalidArgument.return_value()); return; } if self.memory.write(mask, &1_u64.to_le_bytes()).is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(CPU_AFFINITY_BYTES); }
fn dispatch_clock_gettime<S: HostSystem>(&mut self, system: &mut S) { let clock = match self.register(0) { CLOCK_REALTIME => HostClock::Realtime, CLOCK_MONOTONIC | CLOCK_MONOTONIC_RAW => HostClock::Monotonic, _ => { self.set_return(Errno::InvalidArgument.return_value()); return; } }; let time = system.clock_time(clock); if time.nanoseconds >= 1_000_000_000 { self.set_return(Errno::InvalidArgument.return_value()); return; } let mut timespec = [0; 16]; timespec[..8].copy_from_slice(&time.seconds.to_le_bytes()); timespec[8..].copy_from_slice(&i64::from(time.nanoseconds).to_le_bytes()); if self .memory .write(GuestAddress::new(self.register(1)), ×pec) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(0); }
fn dispatch_getrandom<S: HostSystem>(&mut self, system: &mut S) { let count = self.register(1); if self.register(2) & !GETRANDOM_ALLOWED_FLAGS != 0 || count > self.limits.max_memory_bytes { self.set_return(Errno::InvalidArgument.return_value()); return; } let Ok(host_count) = usize::try_from(count) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; let mut bytes = vec![0; host_count]; system.fill_random(&mut bytes); if self .memory .write(GuestAddress::new(self.register(0)), &bytes) .is_err() { self.set_return(Errno::Fault.return_value()); return; } self.set_return(count); }
fn dispatch_rt_sigprocmask(&mut self) { let how = self.register(0); let set_address = GuestAddress::new(self.register(1)); let old_set_address = GuestAddress::new(self.register(2)); if self.register(3) != KERNEL_SIGNAL_SET_SIZE || (set_address.get() != 0 && !matches!(how, SIG_BLOCK | SIG_UNBLOCK | SIG_SETMASK)) { self.set_return(Errno::InvalidArgument.return_value()); return; } let mut replacement = None; if set_address.get() != 0 { let mut bytes = [0; 8]; if self.memory.read_exact(set_address, &mut bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } let requested = u64::from_le_bytes(bytes) & !(SIGKILL_BIT | SIGSTOP_BIT); replacement = Some(match how { SIG_BLOCK => self.signal_mask | requested, SIG_UNBLOCK => self.signal_mask & !requested, SIG_SETMASK => requested, _ => unreachable!("signal-mask operation was validated"), }); } if old_set_address.get() != 0 && self .memory .write(old_set_address, &self.signal_mask.to_le_bytes()) .is_err() { self.set_return(Errno::Fault.return_value()); return; } if let Some(replacement) = replacement { self.signal_mask = replacement; } self.set_return(0); }
fn dispatch_sigaltstack(&mut self) { let stack_address = GuestAddress::new(self.register(0)); let old_stack_address = GuestAddress::new(self.register(1)); let mut replacement = None; if stack_address.get() != 0 { let mut bytes = [0; SIGNAL_STACK_SIZE]; if self.memory.read_exact(stack_address, &mut bytes).is_err() { self.set_return(Errno::Fault.return_value()); return; } replacement = Some(SignalStack { bytes }); } if old_stack_address.get() != 0 && self .memory .write(old_stack_address, &self.signal_stack.bytes) .is_err() { self.set_return(Errno::Fault.return_value()); return; } if let Some(replacement) = replacement { self.signal_stack = replacement; } self.set_return(0); }
fn dispatch_mmap<F: HostFileSystem>(&mut self, filesystem: &mut F) { let requested_address = self.register(0); let length = self.register(1); let protection = self.register(2); let flags = self.register(3); let file_descriptor = self.register(4); let offset = self.register(5); let allowed_flags = MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS | MAP_STACK; let anonymous = flags & MAP_ANONYMOUS != 0; let fixed = flags & MAP_FIXED != 0; if length == 0 || (requested_address != 0 && !requested_address.is_multiple_of(PAGE_SIZE)) || protection & !(PROT_READ | PROT_WRITE | PROT_EXECUTE) != 0 || flags & MAP_PRIVATE == 0 || flags & !allowed_flags != 0 || (fixed && requested_address == 0) || !offset.is_multiple_of(PAGE_SIZE) || (anonymous && (file_descriptor != u64::MAX || offset != 0)) { self.set_return(Errno::InvalidArgument.return_value()); return; } let Some(mapped_length) = length .checked_add(PAGE_SIZE - 1) .map(|value| value & !(PAGE_SIZE - 1)) else { self.set_return(Errno::OutOfMemory.return_value()); return; }; let file_bytes = if anonymous { None } else { let Ok(descriptor) = u32::try_from(file_descriptor) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; let Some(handle) = self.file_descriptors.get(&descriptor).copied() else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; match read_mmap_file(filesystem, handle, offset, mapped_length) { Ok(bytes) => Some(bytes), Err(error) => { self.set_return(filesystem_error_return(error)); return; } } }; let address = if fixed { Some(GuestAddress::new(requested_address)) } else if requested_address == 0 { self.find_mmap_address(mapped_length) } else { let hint = GuestAddress::new(requested_address); self.range_is_unmapped(hint, mapped_length) .then_some(hint) .or_else(|| self.find_mmap_address(mapped_length)) }; let Some(address) = address else { self.set_return(Errno::OutOfMemory.return_value()); return; }; let permissions = Permissions::new( protection & PROT_READ != 0, protection & PROT_WRITE != 0, protection & PROT_EXECUTE != 0, ); if fixed && self.memory.unmap(address, mapped_length).is_err() { self.set_return(Errno::OutOfMemory.return_value()); return; } if self .memory .map_zeroed(address, mapped_length, permissions, RegionKind::Anonymous) .is_err() { self.set_return(Errno::OutOfMemory.return_value()); return; } if let Some(bytes) = file_bytes && self.memory.initialize(address, &bytes).is_err() { let _ = self.memory.unmap(address, mapped_length); self.set_return(Errno::OutOfMemory.return_value()); return; } let mapped_end = address .checked_add(mapped_length) .expect("a successfully mapped range has a valid end"); self.next_mmap_address = self.next_mmap_address.max(mapped_end); self.set_return(address.get()); }
fn range_is_unmapped(&self, start: GuestAddress, length: u64) -> bool { let Some(end) = start.checked_add(length) else { return false; }; self.memory .regions() .iter() .all(|region| region.end() <= start || region.start() >= end) }
fn find_mmap_address(&self, length: u64) -> Option<GuestAddress> { let mut candidate = self.next_mmap_address; for region in self.memory.regions() { let candidate_end = candidate.checked_add(length)?; if candidate_end <= region.start() || candidate >= region.end() { continue; } let aligned = region.end().get().checked_add(PAGE_SIZE - 1)? & !(PAGE_SIZE - 1); candidate = GuestAddress::new(aligned); } candidate.checked_add(length).map(|_| candidate) }
fn dispatch_mprotect(&mut self) { let address = self.register(0); let length = self.register(1); let protection = self.register(2); if !address.is_multiple_of(PAGE_SIZE) || length == 0 || protection & !(PROT_READ | PROT_WRITE | PROT_EXECUTE) != 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let Some(mapped_length) = length .checked_add(PAGE_SIZE - 1) .map(|value| value & !(PAGE_SIZE - 1)) else { self.set_return(Errno::OutOfMemory.return_value()); return; }; let permissions = Permissions::new( protection & PROT_READ != 0, protection & PROT_WRITE != 0, protection & PROT_EXECUTE != 0, ); let result = self .memory .protect(GuestAddress::new(address), mapped_length, permissions); self.set_return(if result.is_ok() { 0 } else { Errno::OutOfMemory.return_value() }); }
fn dispatch_munmap(&mut self) { let address = self.register(0); let length = self.register(1); if !address.is_multiple_of(PAGE_SIZE) || length == 0 { self.set_return(Errno::InvalidArgument.return_value()); return; } let Some(mapped_length) = length .checked_add(PAGE_SIZE - 1) .map(|value| value & !(PAGE_SIZE - 1)) else { self.set_return(Errno::InvalidArgument.return_value()); return; }; let result = self.memory.unmap(GuestAddress::new(address), mapped_length); self.set_return(if result.is_ok() { 0 } else { Errno::InvalidArgument.return_value() }); }
fn register(&self, index: u8) -> u64 { self.state .x(index) .expect("Linux ABI register index is valid") }
fn set_return(&mut self, value: u64) { self.state .set_x(0, value) .expect("Linux return register is valid"); }
fn syscall_arguments(&self) -> [u64; 6] { core::array::from_fn(|index| { self.register(u8::try_from(index).expect("six syscall arguments fit u8")) }) }
fn clear_child_tid(&mut self) { let Some(address) = self.clear_child_tid.filter(|address| address.get() != 0) else { return; }; let _ = self.memory.write(address, &0_u32.to_le_bytes()); }
fn clear_dormant_thread_tid(&mut self) { let Some(thread) = self.dormant_thread.take() else { return; }; let _ = self .memory .write(thread.clear_child_tid, &0_u32.to_le_bytes()); }
fn clear_parked_helper_tids(&mut self) { for address in self .parked_helpers .iter() .chain(self.worker_thread.iter()) .filter_map(|thread| thread.clear_child_tid) .filter(|address| address.get() != 0) { let _ = self.memory.write(address, &0_u32.to_le_bytes()); } }}
fn directory_inode(name: &[u8]) -> u64 { name.iter().fold(0xcbf2_9ce4_8422_2325, |hash, byte| { (hash ^ u64::from(*byte)).wrapping_mul(0x100_0000_01b3) })}
fn linux_stat_bytes(metadata: FileMetadata, identity: &[u8]) -> [u8; STAT_SIZE] { let mut bytes = [0; STAT_SIZE]; bytes[..8].copy_from_slice(&1_u64.to_le_bytes()); bytes[8..16].copy_from_slice(&directory_inode(identity).to_le_bytes()); let mode = match metadata.file_type { FileType::Regular => STAT_REGULAR_MODE, FileType::Directory => STAT_DIRECTORY_MODE, }; bytes[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4].copy_from_slice(&mode.to_le_bytes()); bytes[STAT_LINK_COUNT_OFFSET..STAT_LINK_COUNT_OFFSET + 4].copy_from_slice(&1_u32.to_le_bytes()); bytes[STAT_FILE_SIZE_OFFSET..STAT_FILE_SIZE_OFFSET + 8] .copy_from_slice(&metadata.size.to_le_bytes()); bytes[STAT_BLOCK_SIZE_OFFSET..STAT_BLOCK_SIZE_OFFSET + 4] .copy_from_slice(&4096_u32.to_le_bytes()); let block_count = metadata.size.saturating_add(511) / 512; bytes[STAT_BLOCK_COUNT_OFFSET..STAT_BLOCK_COUNT_OFFSET + 8] .copy_from_slice(&block_count.to_le_bytes()); let seconds = metadata.modified.seconds.to_le_bytes(); let nanoseconds = u64::from(metadata.modified.nanoseconds).to_le_bytes(); for offset in [ STAT_ACCESS_SECONDS_OFFSET, STAT_MODIFIED_SECONDS_OFFSET, STAT_CHANGED_SECONDS_OFFSET, ] { bytes[offset..offset + 8].copy_from_slice(&seconds); } for offset in [ STAT_ACCESS_NANOSECONDS_OFFSET, STAT_MODIFIED_NANOSECONDS_OFFSET, STAT_CHANGED_NANOSECONDS_OFFSET, ] { bytes[offset..offset + 8].copy_from_slice(&nanoseconds); } bytes}
fn linux_terminal_stat_bytes() -> [u8; STAT_SIZE] { let mut bytes = [0; STAT_SIZE]; bytes[..8].copy_from_slice(&1_u64.to_le_bytes()); bytes[8..16].copy_from_slice(&1_u64.to_le_bytes()); bytes[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4] .copy_from_slice(&STAT_CHARACTER_MODE.to_le_bytes()); bytes[STAT_LINK_COUNT_OFFSET..STAT_LINK_COUNT_OFFSET + 4].copy_from_slice(&1_u32.to_le_bytes()); bytes[STAT_BLOCK_SIZE_OFFSET..STAT_BLOCK_SIZE_OFFSET + 4] .copy_from_slice(&4096_u32.to_le_bytes()); bytes}
fn linux_pipe_stat_bytes(pipe_id: u64) -> [u8; STAT_SIZE] { let mut bytes = [0; STAT_SIZE]; bytes[..8].copy_from_slice(&1_u64.to_le_bytes()); bytes[8..16].copy_from_slice(&pipe_id.to_le_bytes()); bytes[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4].copy_from_slice(&STAT_FIFO_MODE.to_le_bytes()); bytes[STAT_LINK_COUNT_OFFSET..STAT_LINK_COUNT_OFFSET + 4].copy_from_slice(&1_u32.to_le_bytes()); bytes[STAT_BLOCK_SIZE_OFFSET..STAT_BLOCK_SIZE_OFFSET + 4] .copy_from_slice(&4096_u32.to_le_bytes()); bytes}
fn copy_file_for_link<F: HostFileSystem>( filesystem: &mut F, old_path: &[u8], new_path: &[u8],) -> Result<(), FileSystemError> { let source = filesystem.open( old_path, FileOpenOptions { access: FileAccess::ReadOnly, flags: FileOpenFlags::NONE, }, )?; let destination = match filesystem.open( new_path, FileOpenOptions { access: FileAccess::WriteOnly, flags: FileOpenFlags::CREATE.union(FileOpenFlags::EXCLUSIVE), }, ) { Ok(destination) => destination, Err(error) => { let _ = filesystem.close(source); return Err(error); } }; let result = (|| { let mut bytes = vec![0; FILE_COPY_CHUNK_BYTES]; loop { let read = filesystem.read(source, &mut bytes)?; if read == 0 { return Ok(()); } let mut written = 0; while written < read { let count = filesystem.write(destination, &bytes[written..read])?; if count == 0 { return Err(FileSystemError::NoSpace); } written += count; } } })(); let destination_close = filesystem.close(destination); let source_close = filesystem.close(source); let result = result.and(destination_close).and(source_close); if result.is_err() { let _ = filesystem.remove(new_path, false); } result}
fn read_mmap_file<F: HostFileSystem>( filesystem: &mut F, handle: u64, offset: u64, length: u64,) -> Result<Vec<u8>, FileSystemError> { let original_offset = filesystem.seek(handle, 0, FileSeekFrom::Current)?; let result = (|| { let offset = i64::try_from(offset).map_err(|_| FileSystemError::InvalidInput)?; filesystem.seek(handle, offset, FileSeekFrom::Start)?; let length = usize::try_from(length).map_err(|_| FileSystemError::NoSpace)?; let mut bytes = Vec::new(); bytes .try_reserve_exact(length) .map_err(|_| FileSystemError::NoSpace)?; bytes.resize(length, 0); let mut filled = 0; while filled < bytes.len() { let read = filesystem.read(handle, &mut bytes[filled..])?; if read == 0 { break; } filled += read; } Ok(bytes) })(); let restored = i64::try_from(original_offset) .map_err(|_| FileSystemError::InvalidInput) .and_then(|offset| filesystem.seek(handle, offset, FileSeekFrom::Start)); match (result, restored) { (Err(error), _) | (Ok(_), Err(error)) => Err(error), (Ok(bytes), Ok(_)) => Ok(bytes), }}
const fn filesystem_error_return(error: FileSystemError) -> u64 { filesystem_error_errno(error).return_value()}
const fn filesystem_error_errno(error: FileSystemError) -> Errno { match error { FileSystemError::NotFound => Errno::NoEntry, FileSystemError::AlreadyExists => Errno::AlreadyExists, FileSystemError::NotDirectory => Errno::NotDirectory, FileSystemError::IsDirectory => Errno::IsDirectory, FileSystemError::BadDescriptor => Errno::BadFileDescriptor, FileSystemError::PermissionDenied => Errno::PermissionDenied, FileSystemError::InvalidInput => Errno::InvalidArgument, FileSystemError::NoSpace => Errno::NoSpace, FileSystemError::NotEmpty => Errno::DirectoryNotEmpty, FileSystemError::Unsupported => Errno::NoSystemCall, }}
const fn process_load_errno(error: &ProcessLoadError) -> Errno { match error { ProcessLoadError::Loader(_) | ProcessLoadError::InterpreterShortRead(_) => { Errno::ExecutableFormat } ProcessLoadError::InterpreterFile { error, .. } => filesystem_error_errno(*error), ProcessLoadError::InterpreterNotRegular(_) => Errno::PermissionDenied, ProcessLoadError::InterpreterTooLarge { .. } => Errno::OutOfMemory, }}
#[cfg(test)]mod tests { use core::convert::Infallible;
use binarrow_host_api::{ DeterministicSystem, FileAccess, FileOpenFlags, FileOpenOptions, FileSeekFrom, HostFileSystem, HostInput, HostTerminal, NullFileSystem, TerminalInputRead, TerminalStream, }; use binarrow_linux_abi::Errno; use binarrow_loader::{Credentials, ProcessConfig, ProcessParameters, load_process}; use binarrow_memory_fs::MemoryFileSystem; use binarrow_runtime_core::{GuestAddress, ResourceLimit, Trap};
use super::{ ACTIVE_THREAD_ID, AT_FDCWD, AnonymousPipe, CHILD_PROCESS_ID, DESCRIPTOR_CLOEXEC, DORMANT_THREAD_CLONE_FLAGS, DORMANT_THREAD_ID, ExecutionError, ExecutionEvent, FCNTL_GET_DESCRIPTOR_FLAGS, FCNTL_GET_STATUS_FLAGS, FCNTL_SET_STATUS_FLAGS, FUTEX_PRIVATE_FLAG, FUTEX_WAIT, INITIAL_PROCESS_ID, IOCTL_CLEAR_CLOSE_ON_EXEC, IOCTL_SET_CLOSE_ON_EXEC, Interpreter, MAP_FIXED, MAP_PRIVATE, NESTED_HELPER_THREAD_ID, OPEN_CLOEXEC, OPEN_DIRECTORY, OPEN_LARGEFILE, OPEN_NOCTTY, OPEN_NOFOLLOW, OPEN_NONBLOCK, OPEN_PATH, PAGE_SIZE, PIPE_CAPACITY_BYTES, PROT_EXECUTE, PROT_READ, PipeEnd, Process, SIGNAL_USER_1, STANDARD_OUTPUT, STAT_CHARACTER_MODE, STAT_FIFO_MODE, STAT_FILE_SIZE_OFFSET, STAT_MODE_OFFSET, STAT_MODIFIED_SECONDS_OFFSET, STAT_REGULAR_MODE, STAT_SIZE, SUPPORTED_CLONE_FLAGS, SUPPORTED_FORK_FLAGS, SyscallEvent, SyscallOutcome, UTIME_NOW, UTIME_OMIT, load_process_with_guest_filesystem, };
const MESSAGE: &[u8] = b"hello, world\n"; const MESSAGE_LENGTH: u16 = 13; const MESSAGE_ADDRESS: u16 = 0x1120; const COMPILER_MESSAGE: &[u8] = b"compiled hello\n"; const COMPILER_MESSAGE_ADDRESS: u64 = 0x100_0158; const COMPILER_HELLO_ELF: &[u8] = include_bytes!("../../../guest-tests/compiler-hello/compiler-hello.aarch64.elf"); const EXECVE_LAUNCHER_ELF: &[u8] = include_bytes!("../../../guest-tests/execve-launcher/execve-launcher.aarch64.elf"); const EXECVE_TOOLCHAIN_IMAGE: &[u8] = include_bytes!("../../../guest-tests/execve-launcher/toolchain.bnfs"); const LIBC_MESSAGE: &[u8] = b"libc hello\n"; const LIBC_MESSAGE_ADDRESS: u64 = 0x100_0200; const LIBC_CLEAR_CHILD_TID_ADDRESS: u64 = 0x103_0af0; const LIBC_HELLO_ELF: &[u8] = include_bytes!("../../../guest-tests/libc-hello/libc-hello.aarch64.elf"); const RUST_HELLO_ELF: &[u8] = include_bytes!("../../../guest-tests/rust-hello/rust-hello.aarch64.elf"); const INFINITE_LOOP_ELF: &[u8] = include_bytes!("../../../guest-tests/infinite-loop/infinite-loop.aarch64.elf"); const FILE_ROUNDTRIP_ELF: &[u8] = include_bytes!("../../../guest-tests/file-roundtrip/file-roundtrip.aarch64.elf"); const TERMINAL_INPUT_ELF: &[u8] = include_bytes!("../../../guest-tests/terminal-input/terminal-input.aarch64.elf"); const FILE_ROUNDTRIP_MESSAGE: &[u8] = b"filesystem hello\n"; const RUST_MESSAGE: &[u8] = b"rust hello\n";
#[derive(Default)] struct RecordingTerminal { standard_output: Vec<u8>, standard_error: Vec<u8>, }
impl HostTerminal for RecordingTerminal { type Error = Infallible;
fn write(&mut self, stream: TerminalStream, bytes: &[u8]) -> Result<(), Self::Error> { match stream { TerminalStream::StandardOutput => self.standard_output.extend_from_slice(bytes), TerminalStream::StandardError => self.standard_error.extend_from_slice(bytes), } Ok(()) } }
struct TestInput(Option<Vec<u8>>);
impl HostInput for TestInput { fn read(&mut self, destination: &mut [u8]) -> TerminalInputRead { let Some(bytes) = self.0.take() else { return TerminalInputRead::WouldBlock; }; let count = destination.len().min(bytes.len()); destination[..count].copy_from_slice(&bytes[..count]); TerminalInputRead::Available(count) } }
#[test] fn static_elf_writes_hello_world_and_exits() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default();
let result = process.run(&mut terminal).unwrap();
assert_eq!(result.exit_code, 0); assert_eq!(result.executed_instructions, 8); assert_eq!(result.dispatched_syscalls, 2); assert_eq!(result.output_bytes, MESSAGE.len() as u64); assert_eq!(terminal.standard_output, MESSAGE); assert!(terminal.standard_error.is_empty()); assert_eq!( process.trace(), [ SyscallEvent { number: 64, arguments: [1, u64::from(MESSAGE_ADDRESS), 13, 0, 0, 0], outcome: SyscallOutcome::Returned(13), }, SyscallEvent { number: 93, arguments: [0, u64::from(MESSAGE_ADDRESS), 13, 0, 0, 0], outcome: SyscallOutcome::Exited(0), }, ] ); }
#[test] fn delivers_sigill_to_an_installed_guest_handler() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let original_stack = process.state.sp(); let handler = 0x1100_u64; let restorer = 0x1200_u64; let requested_mask = 1_u64 << 5; let mut bytes = [0_u8; super::KERNEL_SIGACTION_SIZE]; bytes[..8].copy_from_slice(&handler.to_le_bytes()); bytes[16..24].copy_from_slice(&restorer.to_le_bytes()); bytes[24..].copy_from_slice(&requested_mask.to_le_bytes()); process.signal_actions[usize::try_from(super::SIGILL - 1).unwrap()] = super::SignalAction { bytes };
assert_eq!(process.deliver_synchronous_signal(super::SIGILL), Some(()));
assert_eq!(process.state.pc(), GuestAddress::new(handler)); assert_eq!(process.state.x(0), Some(super::SIGILL)); assert_eq!(process.state.x(30), Some(restorer)); assert_eq!(process.state.sp().get() % 16, 0); assert_eq!( process.state.sp().get(), original_stack.get() - super::SYNCHRONOUS_SIGNAL_FRAME_SIZE ); assert_eq!(process.state.x(1), Some(process.state.sp().get())); assert_eq!( process.state.x(2), Some(process.state.sp().get() + super::SIGNAL_INFO_SIZE) ); assert_eq!( process.signal_mask, requested_mask | (1_u64 << (super::SIGILL - 1)) ); let mut signal_number = [0_u8; 4]; process .memory .read_exact(process.state.sp(), &mut signal_number) .unwrap(); assert_eq!(u32::from_le_bytes(signal_number), 4); }
#[test] fn compiler_produced_elf_writes_hello_world_and_exits() { let image = load_process( COMPILER_HELLO_ELF, &ProcessParameters { argv: vec![b"/compiler-hello".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, ProcessConfig::default(), ) .unwrap(); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default();
let result = process.run(&mut terminal).unwrap();
assert_eq!(result.exit_code, 0); assert_eq!(result.executed_instructions, 15); assert_eq!(result.dispatched_syscalls, 2); assert_eq!(result.output_bytes, COMPILER_MESSAGE.len() as u64); assert_eq!(terminal.standard_output, COMPILER_MESSAGE); assert!(terminal.standard_error.is_empty()); assert_eq!( process.trace(), [ SyscallEvent { number: 64, arguments: [1, COMPILER_MESSAGE_ADDRESS, 15, 0, 0, 0], outcome: SyscallOutcome::Returned(15), }, SyscallEvent { number: 93, arguments: [0, COMPILER_MESSAGE_ADDRESS, 15, 0, 0, 0], outcome: SyscallOutcome::Exited(0), }, ] ); }
#[test] fn execve_replaces_the_process_from_the_guest_filesystem() { let image = load_process( EXECVE_LAUNCHER_ELF, &ProcessParameters { argv: vec![b"/execve-launcher".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, ProcessConfig::default(), ) .unwrap(); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default(); let mut filesystem = MemoryFileSystem::from_snapshot(16 * 1024 * 1024, EXECVE_TOOLCHAIN_IMAGE).unwrap();
let result = process .run_with_filesystem(&mut terminal, &mut filesystem) .unwrap();
assert_eq!(result.exit_code, 0); assert_eq!(result.dispatched_syscalls, 8); assert_eq!(result.output_bytes, 18); assert_eq!(terminal.standard_output, b"exec child phase6\n"); assert!(terminal.standard_error.is_empty()); assert_eq!(process.executable_path, b"/project/toolchain/execve-child"); assert!(process.file_descriptors.is_empty()); assert_eq!( process .trace() .iter() .map(|event| (event.number, event.outcome)) .collect::<Vec<_>>(), [ (56, SyscallOutcome::Returned(3)), (56, SyscallOutcome::Returned(4)), (221, SyscallOutcome::Returned(0)), ( 80, SyscallOutcome::Returned(Errno::BadFileDescriptor.return_value()), ), (80, SyscallOutcome::Returned(0)), (57, SyscallOutcome::Returned(0)), (64, SyscallOutcome::Returned(18)), (93, SyscallOutcome::Exited(0)), ] ); }
#[test] fn static_musl_elf_writes_hello_world_and_exits() { let image = load_process( LIBC_HELLO_ELF, &ProcessParameters { argv: vec![b"/libc-hello".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, ProcessConfig::default(), ) .unwrap(); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default();
let result = process.run(&mut terminal).unwrap();
assert_eq!(result.exit_code, 0); assert_eq!(result.executed_instructions, 1_449); assert_eq!(result.dispatched_syscalls, 3); assert_eq!(result.output_bytes, LIBC_MESSAGE.len() as u64); assert_eq!(terminal.standard_output, LIBC_MESSAGE); assert!(terminal.standard_error.is_empty()); assert_eq!( process.clear_child_tid, Some(GuestAddress::new(LIBC_CLEAR_CHILD_TID_ADDRESS)) ); assert_eq!( process.trace(), [ SyscallEvent { number: 96, arguments: [ LIBC_CLEAR_CHILD_TID_ADDRESS, 0xe0, 0, 0x101_035c, 0x101_0870, 0, ], outcome: SyscallOutcome::Returned(INITIAL_PROCESS_ID), }, SyscallEvent { number: 64, arguments: [1, LIBC_MESSAGE_ADDRESS, 11, 0, 0, 0], outcome: SyscallOutcome::Returned(11), }, SyscallEvent { number: 94, arguments: [0, LIBC_MESSAGE_ADDRESS, 11, 0, 0, 0], outcome: SyscallOutcome::Exited(0), }, ] ); }
#[test] fn static_rust_elf_writes_hello_world_and_exits() { let image = load_process( RUST_HELLO_ELF, &ProcessParameters { argv: vec![b"/rust-hello".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, ProcessConfig::default(), ) .unwrap(); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default();
let result = process.run(&mut terminal).unwrap();
assert_eq!(result.exit_code, 0); assert_eq!(result.executed_instructions, 4_880); assert_eq!(result.dispatched_syscalls, 20); assert_eq!(result.output_bytes, RUST_MESSAGE.len() as u64); assert_eq!(terminal.standard_output, RUST_MESSAGE); assert!(terminal.standard_error.is_empty()); assert_eq!( process .trace() .iter() .map(|event| (event.number, event.outcome)) .collect::<Vec<_>>(), [ (96, SyscallOutcome::Returned(1)), (73, SyscallOutcome::Returned(0)), (134, SyscallOutcome::Returned(0)), (134, SyscallOutcome::Returned(0)), (132, SyscallOutcome::Returned(0)), (222, SyscallOutcome::Returned(0x4000_0000)), (226, SyscallOutcome::Returned(0)), (132, SyscallOutcome::Returned(0)), (135, SyscallOutcome::Returned(0)), (134, SyscallOutcome::Returned(0)), (134, SyscallOutcome::Returned(0)), (134, SyscallOutcome::Returned(0)), (123, SyscallOutcome::Returned(8)), (222, SyscallOutcome::Returned(0x4000_4000)), (215, SyscallOutcome::Returned(0)), (215, SyscallOutcome::Returned(0)), (64, SyscallOutcome::Returned(11)), (132, SyscallOutcome::Returned(0)), (215, SyscallOutcome::Returned(0)), (94, SyscallOutcome::Exited(0)), ] ); }
#[test] fn output_limit_stops_before_host_side_effects() { let mut config = ProcessConfig::default(); config.limits.max_output_bytes = MESSAGE.len() as u64 - 1; let image = load_hello(config, 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default();
let error = process.run(&mut terminal).unwrap_err();
assert!(matches!( error, ExecutionError::Trap(Trap::ResourceLimit(ResourceLimit::Output)) )); assert!(terminal.standard_output.is_empty()); }
#[test] fn infinite_loop_reports_the_completed_instruction_budget() { let mut config = ProcessConfig::default(); config.limits.instruction_budget = 128; let image = load_process( INFINITE_LOOP_ELF, &ProcessParameters { argv: vec![b"/infinite-loop".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, config, ) .unwrap(); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default();
let error = process.run(&mut terminal).unwrap_err();
assert!(matches!( error, ExecutionError::Trap(Trap::ResourceLimit(ResourceLimit::Instructions)) )); assert_eq!(process.executed_instructions(), 128); assert_eq!(process.dispatched_syscalls(), 0); assert_eq!(process.output_bytes(), 0); assert!(process.trace().is_empty()); assert_eq!(process.block_profiles().len(), 1); assert_eq!(process.block_profiles()[0].executions, 128); assert_eq!(process.dropped_block_entries(), 0); }
#[test] fn file_roundtrip_uses_the_in_memory_filesystem_and_terminal() { let image = load_process( FILE_ROUNDTRIP_ELF, &ProcessParameters { argv: vec![b"/file-roundtrip".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, ProcessConfig::default(), ) .unwrap(); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default(); let mut filesystem = MemoryFileSystem::new(1024);
let result = process .run_with_filesystem(&mut terminal, &mut filesystem) .unwrap();
assert_eq!(result.exit_code, 0); assert_eq!(result.executed_instructions, 54); assert_eq!(result.dispatched_syscalls, 7); assert_eq!(result.output_bytes, FILE_ROUNDTRIP_MESSAGE.len() as u64); assert_eq!(terminal.standard_output, FILE_ROUNDTRIP_MESSAGE); assert!(terminal.standard_error.is_empty()); assert_eq!( filesystem.read_file(b"/tmp/roundtrip.txt"), Some(FILE_ROUNDTRIP_MESSAGE) ); assert_eq!( process .trace() .iter() .map(|event| (event.number, event.outcome)) .collect::<Vec<_>>(), [ (56, SyscallOutcome::Returned(3)), (64, SyscallOutcome::Returned(17)), (62, SyscallOutcome::Returned(0)), (63, SyscallOutcome::Returned(17)), (57, SyscallOutcome::Returned(0)), (64, SyscallOutcome::Returned(17)), (93, SyscallOutcome::Exited(0)), ] ); }
#[test] fn pread64_reads_without_changing_the_file_offset() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let writer = filesystem .open( b"/tmp/pread.txt", FileOpenOptions { access: FileAccess::WriteOnly, flags: FileOpenFlags::CREATE, }, ) .unwrap(); filesystem.write(writer, b"hello, world").unwrap(); filesystem.close(writer).unwrap(); let reader = filesystem .open( b"/tmp/pread.txt", FileOpenOptions { access: FileAccess::ReadOnly, flags: FileOpenFlags::NONE, }, ) .unwrap(); filesystem.seek(reader, 2, FileSeekFrom::Start).unwrap(); process.file_descriptors.insert(3, reader); let destination = process.state.sp().checked_sub(64).unwrap(); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 5).unwrap(); process.state.set_x(3, 7).unwrap();
process.dispatch_pread64(&mut filesystem);
assert_eq!(process.register(0), 5); let mut bytes = [0; 5]; process.memory.read_exact(destination, &mut bytes).unwrap(); assert_eq!(&bytes, b"world"); assert_eq!( filesystem.seek(reader, 0, FileSeekFrom::Current).unwrap(), 2 ); }
#[test] fn copy_file_range_copies_between_descriptor_offsets() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let source_writer = filesystem .open( b"/tmp/source.txt", FileOpenOptions { access: FileAccess::WriteOnly, flags: FileOpenFlags::CREATE, }, ) .unwrap(); filesystem.write(source_writer, b"copy me").unwrap(); filesystem.close(source_writer).unwrap(); let source = filesystem .open( b"/tmp/source.txt", FileOpenOptions { access: FileAccess::ReadOnly, flags: FileOpenFlags::NONE, }, ) .unwrap(); let destination = filesystem .open( b"/tmp/destination.txt", FileOpenOptions { access: FileAccess::WriteOnly, flags: FileOpenFlags::CREATE, }, ) .unwrap(); process.file_descriptors.insert(3, source); process.file_descriptors.insert(4, destination); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 0).unwrap(); process.state.set_x(2, 4).unwrap(); process.state.set_x(3, 0).unwrap(); process.state.set_x(4, 7).unwrap(); process.state.set_x(5, 0).unwrap();
process.dispatch_copy_file_range(&mut filesystem);
assert_eq!(process.register(0), 7); assert_eq!( filesystem.read_file(b"/tmp/destination.txt"), Some(b"copy me".as_slice()) ); assert_eq!( filesystem.seek(source, 0, FileSeekFrom::Current).unwrap(), 7 ); assert_eq!( filesystem .seek(destination, 0, FileSeekFrom::Current) .unwrap(), 7 ); }
#[test] fn ftruncate_resizes_without_changing_the_file_offset() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let handle = filesystem .open( b"/tmp/truncate.txt", FileOpenOptions { access: FileAccess::ReadWrite, flags: FileOpenFlags::CREATE, }, ) .unwrap(); filesystem.write(handle, b"hello").unwrap(); process.file_descriptors.insert(3, handle); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 2).unwrap();
process.dispatch_ftruncate(&mut filesystem);
assert_eq!(process.register(0), 0); assert_eq!( filesystem.read_file(b"/tmp/truncate.txt"), Some(b"he".as_slice()) ); assert_eq!( filesystem.seek(handle, 0, FileSeekFrom::Current).unwrap(), 5 );
process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 6).unwrap(); process.dispatch_ftruncate(&mut filesystem);
assert_eq!(process.register(0), 0); assert_eq!( filesystem.read_file(b"/tmp/truncate.txt"), Some(b"he\0\0\0\0".as_slice()) ); assert_eq!( filesystem.seek(handle, 0, FileSeekFrom::Current).unwrap(), 5 ); }
#[test] fn blocking_terminal_read_suspends_and_resumes_without_reexecuting_the_syscall() { let image = load_process( TERMINAL_INPUT_ELF, &ProcessParameters { argv: vec![b"/terminal-input".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, ProcessConfig::default(), ) .unwrap(); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default(); let mut filesystem = NullFileSystem; let mut system = DeterministicSystem::default();
let suspended = process .run_until_event( &mut terminal, &mut filesystem, &mut system, &mut TestInput(None), ) .unwrap(); assert_eq!( suspended, ExecutionEvent::Input(super::InputRequest { max_bytes: 64 }) ); assert_eq!(terminal.standard_output, b"input> "); assert_eq!(process.dispatched_syscalls(), 2); assert_eq!(process.trace().len(), 1);
let completed = process .run_until_event( &mut terminal, &mut filesystem, &mut system, &mut TestInput(Some(b"hello\n".to_vec())), ) .unwrap(); let ExecutionEvent::Exited(result) = completed else { panic!("guest should exit after input is supplied"); }; assert_eq!(result.exit_code, 0); assert_eq!(terminal.standard_output, b"input> echo: hello\n"); assert_eq!(process.dispatched_syscalls(), 5); assert_eq!( process .trace() .iter() .map(|event| event.number) .collect::<Vec<_>>(), [64, 63, 64, 64, 93] ); }
#[test] fn syscall_limit_bounds_dispatch_and_trace_growth() { let mut config = ProcessConfig::default(); config.limits.syscall_budget = 1; let image = load_hello(config, 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default();
let error = process.run(&mut terminal).unwrap_err();
assert!(matches!( error, ExecutionError::Trap(Trap::ResourceLimit(ResourceLimit::Syscalls)) )); assert_eq!(terminal.standard_output, MESSAGE); assert_eq!(process.trace().len(), 1); }
#[test] fn invalid_write_arguments_return_errno_without_host_side_effects() { for (file_descriptor, address, errno) in [ (3, MESSAGE_ADDRESS, Errno::BadFileDescriptor), (1, u16::MAX, Errno::Fault), ] { let image = load_hello(ProcessConfig::default(), file_descriptor, address); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default(); process.state.set_x(0, u64::from(file_descriptor)).unwrap(); process.state.set_x(1, u64::from(address)).unwrap(); process .state .set_x(2, MESSAGE.len().try_into().unwrap()) .unwrap();
process .dispatch_write(&mut terminal, &mut NullFileSystem) .unwrap();
assert_eq!(process.register(0), errno.return_value()); assert!(terminal.standard_output.is_empty()); } }
#[test] fn writev_gathers_guest_vectors_for_terminal_output() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut terminal = RecordingTerminal::default(); let iovecs = process.state.sp().checked_sub(32).unwrap(); let mut bytes = [0; 32]; bytes[..8].copy_from_slice(&u64::from(MESSAGE_ADDRESS).to_le_bytes()); bytes[8..16].copy_from_slice(&5_u64.to_le_bytes()); bytes[16..24].copy_from_slice(&(u64::from(MESSAGE_ADDRESS) + 5).to_le_bytes()); bytes[24..].copy_from_slice(&8_u64.to_le_bytes()); process.memory.write(iovecs, &bytes).unwrap(); process.state.set_x(0, STANDARD_OUTPUT).unwrap(); process.state.set_x(1, iovecs.get()).unwrap(); process.state.set_x(2, 2).unwrap();
process .dispatch_writev(&mut terminal, &mut NullFileSystem) .unwrap();
assert_eq!(process.register(0), MESSAGE.len() as u64); assert_eq!(process.output_bytes(), MESSAGE.len() as u64); assert_eq!(terminal.standard_output, MESSAGE); }
#[test] fn fstat_describes_standard_streams_as_character_devices() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let stat = process.state.sp().checked_sub(STAT_SIZE as u64).unwrap(); process.state.set_x(0, STANDARD_OUTPUT).unwrap(); process.state.set_x(1, stat.get()).unwrap();
process.dispatch_fstat(&mut NullFileSystem);
assert_eq!(process.register(0), 0); let mut bytes = [0; STAT_SIZE]; process.memory.read_exact(stat, &mut bytes).unwrap(); assert_eq!( u32::from_le_bytes( bytes[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4] .try_into() .unwrap() ), STAT_CHARACTER_MODE ); }
#[test] fn getcwd_reports_the_project_directory_and_validates_the_buffer() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let buffer = process.state.sp().checked_sub(32).unwrap(); process.state.set_x(0, buffer.get()).unwrap(); process.state.set_x(1, 32).unwrap();
process.dispatch_getcwd();
assert_eq!(process.register(0), 9); let mut bytes = [0; 9]; process.memory.read_exact(buffer, &mut bytes).unwrap(); assert_eq!(&bytes, b"/project\0");
process.state.set_x(0, buffer.get()).unwrap(); process.state.set_x(1, 8).unwrap(); process.dispatch_getcwd(); assert_eq!(process.register(0), Errno::Range.return_value());
process.state.set_x(0, u64::MAX).unwrap(); process.state.set_x(1, 32).unwrap(); process.dispatch_getcwd(); assert_eq!(process.register(0), Errno::Fault.return_value()); }
#[test] fn chdir_updates_the_guest_working_directory() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); filesystem.create_directory(b"/project/answer").unwrap(); let path = process.state.sp().checked_sub(32).unwrap(); process.memory.write(path, b"answer\0").unwrap(); process.state.set_x(0, path.get()).unwrap();
process.dispatch_chdir(&mut filesystem);
assert_eq!(process.register(0), 0); assert_eq!(process.current_directory, b"/project/answer"); }
#[test] fn file_status_and_executable_link_expose_guest_paths() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); for directory in [ b"/project/python".as_slice(), b"/project/python/lib", b"/project/python/lib/python3.12", ] { filesystem.create_directory(directory).unwrap(); } let file = filesystem .open( b"/project/python/lib/python3.12/os.py", FileOpenOptions { access: FileAccess::WriteOnly, flags: FileOpenFlags::CREATE, }, ) .unwrap(); filesystem.write(file, b"pass\n").unwrap(); filesystem.close(file).unwrap();
let path_address = process.state.sp().checked_sub(512).unwrap(); let status_address = process.state.sp().checked_sub(256).unwrap(); process .memory .write(path_address, b"/project/python/lib/python3.12/os.py\0") .unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process.state.set_x(2, status_address.get()).unwrap(); process.state.set_x(3, 0).unwrap();
process.dispatch_newfstatat(&mut filesystem);
assert_eq!(process.register(0), 0); let mut status = [0; 128]; process .memory .read_exact(status_address, &mut status) .unwrap(); assert_eq!( u32::from_le_bytes( status[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4] .try_into() .unwrap() ), STAT_REGULAR_MODE ); assert_eq!( u64::from_le_bytes( status[STAT_FILE_SIZE_OFFSET..STAT_FILE_SIZE_OFFSET + 8] .try_into() .unwrap() ), 5 ); assert_eq!( i64::from_le_bytes( status[STAT_MODIFIED_SECONDS_OFFSET..STAT_MODIFIED_SECONDS_OFFSET + 8] .try_into() .unwrap() ), filesystem .metadata(b"/project/python/lib/python3.12/os.py") .unwrap() .modified .seconds ); let file_inode = u64::from_le_bytes(status[8..16].try_into().unwrap());
process .memory .write(path_address, b"/project/python/lib\0") .unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process.state.set_x(2, status_address.get()).unwrap(); process.state.set_x(3, 0).unwrap(); process.dispatch_newfstatat(&mut filesystem); assert_eq!(process.register(0), 0); process .memory .read_exact(status_address, &mut status) .unwrap(); let directory_inode = u64::from_le_bytes(status[8..16].try_into().unwrap()); assert_ne!(file_inode, directory_inode);
process .memory .write(path_address, b"/proc/self/exe\0") .unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process.state.set_x(2, status_address.get()).unwrap(); process.state.set_x(3, 64).unwrap(); process.dispatch_readlinkat(&mut filesystem); assert_eq!(process.register(0), 6); let mut executable = [0; 6]; process .memory .read_exact(status_address, &mut executable) .unwrap(); assert_eq!(&executable, b"/hello"); }
#[test] fn utimensat_sets_archive_times_and_supports_open_descriptors() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let handle = filesystem .open( b"/project/archive-entry", FileOpenOptions { access: FileAccess::ReadWrite, flags: FileOpenFlags::CREATE, }, ) .unwrap(); let path = process.state.sp().checked_sub(128).unwrap(); let times = process.state.sp().checked_sub(64).unwrap(); process .memory .write(path, b"/project/archive-entry\0") .unwrap(); let mut time_bytes = [0; 32]; time_bytes[8..16].copy_from_slice(&UTIME_OMIT.to_le_bytes()); time_bytes[16..24].copy_from_slice(&1_650_000_000_i64.to_le_bytes()); time_bytes[24..32].copy_from_slice(&123_i64.to_le_bytes()); process.memory.write(times, &time_bytes).unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path.get()).unwrap(); process.state.set_x(2, times.get()).unwrap(); process.state.set_x(3, 0).unwrap();
process.dispatch_utimensat(&mut filesystem);
assert_eq!(process.register(0), 0); assert_eq!( filesystem .metadata(b"/project/archive-entry") .unwrap() .modified, binarrow_host_api::HostTime { seconds: 1_650_000_000, nanoseconds: 123, } );
process.file_descriptors.insert(3, handle); process .descriptor_paths .insert(3, b"/project/archive-entry".to_vec()); time_bytes[24..32].copy_from_slice(&UTIME_NOW.to_le_bytes()); process.memory.write(times, &time_bytes).unwrap(); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 0).unwrap(); process.dispatch_utimensat(&mut filesystem); assert_eq!(process.register(0), 0); assert!( filesystem .metadata(b"/project/archive-entry") .unwrap() .modified .seconds > 1_650_000_000 ); }
#[test] fn openat_accepts_path_only_directory_descriptors() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); filesystem.create_directory(b"/project/toolchain").unwrap(); let path_address = process.state.sp().checked_sub(64).unwrap(); process .memory .write(path_address, b"/project/toolchain\0") .unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process .state .set_x( 2, OPEN_PATH | OPEN_DIRECTORY | OPEN_LARGEFILE | OPEN_NOFOLLOW | OPEN_CLOEXEC, ) .unwrap();
process.dispatch_openat(&mut filesystem);
assert_eq!(process.register(0), 3); assert_eq!( process.descriptor_paths.get(&3), Some(&b"/project/toolchain".to_vec()) ); assert_eq!(process.descriptor_flags.get(&3), Some(&DESCRIPTOR_CLOEXEC));
filesystem .create_directory(b"/project/toolchain/lib") .unwrap(); process.memory.write(path_address, b"lib\0").unwrap(); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process .state .set_x(2, OPEN_PATH | OPEN_DIRECTORY | OPEN_NOFOLLOW | OPEN_CLOEXEC) .unwrap();
process.dispatch_openat(&mut filesystem);
assert_eq!(process.register(0), 4); assert_eq!( process.descriptor_paths.get(&4), Some(&b"/project/toolchain/lib".to_vec()) );
let executable = filesystem .open( b"/project/toolchain/zig", FileOpenOptions { access: FileAccess::WriteOnly, flags: FileOpenFlags::CREATE, }, ) .unwrap(); filesystem.write(executable, b"elf").unwrap(); filesystem.close(executable).unwrap(); process.memory.write(path_address, b"zig\0").unwrap(); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process .state .set_x(2, OPEN_NOCTTY | OPEN_NOFOLLOW | OPEN_CLOEXEC) .unwrap();
process.dispatch_openat(&mut filesystem);
assert_eq!(process.register(0), 5); assert_eq!( process.descriptor_paths.get(&5), Some(&b"/project/toolchain/zig".to_vec()) );
process.memory.write(path_address, b"child\0").unwrap(); process.state.set_x(0, 5).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process.state.set_x(2, OPEN_PATH).unwrap();
process.dispatch_openat(&mut filesystem);
assert_eq!(process.register(0), Errno::NotDirectory.return_value());
process.memory.write(path_address, b".zig-cache\0").unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path_address.get()).unwrap(); process.state.set_x(2, 0o755).unwrap();
process.dispatch_mkdirat(&mut filesystem);
assert_eq!(process.register(0), 0); assert!(filesystem.metadata(b"/project/.zig-cache").is_ok()); }
#[test] fn linkat_copies_an_immutable_cache_file() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let source = filesystem .open( b"/project/source.bin", FileOpenOptions { access: FileAccess::WriteOnly, flags: FileOpenFlags::CREATE, }, ) .unwrap(); filesystem.write(source, b"incremental cache").unwrap(); filesystem.close(source).unwrap(); let old_path = process.state.sp().checked_sub(128).unwrap(); let new_path = process.state.sp().checked_sub(64).unwrap(); process .memory .write(old_path, b"/project/source.bin\0") .unwrap(); process .memory .write(new_path, b"/project/destination.bin\0") .unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, old_path.get()).unwrap(); process.state.set_x(2, AT_FDCWD).unwrap(); process.state.set_x(3, new_path.get()).unwrap(); process.state.set_x(4, 0).unwrap();
process.dispatch_linkat(&mut filesystem);
assert_eq!(process.register(0), 0); assert_eq!( filesystem.read_file(b"/project/destination.bin"), Some(&b"incremental cache"[..]) ); process.dispatch_linkat(&mut filesystem); assert_eq!(process.register(0), Errno::AlreadyExists.return_value()); }
#[test] fn faccessat_reports_bounded_guest_path_access() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let path = process.state.sp().checked_sub(64).unwrap();
process.memory.write(path, b"/tmp\0").unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path.get()).unwrap(); process.state.set_x(2, 2).unwrap(); process.dispatch_faccessat(&mut filesystem); assert_eq!(process.register(0), 0);
process.memory.write(path, b"/proc/self\0").unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path.get()).unwrap(); process.state.set_x(2, 2).unwrap(); process.dispatch_faccessat(&mut filesystem); assert_eq!(process.register(0), Errno::PermissionDenied.return_value());
process.memory.write(path, b"/project/missing\0").unwrap(); process.state.set_x(0, AT_FDCWD).unwrap(); process.state.set_x(1, path.get()).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_faccessat(&mut filesystem); assert_eq!(process.register(0), Errno::NoEntry.return_value()); }
#[test] fn fcntl_tracks_close_on_exec_descriptor_flags() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); process.state.set_x(0, STANDARD_OUTPUT).unwrap(); process.state.set_x(1, FCNTL_GET_DESCRIPTOR_FLAGS).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 0);
process.file_descriptors.insert(3, 1); process.descriptor_flags.insert(3, 1); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 1).unwrap();
process.dispatch_fcntl(); assert_eq!(process.register(0), 1);
process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 2).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 0);
process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 1).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 0); }
#[test] fn nonblocking_pipe_round_trips_bytes_and_reports_eof_after_close() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = NullFileSystem; let pipe_descriptors = process.state.sp().checked_sub(32).unwrap(); let source = process.state.sp().checked_sub(64).unwrap(); let destination = process.state.sp().checked_sub(96).unwrap(); process.memory.write(source, b"pipe data").unwrap(); process.state.set_x(0, pipe_descriptors.get()).unwrap(); process .state .set_x(1, OPEN_NONBLOCK | OPEN_CLOEXEC) .unwrap();
process.dispatch_pipe2();
assert_eq!(process.register(0), 0); let mut descriptors = [0; 8]; process .memory .read_exact(pipe_descriptors, &mut descriptors) .unwrap(); let reader = u32::from_le_bytes(descriptors[..4].try_into().unwrap()); let writer = u32::from_le_bytes(descriptors[4..].try_into().unwrap()); assert_eq!((reader, writer), (3, 4)); assert_eq!( process.descriptor_flags.get(&reader), Some(&DESCRIPTOR_CLOEXEC) ); assert_eq!( process.poll_descriptor(reader.cast_signed(), super::POLL_INPUT), 0 ); assert_eq!( process.poll_descriptor(writer.cast_signed(), super::POLL_OUTPUT), super::POLL_OUTPUT );
let stat = process.state.sp().checked_sub(256).unwrap(); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, stat.get()).unwrap(); process.dispatch_fstat(&mut filesystem); assert_eq!(process.register(0), 0); let mut stat_bytes = [0; STAT_SIZE]; process.memory.read_exact(stat, &mut stat_bytes).unwrap(); assert_eq!( u32::from_le_bytes( stat_bytes[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4] .try_into() .unwrap() ), STAT_FIFO_MODE ); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, 0).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_lseek(&mut filesystem); assert_eq!(process.register(0), Errno::IllegalSeek.return_value());
process.state.set_x(0, u64::from(writer)).unwrap(); process.state.set_x(1, source.get()).unwrap(); process.state.set_x(2, 9).unwrap(); process.dispatch_file_write(&mut filesystem); assert_eq!(process.register(0), 9); assert_eq!( process.poll_descriptor(reader.cast_signed(), super::POLL_INPUT), super::POLL_INPUT );
process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 16).unwrap(); process.dispatch_read(&mut filesystem); assert_eq!(process.register(0), 9); let mut bytes = [0; 9]; process.memory.read_exact(destination, &mut bytes).unwrap(); assert_eq!(&bytes, b"pipe data");
process.state.set_x(0, u64::from(reader)).unwrap(); process.dispatch_read(&mut filesystem); assert_eq!(process.register(0), Errno::TryAgain.return_value());
process.state.set_x(0, u64::from(writer)).unwrap(); process.dispatch_close(&mut filesystem); assert_eq!(process.register(0), 0); assert_eq!( process.poll_descriptor(reader.cast_signed(), super::POLL_INPUT), super::POLL_HANGUP ); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 16).unwrap(); process.dispatch_read(&mut filesystem); assert_eq!(process.register(0), 0); process.state.set_x(0, u64::from(reader)).unwrap(); process.dispatch_close(&mut filesystem); assert!(process.pipes.is_empty());
process.state.set_x(0, pipe_descriptors.get()).unwrap(); process .state .set_x(1, OPEN_NONBLOCK | OPEN_CLOEXEC) .unwrap(); process.dispatch_pipe2(); process.close_on_exec_descriptors(&mut filesystem); assert!(process.pipe_descriptors.is_empty()); assert!(process.pipes.is_empty()); }
#[test] fn dup3_routes_a_standard_stream_through_a_shared_pipe() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let pipe_descriptors = process.state.sp().checked_sub(32).unwrap(); let destination = process.state.sp().checked_sub(64).unwrap(); process.state.set_x(0, pipe_descriptors.get()).unwrap(); process.state.set_x(1, OPEN_CLOEXEC).unwrap(); process.dispatch_pipe2();
process.state.set_x(0, 4).unwrap(); process.state.set_x(1, STANDARD_OUTPUT).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_dup3(&mut NullFileSystem);
assert_eq!(process.register(0), STANDARD_OUTPUT); assert_eq!( process.pipe_descriptors.get(&1), process.pipe_descriptors.get(&4) ); assert_eq!(process.descriptor_flags.get(&1), Some(&0));
process .memory .write(destination, &1_u32.to_le_bytes()) .unwrap(); process.state.set_x(0, STANDARD_OUTPUT).unwrap(); process.state.set_x(1, super::IOCTL_SET_NONBLOCK).unwrap(); process.state.set_x(2, destination.get()).unwrap(); process.dispatch_ioctl(); assert_eq!(process.register(0), 0); assert_eq!( process.descriptor_status_flags.get(&1).copied().unwrap() & OPEN_NONBLOCK, OPEN_NONBLOCK );
let mut terminal = RecordingTerminal::default(); process.state.set_x(0, STANDARD_OUTPUT).unwrap(); process.state.set_x(1, u64::from(MESSAGE_ADDRESS)).unwrap(); process .state .set_x(2, MESSAGE.len().try_into().unwrap()) .unwrap(); process .dispatch_write(&mut terminal, &mut NullFileSystem) .unwrap(); assert_eq!(process.register(0), MESSAGE.len() as u64); assert!(terminal.standard_output.is_empty());
process.state.set_x(0, 3).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process .state .set_x(2, MESSAGE.len().try_into().unwrap()) .unwrap(); process.dispatch_read(&mut NullFileSystem); let mut bytes = [0_u8; MESSAGE.len()]; process.memory.read_exact(destination, &mut bytes).unwrap(); assert_eq!(&bytes, MESSAGE); }
#[test] fn unix_socketpair_exchanges_child_setup_bytes_in_both_directions() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let sockets = process.state.sp().checked_sub(32).unwrap(); let source = process.state.sp().checked_sub(64).unwrap(); let destination = process.state.sp().checked_sub(96).unwrap(); process.memory.write(source, b"errno").unwrap(); process.state.set_x(0, super::ADDRESS_FAMILY_UNIX).unwrap(); process .state .set_x(1, super::SOCKET_SEQPACKET | OPEN_CLOEXEC) .unwrap(); process.state.set_x(2, 0).unwrap(); process.state.set_x(3, sockets.get()).unwrap();
process.dispatch_socketpair();
assert_eq!(process.register(0), 0); let mut descriptor_bytes = [0; 8]; process .memory .read_exact(sockets, &mut descriptor_bytes) .unwrap(); let first = u32::from_le_bytes(descriptor_bytes[..4].try_into().unwrap()); let second = u32::from_le_bytes(descriptor_bytes[4..].try_into().unwrap()); assert_eq!( process.descriptor_flags.get(&first), Some(&DESCRIPTOR_CLOEXEC) ); assert_eq!( process.descriptor_flags.get(&second), Some(&DESCRIPTOR_CLOEXEC) );
process.state.set_x(0, u64::from(first)).unwrap(); process.state.set_x(1, source.get()).unwrap(); process.state.set_x(2, 5).unwrap(); process.dispatch_file_write(&mut NullFileSystem); assert_eq!(process.register(0), 5); process.state.set_x(0, u64::from(second)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 5).unwrap(); process.state.set_x(3, super::MESSAGE_PEEK).unwrap(); process.state.set_x(4, 0).unwrap(); process.state.set_x(5, 0).unwrap(); process.dispatch_recvfrom(); assert_eq!(process.register(0), 5); let mut bytes = [0; 5]; process.memory.read_exact(destination, &mut bytes).unwrap(); assert_eq!(&bytes, b"errno");
process.state.set_x(0, u64::from(second)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 5).unwrap(); process.dispatch_read(&mut NullFileSystem); assert_eq!(process.register(0), 5); process.memory.read_exact(destination, &mut bytes).unwrap(); assert_eq!(&bytes, b"errno");
process.memory.write(source, b"ok").unwrap(); process.state.set_x(0, u64::from(second)).unwrap(); process.state.set_x(1, source.get()).unwrap(); process.state.set_x(2, 2).unwrap(); process.dispatch_file_write(&mut NullFileSystem); process.state.set_x(0, u64::from(first)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 2).unwrap(); process.dispatch_read(&mut NullFileSystem); assert_eq!(process.register(0), 2); process .memory .read_exact(destination, &mut bytes[..2]) .unwrap(); assert_eq!(&bytes[..2], b"ok"); }
#[test] fn nonblocking_pipe_reports_full_and_broken_writer_boundaries() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); process.pipes.insert( 1, AnonymousPipe { bytes: vec![0; PIPE_CAPACITY_BYTES].into(), reader_open: true, writer_open: true, }, );
process.dispatch_pipe_write(1, b"x"); assert_eq!(process.register(0), Errno::TryAgain.return_value()); process.pipes.get_mut(&1).unwrap().reader_open = false; process.dispatch_pipe_write(1, b"x"); assert_eq!(process.register(0), Errno::BrokenPipe.return_value()); process.dispatch_pipe_write(1, b""); assert_eq!(process.register(0), 0); }
#[test] fn pipe2_accepts_blocking_mode_and_fcntl_toggles_nonblocking() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let pipe_descriptors = process.state.sp().checked_sub(32).unwrap(); process.state.set_x(0, pipe_descriptors.get()).unwrap(); process.state.set_x(1, OPEN_CLOEXEC).unwrap(); process.dispatch_pipe2(); assert_eq!(process.register(0), 0); let mut descriptors = [0; 8]; process .memory .read_exact(pipe_descriptors, &mut descriptors) .unwrap(); let reader = u32::from_le_bytes(descriptors[..4].try_into().unwrap()); let writer = u32::from_le_bytes(descriptors[4..].try_into().unwrap());
process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, FCNTL_GET_STATUS_FLAGS).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 0); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, FCNTL_SET_STATUS_FLAGS).unwrap(); process .state .set_x(2, OPEN_NONBLOCK | OPEN_NOFOLLOW) .unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 0); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, FCNTL_GET_STATUS_FLAGS).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), OPEN_NONBLOCK);
process.state.set_x(0, u64::from(writer)).unwrap(); process.state.set_x(1, FCNTL_GET_STATUS_FLAGS).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 1);
process.state.set_x(0, u64::from(writer)).unwrap(); process .state .set_x(1, super::FCNTL_DUPLICATE_CLOEXEC) .unwrap(); process.state.set_x(2, 10).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 10); assert_eq!( process.pipe_descriptors.get(&10), process.pipe_descriptors.get(&writer) ); assert_eq!(process.descriptor_flags.get(&10), Some(&DESCRIPTOR_CLOEXEC)); }
#[test] fn flock_accepts_advisory_incremental_cache_locks() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let handle = filesystem .open( b"/project/incremental.lock", FileOpenOptions { access: FileAccess::ReadWrite, flags: FileOpenFlags::CREATE, }, ) .unwrap(); process.file_descriptors.insert(3, handle); process.state.set_x(0, 3).unwrap(); process .state .set_x(1, super::FILE_LOCK_EXCLUSIVE | super::FILE_LOCK_NONBLOCKING) .unwrap();
process.dispatch_flock();
assert_eq!(process.register(0), 0); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, super::FILE_LOCK_UNLOCK).unwrap(); process.dispatch_flock(); assert_eq!(process.register(0), 0); }
#[test] fn constrained_clone_shares_file_and_pipe_descriptors() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); let shared_file = filesystem .open( b"/tmp/shared.txt", FileOpenOptions { access: FileAccess::ReadWrite, flags: FileOpenFlags::CREATE, }, ) .unwrap(); process.file_descriptors.insert(3, shared_file); process .descriptor_paths .insert(3, b"/tmp/shared.txt".to_vec()); let pipe_descriptors = process.state.sp().checked_sub(32).unwrap(); process.state.set_x(0, pipe_descriptors.get()).unwrap(); process .state .set_x(1, OPEN_NONBLOCK | OPEN_CLOEXEC) .unwrap(); process.dispatch_pipe2(); let mut descriptors = [0; 8]; process .memory .read_exact(pipe_descriptors, &mut descriptors) .unwrap(); let reader = u32::from_le_bytes(descriptors[..4].try_into().unwrap()); let writer = u32::from_le_bytes(descriptors[4..].try_into().unwrap());
process.state.set_x(0, SUPPORTED_CLONE_FLAGS).unwrap(); for register in 1..=4 { process.state.set_x(register, 0).unwrap(); } process.dispatch_clone(); assert_eq!(process.register(0), 0); assert_eq!(process.current_process_id, CHILD_PROCESS_ID);
let source = process.state.sp().checked_sub(64).unwrap(); process.memory.write(source, b"child").unwrap(); process.state.set_x(0, 3).unwrap(); process.state.set_x(1, source.get()).unwrap(); process.state.set_x(2, 5).unwrap(); process.dispatch_file_write(&mut filesystem); assert_eq!(process.register(0), 5); process.state.set_x(0, 3).unwrap(); process.dispatch_close(&mut filesystem); assert_eq!(process.register(0), 0); assert_eq!( filesystem .seek(shared_file, 0, FileSeekFrom::Current) .unwrap(), 5 );
process.state.set_x(0, u64::from(writer)).unwrap(); process.state.set_x(1, source.get()).unwrap(); process.state.set_x(2, 5).unwrap(); process.dispatch_file_write(&mut filesystem); assert_eq!(process.register(0), 5); process.close_on_exec_descriptors(&mut filesystem); assert!(process.pipe_descriptors.is_empty()); assert!(process.pipes.get(&1).unwrap().reader_open); assert!(process.pipes.get(&1).unwrap().writer_open);
process.finish_child(&mut filesystem, 7); assert_eq!(process.register(0), CHILD_PROCESS_ID); assert_eq!(process.file_descriptors.get(&3), Some(&shared_file)); assert_eq!( process.pipe_descriptors.get(&reader), Some(&PipeEnd::Read(1)) ); assert_eq!( process.pipe_descriptors.get(&writer), Some(&PipeEnd::Write(1)) );
let destination = process.state.sp().checked_sub(96).unwrap(); process.state.set_x(0, u64::from(writer)).unwrap(); process.dispatch_close(&mut filesystem); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 16).unwrap(); process.dispatch_read(&mut filesystem); assert_eq!(process.register(0), 5); let mut pipe_bytes = [0; 5]; process .memory .read_exact(destination, &mut pipe_bytes) .unwrap(); assert_eq!(&pipe_bytes, b"child"); process.state.set_x(0, u64::from(reader)).unwrap(); process.dispatch_read(&mut filesystem); assert_eq!(process.register(0), 0); }
#[test] fn constrained_fork_shape_suspends_the_parent_for_exec() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); process.suspended_thread = Some(super::SuspendedThread { state: process.state.clone(), interpreter: Interpreter::new().unwrap(), clear_child_tid: None, current_thread_id: DORMANT_THREAD_ID, peer: None, }); process.state.set_x(0, SUPPORTED_FORK_FLAGS).unwrap(); for register in 1..=4 { process.state.set_x(register, 0).unwrap(); }
process.dispatch_clone();
assert_eq!(process.register(0), 0); assert_eq!(process.current_process_id, CHILD_PROCESS_ID); assert!(process.suspended_thread.is_none()); assert_eq!(process.suspended_parents.len(), 1); assert!( process .suspended_parents .last() .is_some_and(|parent| parent.suspended_thread.is_some()) ); }
#[test] fn nested_processes_unwind_to_their_immediate_parents() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024);
for expected_process_id in [2, 3] { process.state.set_x(0, SUPPORTED_CLONE_FLAGS).unwrap(); for register in 1..=4 { process.state.set_x(register, 0).unwrap(); } process.dispatch_clone(); assert_eq!(process.register(0), 0); assert_eq!(process.current_process_id, expected_process_id); assert_eq!(process.parent_process_id, expected_process_id - 1); } assert_eq!(process.suspended_parents.len(), 2);
process.finish_child(&mut filesystem, 0); assert_eq!(process.current_process_id, 2); assert_eq!(process.parent_process_id, 1); assert_eq!(process.register(0), 3); assert_eq!(process.suspended_parents.len(), 1);
process.state.set_x(0, u64::MAX).unwrap(); process.state.set_x(1, 0).unwrap(); process.state.set_x(2, 0).unwrap(); process.state.set_x(3, 0).unwrap(); process.dispatch_wait4(); assert_eq!(process.register(0), 3);
process.finish_child(&mut filesystem, 7); assert_eq!(process.current_process_id, INITIAL_PROCESS_ID); assert_eq!(process.parent_process_id, 0); assert_eq!(process.register(0), CHILD_PROCESS_ID); assert!(process.suspended_parents.is_empty()); }
#[test] fn spawned_compiler_can_park_its_signal_waiter_thread() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); process.state.set_x(0, SUPPORTED_CLONE_FLAGS).unwrap(); for register in 1..=4 { process.state.set_x(register, 0).unwrap(); } process.dispatch_clone(); assert_eq!(process.current_process_id, CHILD_PROCESS_ID);
let parent_tid = process.state.sp().checked_sub(32).unwrap(); let clear_child_tid = process.state.sp().checked_sub(28).unwrap(); process.state.set_x(0, DORMANT_THREAD_CLONE_FLAGS).unwrap(); process .state .set_x(1, process.state.sp().checked_sub(4096).unwrap().get()) .unwrap(); process.state.set_x(2, parent_tid.get()).unwrap(); process.state.set_x(3, 0x1234).unwrap(); process.state.set_x(4, clear_child_tid.get()).unwrap(); process.dispatch_clone(); assert_eq!(process.register(0), 0); assert_eq!(process.current_thread_id, DORMANT_THREAD_ID); assert_eq!(process.suspended_parents.len(), 1);
process.state.set_x(0, clear_child_tid.get()).unwrap(); process.state.set_x(1, FUTEX_WAIT).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_futex(); assert_eq!(process.current_thread_id, CHILD_PROCESS_ID); assert!(process.dormant_thread.is_some()); assert_eq!(process.suspended_parents.len(), 1); }
#[test] fn tkill_stops_the_dormant_signal_helper() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let clear_child_tid = process.state.sp().checked_sub(4).unwrap(); process .memory .write(clear_child_tid, &DORMANT_THREAD_ID.to_le_bytes()[..4]) .unwrap(); process.dormant_thread = Some(super::DormantThread { clear_child_tid }); process.state.set_x(0, DORMANT_THREAD_ID).unwrap(); process.state.set_x(1, SIGNAL_USER_1).unwrap();
assert!(!process.dispatch_tkill());
assert_eq!(process.register(0), 0); assert!(process.dormant_thread.is_none()); let mut tid = [0; 4]; process .memory .read_exact(clear_child_tid, &mut tid) .unwrap(); assert_eq!(u32::from_le_bytes(tid), 0);
process.state.set_x(0, DORMANT_THREAD_ID).unwrap(); process.state.set_x(1, SIGNAL_USER_1).unwrap(); assert!(!process.dispatch_tkill()); assert_eq!(process.register(0), 0); process .memory .write(clear_child_tid, &2_u32.to_le_bytes()) .unwrap(); process.state.set_x(0, clear_child_tid.get()).unwrap(); process.state.set_x(1, FUTEX_WAIT).unwrap(); process.state.set_x(2, 2).unwrap();
process.dispatch_futex();
process .memory .read_exact(clear_child_tid, &mut tid) .unwrap(); assert_eq!(u32::from_le_bytes(tid), 3); assert!(!process.pending_signal_sync); }
#[test] fn private_futex_releases_only_when_no_runnable_peer_remains() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let clear_child_tid = process.state.sp().checked_sub(8).unwrap(); let address = process.state.sp().checked_sub(4).unwrap(); process.dormant_thread = Some(super::DormantThread { clear_child_tid }); process.memory.write(address, &2_u32.to_le_bytes()).unwrap(); process.state.set_x(0, address.get()).unwrap(); process .state .set_x(1, FUTEX_PRIVATE_FLAG | FUTEX_WAIT) .unwrap(); process.state.set_x(2, 2).unwrap();
process.dispatch_futex();
let mut value = [0; 4]; process.memory.read_exact(address, &mut value).unwrap(); assert_eq!(u32::from_le_bytes(value), 3);
process.memory.write(address, &2_u32.to_le_bytes()).unwrap(); process.state.set_x(0, address.get()).unwrap(); process.suspended_thread = Some(super::SuspendedThread { state: process.state.clone(), interpreter: Interpreter::new().unwrap(), clear_child_tid: None, current_thread_id: ACTIVE_THREAD_ID, peer: None, }); process.dispatch_futex();
process.memory.read_exact(address, &mut value).unwrap(); assert_eq!(u32::from_le_bytes(value), 2); assert_eq!(process.current_thread_id, ACTIVE_THREAD_ID); }
#[test] fn serializes_one_active_thread_beside_the_dormant_signal_helper() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let parent_tid = process.state.sp().checked_sub(32).unwrap(); let clear_child_tid = process.state.sp().checked_sub(28).unwrap(); process .memory .write(clear_child_tid, &DORMANT_THREAD_ID.to_le_bytes()[..4]) .unwrap(); process.state.set_x(0, DORMANT_THREAD_CLONE_FLAGS).unwrap(); process .state .set_x(1, process.state.sp().checked_sub(4096).unwrap().get()) .unwrap(); process.state.set_x(2, parent_tid.get()).unwrap(); process.state.set_x(3, 0x1234).unwrap(); process.state.set_x(4, clear_child_tid.get()).unwrap(); process.dispatch_clone(); assert_eq!(process.register(0), 0); assert_eq!(process.current_thread_id, DORMANT_THREAD_ID); assert!(process.suspended_thread.is_some()); process.state.set_x(0, clear_child_tid.get()).unwrap(); process.state.set_x(1, FUTEX_WAIT).unwrap(); process.dispatch_futex(); assert_eq!(process.register(0), DORMANT_THREAD_ID); assert_eq!(process.current_thread_id, INITIAL_PROCESS_ID); assert!(process.dormant_thread.is_some()); let mut tid = [0; 4]; process.memory.read_exact(parent_tid, &mut tid).unwrap(); assert_eq!( u32::from_le_bytes(tid), u32::try_from(DORMANT_THREAD_ID).unwrap() ); process .memory .read_exact(clear_child_tid, &mut tid) .unwrap(); assert_eq!(u32::from_le_bytes(tid), 0);
process.state.set_x(0, DORMANT_THREAD_CLONE_FLAGS).unwrap(); process.dispatch_clone(); assert_eq!(process.register(0), 0); assert_eq!(process.current_thread_id, ACTIVE_THREAD_ID); assert!(process.suspended_thread.is_some()); process.memory.read_exact(parent_tid, &mut tid).unwrap(); assert_eq!( u32::from_le_bytes(tid), u32::try_from(ACTIVE_THREAD_ID).unwrap() );
process.state.set_x(0, clear_child_tid.get()).unwrap(); process.state.set_x(1, FUTEX_WAIT).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_futex(); assert_eq!(process.current_thread_id, INITIAL_PROCESS_ID); assert_eq!(process.register(0), ACTIVE_THREAD_ID); assert!(process.suspended_thread.is_some());
process.state.set_x(0, DORMANT_THREAD_CLONE_FLAGS).unwrap(); process.dispatch_clone(); assert_eq!(process.register(0), Errno::TryAgain.return_value());
process.state.set_x(0, clear_child_tid.get()).unwrap(); process.state.set_x(1, FUTEX_WAIT).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_futex(); assert_eq!(process.current_thread_id, ACTIVE_THREAD_ID); assert_eq!(process.register(0), 0);
process.clear_child_tid(); process.finish_thread(); assert_eq!(process.current_thread_id, INITIAL_PROCESS_ID); assert_eq!(process.register(0), 0); process.clear_dormant_thread_tid(); process .memory .read_exact(clear_child_tid, &mut tid) .unwrap(); assert_eq!(u32::from_le_bytes(tid), 0); }
#[test] fn cooperatively_schedules_a_nested_helper_with_the_active_thread() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let parent_tid = process.state.sp().checked_sub(32).unwrap(); let clear_child_tid = process.state.sp().checked_sub(28).unwrap(); process.current_thread_id = ACTIVE_THREAD_ID; process.suspended_thread = Some(super::SuspendedThread { state: process.state.clone(), interpreter: Interpreter::new().unwrap(), clear_child_tid: None, current_thread_id: INITIAL_PROCESS_ID, peer: None, }); process.state.set_x(0, DORMANT_THREAD_CLONE_FLAGS).unwrap(); process .state .set_x(1, process.state.sp().checked_sub(4096).unwrap().get()) .unwrap(); process.state.set_x(2, parent_tid.get()).unwrap(); process.state.set_x(3, 0x5678).unwrap(); process.state.set_x(4, clear_child_tid.get()).unwrap(); process.dispatch_clone(); assert_eq!(process.current_thread_id, NESTED_HELPER_THREAD_ID);
for expected_thread in [ACTIVE_THREAD_ID, NESTED_HELPER_THREAD_ID, ACTIVE_THREAD_ID] { process.state.set_x(0, clear_child_tid.get()).unwrap(); process.state.set_x(1, FUTEX_WAIT).unwrap(); process.state.set_x(2, 0).unwrap(); process.dispatch_futex(); assert_eq!(process.current_thread_id, expected_thread); } assert_eq!(process.parked_helpers.len(), 1); assert!(process.compiler_thread.is_none()); assert!(process.suspended_thread.is_some());
process.schedule_parked_helper(); process.clear_child_tid(); process.finish_running_helper(); assert_eq!(process.current_thread_id, ACTIVE_THREAD_ID); assert!(process.parked_helpers.is_empty()); assert!(process.compiler_thread.is_none()); assert!(process.suspended_thread.is_some()); }
#[test] fn wait4_preserves_unreaped_status_on_fault() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = NullFileSystem; process.state.set_x(0, SUPPORTED_CLONE_FLAGS).unwrap(); process.state.set_x(1, 0).unwrap(); process.state.set_x(2, 0x1111).unwrap(); process.state.set_x(3, 0x2222).unwrap(); process.state.set_x(4, 0x3333).unwrap(); process.dispatch_clone(); process.finish_child(&mut filesystem, 7);
process.state.set_x(0, CHILD_PROCESS_ID).unwrap(); process.state.set_x(1, 1).unwrap(); process.state.set_x(2, 0).unwrap(); process.state.set_x(3, 0).unwrap(); process.dispatch_wait4(); assert_eq!(process.register(0), Errno::Fault.return_value()); assert!(process.exited_child.is_some());
let status = process.state.sp().checked_sub(4).unwrap(); process.state.set_x(0, CHILD_PROCESS_ID).unwrap(); process.state.set_x(1, status.get()).unwrap(); process.dispatch_wait4(); assert_eq!(process.register(0), CHILD_PROCESS_ID); let mut status_bytes = [0; 4]; process .memory .read_exact(status, &mut status_bytes) .unwrap(); assert_eq!(u32::from_le_bytes(status_bytes), 7 << 8);
process.state.set_x(0, CHILD_PROCESS_ID).unwrap(); process.dispatch_wait4(); assert_eq!(process.register(0), Errno::NoChild.return_value()); }
#[test] fn ioctl_tracks_close_on_exec_and_rejects_other_requests_as_not_tty() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); process.file_descriptors.insert(3, 1);
process.state.set_x(0, 3).unwrap(); process.state.set_x(1, IOCTL_SET_CLOSE_ON_EXEC).unwrap(); process.dispatch_ioctl(); assert_eq!(process.register(0), 0); assert_eq!(process.descriptor_flags.get(&3), Some(&DESCRIPTOR_CLOEXEC));
process.state.set_x(0, 3).unwrap(); process.state.set_x(1, IOCTL_CLEAR_CLOSE_ON_EXEC).unwrap(); process.dispatch_ioctl(); assert_eq!(process.register(0), 0); assert_eq!(process.descriptor_flags.get(&3), Some(&0));
process.state.set_x(0, 3).unwrap(); process.state.set_x(1, 0x5413).unwrap(); process.dispatch_ioctl(); assert_eq!(process.register(0), Errno::NotTty.return_value()); }
#[test] fn anonymous_mmap_honors_an_available_address_hint() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(1024); process.state.set_x(0, 0x3ffe_1000).unwrap(); process.state.set_x(1, 0x1f_000).unwrap(); process.state.set_x(2, 3).unwrap(); process.state.set_x(3, 0x22).unwrap(); process.state.set_x(4, u64::MAX).unwrap(); process.state.set_x(5, 0).unwrap();
process.dispatch_mmap(&mut filesystem);
assert_eq!(process.register(0), 0x3ffe_1000); assert!(process.memory().regions().iter().any(|region| { region.start() == GuestAddress::new(0x3ffe_1000) && region.end() == GuestAddress::new(0x4000_0000) })); }
#[test] fn file_backed_fixed_mmap_reads_without_changing_descriptor_offset() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); let mut filesystem = MemoryFileSystem::new(8192); let handle = filesystem .open( b"/project/library.so", FileOpenOptions { access: FileAccess::ReadWrite, flags: FileOpenFlags::CREATE, }, ) .unwrap(); filesystem.write(handle, b"dynamic code").unwrap(); filesystem.seek(handle, 3, FileSeekFrom::Start).unwrap(); process.file_descriptors.insert(3, handle);
process.state.set_x(0, 0x5000_0000).unwrap(); process.state.set_x(1, PAGE_SIZE).unwrap(); process.state.set_x(2, PROT_READ | PROT_EXECUTE).unwrap(); process.state.set_x(3, MAP_PRIVATE | MAP_FIXED).unwrap(); process.state.set_x(4, 3).unwrap(); process.state.set_x(5, 0).unwrap(); process.dispatch_mmap(&mut filesystem);
assert_eq!(process.register(0), 0x5000_0000); let mut bytes = [0; 12]; process .memory .read_exact(GuestAddress::new(0x5000_0000), &mut bytes) .unwrap(); assert_eq!(&bytes, b"dynamic code"); assert_eq!( filesystem.seek(handle, 0, FileSeekFrom::Current).unwrap(), 3 ); }
#[test] fn resolves_dynamic_interpreter_from_guest_filesystem() { let main = dynamic_hello_elf(); let mut interpreter = hello_elf(1, MESSAGE_ADDRESS); write_u16(&mut interpreter, 16, 3); let path = b"/lib/ld-musl-aarch64.so.1"; let mut snapshot = b"BNFS\x02\0\0\0".to_vec(); snapshot.extend_from_slice(&2_u32.to_le_bytes()); snapshot.push(1); snapshot.extend_from_slice(&4_u32.to_le_bytes()); snapshot.extend_from_slice(&0_u64.to_le_bytes()); snapshot.extend_from_slice(b"/lib"); snapshot.push(2); snapshot.extend_from_slice(&u32::try_from(path.len()).unwrap().to_le_bytes()); snapshot.extend_from_slice(&u64::try_from(interpreter.len()).unwrap().to_le_bytes()); snapshot.extend_from_slice(path); snapshot.extend_from_slice(&interpreter); let mut filesystem = MemoryFileSystem::from_snapshot(1024 * 1024, &snapshot).unwrap();
let image = load_process_with_guest_filesystem( &main, &ProcessParameters { argv: vec![b"/hello".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, ProcessConfig::default(), &mut filesystem, ) .unwrap();
assert_eq!(image.initial_state.pc, GuestAddress::new(0x2000_1100)); assert_eq!( image.interpreter_path, Some(b"/lib/ld-musl-aarch64.so.1".to_vec()) ); }
fn load_hello( config: ProcessConfig, file_descriptor: u16, address: u16, ) -> binarrow_loader::ProcessImage { load_process( &hello_elf(file_descriptor, address), &ProcessParameters { argv: vec![b"/hello".to_vec()], envp: Vec::new(), random_bytes: [0x42; 16], credentials: Credentials::default(), }, config, ) .unwrap() }
fn hello_elf(file_descriptor: u16, address: u16) -> Vec<u8> { let mut bytes = vec![0; 0x130]; bytes[..16].copy_from_slice(&[0x7f, b'E', b'L', b'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]); write_u16(&mut bytes, 16, 2); write_u16(&mut bytes, 18, 183); write_u32(&mut bytes, 20, 1); write_u64(&mut bytes, 24, 0x1100); write_u64(&mut bytes, 32, 64); write_u16(&mut bytes, 52, 64); write_u16(&mut bytes, 54, 56); write_u16(&mut bytes, 56, 1);
write_u32(&mut bytes, 64, 1); write_u32(&mut bytes, 68, 5); write_u64(&mut bytes, 72, 0); write_u64(&mut bytes, 80, 0x1000); write_u64(&mut bytes, 88, 0x1000); write_u64(&mut bytes, 96, 0x130); write_u64(&mut bytes, 104, 0x130); write_u64(&mut bytes, 112, 0x1000);
let code = [ move_wide(0, file_descriptor), move_wide(1, address), move_wide(2, MESSAGE_LENGTH), move_wide(8, 64), 0xd400_0001, move_wide(0, 0), move_wide(8, 93), 0xd400_0001, ]; for (index, instruction) in code.into_iter().enumerate() { let offset = 0x100 + index * 4; bytes[offset..offset + 4].copy_from_slice(&instruction.to_le_bytes()); } bytes[0x120..0x120 + MESSAGE.len()].copy_from_slice(MESSAGE); bytes }
fn dynamic_hello_elf() -> Vec<u8> { let mut bytes = hello_elf(1, MESSAGE_ADDRESS); bytes.resize(0x160, 0); write_u16(&mut bytes, 56, 2); write_u64(&mut bytes, 96, 0x160); write_u64(&mut bytes, 104, 0x160); let interpreter_header = 120; write_u32(&mut bytes, interpreter_header, 3); write_u64(&mut bytes, interpreter_header + 8, 0x140); write_u64(&mut bytes, interpreter_header + 32, 26); bytes[0x140..0x15a].copy_from_slice(b"/lib/ld-musl-aarch64.so.1\0"); bytes }
const fn move_wide(register: u8, immediate: u16) -> u32 { 0xd280_0000 | ((immediate as u32) << 5) | (register as u32) }
fn write_u16(bytes: &mut [u8], offset: usize, value: u16) { bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes()); }
fn write_u32(bytes: &mut [u8], offset: usize, value: u32) { bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); }
fn write_u64(bytes: &mut [u8], offset: usize, value: u64) { bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes()); }}