Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at fix-function-merge 224 lines 8.7 kB view raw
1{ lib 2, fetchFromGitHub 3, runCommand 4, yallback 5, yara 6}: 7 8/* TODO/CAUTION: 9 10I don't want to discourage use, but I'm not sure how stable 11the API is. Have fun, but be prepared to track changes! :) 12 13For _now_, binlore is basically a thin wrapper around 14`<invoke yara> | <postprocess with yallback>` with support 15for running it on a derivation, saving the result in the 16store, and aggregating results from a set of packages. 17 18In the longer term, I suspect there are more uses for this 19general pattern (i.e., run some analysis tool that produces 20a deterministic output and cache the result per package...). 21 22I'm not sure how that'll look and if it'll be the case that 23binlore automatically collects all of them, or if you'll be 24configuring which "kind(s)" of lore it generates. Nailing 25that down will almost certainly mean reworking the API. 26 27*/ 28 29let 30 src = fetchFromGitHub { 31 owner = "abathur"; 32 repo = "binlore"; 33 rev = "v0.3.0"; 34 hash = "sha256-4Fs6HThfDhKRskuDJx2+hucl8crMRm10K6949JdIwPY="; 35 }; 36 /* 37 binlore has one one more yallbacks responsible for 38 routing the appropriate lore to a named file in the 39 appropriate format. At some point I might try to do 40 something fancy with this, but for now the answer to 41 *all* questions about the lore are: the bare minimum 42 to get resholve over the next feature hump in time to 43 hopefully slip this feature in before the branch-off. 44 */ 45 # TODO: feeling really uninspired on the API 46 loreDef = { 47 # YARA rule file 48 rules = (src + "/execers.yar"); 49 # output filenames; "types" of lore 50 types = [ "execers" "wrappers" ]; 51 # shell rule callbacks; see github.com/abathur/yallback 52 yallback = (src + "/execers.yall"); 53 # TODO: 54 # - echo for debug, can be removed at some point 55 # - I really just wanted to put the bit after the pipe 56 # in here, but I'm erring on the side of flexibility 57 # since this form will make it easier to pilot other 58 # uses of binlore. 59 callback = lore: drv: '' 60 if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then 61 echo generating binlore for $drv by running: 62 echo "${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback}" 63 else 64 echo "failed to generate binlore for $drv (none of ${drv}/{bin,lib,libexec} exist)" 65 fi 66 67 if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then 68 ${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback} 69 fi 70 ''; 71 }; 72 73in rec { 74 /* 75 Output a directory containing lore for multiple drvs. 76 77 This will `make` lore for drv in drvs and then combine lore 78 of the same type across all packages into a single file. 79 80 When drvs are also specified in the strip argument, corresponding 81 lore is made relative by stripping the path of each drv from 82 matching entries. (This is mainly useful in a build process that 83 uses a chain of two or more derivations where the output of one 84 is the source for the next. See resholve for an example.) 85 */ 86 collect = { lore ? loreDef, drvs, strip ? [ ] }: (runCommand "more-binlore" { } '' 87 mkdir $out 88 for lorefile in ${toString lore.types}; do 89 cat ${lib.concatMapStrings (x: x + "/$lorefile ") (map (make lore) (map lib.getBin (builtins.filter lib.isDerivation drvs)))} > $out/$lorefile 90 substituteInPlace $out/$lorefile ${lib.concatMapStrings (x: "--replace-quiet '${x}/' '' ") strip} 91 done 92 ''); 93 94 /* 95 Output a directory containing lore for a single drv. 96 97 This produces lore for the derivation (via lore.callback) and 98 appends any lore that the derivation itself wrote to nix-support 99 or which was overridden in drv.binlore.<outputName> (passthru). 100 101 > *Note*: Since the passthru is attached to all outputs, binlore 102 > is an attrset namespaced by outputName to support packages with 103 > executables in more than one output. 104 105 Since the last entry wins, the effective priority is: 106 drv.binlore.<outputName> > $drv/nix-support > lore generated here by callback 107 */ 108 make = lore: drv: runCommand "${drv.name}-binlore" { 109 drv = drv; 110 } ('' 111 mkdir $out 112 touch $out/{${builtins.concatStringsSep "," lore.types}} 113 114 ${lore.callback lore drv} 115 '' + 116 # append lore from package's $out and drv.binlore.${drv.outputName} (last entry wins) 117 '' 118 for lore_type in ${builtins.toString lore.types}; do 119 if [[ -f "${drv}/nix-support/$lore_type" ]]; then 120 cat "${drv}/nix-support/$lore_type" >> "$out/$lore_type" 121 fi 122 '' + lib.optionalString (builtins.hasAttr "binlore" drv && builtins.hasAttr drv.outputName drv.binlore) '' 123 if [[ -f "${drv.binlore."${drv.outputName}"}/$lore_type" ]]; then 124 cat "${drv.binlore."${drv.outputName}"}/$lore_type" >> "$out/$lore_type" 125 fi 126 '' + '' 127 done 128 129 echo binlore for $drv written to $out 130 ''); 131 132 /* 133 Utility function for creating override lore for drv. 134 135 We normally attach this lore to `drv.passthru.binlore.<outputName>`. 136 137 > *Notes*: 138 > - Since the passthru is attached to all outputs, binlore is an 139 > attrset namespaced by outputName to support packages with 140 > executables in more than one output. You'll generally just use 141 > `out` or `bin`. 142 > - We can reconsider the passthru attr name if someone adds 143 > a new lore provider. We settled on `.binlore` for now to make it 144 > easier for people to figure out what this is for. 145 146 The lore argument should be a Shell script (string) that generates 147 the necessary lore. You can use arbitrary Shell, but this function 148 includes a shell DSL you can use to declare/generate lore in most 149 cases. It has the following functions: 150 151 - `execer <verdict> [<path>...]` 152 - `wrapper <wrapper_path> <original_path>` 153 154 Writing every override explicitly in a Nix list would be tedious 155 for large packages, but this small shell DSL enables us to express 156 many overrides efficiently via pathname expansion/globbing. 157 158 Here's a very general example of both functions: 159 160 passthru.binlore.out = binlore.synthesize finalAttrs.finalPackage '' 161 execer can bin/hello bin/{a,b,c} 162 wrapper bin/hello bin/.hello-wrapped 163 ''; 164 165 And here's a specific example of how pathname expansion enables us 166 to express lore for the single-binary variant of coreutils while 167 being both explicit and (somewhat) efficient: 168 169 passthru = {} // optionalAttrs (singleBinary != false) { 170 binlore.out = binlore.synthesize coreutils '' 171 execer can bin/{chroot,env,install,nice,nohup,runcon,sort,split,stdbuf,timeout} 172 execer cannot bin/{[,b2sum,base32,base64,basename,basenc,cat,chcon,chgrp,chmod,chown,cksum,comm,cp,csplit,cut,date,dd,df,dir,dircolors,dirname,du,echo,expand,expr,factor,false,fmt,fold,groups,head,hostid,id,join,kill,link,ln,logname,ls,md5sum,mkdir,mkfifo,mknod,mktemp,mv,nl,nproc,numfmt,od,paste,pathchk,pinky,pr,printenv,printf,ptx,pwd,readlink,realpath,rm,rmdir,seq,sha1sum,sha224sum,sha256sum,sha384sum,sha512sum,shred,shuf,sleep,stat,stty,sum,sync,tac,tail,tee,test,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,vdir,wc,who,whoami,yes} 173 ''; 174 }; 175 176 Caution: Be thoughtful about using a bare wildcard (*) glob here. 177 We should generally override lore only when a human understands if 178 the executable will exec arbitrary user-passed executables. A bare 179 glob can match new executables added in future package versions 180 before anyone can audit them. 181 */ 182 synthesize = drv: loreSynthesizingScript: runCommand "${drv.name}-lore-override" { 183 drv = drv; 184 } ('' 185 execer(){ 186 local verdict="$1" 187 188 shift 189 190 for path in "$@"; do 191 if [[ -f "$PWD/$path" ]]; then 192 echo "$verdict:$PWD/$path" 193 else 194 echo "error: Tried to synthesize execer lore for missing file: $PWD/$path" >&2 195 exit 2 196 fi 197 done 198 } >> $out/execers 199 200 wrapper(){ 201 local wrapper="$1" 202 local original="$2" 203 204 if [[ ! -f "$wrapper" ]]; then 205 echo "error: Tried to synthesize wrapper lore for missing wrapper: $PWD/$wrapper" >&2 206 exit 2 207 fi 208 209 if [[ ! -f "$original" ]]; then 210 echo "error: Tried to synthesize wrapper lore for missing original: $PWD/$original" >&2 211 exit 2 212 fi 213 214 echo "$PWD/$wrapper:$PWD/$original" 215 216 } >> $out/wrappers 217 218 mkdir $out 219 220 # lore override commands are relative to the drv root 221 cd $drv 222 223 '' + loreSynthesizingScript); 224}