Document directory
- Problem
- Restrictions
- Solution
Problem
My goal is very simple, that is, to use code written by others in my own system, but this Code may pollute global variables or even be malicious and destructive. I want to ensure that the Code is correctly executed and Its Impact scope is fully controlled. This is the sandbox I want.
Based on my own thoughts and discussions with some friends, I think I need to solve the following four problems:
1. variable access problems: third parties can use the variable name to access global variables.
2. this problem: the default value of this during function execution is the global variable.
3. eval and Function problems: eval can dynamically generate code, which can only be determined at runtime.
4. literal and automatic packing: [] {} and function can construct some built-in class instances so that they can access native global objects through constructor and _ proto.
Restrictions
In this case, I do not want to introduce a solution that is too heavy. For example, it is feasible to use js engines such as Narcissus to execute the entire code, however, its performance greatly limits the code capabilities. Also, some libraries and frameworks (such as wind. js) depending on some dynamic features, it is unacceptable to disable eval and Function. Even directly, eval must be able to access the context of its call. Such features must also be retained.
Solution variable access solution
Some lightweight tools (such as my JSinJS, Esprima, UglifyJS) can parse AST (Abstract Syntax Tree), according to the Abstract Syntax Tree, you can find all variables that are not declared but have been assigned a value.
For example, the following code:
Var;
Function my (){
Var I = j;
J = 2;
}
Through AST, we can find that j and a are referenced global variables.
The only exception to this problem is with. Some variables in with may not be global:
With ({s: 1 }){
S = 2;
}
Because the content in with can be determined at runtime, it cannot be predicted. Here we can only handle it in the worst case and think that the global s is used.
After finding all the referenced global variables, you only need to use an IFFE (Immediately Invoked Function Expression to Immediately execute the Function Expression) to set up the code and declare those variables that are not declared, you can change the global variable to a local variable:
Void function (){
Var j, k; // generated from AST
Var;
Function my (){
Var I = k;
J = 2;
}
}()
We also need to expose some global methods for third-party code, and add a
With (safe_global)
Void function (){//......
The implementation of safe_global can be defined freely to expose things to be exposed.
Solve this problem
This problem is troublesome. It is known that there is no solution without modifying the code. The value of this is determined at runtime. In AST, there is no way to know what is safe. So my idea is to add a check for all this: for example
Function f (){
Return this;
}
Will be changed
Function f (){
Return _ $ wrap (this );
}
The _ $ wrap function checks whether this is a global object and replaces it with safe_window if necessary.
Because the _ $ wrap function also performs a check at runtime, this problem can be effectively solved.
Solutions to eval and Function problems
Eval is divided into direct eval and indirect eval. The ES specification requires that the direct eval must be able to retain the context of the call. Therefore, the method for implementing safe_eval is definitely not good (see unencapsulated functions: eval). Fortunately, eval can be found directly from AST, And the generated code must still use eval. My solution is:
Eval (......);
Change
Eval (_ $ check (......));
The _ $ check function recursively performs the AST check described in the full text at run time and returns the result. This solves the problem of direct eval.
The problem of indirect eval and Function is similar. The code is executed globally. The problem is that we cannot identify it directly from AST, so we still need to handle it at runtime. My solution is to change eval in safe_global to safe_eval.
Safe_global.eval = function safe_eval (){
Return global. eval (_ $ check (......));
};
Function is similar to indirect eval.
There is still a fatal problem here, that is, eval in safe_global will prevent direct eval from finding the true eval function. Definitions based on eval function behavior:
The direct call of an eval function is a CallExpression that meets the following two conditions:
The result of executing MemberExpression in CallExpression is a reference. This reference has an environment record item as its base value, and the reference name is "eval ".
The result of calling the GetValue abstract operation using this reference as a parameter is the standard built-in function defined in 15.1.2.1.
We can convert eval (xxx) into an IFFE.
Eval (......);
Change
(Function () {var eval =_$ unsafe_eval; return eval (_ $ check (......)); }());
In this way, the context is saved, and the IFFE can be used in expressions like eval.
Literal and automatic packing Solution
This also happens at runtime, so it cannot be solved through AST analysis, because it is neither possible, so my solution is to execute the code in an iframe.
The only thing worth noting is that you need to modify Function. prototype. constructor to safe_Function to avoid unsafe Function calls.