jnv: 0.4.2 -> 0.5.0 (#371655)
[NixPkgs.git] / pkgs / development / tools / analysis / binlore / default.nix
blob5033a73c515eb7039757887953098e64675f8653
2   lib,
3   fetchFromGitHub,
4   runCommand,
5   yallback,
6   yara,
7 }:
9 /*
10   TODO/CAUTION:
12   I don't want to discourage use, but I'm not sure how stable
13   the API is. Have fun, but be prepared to track changes! :)
15   For _now_, binlore is basically a thin wrapper around
16   `<invoke yara> | <postprocess with yallback>` with support
17   for running it on a derivation, saving the result in the
18   store, and aggregating results from a set of packages.
20   In the longer term, I suspect there are more uses for this
21   general pattern (i.e., run some analysis tool that produces
22   a deterministic output and cache the result per package...).
24   I'm not sure how that'll look and if it'll be the case that
25   binlore automatically collects all of them, or if you'll be
26   configuring which "kind(s)" of lore it generates. Nailing
27   that down will almost certainly mean reworking the API.
30 let
31   src = fetchFromGitHub {
32     owner = "abathur";
33     repo = "binlore";
34     rev = "v0.3.0";
35     hash = "sha256-4Fs6HThfDhKRskuDJx2+hucl8crMRm10K6949JdIwPY=";
36   };
37   /*
38     binlore has one one more yallbacks responsible for
39     routing the appropriate lore to a named file in the
40     appropriate format. At some point I might try to do
41     something fancy with this, but for now the answer to
42     *all* questions about the lore are: the bare minimum
43     to get resholve over the next feature hump in time to
44     hopefully slip this feature in before the branch-off.
45   */
46   # TODO: feeling really uninspired on the API
47   loreDef = {
48     # YARA rule file
49     rules = (src + "/execers.yar");
50     # output filenames; "types" of lore
51     types = [
52       "execers"
53       "wrappers"
54     ];
55     # shell rule callbacks; see github.com/abathur/yallback
56     yallback = (src + "/execers.yall");
57     # TODO:
58     # - echo for debug, can be removed at some point
59     # - I really just wanted to put the bit after the pipe
60     #   in here, but I'm erring on the side of flexibility
61     #   since this form will make it easier to pilot other
62     #   uses of binlore.
63     callback = lore: drv: ''
64       if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then
65         echo generating binlore for $drv by running:
66         echo "${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback}"
67       else
68         echo "failed to generate binlore for $drv (none of ${drv}/{bin,lib,libexec} exist)"
69       fi
71       if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then
72         ${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback}
73       fi
74     '';
75   };
78 rec {
79   /*
80     Output a directory containing lore for multiple drvs.
82     This will `make` lore for drv in drvs and then combine lore
83     of the same type across all packages into a single file.
85     When drvs are also specified in the strip argument, corresponding
86     lore is made relative by stripping the path of each drv from
87     matching entries. (This is mainly useful in a build process that
88     uses a chain of two or more derivations where the output of one
89     is the source for the next. See resholve for an example.)
90   */
91   collect =
92     {
93       lore ? loreDef,
94       drvs,
95       strip ? [ ],
96     }:
97     (runCommand "more-binlore" { } ''
98       mkdir $out
99       for lorefile in ${toString lore.types}; do
100         cat ${
101           lib.concatMapStrings (x: x + "/$lorefile ") (
102             map (make lore) (map lib.getBin (builtins.filter lib.isDerivation drvs))
103           )
104         } > $out/$lorefile
105         substituteInPlace $out/$lorefile ${lib.concatMapStrings (x: "--replace-quiet '${x}/' '' ") strip}
106       done
107     '');
109   /*
110     Output a directory containing lore for a single drv.
112     This produces lore for the derivation (via lore.callback) and
113     appends any lore that the derivation itself wrote to nix-support
114     or which was overridden in drv.binlore.<outputName> (passthru).
116     > *Note*: Since the passthru is attached to all outputs, binlore
117     > is an attrset namespaced by outputName to support packages with
118     > executables in more than one output.
120     Since the last entry wins, the effective priority is:
121     drv.binlore.<outputName> > $drv/nix-support > lore generated here by callback
122   */
123   make =
124     lore: drv:
125     runCommand "${drv.name}-binlore"
126       {
127         drv = drv;
128       }
129       (
130         ''
131           mkdir $out
132           touch $out/{${builtins.concatStringsSep "," lore.types}}
134           ${lore.callback lore drv}
135         ''
136         +
137           # append lore from package's $out and drv.binlore.${drv.outputName} (last entry wins)
138           ''
139             for lore_type in ${builtins.toString lore.types}; do
140               if [[ -f "${drv}/nix-support/$lore_type" ]]; then
141                 cat "${drv}/nix-support/$lore_type" >> "$out/$lore_type"
142               fi
143           ''
144         +
145           lib.optionalString (builtins.hasAttr "binlore" drv && builtins.hasAttr drv.outputName drv.binlore)
146             ''
147               if [[ -f "${drv.binlore."${drv.outputName}"}/$lore_type" ]]; then
148                 cat "${drv.binlore."${drv.outputName}"}/$lore_type" >> "$out/$lore_type"
149               fi
150             ''
151         + ''
152           done
154           echo binlore for $drv written to $out
155         ''
156       );
158   /*
159     Utility function for creating override lore for drv.
161     We normally attach this lore to `drv.passthru.binlore.<outputName>`.
163     > *Notes*:
164     > - Since the passthru is attached to all outputs, binlore is an
165     >   attrset namespaced by outputName to support packages with
166     >   executables in more than one output. You'll generally just use
167     >   `out` or `bin`.
168     > - We can reconsider the passthru attr name if someone adds
169     >   a new lore provider. We settled on `.binlore` for now to make it
170     >   easier for people to figure out what this is for.
172     The lore argument should be a Shell script (string) that generates
173     the necessary lore. You can use arbitrary Shell, but this function
174     includes a shell DSL you can use to declare/generate lore in most
175     cases. It has the following functions:
177     - `execer <verdict> [<path>...]`
178     - `wrapper <wrapper_path> <original_path>`
180     Writing every override explicitly in a Nix list would be tedious
181     for large packages, but this small shell DSL enables us to express
182     many overrides efficiently via pathname expansion/globbing.
184     Here's a very general example of both functions:
186     passthru.binlore.out = binlore.synthesize finalAttrs.finalPackage ''
187       execer can bin/hello bin/{a,b,c}
188       wrapper bin/hello bin/.hello-wrapped
189     '';
191     And here's a specific example of how pathname expansion enables us
192     to express lore for the single-binary variant of coreutils while
193     being both explicit and (somewhat) efficient:
195     passthru = {} // optionalAttrs (singleBinary != false) {
196       binlore.out = binlore.synthesize coreutils ''
197         execer can bin/{chroot,env,install,nice,nohup,runcon,sort,split,stdbuf,timeout}
198         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}
199       '';
200     };
202     Caution: Be thoughtful about using a bare wildcard (*) glob here.
203     We should generally override lore only when a human understands if
204     the executable will exec arbitrary user-passed executables. A bare
205     glob can match new executables added in future package versions
206     before anyone can audit them.
207   */
208   synthesize =
209     drv: loreSynthesizingScript:
210     runCommand "${drv.name}-lore-override"
211       {
212         drv = drv;
213       }
214       (
215         ''
216           execer(){
217             local verdict="$1"
219             shift
221             for path in "$@"; do
222               if [[ -f "$PWD/$path" ]]; then
223                 echo "$verdict:$PWD/$path"
224               else
225                 echo "error: Tried to synthesize execer lore for missing file: $PWD/$path" >&2
226                 exit 2
227               fi
228             done
229           } >> $out/execers
231           wrapper(){
232             local wrapper="$1"
233             local original="$2"
235             if [[ ! -f "$wrapper" ]]; then
236               echo "error: Tried to synthesize wrapper lore for missing wrapper: $PWD/$wrapper" >&2
237               exit 2
238             fi
240             if [[ ! -f "$original" ]]; then
241               echo "error: Tried to synthesize wrapper lore for missing original: $PWD/$original" >&2
242               exit 2
243             fi
245             echo "$PWD/$wrapper:$PWD/$original"
247           } >> $out/wrappers
249           mkdir $out
251           # lore override commands are relative to the drv root
252           cd $drv
254         ''
255         + loreSynthesizingScript
256       );