◢███◤      ◢██◤                            ◢██◤                            
     ◢██◤       ◢██◤                            ◢██◤                             
    ◢██◤       ◢██◤                            ◢██◤                              
   ◢██◤       ◢██◤                            ◢██◤                               
  ◢██◤       ◢██◤                            ◢██◤                                
◢███◤       ◢██◤                            ◢██◤                          ◥██◣   
◥███       ◢█████◣    ◢████████◤ ◢███████◤ ◢██◤ ◢██◤                 ◢██◤   ██◣  
 ███      ◢███████◣        ◢██◤ ◢██◤ ◢██◤ ◢███████◤                 ◢██◤    ███  
 ███     ◢██◤  ◢██◤ ◢████████◤ ◢██◤      ◢█████◣                   ◢██◤     ███  
 ███    ◢██◤  ◢██◤ ◢██◤  ███◤ ◢██◤ ◢██◤ ◢██◤◥███◣                 ◢██◤      ███  
 ◥██   ◢██◤  ◢██◤ ◢████████◤ ◢███████◤ ◢██◤  ◥███◣               ◢██◤       ███◣ 
  ◥██◣                                                          ◢██◤       ◢███◤ 
                                 ◢███◤ ◢███◤ ◢██◤  ◢██◤ ◢█████████◤       ◢██◤   
                                ◢█████████◤ ◢██◤  ◢██◤ ◢██◤  ████◤       ◢██◤    
                               ◢██◤◢█◤◢██◤ ◢██◤  ◢██◤ ◢██◤   ███◤       ◢██◤     
                              ◢██◤   ◢██◤ ◢████████◤ ◢█████████◤       ◢██◤      
                             ◢██◤   ◢██◤ ◢████████◤ ◢█████████◤      ◢███◤       

0003
[Feature] Validate scriptors in nested scripts
vaegvc
`ATHE PROBLEM` There are many cases where one script (call it "the utility script") wants to accept some other script as a scriptor ("the scriptor"), but wants to leave open the possibility that it is itself called as a subscript or as a scriptor by one or more other scripts ("intermediary scripts"). If the utility script doesn't really care if the scriptor is what it says it is, there's no real problem here. For example, many lock breakers do not care if their scriptor is a real loc or a lock sim or a random script that returns "hi". These scripts need to do minimal validation of their arguments, and currently work just fine on the CLI, or if subscripted or called as a scriptor. But in some cases, a utility script either needs to rely on the output or the side effects of a specific script being legitimate. Some examples: *) A fullsec bank that takes an `Faccts`.`Lxfer_gc_to` scriptor for deposits, and depends on the return value truthfully indicating whether GC was sent to avoid losing money. *) A cracker that wants to protect callers from fake locs, and so checks `Fscripts`.`Lget_access_level` and refuses if the result doesn't indicate the target is hidden. *) A cracker that rewards callers with some of the owner's GC every time they crack a loc no one else has, which depends on being able to record true names to check against in the future *) An upgrade logger (for computing drop rates or rarities), which wants to be fullsec and take a `Fsys`.`Lupgrades` scriptor to check your upgrades and doesn't want a fake scriptor to lie about having an orange t2 ez_40 with special properties. In all these cases, the utility script can currently only trust it's scriptor if the utility script is itself called on the CLI. The existence of any intermediary scripts destroys the utility script's ability to have faith that it's scriptor is real and un-tampered with. This proposal seeks to allow utility scripts to validate scriptors even in the presence of unknown or even hostile intermediary scripts if they want to (any utility script that doesn't care can continue, as today, without any precautions). `ATHE PROPOSAL` Introduce a new preprocessor-macro-thing, such as `C#S` (alternate names would be fine, but for golf, may as well be short), which expands to a function that takes as arguments a name and a function. If the function is the proper scriptor `A.``Ccall` function for that name, `C#S` returns the function, otherwise it returns null. An example to demonstrate and clarify: `Vfunction``A(``Ccontext``A,``Cargs``A) {` `l// xfer:#s.accts.xfer_gc_to` `Vvar` `Cscr``A=``C#S``A(``V"``Faccts``V.``Lxfer_gc_to``V"``A,``Cargs``A.``Cxfer``A.``Ccall``A);` `Vif``A(``Cscr``A) {` `Vreturn` `Cscr``A({``Nto`:`V"dtr"``A,``Namount`:`V1`,`Nmemo`:`V"This is real! Thanks!"``A});` `A}` `Velse` `A{` `Vreturn` `A{``Nok`:`Vfalse``A,``Nmsg`:`V":( Please pass a real ``Faccts``V.``Lxfer_gc_to``V scriptor"``A};` `A}` `A}` `ADESIGN DECISIONS` If the proposed `C#S` just returned a bool, callers would require extra precaution against getters and other trickery which are all to easy to mess up. Returning the function allows for relatively straight-forward very secure code. If the proposed `C#S` returned a function-that-returned-an-error on invalid calls (instead of null), then it would be impossible to check if a function is real before calling it, which some scripts may need (i.e. if the script is real, do some preparatory DB work, then call it, else error). There is no way to get a scriptor "out of thin air" anywhere in the stack -- you can only check if a function is a legitimate scriptor function, but you must already have a reference to it, you can't pull it out of thin air by name alone. By taking the name as a separate argument, a caller can indicate very strongly if they only want a very specific script (`C#S``A(``V"``Faccts``V.``Lxfer_gc_to``V"``A, ``Cargs``A.``Cxfer``A.``Ccall``A)`) or if they just want to check if a scriptor is lying about it's own name (`C#S``A(``Cargs``A.``Ctarget``A.``Cname``A, ``Cargs``A.``Ctarget``A.``Ccall``A)`) in a relatively obvious manner, and again getter troubles are minimized. `AUNDER THE HOOD` In order to support `C#S`, one significant-ish change would need to be made: the game would need to keep a list of all scriptors passed on the CLI, by name, and, critically, deduplicate them. Currently, if you pass `V#s.``Faccts``V.``Lbalance` as two separate arguments, they get distinct `A.``Ccall` functions, which is harmless today but would cripple this proposal. Since the game gets the raw arguments (with `C__scriptor` keys), however, I believe it would not be difficult to track these and set up an object to map them to their names. For example, something like `C__SCRIPTOR_LIST` (which would need to be string-blocklisted like all the other `C#`-related functions). Once this is done, the function `C#S` expands to would be almost trivial. The following would suffice (it would, again, need to be locked down as is standard): `Vfunction` `C__SCRIPTOR_VALIDATE``A(``Cname``A,``Cfunc``A) {` `Vif``A(``Cfunc` `A&&` `C__SCRIPTOR_LIST``A[``Cname``A] ===` `Cfunc``A)``Vreturn` `Cfunc``A;` `Vreturn null``A;` `A}` As with other functions, the preproc should probably look for `C#S``A(`, not just `C#S` before expanding, and all the usual precautions (blocklisting the name, making it const, etc etc) would need to be followed. `ADISCUSSION` Before voting on this proposal, *please* think about if it is good for the game to have this. It might add too much safety (I have a very hard time judging this). I'd appreciate comments and suggestions and am happy to change this (if the API is too awkward or problematic, for example). If you think the proposal is good, pretend it already exists and try to break it.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
A proposed alternate syntax (by ast) would be that `C#S` takes the scriptor object as an arg and returns if it is legitimate. It can do this by using a WeakSet to store all scriptors (which removes the need for the game to dedupe; if there are 2 scriptors for the same script, oh well, it still works), and relies on the fact that scriptors are frozen to know that nothing unexpected was set on them. In that case, the first sample code snippet above would be written as follows: `Vfunction``A(``Ccontext``A,``Cargs``A) {` `l// xfer:#s.accts.xfer_gc_to` `Vvar` `Cscr``A=``Cargs``A.``Cxfer``A;` `Vif``A(``C#S``A(``Cscr``A) &&` `Cscr``A.``Cname` `A===` `V"``Faccts``V.``Lxfer_gc_to``A) {` `Vreturn` `Cscr``A.``Ccall``A({``Nto`:`V"dtr"``A,``Namount`:`V1`,`Nmemo`:`V"This is real! Thanks!"``A});` `A}` `Velse` `A{` `Vreturn` `A{``Nok`:`Vfalse``A,``Nmsg`:`V":( Please pass a real ``Faccts``V.``Lxfer_gc_to``V scriptor"``A};` `A}` `A}` This is more clear and much more explicit -- the name check is now on the caller, all `C#S` does is verify that the scriptor is one that was passed on the command line -- and opens up some new ways to mess it up (checking name before `C#S` would be a problem) which might be good (too much safety is bad). This adds the limitation that you can't break a scriptor into it's name and function and re-assemble it and still verify it, but that is rare and probably acceptable.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -